Parce que votre configuration i18n vit dans votre tête et dans votre config, pas dans le code que l'agent a sous les yeux. Cursor régénère un composant à partir du contexte qu'il a devant lui, et un texte JSX brut est la solution la plus rapide qui fonctionne. Rien dans le dépôt ne s'y oppose, alors le littéral part en production. globalize.now est une infrastructure de localisation propulsée par l'IA qui referme cette boucle : elle extrait les nouvelles chaînes, génère les clés, et synchronise les traductions à chaque push Git.
C'est le mode de défaillance dont personne ne vous prévient. Configurer i18n prend une après-midi. Le maintenir en place pendant qu'un agent réécrit vos composants deux fois par jour, voilà le vrai problème.
Pourquoi Cursor continue-t-il d'ajouter des chaînes codées en dur après la configuration d'i18n ?
Trois faits sont vrais simultanément, et ensemble ils garantissent la fuite.
L'agent voit un fichier, pas une convention. Quand vous demandez à Cursor d'ajouter un panneau de paramètres, il lit le composant qu'il modifie et peut-être quelques voisins. Votre répertoire de locales, votre schéma de namespaces, et la règle selon laquelle toute chaîne visible par l'utilisateur passe par une fonction de traduction ne sont pas dans cette fenêtre, sauf si vous les y mettez.
Le texte non traduit est le choix statistique par défaut. L'écrasante majorité du code React présent dans les données d'entraînement de n'importe quel modèle écrit les chaînes en ligne. Quand l'agent choisit le token suivant le plus probable pour un libellé de bouton, Save changes l'emporte sur t('settings.save') par pure fréquence. Vous lui demandez de choisir le schéma le moins courant à chaque fois, sans aucun retour s'il ne le fait pas.
Rien ne casse. C'est le facteur décisif. Une chaîne codée en dur passe le typage. Elle compile. Elle s'affiche. Elle passe vos tests. Aucun voyant rouge nulle part dans votre pipeline pour signaler qu'un utilisateur en Allemagne est sur le point de voir un toast en anglais. Le défaut reste invisible jusqu'à ce que quelqu'un change de locale et fasse défiler la page.
Comparez cela à un import manquant ou à un mauvais type de prop, que l'agent voit corrigé en quelques secondes. Le texte non traduit est la seule catégorie de bug que votre chaîne d'outils accepte silencieusement, c'est donc la seule catégorie de bug qui s'accumule.
Quelles chaînes fuient le plus souvent ?
Celles ajoutées en dernier, sous la surveillance la plus faible.
Parcourez n'importe quelle base de code assistée par IA qui a démarré multilingue, et l'anglais qui a survécu se trouve dans des endroits prévisibles :
- Les toasts d'erreur et de succès, surtout ceux ajoutés en corrigeant un bug sans rapport
- Les états vides et les textes de chargement, écrits une fois et jamais relus
- Les messages de validation de formulaire, souvent générés en ligne dans un schéma
- Les attributs d'accessibilité :
aria-label,alt,title - Les boîtes de dialogue de confirmation et les textes d'actions destructrices
- Tout ce qui se trouve dans un bloc
catch
Le schéma est constant. Vos parcours principaux sont traduits parce que vous les regardez. Les cas marginaux restent codés en dur parce que personne ne les ouvre dans une autre locale.
Il existe aussi une variante de second ordre. Quand un agent ajoute un littéral à côté de code déjà traduit, il lui arrive d'inventer « utilement » une clé qui ne correspond pas à votre schéma de namespaces, et vous vous retrouvez avec errors.saveFailed dans un fichier et settings_save_error dans un autre. Vous cumulez alors chaînes non traduites et espace de clés fragmenté. Si vous en êtes plus tôt dans ce parcours, notre guide sur la localisation d'une application générée par IA couvre le nettoyage initial.
.cursor/rules ou AGENTS.md peuvent-ils empêcher cela ?
Ils aident, et vous devriez en rédiger un, mais il s'agit de recommandations, pas de mécanismes contraignants.
La couche des fichiers d'instructions s'est standardisée récemment. AGENTS.md a débuté comme convention neutre en 2025 et est désormais géré par l'Agentic AI Foundation sous l'égide de la Linux Foundation. Il est lu nativement par Cursor, Codex, Copilot, Gemini CLI, Aider, Windsurf et Zed, ce qui signifie qu'un seul fichier à la racine de votre dépôt touche tous les agents que votre équipe utilise. Cursor prend aussi en charge des règles de projet ciblées sous forme de fichiers .mdc dans .cursor/rules/, avec un frontmatter contrôlant la description, les globs de fichiers, et si la règle s'applique toujours. La configuration pratique en 2026 consiste en un AGENTS.md à la racine pour la portabilité, plus des règles Cursor ciblées là où l'auto-attachement se justifie.
Un bloc i18n exploitable ressemble à ceci :
## Internationalization
- Never write user-facing text as a literal in JSX or in a string passed to a UI component.
- All user-facing text goes through the translation function from `next-intl`.
- Keys follow `feature.element.state` in lowerCamelCase, e.g. `settings.save.error`.
- Add the English value to `messages/en.json` only. Never hand-edit other locale files.
- This includes toasts, empty states, validation messages, `aria-label`, `alt` and `title`.
Cela réduira nettement le taux de fuite. Cela ne le ramènera pas à zéro, car les fichiers de règles se disputent l'attention avec tout le reste de la fenêtre de contexte, et les longues sessions d'agent dérivent. Considérez cette couche comme une réduction de fréquence, pas comme une garantie.
Comment faire échouer le build pour une chaîne non traduite ?
Ajoutez une règle de lint qui traite un littéral en JSX comme une erreur, puis exécutez-la en intégration continue.
Le choix standard est la règle no-literal-string de eslint-plugin-i18next. Par défaut, elle valide le texte brut dans le balisage JSX plutôt que chaque littéral du fichier, ce qui la rend utilisable sur une vraie base de code plutôt que noyée sous les faux positifs. Elle prend en charge des motifs d'exclusion par expression régulière, des exclusions de callee pour les fonctions que vous savez sûres, et un mode markup uniquement.
Une configuration de départ :
// eslint.config.js
import i18next from 'eslint-plugin-i18next';
export default [
{
files: ['**/*.{jsx,tsx}'],
plugins: { i18next },
rules: {
'i18next/no-literal-string': [
'error',
{
markupOnly: true,
ignoreAttribute: ['data-testid', 'className'],
ignore: ['^[0-9]+$', '^\\s*$'],
},
],
},
},
];
Exécutez-la dans votre pipeline, pas seulement dans l'éditeur :
npx eslint . --max-warnings=0
Le défaut invisible a désormais un voyant rouge. L'agent reçoit la même boucle de retour pour une chaîne non traduite que pour une erreur de type, ce qui lui permet de corriger sa propre erreur dans la même session plutôt que de la laisser à un utilisateur d'une autre locale. Pour une vue plus large de la mise en place d'i18n dans un projet Cursor depuis zéro, consultez notre guide Cursor et Next.js en 15 minutes et la page d'intégration Cursor.
Pourquoi une règle de lint qui échoue ne suffit-elle pas ?
Parce qu'un build rouge vous dit qu'une chaîne existe. Il ne la traduit pas.
Une fois la règle déclenchée, quelqu'un doit encore choisir une clé cohérente avec vos namespaces existants, ajouter la valeur source en anglais, et produire les autres locales. Sur un projet en solo livré plusieurs fois par jour, ce travail est reporté. Reporté assez longtemps, la correction honnête devient l'ajout du fichier à une liste d'exclusion, et vous voilà de retour au point de départ, avec de la config en plus.
C'est cet écart qui transforme un problème résolu en dette récurrente. Les couches une et deux changent la fréquence d'apparition du problème et la rapidité avec laquelle vous le découvrez. Aucune des deux ne fait le travail.
À quoi ressemble la configuration à trois couches ?
Instruction, contrainte, automatisation. Chaque couche rattrape ce que la précédente a manqué.
Couche une, instruction. Écrivez le bloc i18n dans AGENTS.md à la racine de votre dépôt, et ajoutez un .cursor/rules/i18n.mdc ciblé avec des globs pour vos répertoires de composants. Coût : quinze minutes, une fois. Effet : moins de littéraux écrits dès le départ.
Couche deux, contrainte. Activez no-literal-string et faites échouer l'intégration continue dessus. Coût : une après-midi à ajuster les motifs d'exclusion sur une base de code existante. Effet : aucun littéral n'atteint votre branche principale sans être remarqué, et l'agent peut s'auto-corriger.
Layer three, automation. Let the extraction, keying and translation happen without you. This is the layer globalize.now occupies. It sits above your i18n runtime rather than replacing it: next-intl or i18next still renders the strings. globalize.now detects the new hardcoded text, generates keys consistent with the namespaces already in your project, translates them across every configured locale, and commits the updated locale files back on every Git push. Set it up once — connect the repository at globalize.now — and there is no manual step to skip. You can also install it into your agent with:
npx skills add globalize-now/globalize-skills
Les forfaits commencent à 5 € par mois. Le forfait Starter à 20 € inclut 20 € de crédit de traduction par mois — soit environ 200 000 mots —, puis l’utilisation au-delà du volume inclus est facturée au même tarif par caractère. Aucun frais par siège ni par langue. L’inscription inclut 5 € de crédit de traduction, sans carte bancaire. Les détails pour les ingénieurs sont disponibles sur la page développeurs, et la version courte destinée aux personnes qui développent avec des outils d’IA se trouve sur la page vibe coders.
L'intérêt des trois couches, c'est qu'aucune ne dépend de votre discipline tenant sur une centaine de sessions d'agent. Le fichier de règles réduit le taux, la règle de lint rend les échecs visibles, et la synchronisation rend la correction gratuite. C'est cette combinaison qui garde une application multilingue pendant qu'un agent continue de la réécrire.
Votre configuration i18n n'est pas le maillon fragile. C'est la boucle entre le moment où l'agent écrit une chaîne et celui où cette chaîne atteint un fichier de traduction. globalize.now referme cette boucle à chaque push.
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