Was macht Web i18n?
Internationalisierung ist Architektur, kein Übersetzungsdurchlauf am Ende. Das Ziel ist eine UI, die für jedes Gebietsschema korrekt dargestellt wird – ohne Sonderfälle: keine konkatenierten Sätze, keine hartcodierten Formate, kein Layout, das bricht, wenn Deutsch 35 % länger ist oder Arabisch die Richtung umkehrt.
Wann dieser Skill verwendet wird
Verwende ihn, wenn der Benutzer darum bittet:
- Eine Sprache, einen Sprachumschalter oder ein Locale-Routing hinzuzufügen
- Eine App zu übersetzen, Zeichenketten zu extrahieren oder einen Übersetzungsworkflow einzurichten
- Pluralisierungs-, Geschlechter- oder "1 item / 2 items"-Fehler zu beheben
- Datums-, Zahlen-, Währungs-, relative Zeit- oder Listenangaben pro Locale zu formatieren
- RTL (Arabisch, Hebräisch, Persisch, Urdu) zu unterstützen
- Hreflang, lokalisierte Metadaten oder mehrsprachige Sitemaps hinzuzufügen
- Ein bestehendes i18n-Setup, fehlende Schlüssel oder Übersetzungsdrift zu überprüfen
- Zwischen next-intl, react-i18next, FormatJS, Lingui oder einem TMS zu wählen
Verwandte Referenzen befinden sich in references/ und Skripte in scripts/. Lies nur, was die Aufgabe benötigt. Pfade sind relativ zum Verzeichnis dieses Skills (die Agentenlaufzeit gibt es aus, wenn der Skill geladen wird).
Unverhandelbar
- Baue niemals einen Satz durch Verkettung übersetzter Fragmente. Übersetze die gesamte Nachricht mit Platzhaltern:
"{count} neue Nachrichten", nichtt('neu') + ' ' + count + ' ' + t('nachrichten'). - Verwende niemals englischen Text als Schlüssel. Schlüssel sind stabile Identifikatoren (
inbox.unreadCount), damit Textänderungen keine Lookups brechen oder alle Übersetzungen ungültig machen. - Jede benutzersichtbare Zeichenkette wird externalisiert, einschließlich
aria-label,alt,title,placeholder, Validierungsfehler, leere Zustände, Toasts, E-Mails, Push-Benachrichtigungen und PDF-Ausgabe. - Hartcodiere niemals Formate. Kein
toFixed(2) + '$', kein manuellesMM/DD/YYYY, keine englischen Monatsnamen, keine24h-Annahmen. VerwendeIntl. - Ein Gebietsschema ist ein BCP-47-Tag, keine Sprache.
pt-BRundpt-PT,zh-Hansundzh-Hant,es-MXundes-ESsind verschiedene Produkte. Speichere den vollständigen Tag. - Der Locale-Status muss auf dem Server anforderungsbezogen sein. Setze das aktuelle Locale während SSR niemals in eine veränderliche Variable auf Modulebene; gleichzeitige Anfragen würden Sprachen ineinander lecken lassen.
- Übersetzungen sind nicht vertrauenswürdige Eingaben, wenn sie von einem TMS, einer Community oder einem CMS stammen. Übergib sie niemals an
innerHTML/dangerouslySetInnerHTMLohne Bereinigung.
Arbeitsablauf
1. Prüfen vor Codeänderungen
- Erkenne den Stack, den Router und die vorhandene i18n-Bibliothek, bevor du etwas vorschlägst.
- Finde hartcodierte Zeichenketten:
Behandle die Ausgabe als Kandidaten, nicht als Evangelium: überspringe Test-Fixtures, Konstanten, URLs und entwicklerorientierte Logs.node scripts/scan-hardcoded.mjs src - Inventarisiere jedes derzeit ausgelieferte Gebietsschema und den Speicherort der Kataloge.
- Identifiziere zuerst die Bereiche mit dem höchsten Risiko: Authentifizierung, Checkout, Fehler, E-Mails und rechtliche Texte.
2. Architektur entscheiden (schreibe sie auf)
Beantworte diese Fragen, bevor du Code schreibst:
| Entscheidung | Standard | Anmerkungen |
|---|---|---|
| Locale-Strategie | Pfadpräfix /es/ | Beste SEO und Cache-Fähigkeit. Subdomains/ccTLDs nur mit Grund. |
| Standard-Locale | Quellsprache | Leite Erstbesucher ohne Überschreibung nicht automatisch in ein erratenes Locale weiter. |
| Locale-Erkennung | URL → Cookie → Accept-Language → Standard | URL gewinnt, damit geteilte Links nie die Sprache ändern. |
| Katalogformat | JSON pro Locale pro Namespace | Lazy-Lade-Namespaces; sende niemals alle Locales an den Client. |
| Nachrichtensyntax | ICU MessageFormat | Portabel über Bibliotheken und TMS hinweg; unterstützt Plural/Select/Ordinal. |
| Schlüsselstil | feature.component.element | Stabil, grepbar, kein englischer Text. |
| Übersetzungsquellsystem | TMS oder Repo, nicht beides | Wähle eines aus; synchronisiere in eine Richtung in CI. |
| Fallback | Locale-Kette es-MX → es → en | Protokolliere Fallbacks in der Entwicklung, um Lücken zu finden. |
3. Externalisieren und strukturieren von Nachrichten
- Ersetze Zeichenketten durch semantische Schlüssel und verschiebe den Text in den Basislocale-Katalog.
- Verwende einen Schlüssel pro vollständigem Satz. Wenn ein Satz einen Link oder ein fettgedrucktes Wort enthält, verwende Rich-Text-Interpolation anstelle einer Aufteilung.
- Halte Platzhalter benannt (
{userName}), niemals positionell ({0}). - Füge kein HTML in Kataloge ein, es sei denn, die Bibliothek unterstützt explizit getypte Tags.
- Gruppiere nach Produktoberfläche, nicht nach Seite:
checkout.json,auth.json,errors.json.
Siehe references/icu.md für Plural-, Select-, Ordinal- und Rich-Text-Regeln.
4. Locale-Routing und -Rendering einrichten
- Leite das Locale aus der Route ab, validiere es gegen die Liste der unterstützten Locales und gib 404 für unbekannte Locales zurück, anstatt stillschweigend zurückzufallen.
- Stelle einen Sprachumschalter bereit, der den aktuellen Pfad und die Query-Strings beibehält.
- Setze
<html lang="...">und<html dir="...">auf dem serverseitig gerenderten Dokument. - Lade nur den Katalog des aktiven Locales plus den Standard-Fallback.
- Mache denselben Katalog für Server- und Client-Rendering verfügbar; vermeide doppelte oder abweichende Übersetzungslogik.
Framework-spezifische Einrichtung und Fallstricke: references/frameworks.md.
Routing, Erkennung, Hreflang und Sitemaps: references/routing-seo.md.
5. Locale-sensitive Werte behandeln
Verwende Intl (oder den Wrapper des Frameworks), niemals manuelle Formatierung:
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)
new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone }).format(date)
new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(-3, 'day')
new Intl.ListFormat(locale, { type: 'conjunction' }).format(items)
new Intl.PluralRules(locale).select(count)
new Intl.Collator(locale).compare(a, b)
- Speichere Zeitstempel in UTC; rendere in der Zeitzone des Benutzers, nicht des Servers.
- Währung ist nicht gleich Locale: Ein französischer Benutzer kann in USD bezahlen.
- Sortiere/suche mit
Intl.Collator, nicht mit<oderlocaleCompare()ohne Locale. - Groß-/Kleinschreibung:
toLocaleUpperCase(locale), weil türkischesizuİwird.
6. Gestaltung für Erweiterung und RTL
- Gehe von +30–40 % Textlänge aus (Deutsch, Finnisch, Russisch) und vermeide Schaltflächen mit fester Breite oder einzeilige Labels.
- Lasse Container wachsen, erlaube Zeilenumbrüche und teste mit Pseudo-Lokalisierung:
node scripts/pseudo-localize.mjs locales/en.json --out locales/en-XA.json - Verwende logische CSS-Eigenschaften (
margin-inline-start,padding-inline,inset-inline) anstelle vonleft/right. - Spiegele richtungsabhängige Symbole (Pfeile, Chevrons, Fortschritt), nicht Logos, Uhren, Mediensteuerungen oder Häkchen.
- Umfasse benutzergenerierte Inline-Inhalte mit
<bdi>oderdir="auto".
Vollständige RTL-Checkliste: references/rtl.md.
7. Übersetzungs-QA in CI einrichten
Mindestens sollte der Build fehlschlagen, wenn Kataloge strukturell ungültig sind:
node scripts/check-locales.mjs locales --base en
Es prüft auf fehlende/zusätzliche Schlüssel, leere Werte, Platzhalterkonflikte und fehlerhafte ICU-Syntax. Füge die ICU-Kompilierung über die CLI deiner Bibliothek hinzu, wenn verfügbar:
npx formatjs compile-folder --ast locales/en locales/compiled/en
Füge Tests für Pluralkategorien, Datums-/Zahlenausgabe mit einer festen Zeitzone und RTL-Rendering hinzu. Siehe references/testing.md.
Wichtige Entscheidungen und Fallen
Bibliotheksauswahl
| Bedarf | Wähle |
|---|---|
| Next.js App Router, Server-Komponenten | next-intl |
| React SPA, ausgereiftes Ökosystem, viele Plugins | react-i18next |
| ICU-first, framework-unabhängig, CLI-Extraktion | FormatJS (react-intl) |
| Kompilierte Kataloge, kleine Laufzeit, Typsicherheit | Lingui |
| Vue | vue-i18n |
| Svelte | svelte-i18n oder paraglide |
| Astro | integriertes i18n-Routing + deine Nachrichtenbibliothek |
| Vanilla / Minimal | Intl + ein kleiner Katalog-Lader |
Führe keine zweite i18n-Bibliothek in eine App ein, die bereits eine hat. Erweitere das bestehende Setup.
Pluralisierung
Pluralkategorien sind sprachspezifisch. Englisch hat one/other; Arabisch hat zero/one/two/few/many/other; Japanisch hat nur other. Schreibe niemals count === 1 ? ... : ....
"inbox.unread": "{count, plural, =0 {Keine ungelesenen Nachrichten} one {# ungelesene Nachricht} other {# ungelesene Nachrichten}}"
Geschlecht und grammatikalische Übereinstimmung
Verwende select für eine Geschlechtervariable und halte den Wert der Variable sprachneutral (female, male, neutral, unknown), nicht lokalisierte Wörter:
"profile.greeting": "{gender, select, female {Bienvenida, {name}} male {Bienvenido, {name}} other {Bienvenido/a, {name}}}"
Wenn eine Sprache Übereinstimmung bei Adjektiven, Nomen oder Artikeln erfordert, stelle das grammatikalische Geschlecht/den Fall als Argument zur Verfügung, anstatt die Zeichenkette zu manipulieren.
Rich-Text
Teile "Lesen Sie unsere " + link + " Bedingungen" nicht in drei Schlüssel auf. Verwende Tag-Interpolation:
"signup.terms": "Ich stimme den <link>Nutzungsbedingungen</link> zu"
t.rich('signup.terms', { link: (chunks) => <a href="/terms">{chunks}</a> })
SEO
- Eine indexierbare URL pro Locale; liefere niemals maschinell übersetzte Inhalte hinter derselben URL aus.
- Selbstreferenzierendes
hreflangplusx-default; absolute URLs; inkludiere jedes Locale. - Lokalisiere
<title>, Meta-Beschreibung, OG-Tags und strukturierte Daten. - Kombiniere
hreflangnicht mitnoindexauf lokalisierten Seiten.
Details in references/routing-seo.md.
Caching
- Das Locale im Pfad macht CDN-Caching unkompliziert. Wenn du per Header verhandelst, sende
Vary: Accept-Languageund erwarte Cache-Fragmentierung. - Cache keine personalisierte Sprachpräferenz in einer gemeinsam genutzten CDN-Antwort.
- Halte
Content-Languagekonsistent mit dem gerenderten Locale.
Review-Checkliste
Bevor du i18n als abgeschlossen erklärst:
- Keine hartcodierten benutzersichtbaren Zeichenketten in Komponenten, E-Mails oder Fehlern
- Keine Satzkonkatenation; jede Nachricht ist eine vollständig übersetzbare Einheit
- Schlüssel sind semantisch und stabil; kein englischer Text als Schlüssel
- Jeder Platzhalter existiert in jedem Locale; ICU-Syntax kompiliert
- Pluralformen verwenden
Intl.PluralRules/ICU, nichtcount === 1 - Geschlecht/Übereinstimmung wird über
selectbehandelt, nicht über Suffixe wie"/a" - Daten, Zahlen, Währung, relative Zeit verwenden
Intlmit explizitem Locale/Zeitzone -
<html lang>unddirsind beim ersten Rendern (SSR) korrekt und werden nicht in einem Effekt gesetzt - Der Sprachumschalter erhält Pfad/Query und funktioniert mit Deep Links
- Unbekanntes Locale gibt 404 zurück, keinen stillen Fallback
- Layout übersteht +35 % Expansion und RTL
-
hreflang, kanonisch, Sitemap und lokalisierte Metadaten sind korrekt - Katalogvalidierung läuft in CI
- Fallback-Verhalten ist beabsichtigt und beobachtbar
Anti-Patterns
t('hallo') + ', ' + name— fragmentierte Sätzekey={englischerText}— übersetzter Text als Schlüsselcount === 1 ? 'Artikel' : 'Artikel'— englischsprachige Plural-Logiknew Date().toLocaleDateString()ohne Locale oder Zeitzonevalue.toFixed(2)für Geldbeträgelocale === 'ar' && { direction: 'rtl' }über Komponenten verstreut, anstattdirauf Dokumentebene zu setzen- Setzen von
<html lang>innerhalb vonuseEffect— verursacht einen Flash und bricht Crawler - Laden jedes Locales in das anfängliche Bundle
- Automatische Weiterleitung nach IP ohne Überschreibungsmöglichkeit
- Übersetzen von Eigennamen, Produktnamen, Code-Identifikatoren oder URLs
- maschinelles Übersetzen von Zeichenketten und Ausliefern ohne Überprüfung
- Annahme, dass eine einzige Katalogdatei für mehr als ein paar hundert Schlüssel skalierbar ist