La section i18n ci-dessous est celle à coller dans AGENTS.md : dix règles courtes qui demandent à un agent de code de faire passer chaque chaîne visible par l'utilisateur dans la fonction de traduction, de l'identifier par une clé organisée par fonctionnalité, de ne jamais concaténer de fragments, de ne jamais simuler des pluriels avec un ternaire et de ne jamais toucher à un fichier de locale généré. globalize.now est une infrastructure de localisation propulsée par l'IA : nous lisons beaucoup d'i18n écrit par des agents, et la même poignée d'erreurs en explique presque la totalité. Chaque règle correspond à l'une d'elles.
La seconde moitié de cet article porte sur les limites du fichier. Deux des surfaces de code que les développeurs utilisent le plus ne le lisent jamais, et aucune formulation n'y change rien.
Que doit dire la section i18n d'AGENTS.md ?
Ce bloc. Adaptez les trois chemins de fichiers et la commande de lint à votre projet, et laissez le reste.
## 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.
Dix règles, c'est volontaire. La documentation des règles de Cursor recommande de garder les règles centrées sur les patterns que vous utilisez souvent et d'utiliser un linter plutôt que de coller un guide de style, et les conseils de Claude Code vont dans le même sens : écrire des instructions assez concrètes pour être vérifiées, et garder le fichier court parce que l'adhésion baisse à mesure qu'il grossit. Une section i18n de quarante puces est survolée.
Pourquoi ces dix règles et pas simplement « utiliser l'i18n » ?
Parce que « utiliser l'i18n », c'est exactement ce que l'agent croit déjà faire quand il écrit <Button>Save changes</Button>. Les instructions vagues sont satisfaites par la propre idée de conformité de l'agent. Chaque règle ci-dessous nomme une sortie précise et l'interdit.
Littéraux dans les attributs. Les agents apprennent que les enfants JSX doivent être traduits, puis écrivent aria-label="Close" et alt="Company logo" comme de simples chaînes, parce que les attributs ressemblent à de la configuration plutôt qu'à du contenu. Lister les attributs par leur nom comble cette lacune ; un « traduire tout » générique, non.
Concaténation. t('greeting') + ' ' + name + '!' s'affiche correctement en anglais et ne peut pas être traduit dans une langue qui place le nom en premier ou qui le décline. L'interpolation donne au traducteur une seule chaîne avec une seule variable à déplacer.
Pluriels par ternaire. count === 1 ? 'item' : 'items' est ce que nous voyons le plus souvent un agent écrire une fois l'i18n en place, et c'est faux pour toute langue ayant plus de deux formes de pluriel. La règle nomme le pattern pour que l'agent puisse le reconnaître. Si votre catalogue utilise ICU, ce qu'est ICU MessageFormat et où il casse dans les apps générées par IA détaille la syntaxe.
Fichiers de locale générés. Celle-ci existe parce que les agents sont serviables. À qui demande de corriger une coquille en allemand, un agent ouvrira locales/de.json et la modifiera, ce qui marche jusqu'à ce que le prochain job de traduction régénère le fichier à partir de la source. Lui dire que les fichiers non sources sont générés, et où se fait la vraie correction, évite toute une classe de régressions silencieuses.
Modifier en dans le même changement. Sans cela, l'agent ajoute t('checkout.summary.total') et passe à autre chose, et la clé s'affiche sous son propre nom jusqu'à ce que quelqu'un le remarque. L'entrée du catalogue et son premier usage appartiennent au même diff.
Où chaque outil lit-il réellement le fichier ?
Le même contenu va à des endroits différents selon l'agent, et l'emplacement décide si la règle est toujours en contexte ou chargée seulement quand elle compte.
AGENTS.md à la racine du dépôt est l'emplacement partagé. Le format est du Markdown simple, sans champ obligatoire, piloté par l'Agentic AI Foundation sous l'égide de la Linux Foundation, et lu par Cursor, Codex, l'agent de code Copilot et une longue liste d'autres. Les fichiers imbriqués sont prévus par la spécification, le plus proche du fichier modifié l'emportant.
Cursor lit AGENTS.md directement, y compris imbriqués, et dispose aussi de son propre format ciblé. Une règle ciblant les fichiers d'UI reste hors du contexte tant qu'un fichier correspondant n'est pas ouvert :
---
description: i18n rules for user-visible strings
globs: src/**/*.tsx, src/**/*.jsx
alwaysApply: false
---
(paste the Internationalization section here)
Le fichier doit se terminer par .mdc et se trouver dans .cursor/rules/ ; un simple .md dans ce dossier est ignoré, faute de frontmatter.
GitHub Copilot dans VS Code offre trois options. .github/copilot-instructions.md s'applique à chaque requête de chat. Un fichier .github/instructions/i18n.instructions.md avec applyTo: "**/*.tsx,**/*.jsx" dans son frontmatter ne s'applique que lorsque ces fichiers sont créés ou modifiés. Et AGENTS.md à la racine du dépôt est lu par l'agent local quand le paramètre chat.useAgentsMdFile est activé ; les fichiers AGENTS.md imbriqués dans des sous-dossiers dépendent d'un autre paramètre expérimental, chat.useNestedAgentsMdFiles, désactivé par défaut. Le comportement propre à Copilot est détaillé dans comment ajouter l'i18n à une app construite avec GitHub Copilot.
Claude Code lit CLAUDE.md et, depuis la v2.1.277, lit de lui-même un AGENTS.md à la racine quand le projet n'a pas de CLAUDE.md au-dessus du répertoire de travail. Si vous gardez les deux, le pattern documenté est un import @AGENTS.md en haut de CLAUDE.md pour que le fichier ne soit chargé qu'une fois. Pour le ciblage, .claude/rules/i18n.md avec une liste paths: dans son frontmatter ne se charge que lorsque Claude lit un fichier correspondant. Le workflow complet de Claude Code est décrit dans comment localiser une app générée par Claude Code.
L'organisation pratique pour un repo que plusieurs agents touchent : les dix règles dans un fichier ciblé pour l'agent que votre équipe utilise le plus, et un pointeur de deux lignes dans AGENTS.md (« Chaînes d'UI : voir les règles i18n dans .cursor/rules/i18n.mdc, et les appliquer ») pour que tout agent sans gestion du ciblage reçoive quand même l'instruction.
Quelles surfaces de code ne lisent jamais AGENTS.md ?
Les complétions en ligne. Les deux éditeurs le documentent, et c'est la raison pour laquelle le fichier de règles ne peut pas être toute la réponse.
La Demande Fréquente des règles de Cursor répond par un non net à « Les règles ont-elles un impact sur Cursor Tab ou d'autres fonctionnalités d'IA ? ». Les règles alimentent Agent ; Tab, l'autocomplétion qui termine la ligne que vous tapez, ne les voit pas. La page des instructions personnalisées de VS Code porte la même note pour Copilot : les instructions ne sont pas prises en compte pour les suggestions en ligne pendant la frappe dans l'éditeur.
Ainsi, la surface où un développeur tape <p>No results found</p> et accepte le texte fantôme est exactement celle que les règles n'atteignent jamais. Le chat et le mode agent associeront une clé à la chaîne ; la complétion qui a terminé la ligne pendant que vous pensiez à autre chose, non. Sur une semaine de travail normal, le code accumule des littéraux issus du seul chemin que le fichier d'instructions ne couvre pas, et le développeur, qui a pourtant écrit un fichier de règles soigné, croit que l'agent l'ignore.
Claude Code n'a pas de surface de complétion en ligne, mais sa documentation fait le point équivalent dans l'autre sens : le contenu de CLAUDE.md est du contexte, pas une configuration appliquée, et pour bloquer une action quoi que décide le modèle, on utilise un hook. C'est le bon modèle mental pour tous les outils ici. Les fichiers d'instructions déplacent des probabilités. Ils n'imposent rien.
Qu'est-ce qui applique réellement la règle ?
Une règle de lint en CI, et c'est un ajout d'une ligne dès lors que le fichier de règles demande déjà à l'agent de l'exécuter. eslint-plugin-i18next fournit no-literal-string ; activez-la sur vos répertoires de composants et un littéral dans le JSX fait échouer le build au lieu de recevoir un rappel poli. La mise en place, les options de la règle, et pourquoi instruction et application sont deux couches distinctes sont détaillées dans pourquoi Cursor continue d'ajouter des chaînes codées en dur après la mise en place de l'i18n, donc cet article ne les répétera pas.
Deux choses que la couche lint apporte et que le fichier de règles ne peut pas apporter. Elle attrape la sortie des complétions en ligne, parce qu'elle s'exécute sur le fichier, pas sur la conversation. Et elle transforme la boucle propre à l'agent en correctif : la dernière règle du bloc ci-dessus demande à l'agent d'exécuter le lint avant de terminer, et un agent qui voit no-literal-string échouer associera lui-même une clé à la chaîne dans la même session.
Pour Claude Code en particulier, un hook PreToolUse ou post-édition qui exécute le linter sur le fichier qui vient d'être écrit est le mécanisme d'application vers lequel pointe sa documentation. Les hooks s'exécutent comme des commandes shell à des moments fixes et s'appliquent que le modèle ait choisi de suivre la règle ou non.
Que deviennent les chaînes qui passent quand même ?
Elles doivent être extraites, associées à une clé dans les namespaces existants, puis traduites, et c'est la partie qui vaut la peine d'être automatisée plutôt que de relancer des prompts. Un build rouge vous dit qu'un littéral existe. Quelqu'un doit encore le transformer en clé, l'ajouter à en et le propager dans toutes les autres locales.
C'est la couche où se place globalize.now. La conversion est ponctuelle et se fait dans l'app : connectez le dépôt, et la base de code est convertie une fois, le catalogue étant livré sous forme de pull request que vous relisez. Ensuite, des jobs de push traduisent les nouvelles unités du catalogue au fil de leur arrivée : une clé ajoutée dans la PR du mardi a ses traductions dans celle du mercredi. La bibliothèque runtime, le format du catalogue et le fichier de règles ci-dessus restent à vous ; la référence développeur explique comment les agents dans Cursor, Claude Code, Codex et Copilot assument le rôle après la mise en place, et l'intégration Cursor est le pas-à-pas le plus court.
Deux modes de défaillance voisins ont leurs propres articles. Si le problème est l'agent qui reformule du texte déjà associé à une clé, c'est comment empêcher les agents IA de réécrire le texte de votre UI, et la solution est la même discipline de locale source que la règle neuf ci-dessus. Si les locales existent mais divergent sans cesse, pourquoi les fichiers de traduction se désynchronisent explique la défaillance de routage qui en est la cause.
Par où commencer ?
Collez le bloc, ciblez-le sur vos répertoires d'UI et activez no-literal-string en CI dès cet après-midi. Observez ensuite ce que la règle de lint attrape sur une semaine ; c'est la mesure de ce que le fichier de règles aurait jamais pu faire seul. Si vous livrez une app construite par IA et voulez que l'extraction et la traduction de tout ce qui passe quand même soient prises en charge plutôt que maintenues à la main, la page vibe coders donne la vue d'ensemble et la tarification a sa propre page.
globalize.now transforme le texte codé en dur de votre application en fichiers de traduction prêts à l'emploi et les maintient à jour au fur et à mesure de vos déploiements.
Essayer globalize.now gratuitement