Der folgende i18n-Abschnitt gehört in AGENTS.md: zehn kurze Regeln, die einem Coding-Agent sagen, jeden sichtbaren String durch die Übersetzungsfunktion zu leiten, ihn nach Feature zu benennen, nie Fragmente zu konkatenieren, nie Plurale mit einem Ternary zu simulieren und nie eine generierte Locale-Datei anzufassen. globalize.now ist KI-gestützte Lokalisierungsinfrastruktur, wir lesen also viel agentengeschriebenes i18n – und dieselben paar Fehler machen fast alles davon aus. Jede Regel entspricht einem davon.
In der zweiten Hälfte dieses Posts geht es darum, wo die Datei nicht mehr wirkt. Zwei der meistgenutzten Code-Oberflächen lesen sie nie, und keine Formulierung ändert das.
Was sollte der i18n-Abschnitt der AGENTS.md enthalten?
Diesen Block. Passe die drei Dateipfade und den Lint-Befehl an dein Projekt an und lass den Rest, wie er ist.
## Internationalization
- Every user-visible string goes through the translation function. That
includes JSX text, input hint text, `title`, `aria-label`, `alt`, toast and
error messages, empty states and the document `<title>`.
- The source locale is `en`. The catalog is `locales/en.json`. Add the key in
the same change that uses it.
- Keys are namespaced by feature: `checkout.summary.total`, not `total3`.
Search the catalog for an existing key before creating one.
- Never concatenate translated fragments. Use interpolation:
`t('cart.items', { count })`, not `t('cart.you_have') + count`.
- Plurals use the library's plural forms (`_one` / `_other` keys, or ICU
`{count, plural, ...}`). Never `count === 1 ? 'item' : 'items'`.
- Dates, numbers and currency go through `Intl.DateTimeFormat` and
`Intl.NumberFormat` with the active locale. Never a bare
`toLocaleString()`, never a currency symbol glued to a number.
- Do not hardcode the locale list, language names or text direction in
components. Read them from `i18n/config.ts`.
- Do not translate brand names, code identifiers, URLs or environment values.
- Do not edit locale files other than `en`. They are generated. If a
translation looks wrong, fix the source key or the glossary, not the file.
- Before finishing, run `npm run lint`. The `i18next/no-literal-string`
rule must pass.
Zehn Regeln sind Absicht. Die Regeldokumentation von Cursor rät, Regeln auf häufig genutzte Muster zu fokussieren und einen Linter zu verwenden, statt einen Styleguide einzufügen; bei Claude Code gilt dasselbe: Schreib Instruktionen so konkret, dass sie sich überprüfen lassen, und halte die Datei kurz, weil die Befolgung mit ihrer Länge nachlässt. Ein i18n-Abschnitt mit vierzig Bullets wird überflogen.
Warum diese zehn Regeln und nicht einfach „i18n verwenden“?
Weil „i18n verwenden“ genau das ist, was der Agent schon zu tun glaubt, wenn er <Button>Save changes</Button> schreibt. Vage Instruktionen erfüllt der Agent nach seiner eigenen Vorstellung von Konformität. Jede Regel unten benennt eine konkrete Ausgabe und verbietet sie.
Literale in Attributen. Agents lernen, dass JSX-Children übersetzt werden müssen, und schreiben dann aria-label="Close" und alt="Company logo" als einfache Strings, weil Attribute wie Konfiguration aussehen und nicht wie Text. Die Attribute namentlich aufzuzählen schließt diese Lücke; ein allgemeines „übersetze alles“ tut es nicht.
Konkatenation. t('greeting') + ' ' + name + '!' rendert auf Englisch einwandfrei, lässt sich aber in keine Sprache übersetzen, die den Namen voranstellt oder flektiert. Interpolation gibt der Übersetzerin einen String mit einer Variable, die sie verschieben kann.
Ternary-Plurale. count === 1 ? 'item' : 'items' schreibt ein Agent nach dem i18n-Setup am häufigsten, und es ist falsch für jede Sprache mit mehr als zwei Pluralformen. Die Regel benennt das Muster, damit der Agent es erkennen kann. Nutzt dein Katalog ICU, behandelt was ICU MessageFormat ist und wo es in KI-generierten Apps bricht die Syntax.
Generierte Locale-Dateien. Diese Regel gibt es, weil Agents hilfsbereit sind. Soll er einen deutschen Tippfehler beheben, öffnet ein Agent locales/de.json und editiert sie – das klappt, bis der nächste Übersetzungsjob die Datei aus der Quelle neu generiert. Sagst du ihm, dass Nicht-Quelldateien generiert sind und wo der eigentliche Fix hingehört, verhinderst du eine ganze Klasse stiller Regressionen.
en im selben Change editieren. Ohne diese Regel ergänzt der Agent t('checkout.summary.total') und macht weiter, und der Schlüssel wird als sein eigener Name gerendert, bis es jemand merkt. Der Katalogeintrag und seine erste Verwendung gehören in einen Diff.
Wo liest welches Tool die Datei tatsächlich?
Derselbe Inhalt kommt je nach Agent an unterschiedliche Orte, und der Ort entscheidet, ob die Regel immer im Kontext ist oder nur geladen wird, wenn sie relevant ist.
AGENTS.md im Root des Repositorys ist der gemeinsame Ort. Das Format ist schlichtes Markdown ohne Pflichtfelder, betreut von der Agentic AI Foundation unter der Linux Foundation und gelesen von Cursor, Codex, dem Copilot-Coding-Agent und einer langen Liste weiterer. Die Spezifikation unterstützt verschachtelte Dateien; dabei gewinnt die, die der editierten Datei am nächsten liegt.
Cursor liest AGENTS.md direkt, auch verschachtelte, und hat zusätzlich ein eigenes Format mit Scope. Eine auf UI-Dateien begrenzte Regel bleibt aus dem Kontext, bis eine passende Datei geöffnet ist:
---
description: i18n rules for user-visible strings
globs: src/**/*.tsx, src/**/*.jsx
alwaysApply: false
---
(paste the Internationalization section here)
Die Datei muss auf .mdc enden und in .cursor/rules/ liegen; eine einfache .md in diesem Ordner wird ignoriert, weil sie kein Frontmatter hat.
GitHub Copilot in VS Code bietet drei Optionen. .github/copilot-instructions.md gilt für jede Chat-Anfrage. Eine .github/instructions/i18n.instructions.md-Datei mit applyTo: "**/*.tsx,**/*.jsx" im Frontmatter gilt nur, wenn diese Dateien erstellt oder geändert werden. Und AGENTS.md im Stammverzeichnis des Repositorys liest der Local Agent, wenn die Einstellung chat.useAgentsMdFile aktiv ist; verschachtelte AGENTS.md-Dateien in Unterordnern hängen an einer separaten experimentellen Einstellung, chat.useNestedAgentsMdFiles, die standardmäßig deaktiviert ist. Das Copilot-spezifische Verhalten wird in wie du i18n in eine mit GitHub Copilot gebaute App einbaust behandelt.
Claude Code liest CLAUDE.md und liest seit v2.1.277 von selbst eine AGENTS.md im Root, wenn das Projekt oberhalb des Arbeitsverzeichnisses keine CLAUDE.md hat. Behältst du beide, ist das dokumentierte Muster ein @AGENTS.md-Import am Anfang der CLAUDE.md, damit die Datei nur einmal geladen wird. Fürs Scoping lädt .claude/rules/i18n.md mit einer paths:-Liste im Frontmatter nur, wenn Claude eine passende Datei liest. Den kompletten Claude-Code-Workflow findest du in wie du eine von Claude Code generierte App lokalisierst.
Die praktische Aufteilung für ein Repo, das mehrere Agents anfassen: die zehn Regeln in einer Datei mit Scope für den Agent, den dein Team am meisten nutzt, und ein Zwei-Zeilen-Verweis in AGENTS.md („UI-Strings: siehe die i18n-Regeln in .cursor/rules/i18n.mdc und befolge sie“), damit auch ein Agent ohne Scoping-Unterstützung die Anweisung bekommt.
Welche Stellen im Code-Workflow lesen AGENTS.md nie?
Inline-Vervollständigungen. Beide Hersteller dokumentieren das, und es ist der Grund, warum die Regeldatei nicht die ganze Antwort sein kann.
In der Regeln-FAQ von Cursor lautet die Antwort auf „Do rules impact Cursor Tab or other AI features?“ schlicht Nein. Regeln speisen den Agent; Tab, die Autovervollständigung, die die gerade getippte Zeile ergänzt, sieht sie nicht. Die Seite zu benutzerdefinierten Instruktionen von VS Code enthält denselben Hinweis für Copilot: Instruktionen werden bei Inline-Vorschlägen beim Tippen im Editor nicht berücksichtigt.
Die Oberfläche, auf der jemand <p>No results found</p> tippt und den Ghost Text akzeptiert, ist also genau die, die die Regeln nie erreichen. Chat und Agent-Modus vergeben einen Schlüssel für den String; die Vervollständigung, die die Zeile beendet hat, während du an etwas anderes dachtest, tut das nicht. Über eine normale Arbeitswoche sammelt die Codebase Literale über den einen Pfad, den die Instruktionsdatei nicht abdeckt – und der Entwickler, der eine sorgfältige Regeldatei geschrieben hat, nimmt an, der Agent ignoriere sie.
Claude Code hat keine Inline-Vervollständigung, aber seine Dokumentation macht denselben Punkt von der anderen Seite: CLAUDE.md-Inhalt ist Kontext, keine erzwungene Konfiguration, und um eine Aktion unabhängig von der Entscheidung des Modells zu blockieren, nutzt du einen Hook. Das ist das richtige Denkmodell für jedes Tool hier. Instruktionsdateien verschieben Wahrscheinlichkeiten. Sie erzwingen nichts.
Was setzt die Regel tatsächlich durch?
Eine Lint-Regel in der CI – und sie ist ein Einzeiler, sobald die Regeldatei dem Agent ohnehin sagt, sie auszuführen. eslint-plugin-i18next liefert no-literal-string mit; aktiviere sie für deine Komponentenverzeichnisse, und ein Literal in JSX lässt den Build fehlschlagen, statt nur eine höfliche Erinnerung zu erzeugen. Das Setup, die Optionen der Regel und warum Instruktion und Durchsetzung zwei verschiedene Ebenen sind, steht in warum Cursor nach dem i18n-Setup weiter hartcodierte Strings ergänzt, daher wiederholt dieser Post es nicht.
Zwei Dinge liefert dir die Lint-Ebene, die die Regeldatei nicht kann. Sie fängt die Ausgabe der Inline-Vervollständigung ab, weil sie auf der Datei läuft, nicht auf der Konversation. Und sie macht den eigenen Loop des Agents zum Fix: Die letzte Regel im Block oben sagt dem Agent, vor dem Abschluss Lint auszuführen, und ein Agent, der no-literal-string fehlschlagen sieht, vergibt den Schlüssel für den String noch in derselben Session.
Speziell für Claude Code ist ein PreToolUse- oder Post-Edit-Hook, der den Linter auf die gerade geschriebene Datei laufen lässt, der Durchsetzungsmechanismus, auf den die Docs verweisen. Hooks laufen als Shell-Befehle an festen Punkten und gelten unabhängig davon, ob das Modell die Regel befolgen wollte.
Was passiert mit den Strings, die trotzdem durchkommen?
Sie müssen extrahiert, gegen die bestehenden Namespaces mit Schlüsseln versehen und übersetzt werden – und genau das ist der Teil, den man besser automatisiert, statt erneut zu prompten. Ein roter Build zeigt dir, dass ein Literal existiert. Jemand muss daraus noch einen Schlüssel machen, ihn in en eintragen und in alle anderen Locales bringen.
Auf dieser Ebene sitzt globalize.now. Die Konvertierung passiert einmalig in der App: Verbinde das Repository, und die Codebase wird einmal konvertiert, der Katalog kommt als Pull Request, den du reviewst. Danach übersetzen Push-Jobs neue Katalogeinheiten, sobald sie landen – ein Schlüssel aus dem PR vom Dienstag hat seine Übersetzungen im PR vom Mittwoch. Runtime-Bibliothek, Katalogformat und die Regeldatei oben gehören alle dir; die Entwicklerreferenz beschreibt, wie Agents in Cursor, Claude Code, Codex und Copilot die Rolle nach dem Setup übernehmen, und die Cursor-Integration ist die kürzeste Anleitung.
Zwei verwandte Fehlerbilder haben eigene Beiträge. Wenn das Problem darin besteht, dass der Agent Texte umformuliert, die bereits einen Schlüssel haben, ist das wie du verhinderst, dass KI-Agents deine UI-Texte umschreiben – die Lösung ist dieselbe Source-Locale-Disziplin wie bei Regel neun oben. Wenn die Locales zwar existieren, aber immer weiter auseinanderlaufen, erklärt warum Übersetzungsdateien aus dem Sync laufen den Routing-Fehler dahinter.
Wo fängst du an?
Füg den Block ein, begrenze den Scope auf deine UI-Verzeichnisse und aktiviere no-literal-string noch am selben Nachmittag in der CI. Beobachte dann eine Woche, was die Lint-Regel fängt; das ist dein Maß dafür, wie viel die Regeldatei von allein je bewirkt hätte. Wenn du eine KI-gebaute App releast und die Extraktion und Übersetzung von allem, was trotzdem durchrutscht, abgenommen statt gepflegt haben willst, ist die Vibe-Coder-Seite die Übersicht, 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