next-intl
Kurz: Eine Bibliothek für Mehrsprachigkeit (i18n) in Next.js-Projekten — übersetzt fest programmierte UI-Texte über zentrale Übersetzungsdateien.
Genauer: Texte liegen in JSON-Dateien pro Sprache (z. B. messages/de.json, messages/en.json) und werden im Code über useTranslations/getTranslations per Key abgerufen (t("key")) statt hart codiert zu sein. Wichtig: next-intl übersetzt nur diese UI-Texte, keine Datenbank-Inhalte (z. B. selbst geschriebene Blogartikel) — dafür bräuchte es ein eigenes Feature.
Kontext bei uns: Bei Emzett eingeführt für Deutsch/Englisch, ohne URL-Prefix (/de/, /en/) — die Sprache steckt stattdessen in einem Cookie. Schrittweise Seite für Seite umgestellt (Footer, Header, Shop, Tickets, …), nicht alles auf einmal.
Im Detail
Struktur der Übersetzungsdateien
Übersetzungsdateien sind verschachtelte JSON-Objekte, damit zusammengehörige Texte gruppiert bleiben:
{
"Cart": {
"title": "Warenkorb",
"empty": "Dein Warenkorb ist leer.",
"itemCount": "{count, plural, one {# Artikel} other {# Artikel}}"
}
}Diese Gruppierung (Namespace pro UI-Bereich, z. B. Cart, Checkout, Footer) hat einen praktischen Nutzen über reine Ordnung hinaus: useTranslations("Cart") gibt eine auf diesen Namespace beschränkte Übersetzungsfunktion zurück, sodass im restlichen Code kürzere Keys (t("title") statt t("Cart.title")) reichen und Namenskollisionen zwischen Bereichen ausgeschlossen sind.
ICU-Message-Syntax
itemCount zeigt eine wichtige next-intl-Fähigkeit: ICU-Message-Syntax für Pluralformen — statt selbst count === 1 ? "Artikel" : "Artikel"-Logik im Code zu schreiben (was bei Sprachen mit komplexeren Pluralregeln als Deutsch/Englisch schnell falsch wird), übernimmt die Bibliothek das automatisch pro Sprache korrekt. Manche Sprachen (z. B. Polnisch, Russisch) haben mehr als zwei Pluralformen (eins für “eins”, eins für “wenige”, eins für “viele”) — ICU-Syntax bildet das über zusätzliche Kategorien (few, many) ab, ohne dass im Anwendungscode je eine sprachspezifische Fallunterscheidung nötig wird. Auch bedingte Texte lassen sich darüber abbilden, z. B. Geschlechtsformen über select-Syntax ({gender, select, male {er} female {sie} other {es}}).
Server- und Client-seitige Nutzung
In Server Components wird getTranslations() (async) verwendet, in Client Components useTranslations() (ein Hook, synchron, da die Übersetzungen bereits vom Server mitgeliefert werden). Beide greifen auf dieselben JSON-Dateien zu, aber der Mechanismus dahinter unterscheidet sich: Server-seitig werden die Nachrichten direkt aus dem Dateisystem gelesen, Client-seitig werden sie über einen NextIntlClientProvider (der die relevanten Übersetzungen als Props an den Client weiterreicht) verfügbar gemacht — nur die tatsächlich von Client Components benötigten Nachrichten sollten dabei übergeben werden, um das an den Browser gesendete JavaScript-Bundle klein zu halten.
Cookie- vs. URL-basiertes Routing
Die Alternative zum Cookie-basierten Ansatz (den Emzett nutzt) wäre URL-basiertes Routing (/de/shop, /en/shop) — Vorteil dort: Suchmaschinen können jede Sprachversion einzeln indexieren und verlinkte URLs sind eindeutig sprachspezifisch (wichtig für internationales SEO über hreflang-Tags); Nachteil: mehr Routing-Komplexität (jede Route existiert effektiv doppelt), und Nutzer landen bei falsch geteilten Links ggf. in der falschen Sprache statt automatisch in ihrer bevorzugten. Der Cookie-Ansatz ist einfacher umzusetzen und bewahrt “saubere” URLs ohne Sprachpräfix, hat aber den Nachteil, dass ein und dieselbe URL je nach Cookie unterschiedlichen Inhalt zeigen kann — was aus reiner SEO-Sicht suboptimal ist, für ein primär auf ein Sprachgebiet ausgerichtetes Projekt aber meist kein praktisches Problem darstellt.
Siehe auch: Next.js App Router, i18n