Next.js i18n
Strukturelle Anleitung, um eine Next.js-App in mehrere Sprachen zu bringen: wie die Ordnerstruktur sein muss, was kaputt geht, wenn sie falsch ist, und was zu vermeiden ist.
Bibliothek zuerst auswählen
Standardmäßig next-intl für alles Neue mit dem App Router verwenden. Es benötigt kein Build-Plugin, keinen Makro-Schritt und kein manuelles Verwalten von sprachspezifischen Instanzen; Nachrichten sind einfaches JSON, das anhand der Quellsprache typisiert wird.
| Bibliothek | Wann verwenden | Kosten |
|---|---|---|
| next-intl (Standard) | Neue App Router Arbeit, jede Teamgröße | Keine – einfaches JSON, kein Code-Generator |
| @lingui/react | Wenn Du eine Compile-Zeit-Extraktion und eine minimale Laufzeitumgebung möchtest, oder eine einzige i18n-Schicht für React/Vue/Astro | SWC- oder Babel-Makro-Plugin, genau festgelegt |
| next-i18next | Das Team kennt bereits i18next, oder Du migrierst Pages Router Code | Pro-Anfrage-Instanz, getT/useT-Aufteilung |
Next.js eingebautes i18n | Nur Pages Router. Niemals App Router | Nicht anwendbar |
Die Next.js-Konfiguration i18n: { locales, defaultLocale } ist nur für den Pages Router. Im App Router tut sie nichts – Sprach-Routing bedeutet dort ein [locale]-Segment plus einen Proxy. Lass den Agenten nicht das eingebaute i18n für ein App Router Projekt „aktivieren".
Vollständiger Vergleich, einschließlich Bundle-Kosten und bibliotheksspezifischer Trade-offs: references/library-comparison.md.
Kanonische Architektur
Halte statische Wörterbücher außerhalb von src/, damit Bündelung und Importpfade offensichtlich bleiben, und halte die gesamte Sprach-Maschinerie in einem Ordner.
messages/ # reine statische Wörterbücher – außerhalb von /src halten
├── en.json # Quelle der Wahrheit; jedes andere Gebietsschema spiegelt es
├── de.json
└── ja.json
src/
├── i18n/
│ ├── routing.ts # defineRouting: Gebietsschemata + defaultLocale
│ ├── request.ts # getRequestConfig: Gebietsschema auflösen, Nachrichten laden
│ └── navigation.ts # createNavigation: sprachbewusster Link/Redirect
├── proxy.ts # Gebietsschema-Erkennung + Umleitung (Next 16; war middleware.ts)
└── app/
├── [locale]/ # umschließt die gesamte lokalisierte App
│ ├── layout.tsx # Gebietsschema validieren, <html lang>, generateStaticParams
│ └── page.tsx
└── not-found.tsx # globale 404 lebt AUSSERHALB [locale]
Regeln, die diese Form zum Funktionieren bringen:
[locale]ist ein dynamisches Root-Segment. Alles Lokalisierte lebt darunter. Root-Level Routen (not-found.tsx,error.tsx,api/) bleiben außerhalb und dürfen keine i18n-Hooks bedingungslos aufrufen, sonst stürzen sie zur Laufzeit ab.- Eine Quellsprache.
messages/en.jsonist die Master-Datei. Jede andere Datei muss ihre Form exakt spiegeln. - Das gesamte Gebietsschema-Wissen in
src/i18n/. Komponenten importierenLinkaus@/i18n/navigation, niemals ausnext/link. Ein Ort, um eine Sprache hinzuzufügen.
Einrichtung (Neuanlage)
In dieser Reihenfolge ausführen. Die Reihenfolge ist wichtig – proxy.ts hängt von routing.ts ab, und das Layout hängt von request.ts ab.
- Installiere
next-intl, umschließenext.config.tsmitcreateNextIntlPlugin(). - Erstelle
src/i18n/routing.tsmitdefineRouting. - Erstelle
src/proxy.tsmit dem Export voncreateMiddleware(routing)plus dem Standard-Matcher. - Erstelle
src/i18n/request.tsmitgetRequestConfig, das das Gebietsschema auflöst. - Verschiebe
app/**unterapp/[locale]/**; fügegenerateStaticParamshinzu und validiere den Parameter. - Erstelle
src/i18n/navigation.tsmitcreateNavigation(routing). - Erstelle
messages/en.jsonund ersetze hartcodierte Strings durchuseTranslations. - Setze
<html lang>und füge lokalisierte Metadaten + hreflang hinzu.
Exakte Dateiinhalte für jeden Schritt: references/next-intl-setup.md.
Fallstricke
Lies diesen Abschnitt, bevor Du i18n-Code schreibst. Dies sind die Fehler, die Stunden kosten.
setRequestLocaleist veraltet. Auf Next.js 16.3+ lies das Gebietsschema mitnext/root-paramsinnerhalb vongetRequestConfig. Die meisten Anleitungen, Tutorials und LLM-Trainingsdaten lehren immer nochsetRequestLocale(params.locale). Es funktioniert noch und ist als abwärtskompatibel dokumentiert – aber es ist die alte API. Verwende es nur auf Next < 16.3.generateStaticParamsist trotzdem erforderlich.paramsist ein Promise auf Next 15+.const {locale} = await params. Behandleparams.localeals Typfehler, nicht als Laufzeitfehler.- Validiere das Gebietsschema oder unbekannte Werte werden gerendert. Verwende
hasLocale(routing.locales, locale)undnotFound(). Ohne dies rendert/xx/aboutDeine englische Seite unter einer gefälschten URL. proxy.ts, nichtmiddleware.ts, auf Next 16.middleware.tsist veraltet. Der Proxy läuft nur auf Node.js – die Edge-Laufzeit wird nicht unterstützt. Lingui und next-i18next bewerben Edge-sichere Proxies; wenn Du Edge brauchst, hast Du einen echten Kompromiss, keinen Freifahrtschein.- Übersetzte Strings niemals verketten.
"Du hast " + count + " Artikel"ist ungrammatisch im Japanischen und Arabischen. Verwende ICU:"{count, plural, =1 {Ein Artikel} other {# Artikel}}". - Niemals mit
+oder Template-Literalen innerhalb einer Nachricht interpolieren. Verwende benannte Platzhalter –{name}– damit die Wortreihenfolge geändert werden kann. - Daten und Zahlen verursachen Hydratationskonflikte.
new Date()auf dem Server und im Browser unterscheiden sich um Zeitzone und Uhrzeit. SetzetimeZoneingetRequestConfig, oder rendere clientseitig für relative Zeiten. Symptom: „Text content does not match server-rendered HTML". - Formatierung ist gebietsschemaabhängig.
toLocaleString()ohne explizites Gebietsschema formatiert nach Laufzeit-Gebietsschema, nicht nach angefordertem Gebietsschema. VerwendeuseFormatter()/getFormatter(). getRequestConfigläuft pro Anfrage und wird von React zwischengespeichert. Setze keinen anfrageabhängigen Status (Warenkorb, Benutzereinstellungen) dort hinein. Nur gebietsschemaabhängige Konfiguration.- Server Actions und Route Handler haben keine Root-Parameter.
await locale()funktioniert in Server Components nur. Gib das Gebietsschema explizit als Argument in Actions und Handlern weiter.
Nachrüsten einer bestehenden App
Behandle dies als eine Migration mit URL-Auswirkungen, nicht als eine Konfigurationsänderung. Jede bestehende URL ändert ihre Form, wenn Du ein [locale]-Präfix hinzufügst.
Plane in dieser Reihenfolge:
- Strings inventarisieren – führe
scripts/find-untranslated.shaus, um hartcodierte JSX-Texte zu finden. - Wähle die Routing-Strategie vor dem Codieren. Immer mit Sprachpräfix (
/en/about,/de/about) ist die Standardeinstellung; es ist eindeutig und hreflang-sauber.localePrefix: 'as-needed'bedient das Standardgebietsschema unter dem bloßen Pfad – bessere URLs, schwächeres SEO-Signal. Entscheide jetzt; später zu wechseln ist eine vollständige Neumigration. - Baue das Routing auf, bevor Du Komponenten anfasst. Bringe
proxy.ts+[locale]mit nur englischen Nachrichten zum Laufen. - Verschiebe Routen unter
[locale]/– dies ist ein großer mechanischergit mv. Führe es in einem Commit aus, damitgit log --follownützlich bleibt. - Ersetze Strings Namespace für Namespace, nicht Datei für Datei. Übersetze einen Namespace vollständig, damit die Schlüsselform bewiesen ist, bevor sie sich vervielfacht.
- Füge Weiterleitungen für jede URL hinzu, die sich geändert hat.
/about→/en/about(oder 307 zum erkannten Gebietsschema). Ohne dies verlierst Du alle bestehenden Rankings. Nicht verhandelbar. - Füge hreflang + lokalisierte Metadaten erst hinzu, nachdem jedes Gebietsschema die Route hat, sonst gibst Du Alternativen aus, die auf 404er zeigen.
Vollständige Migrationssequenz mit Weiterleitungs- und Rollback-Notizen: references/migration.md.
Was zu vermeiden ist
- Lege
messages/nicht insrc/ab. Server-only Wörterbücher im Quellbaum verschwimmen, was zum Client geht. - Importiere
Linknicht ausnext/linkin lokalisierten Routen. Es verliert das Sprachpräfix und verlinkt still auf die falsche Sprache. - Schreibe Sprachpräfixe nicht von Hand in hrefs (
href={/${locale}/about}). VerwendeLinkaus@/i18n/navigation, das Präfix-, as-needed- und Domain-Routing handhabt. - Verwende keinen flachen Schlüssel-Namespace. Gruppiere nach Funktion/Seite. Flache Schlüssel kollidieren und sind nicht durchsuchbar.
- Übersetze nicht automatisch zur Build-Zeit als Quelle der Wahrheit. Maschinenausgabe ist ein Entwurf; sie ungeprüft zu committen macht Fehler dauerhaft und unauffindbar.
- Füge kein Gebietsschema zu
routing.localeshinzu und nenne es erledigt. Ohne eine übersetztemessages/xx.jsonrendert jede Seite Schlüssel oder fällt still zurück. - Mische keine Routing-Strategien.
as-neededplus einhreflang, das auf Präfix-URLs zeigt, ist ein selbst zugefügter SEO-Bug.
Referenzen
Lies diese bei Bedarf, nicht im Voraus:
references/library-comparison.md– Auswahl zwischen next-intl, Lingui, next-i18next; Bundle-Kosten, Migrationsauslöser, bibliotheksspezifische Einrichtungsstruktur.references/next-intl-setup.md– jede Datei in der kanonischen Einrichtung, kopierfertig.references/messages-and-keys.md– Namespace-Struktur, Schlüsselbenennung, ICU-Syntax, Datums-/Zahlen-/Listenformatierung, typisierte Nachrichten.references/seo.md– hreflang,<html lang>, lokalisiertesgenerateMetadata, kanonisch, Sitemap-Alternativen, Sprachwechsler-SEO-Regeln.references/migration.md– Nachrüsten einer bestehenden App, Weiterleitungen, Rollback.
Skripte
scripts/find-untranslated.sh– erkennt wahrscheinlich hartcodierte benutzerorientierte Strings in einem Projekt, damit Du den Arbeitsumfang vor dem Start einschätzen kannst. Führe es als Schritt 1 einer Nachrüstung aus.scripts/run-trigger-eval.sh– misst, ob diedescriptiondieser Fähigkeit bei den richtigen Prompts ausgelöst wird. Verwende es, wenn Du die Beschreibung änderst.
Testen der Beschreibung
Die description ist das Einzige, was der Agent sieht, bevor er entscheidet, diese Fähigkeit zu laden. Wenn sie nicht ausgelöst wird, ist nichts darunter wichtig.
bash scripts/run-trigger-eval.sh --dry-run # Zeige den Plan, keine API-Aufrufe
bash scripts/run-trigger-eval.sh --split train # 12 Abfragen x 3 Durchläufe
bash scripts/run-trigger-eval.sh --split validation --out report.json
Abfragesatz: evals/trigger-queries.json – 12 sollen ausgelöst werden, 8 sollen nicht ausgelöst werden, aufgeteilt
60/40 in einen festen Trainings- und Validierungssatz.
Beim Bearbeiten der Beschreibung:
- Behebe Fehler mit dem Trainings-Satz. Berühre den Validierungssatz nur einmal am Ende, um zu prüfen, ob die Änderung verallgemeinert – wenn das Training besser wird und die Validierung schlechter, hast Du überangepasst.
- Soll-ausgelöst-Fehler bedeuten, dass die Beschreibung zu eng ist: Füge das Konzept hinzu, das die Abfrage repräsentiert, niemals die Schlüsselwörter der Abfrage.
- Soll-nicht-ausgelöst-Fehler bedeuten, dass sie zu breit ist: Sage, was die Fähigkeit nicht abdeckt.
- Führe vor dem Bearbeiten erneut aus. Das Auslösen ist nicht deterministisch, und ein Durchlauf beweist nichts.
Füge keine Schlüsselwörter aus einer fehlschlagenden Abfrage hinzu – so enden Beschreibungen wie eine Schlüsselwortliste und passen nicht mehr zu echten Prompts.