Communitygithub.com

i18n Web

Internationalisation et localisation de niveau production pour applications web. À utiliser pour ajouter plusieurs langues, traduire l'interface utilisateur, extraire des chaînes codées en dur, gérer les pluriels/le genre/les messages ICU, le routage localisé, les dispositions RTL, les dates/nombres/monnaies localisés, le hreflang/SEO, ou valider les catalogues de traduction. Indications indépendantes du framework pour React, Next.js, Vue, Svelte, Astro, Remix, et vanilla JS, avec des exemples pour next-intl, react-i18next, FormatJS, et Lingui.

Qu'est-ce que i18n Web ?

Cette compétence fournit une approche architecturale complète pour l'internationalisation (i18n) et la localisation d'applications web. Elle met l'accent sur l'i18n en tant qu'architecture fondamentale, et non comme un simple passage de traduction en fin de projet. Vous apprendrez à auditer une base de code existante, à choisir une stratégie de routage localisé (préfixe de chemin recommandé), à externaliser toutes les chaînes destinées à l'utilisateur (y compris les attributs ARIA, les textes alternatifs, les messages d'erreur), et à utiliser les API `Intl` natives du navigateur pour formater correctement les dates, les nombres et les monnaies. La compétence couvre également la gestion des pluriels spécifiques à chaque langue (via ICU MessageFormat), le support des langues droite-à-gauche (RTL) avec les propriétés CSS logiques, les bonnes pratiques SEO (hreflang, sitemaps multilingues), et la mise en place d'une validation CI des catalogues de traduction pour éviter les clés manquantes ou les divergences de syntaxe. Des références détaillées pour les frameworks populaires (Next.js, React, Vue, Svelte, Astro, Remix) sont disponibles dans les dossiers `references/` et `scripts/`.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Nelsutro/agent-skills/tree/HEAD/web-i18n

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait i18n Web ?

L'internationalisation est une architecture, pas un passage de traduction en fin de projet. L'objectif est une interface qui s'affiche correctement pour n'importe quelle locale sans cas particuliers : pas de phrases concaténées, pas de formats codés en dur, pas de disposition qui casse quand l'allemand est 35 % plus long ou que l'arabe change le sens d'écriture.

Quand utiliser cette compétence

À utiliser lorsque l'utilisateur demande de :

  • Ajouter une langue, un sélecteur de langue ou un routage localisé
  • Traduire une application, extraire des chaînes ou mettre en place un flux de traduction
  • Corriger la plurilisation, le genre ou les bogues du type "1 élément / 2 éléments"
  • Formater les dates, nombres, monnaies, temps relatifs ou listes par locale
  • Prendre en charge le RTL (arabe, hébreu, persan, ourdou)
  • Ajouter hreflang, métadonnées localisées ou sitemaps multilingues
  • Réviser une configuration i18n existante, des clés manquantes ou une dérive de traduction
  • Choisir entre next-intl, react-i18next, FormatJS, Lingui ou un TMS

Les références associées se trouvent dans references/ et les scripts dans scripts/. Ne lisez que ce dont la tâche a besoin. Les chemins sont relatifs au répertoire de cette compétence (l'environnement d'exécution de l'agent l'affiche lorsque la compétence est chargée).

Non-négociables

  1. Ne jamais construire une phrase en concaténant des fragments traduits. Traduisez le message entier avec des espaces réservés : "{count} nouveaux messages", pas t('nouveau') + ' ' + count + ' ' + t('messages').
  2. Ne jamais utiliser un texte anglais comme clé. Les clés sont des identifiants stables (inbox.unreadCount), afin que les modifications de copie ne cassent pas les recherches ou n'invalident pas toutes les traductions.
  3. Chaque chaîne destinée à l'utilisateur est externalisée, y compris aria-label, alt, title, placeholder, les erreurs de validation, les états vides, les toasts, les e-mails, les notifications push et la sortie PDF.
  4. Ne jamais coder les formats en dur. Pas de toFixed(2) + '$', pas de MM/DD/YYYY manuel, pas de noms de mois en anglais, pas de supposition 24h. Utilisez Intl.
  5. Une locale est une balise BCP 47, pas une langue. pt-BR et pt-PT, zh-Hans et zh-Hant, es-MX et es-ES sont des produits différents. Stockez la balise complète.
  6. L'état de la locale doit être limité à la requête côté serveur. Ne placez jamais la locale courante dans une variable mutable au niveau du module pendant le SSR ; les requêtes concurrentes fuiraient les langues les unes dans les autres.
  7. Les traductions sont des entrées non fiables si elles proviennent d'un TMS, d'une communauté ou d'un CMS. Ne les passez jamais à innerHTML/dangerouslySetInnerHTML sans les assainir.

Flux de travail

1. Auditer avant de modifier le code

  • Détectez la pile, le routeur et la bibliothèque i18n existante avant de proposer quoi que ce soit.
  • Trouvez les chaînes codées en dur :
    node scripts/scan-hardcoded.mjs src
    
    Considérez la sortie comme des candidats, pas comme une vérité absolue : ignorez les fixtures de test, les constantes, les URLs et les logs destinés aux développeurs.
  • Inventoriez chaque locale actuellement livrée et où se trouvent les catalogues.
  • Identifiez d'abord les zones à plus haut risque : authentification, paiement, erreurs, e-mails et mentions légales.

2. Décider de l'architecture (l'écrire)

Répondez à ces questions avant d'écrire du code :

DécisionValeur par défautRemarques
Stratégie de localePréfixe de chemin /es/Meilleur SEO et mise en cache. Sous-domaines/ccTLD seulement avec une raison.
Locale par défautLangue sourceNe pas rediriger automatiquement les nouveaux visiteurs vers une locale devinée sans possibilité de la remplacer.
Détection de la localeURL → cookie → Accept-Language → défautL'URL gagne afin que les liens partagés ne changent jamais de langue.
Format du catalogueJSON par locale et par espace de nomsChargez les espaces de noms à la demande ; n'expédiez jamais toutes les locales au client.
Syntaxe des messagesICU MessageFormatPortable entre bibliothèques et TMS ; prend en charge pluriel/select/ordinal.
Style de cléfonctionnalité.composant.élémentStable, recherchable, pas de texte anglais.
Source de vérité des traductionsTMS ou dépôt, pas les deuxChoisissez-en un ; synchronisez dans un sens en CI.
RepliChaîne de locales es-MX → es → enJournalisez les replis en développement pour détecter les lacunes.

3. Externaliser et structurer les messages

  • Remplacez les chaînes par des clés sémantiques et déplacez la copie vers le catalogue de la locale de base.
  • Utilisez une clé par phrase complète. Si une phrase contient un lien ou un mot en gras, utilisez l'interpolation de texte enrichi au lieu de la diviser.
  • Gardez les espaces réservés nommés ({nomUtilisateur}), jamais positionnels ({0}).
  • Ne mettez pas de HTML dans les catalogues sauf si la bibliothèque prend explicitement en charge les balises typées.
  • Regroupez par surface de produit, pas par page : paiement.json, authentification.json, erreurs.json.

Voir references/icu.md pour les règles relatives au pluriel, select, ordinal et texte enrichi.

4. Câbler le routage et le rendu localisés

  • Dérivez la locale de la route, validez-la par rapport à la liste prise en charge et renvoyez une erreur 404 pour les locales inconnues plutôt que de faire un repli silencieux.
  • Fournissez un sélecteur de langue qui préserve le chemin et la chaîne de requête courants.
  • Définissez <html lang="..."> et <html dir="..."> sur le document rendu côté serveur.
  • Chargez uniquement le catalogue de la locale active ainsi que le repli par défaut.
  • Rendez le même catalogue disponible pour le rendu côté serveur et côté client ; évitez une logique de traduction dupliquée ou divergente.

Configuration spécifique au framework et pièges : references/frameworks.md. Routage, détection, hreflang et sitemaps : references/routing-seo.md.

5. Gérer les valeurs sensibles à la locale

Utilisez Intl (ou l'enveloppe du framework), jamais de formatage manuel :

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)
  • Stockez les horodatages en UTC ; effectuez le rendu dans le fuseau horaire de l'utilisateur, pas celui du serveur.
  • La monnaie n'est pas la même chose que la locale : un utilisateur français peut payer en USD.
  • Triez/recherchez avec Intl.Collator, pas avec < ou localeCompare() sans locale.
  • Conversion de casse : toLocaleUpperCase(locale), car le i turc devient İ.

6. Concevoir pour l'expansion et le RTL

  • Supposez une augmentation de +30 à 40 % de la longueur du texte (allemand, finnois, russe) et évitez les boutons à largeur fixe ou les étiquettes sur une seule ligne.
  • Laissez les conteneurs s'agrandir, autorisez le retour à la ligne et testez avec la pseudo-localisation :
    node scripts/pseudo-localize.mjs locales/en.json --out locales/en-XA.json
    
  • Utilisez les propriétés CSS logiques (margin-inline-start, padding-inline, inset-inline) au lieu de left/right.
  • Miroirez les icônes directionnelles (flèches, chevrons, progression), pas les logos, horloges, contrôles média ou coches.
  • Encadrez le contenu en ligne généré par l'utilisateur avec <bdi> ou dir="auto".

Liste de contrôle RTL complète : references/rtl.md.

7. Mettre en place une QA de traduction dans le CI

Au minimum, échouez la build lorsque les catalogues sont structurellement invalides :

node scripts/check-locales.mjs locales --base en

Il vérifie les clés manquantes/supplémentaires, les valeurs vides, les incohérences d'espaces réservés et la syntaxe ICU invalide. Ajoutez la compilation ICU via l'interface en ligne de commande de votre bibliothèque lorsqu'elle est disponible :

npx formatjs compile-folder --ast locales/en locales/compiled/en

Ajoutez des tests pour les catégories de pluriel, la sortie de date/numéro avec un fuseau horaire fixe et le rendu RTL. Voir references/testing.md.

Décisions clés et pièges

Sélection de la bibliothèque

BesoinChoisir
Next.js App Router, composants serveurnext-intl
React SPA, écosystème mature, nombreux pluginsreact-i18next
Priorité à ICU, indépendant du framework, extraction en CLIFormatJS (react-intl)
Catalogues compilés, petite empreinte runtime, sécurité de typeLingui
Vuevue-i18n
Sveltesvelte-i18n ou paraglide
Astroroutage i18n intégré + votre bibliothèque de messages
Vanilla / minimalIntl + un chargeur de catalogue léger

N'introduisez pas une deuxième bibliothèque i18n dans une application qui en a déjà une. Étendez la configuration existante.

Plurilisation

Les catégories de pluriel sont spécifiques à la langue. L'anglais a one/other ; l'arabe a zero/one/two/few/many/other ; le japonais n'a que other. N'écrivez jamais count === 1 ? ... : ....

"inbox.unread": "{count, plural, =0 {Aucun message non lu} one {# message non lu} other {# messages non lus}}"

Genre et accord grammatical

Utilisez select sur une variable de genre et gardez la valeur de la variable neutre en termes de langue (female, male, neutral, unknown), pas des mots localisés :

"profile.greeting": "{gender, select, female {Bienvenue, {name}} male {Bienvenue, {name}} other {Bienvenue, {name}}}"

Si une langue a besoin d'un accord sur les adjectifs, les noms ou les articles, exposez le genre/cas grammatical comme argument au lieu de modifier la chaîne.

Texte enrichi

Ne divisez pas "Lisez notre " + lien + " conditions" en trois clés. Utilisez l'interpolation de balises :

"signup.terms": "J'accepte les <link>Conditions d'utilisation</link>"
t.rich('signup.terms', { link: (chunks) => <a href="/terms">{chunks}</a> })

SEO

  • Une URL indexable par locale ; ne servez jamais de contenu traduit automatiquement derrière la même URL.
  • hreflang auto-référencé plus x-default ; URLs absolues ; incluez chaque locale.
  • Localisez <title>, la méta-description, les balises OG et les données structurées.
  • Ne combinez pas hreflang avec noindex sur les pages localisées.

Détails dans references/routing-seo.md.

Mise en cache

  • La locale dans le chemin simplifie la mise en cache CDN. Si vous négociez par en-tête, envoyez Vary: Accept-Language et attendez-vous à une fragmentation du cache.
  • Ne mettez pas en cache une préférence linguistique personnalisée dans une réponse CDN partagée.
  • Maintenez Content-Language cohérent avec la locale rendue.

Liste de contrôle de révision

Avant de déclarer l'i18n terminée :

  • Aucune chaîne codée en dur destinée à l'utilisateur dans les composants, les e-mails ou les erreurs
  • Aucune concaténation de phrases ; chaque message est une unité traduisible complète
  • Les clés sont sémantiques et stables ; aucun texte anglais comme clé
  • Chaque espace réservé existe dans chaque locale ; la syntaxe ICU compile
  • Les pluriels utilisent Intl.PluralRules/ICU, pas count === 1
  • Le genre/l'accord est géré via select, pas de suffixes "/a"
  • Les dates, nombres, monnaies, temps relatifs utilisent Intl avec une locale/fuseau horaire explicite
  • <html lang> et dir sont corrects dès le premier affichage (SSR), pas définis dans un effet
  • Le sélecteur de locale préserve le chemin/la requête et fonctionne avec les liens profonds
  • Une locale inconnue renvoie 404, pas un repli silencieux
  • La disposition survit à une expansion de +35 % et au RTL
  • hreflang, canonical, sitemap et métadonnées localisées sont corrects
  • La validation du catalogue s'exécute dans le CI
  • Le comportement de repli est intentionnel et observable

Anti-patrons

  • t('bonjour') + ', ' + nom — phrases fragmentées
  • clé={texteAnglais} — texte traduit comme clé
  • count === 1 ? 'élément' : 'éléments' — logique de pluriel uniquement en anglais
  • new Date().toLocaleDateString() sans locale ni fuseau horaire
  • value.toFixed(2) pour de l'argent
  • locale === 'ar' && { direction: 'rtl' } dispersé dans les composants au lieu d'un dir au niveau du document
  • Définir <html lang> dans useEffect — provoque un flash et casse les robots d'indexation
  • Charger chaque locale dans le bundle initial
  • Redirection automatique par IP sans possibilité de la remplacer
  • Traduire des noms propres, des noms de produits, des identifiants de code ou des URLs
  • Traduire automatiquement des chaînes et les expédier sans révision
  • Supposer qu'un seul fichier de catalogue passe à l'échelle au-delà de quelques centaines de clés

Skills associés