Wenn deine Next.js-App übersetzt ist, aber der Browser-Tab, die 404-Seite, die Formularfehler und die Link-Vorschau noch englisch sind, ist die Ursache fast immer dieselbe: Diese Strings entstehen außerhalb einer Komponente, ein useTranslations-Aufruf hat sie also nie angefasst. globalize.now ist KI-gestützte Lokalisierungsinfrastruktur, und genau diese Schicht sehen wir halb fertig, wenn ein KI-Agent i18n in ein App-Router-Projekt einbaut: Die sichtbare Seite ist konvertiert, der Rest der Oberfläche nicht.
Unten steht, wo jeder dieser Strings sitzt und welcher next-intl-Aufruf ihn abdeckt. Alle Beispiele folgen der aktuellen next-intl-Dokumentation, abgerufen am 28.10.2026.
Wo verstecken sich unübersetzte Strings in einer App-Router-App?
In sechs Stellen, die keine React-Komponenten sind, die Text rendern. Ein Coding-Agent, der i18n ergänzt, indem er deine Komponenten abarbeitet, konvertiert die sichtbare UI und läuft an diesen vorbei:
- Seitentitel und Beschreibungen in
generateMetadata. - Validierungsfehler und Ergebnismeldungen in Server Actions.
not-found.tsx, die 404-Seite.error.tsx, die Seite für Laufzeitfehler.- Generierte Dateien wie
opengraph-image.tsx,manifest.tsundsitemap.ts. - Route Handlers, die Text zurückgeben.
Der gemeinsame Nenner: Keiner davon wird im normalen Ablauf innerhalb deines [locale]-Layout-Baums gerendert, die Locale muss ihnen also übergeben oder anders ausgelesen werden. Der Beitrag zum Next.js-16-Routing behandelt die Routing-Verdrahtung; dieser hier handelt davon, was diese Verdrahtung nicht erreicht.
Wie übersetzt du Seitentitel und Beschreibungen?
Rufe getTranslations aus next-intl/server innerhalb von generateMetadata auf. Die Funktion lässt sich aus einer Seite oder einem Layout exportieren und ist asynchron, die awaitbare API ist also die richtige:
// app/[locale]/page.tsx
import {getTranslations} from 'next-intl/server';
export async function generateMetadata() {
const t = await getTranslations('Metadata');
return {title: t('title'), description: t('description')};
}
Die Falle ist die statische Form. Agents schreiben oft export const metadata = {title: 'Dashboard'}, weil es kürzer ist, und eine Konstante kann keine Übersetzungsfunktion aufrufen. Stell sie auf die Funktionsform um und ergänze dieselben Keys im Katalog für jede Locale. Das ist auch der Text, den Suchmaschinen sehen – deshalb bringt es für das Ranking kaum etwas, die App zu übersetzen, ohne ihre Metadaten zu übersetzen.
Wie übersetzt du Meldungen aus Server Actions und Validierung?
Übergib die Locale an die Action und rufe getTranslations mit Namespace und dieser Locale auf. Eine Server Action weiß nicht, in welcher Sprache die Seite läuft, daher sieht die next-intl-Doku vor, dass du sie mitgibst, zum Beispiel als Argument:
'use server';
import {getTranslations} from 'next-intl/server';
export async function subscribe(locale: string, formData: FormData) {
const t = await getTranslations({locale, namespace: 'Subscribe'});
// ...validate, then return t('success') or t('invalidEmail')
}
Ein Vorbehalt aus derselben Doku: Überleg, was passiert, wenn jemand die Sprache wechselt, während die Meldung einer Action auf dem Bildschirm steht. Mit einem [locale]-Segment in der URL erledigt sich das von selbst, weil sich die Route ändert. Ohne ein solches Segment solltest du die Meldung selbst zurücksetzen, etwa indem du der Komponente key={locale} gibst, damit ihr State neu startet.
Für Formularvalidierung mit Zod ist der dokumentierte Weg eine Error Map. Zod akzeptiert eine pro safeParse-Aufruf, und die Map kann getTranslations aufrufen. Pack das in einen asynchronen Helper und übergib sein Ergebnis bei jedem Parse als error-Option. Dann enthalten deine Schemas Regeln statt englischer Sätze, und KI-generierte Formulare liefern kein "Invalid email" mehr auf eine lettische Seite aus.
Wie übersetzt du die 404- und Fehlerseiten?
Leg not-found.tsx in app/[locale]/ ab und ergänze eine Catch-all-Route, damit sie tatsächlich greift. Next.js rendert diese Datei, wenn ein Segment notFound() aufruft – und eine unbekannte URL ruft das nicht von allein auf:
// app/[locale]/[...rest]/page.tsx
import {notFound} from 'next/navigation';
export default function CatchAllPage() {
notFound();
}
Requests, die die next-intl-Middleware nicht matcht, tragen gar keine Locale, die [locale]-404 greift für sie also nie. Die Antwort der Doku ist app/global-not-found.tsx, das Next.js als experimentell dokumentiert und das sein eigenes html und body rendert. Für einen unbekannten [locale]-Wert sagt die Doku, du sollst notFound() aufrufen, typischerweise in i18n/request.ts.
error.tsx ist anders. Next.js verlangt, dass es eine Client Component ist, und es wird unter deinem Layout gerendert; es braucht also einen NextIntlClientProvider mit mindestens dem Error-Namespace. Gib nur diese Messages mit, nicht den ganzen Katalog:
// app/[locale]/layout.tsx (excerpt)
const messages = await getMessages();
// ...
<NextIntlClientProvider locale={locale} messages={pick(messages, 'Error')}>
Dann funktioniert useTranslations('Error') innerhalb von error.tsx. Die Seite deckt global-error nicht ab; prüf diese Datei also von Hand, falls du eine hast.
Was ist mit Share-Images, Manifest und Sitemap?
Bei allen musst du die Locale selbst angeben, weil sie außerhalb des Layout-Baums liegen. Lies für opengraph-image.tsx den Wert locale aus params und rufe getTranslations({locale, namespace}) auf. Bei einem angepassten localePrefix warnt die Dokumentation, dass die generierte Route unerreichbar werden kann, weil Next.js die Rewrites des Proxys (Middleware) nicht sieht; die Lösung: Lass den Matcher Pfade überspringen, die auf opengraph-image enden.
app/manifest.ts liegt außerhalb von [locale], next-intl kann also keine Locale aus dem Pfad ableiten. Nimm eine repräsentative, meist die Default-Locale, und übergib sie explizit. Ergänze für sitemap.ts die alternates.languages-Einträge und baue jede URL mit dem asynchronen getPathname({locale, href}) aus deinem Navigationsmodul, mit dem Host als Präfix. next-intl sendet standardmäßig auch einen link-Response-Header, der die Sprachversionen ankündigt; eine Sitemap ist die Option, wenn du CMS-getriebene Slugs kontrollieren musst.
Wie gehst du mit Route Handlers um?
Nimm die Locale aus einem Search-Param, einem Segment oder dem accept-language-Header, validiere sie mit hasLocale(routing.locales, locale) und gib bei einem Fehlschlag einen 400er zurück. Rufe dann getTranslations({locale, namespace}) auf. Ohne Validierung landet ein beliebiger String in deinem Katalog-Lookup.
Wie findest du das Englisch, das dein Agent übrig gelassen hat?
Führe diese Befehle im Projekt-Root aus. Sie sind absichtlich grob; das Ziel ist eine Liste zum Durchlesen, kein Score.
# 1. Static metadata that cannot be translated
grep -rn "export const metadata" app
# 2. Titles and descriptions already in generateMetadata
grep -rn "title:" app --include=page.tsx --include=layout.tsx
# 3. Server Actions with literal return messages
grep -rln "use server" app src 2>/dev/null | xargs grep -n "message:\|error:"
# 4. Zod schemas with inline messages
grep -rn "message:\s*['\"]" app src 2>/dev/null
# 5. 404 and error files, and whether a catch-all exists
ls app/\[locale\]/not-found.tsx app/\[locale\]/error.tsx "app/[locale]/[...rest]/page.tsx" 2>&1
Alles in den ersten vier Ergebnissen, was ein reines englisches Literal ist, gehört in den Katalog. Warum Agents immer wieder hartcodierte Strings einbauen erklärt, warum das wiederkehrt: Der Agent folgt dem sichtbaren Muster, und das sichtbare Muster ist die Komponente. Wenn du Copilot nutzt, gelten auch hier die Limits der Instruction-Dateien für Copilot.
Was ändert das an deinem Katalog?
Es kommen Keys dazu, und bei Keys beginnt der Drift. Jeder Punkt oben zieht neue Einträge in den Katalog, etwa Metadata, Error, Subscribe und die Validierungsmeldungen, und jeder braucht in jeder Locale, die du auslieferst, eine Übersetzung. Ein Projekt, das seine Komponenten einmal übersetzt und später eine Server Action oder einen neuen Seitentitel ergänzt, ist genau das Projekt aus warum Übersetzungsdateien auseinanderlaufen.
next-intl liefert die Übersetzungen zur Laufzeit aus, und wir ersetzen es nicht. globalize.now sitzt eine Schicht darüber: Du verbindest das Repo in der App, die Codebase wird einmal in einen Katalog konvertiert, und Push-Jobs übersetzen neue Katalogeinheiten, sobald sie auftauchen – geliefert als Änderungen, die du reviewst. Die Next.js-Integration beschreibt das Setup, und was wir in einem bestehenden i18n-Setup anfassen und was nicht behandelt den Fall, dass next-intl schon von Hand eingebunden ist.
Wo du anfängst
Lass die fünf greps laufen, wandle jeden statischen metadata-Export um, ergänze die Catch-all-Route und gib error.tsx seinen Provider. Prüfe dann den Katalog auf die neuen Namespaces in jeder Locale. Wenn du eine KI-gebaute App ausgeliefert hast und Konvertierung und Übersetzung abgeben statt pflegen willst, ist die Seite für Vibe Coder der Überblick, die Developer-Referenz behandelt Agent-Setups, und die Preise stehen auf einer eigenen Seite.
globalize.now konvertiert hartcodierte App-Texte in übersetzungsreife Locale-Dateien und hält sie aktuell, während Sie veröffentlichen.
Probiere globalize.now kostenlos