Communitygithub.com

mwasobaddy/skills

Architecte et met en œuvre l'internationalisation pour les applications Next.js App Router — structure des dossiers, routage avec préfixe de locale, fichiers de messages typés, pluriels ICU et SEO localisé avec hreflang.

Qu'est-ce que skills ?

Cette compétence fournit des directives structurelles et des configurations prêtes à l'emploi pour ajouter la prise en charge de plusieurs langues dans une application Next.js App Router. Elle guide le choix entre les bibliothèques d'i18n (next-intl par défaut, Lingui, next-i18next), explique l'architecture canonique des dossiers (messages/ en dehors de src/, dossier i18n/ centralisé, segment racine [locale]), et détaille la configuration complète étape par étape. Elle couvre également la migration d'une application existante (répertoire des chaînes, modification des routes, redirections SEO) et met en garde contre les pièges courants comme params asynchrone, la validation des locales, l'utilisation de setRequestLocale (obsolète sur Next 16.3+), et les problèmes de formatage des dates. Des références détaillées et des scripts automatisés sont fournis pour accélérer le développement et tester le déclenchement de la compétence.

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

Demander à votre IA préférée

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

Documentation

Next.js i18n

Directives structurelles pour mettre une application Next.js en plusieurs langues : comment l'organisation des dossiers doit être, ce qui casse quand elle est incorrecte, et ce qu'il faut éviter.

Choisir d'abord la bibliothèque

Par défaut, utilisez next-intl pour tout nouveau projet sur App Router. Il ne nécessite pas de plugin de build, ni d'étape macro, ni de gestion d'instances par locale ; les messages sont en JSON simple typés à partir de votre locale source.

BibliothèqueQuand l'utiliserCoût
next-intl (défaut)Nouveau projet App Router, équipe de toute tailleAucun — JSON simple, pas de génération de code
@lingui/reactVous voulez une extraction à la compilation et un runtime minimal, ou une couche i18n commune à React/Vue/AstroPlugin macro SWC ou Babel, épinglé exactement
next-i18nextL'équipe connaît déjà i18next, ou vous migrez du code Pages RouterInstance par requête, séparation getT/useT
i18n intégré à Next.jsPages Router uniquement. Jamais App RouterNon applicable

La configuration i18n: { locales, defaultLocale } de Next.js est réservée au Pages Router. Dans App Router, elle ne fait rien — le routage des locales y nécessite un segment [locale] plus un proxy. Ne laissez pas l'agent « activer » l'i18n intégré pour un projet App Router.

Comparaison complète, y compris le coût en bundle et les compromis par bibliothèque : references/library-comparison.md.

Architecture canonique

Gardez les dictionnaires statiques en dehors de src/ pour que le bundling et les chemins d'import restent évidents, et regroupez toute la machinerie des locales dans un seul dossier.

messages/                      # dictionnaires statiques purs — hors de /src
├── en.json                     # source de vérité ; chaque autre locale le reflète
├── de.json
└── ja.json
src/
├── i18n/
│   ├── routing.ts              # defineRouting : locales + defaultLocale
│   ├── request.ts              # getRequestConfig : résoudre la locale, charger les messages
│   └── navigation.ts           # createNavigation : Link/redirect tenant compte de la locale
├── proxy.ts                    # détection de locale + redirection (Next 16 ; était middleware.ts)
└── app/
    ├── [locale]/               # englobe toute l'application localisée
    │   ├── layout.tsx          # valider la locale, <html lang>, generateStaticParams
    │   └── page.tsx
    └── not-found.tsx           # 404 global vit EN DEHORS de [locale]

Règles qui font fonctionner cette structure :

  • [locale] est un segment racine dynamique. Tout ce qui est localisé se trouve sous lui. Les routes racines (not-found.tsx, error.tsx, api/) restent à l'extérieur et ne doivent pas appeler les hooks i18n de manière inconditionnelle, sinon elles planteront à l'exécution.
  • Une seule locale source. messages/en.json est le maître. Chaque autre fichier doit refléter exactement sa structure.
  • Toute la connaissance des locales dans src/i18n/. Les composants importent Link depuis @/i18n/navigation, jamais depuis next/link. Un seul endroit pour ajouter une langue.

Configuration (projet neuf)

Exécutez dans cet ordre. L'ordre est important — proxy.ts dépend de routing.ts, et le layout dépend de request.ts.

  1. Installez next-intl, enveloppez next.config.ts avec createNextIntlPlugin().
  2. Créez src/i18n/routing.ts avec defineRouting.
  3. Créez src/proxy.ts exportant createMiddleware(routing) plus le matcher par défaut.
  4. Créez src/i18n/request.ts avec getRequestConfig résolvant la locale.
  5. Déplacez app/** sous app/[locale]/** ; ajoutez generateStaticParams et validez le paramètre.
  6. Créez src/i18n/navigation.ts avec createNavigation(routing).
  7. Créez messages/en.json et remplacez les chaînes codées en dur par useTranslations.
  8. Définissez <html lang> et ajoutez les métadonnées localisées + hreflang.

Contenu exact des fichiers pour chaque étape : references/next-intl-setup.md.

Pièges à éviter

Lisez cette section avant d'écrire du code i18n. Ce sont les échecs qui coûtent des heures.

  • setRequestLocale est obsolète. Sur Next.js 16.3+, lisez la locale avec next/root-params dans getRequestConfig à la place. La plupart des guides, tutoriels et données d'entraînement LLM enseignent encore setRequestLocale(params.locale). Cela fonctionne encore et est documenté comme rétrocompatible — mais c'est l'ancienne API. Utilisez-la uniquement sur Next < 16.3. generateStaticParams reste obligatoire dans tous les cas.
  • params est une Promise sur Next 15+. const {locale} = await params. Considérez params.locale comme une erreur de type, pas d'exécution.
  • Validez la locale ou les valeurs inconnues s'affichent. Utilisez hasLocale(routing.locales, locale) et notFound(). Sans cela, /xx/about affichera votre page anglaise à une URL factice.
  • proxy.ts, pas middleware.ts, sur Next 16. middleware.ts est déprécié. Le proxy s'exécute uniquement sur Node.js — le runtime Edge n'est pas supporté. Lingui et next-i18next annoncent des proxies compatibles Edge ; si vous avez besoin d'Edge, vous avez un véritable compromis, pas un avantage gratuit.
  • Ne concaténez jamais les chaînes traduites. "Vous avez " + count + " éléments" est grammaticalement incorrect en japonais et en arabe. Utilisez ICU : "{count, plural, =1 {Un élément} other {# éléments}}".
  • N'interpolez jamais avec + ou des littéraux de gabarit dans un message. Utilisez des espaces réservés nommés — {nom} — pour que l'ordre des mots puisse changer.
  • Les dates et les nombres provoquent des décalages d'hydratation. new Date() sur le serveur et dans le navigateur diffèrent par le fuseau horaire et l'horloge. Définissez timeZone dans getRequestConfig, ou rendez côté client uniquement pour les heures relatives. Symptôme : « Text content does not match server-rendered HTML ».
  • Le formatage dépend de la locale. toLocaleString() sans locale explicite formate selon la locale d'exécution, pas la locale demandée. Utilisez useFormatter() / getFormatter().
  • getRequestConfig s'exécute par requête et est mis en cache par React. N'y mettez pas d'état variable par requête (panier, préférences utilisateur). Seulement une configuration variant selon la locale.
  • Les Server Actions et les Route Handlers n'ont pas de paramètres racine. await locale() fonctionne uniquement dans les Server Components. Passez la locale explicitement comme argument dans les actions et les handlers.

Mise à niveau d'une application existante

Considérez cela comme une migration avec un impact sur les URL, pas un changement de configuration. Chaque URL existante change de forme lorsque vous ajoutez un préfixe [locale].

Planifiez dans cet ordre :

  1. Inventaire des chaînes — exécutez scripts/find-untranslated.sh pour trouver le texte JSX codé en dur.
  2. Choisissez la stratégie de routage avant de coder. Le préfixe de locale systématique (/en/about, /de/about) est le défaut ; il est sans ambiguïté et propre pour hreflang. localePrefix: 'as-needed' sert la locale par défaut au chemin nu — meilleures URL, signal SEO plus faible. Décidez maintenant ; changer plus tard est une migration complète.
  3. Mettez en place le routage avant de toucher à un composant. Faites fonctionner proxy.ts + [locale] avec des messages en anglais uniquement.
  4. Déplacez les routes sous [locale]/ — c'est un grand git mv mécanique. Faites-le en un seul commit pour que git log --follow reste utile.
  5. Remplacez les chaînes espace de noms par espace de noms, pas fichier par fichier. Traduisez un espace de noms de bout en bout pour que la forme des clés soit validée avant de se multiplier.
  6. Ajoutez des redirections pour chaque URL qui a changé. /about → /en/about (ou 307 vers la locale détectée). Sans cela, vous perdez tous les classements existants. Non négociable.
  7. Ajoutez hreflang + métadonnées localisées uniquement après que chaque locale a la route, sinon vous émettez des alternatives pointant vers des 404.

Séquence complète de migration avec les notes de redirection et de retour arrière : references/migration.md.

Ce qu'il faut éviter

  • Ne mettez pas messages/ dans src/. Les dictionnaires réservés au serveur dans l'arborescence source brouillent ce qui est envoyé au client.
  • N'importez pas Link depuis next/link dans les routes localisées. Cela supprime le préfixe de locale et crée silencieusement des liens vers la mauvaise langue.
  • N'écrivez pas manuellement les préfixes de locale dans les hrefs (href={/${locale}/about}). Utilisez Link depuis @/i18n/navigation, qui gère les préfixés, as-needed et le routage par domaine.
  • N'utilisez pas un seul espace de noms plat pour les clés. Regroupez par fonctionnalité/page. Les clés plates entrent en collision et sont impossibles à rechercher.
  • Ne prenez pas la traduction automatique au moment du build comme source de vérité. La sortie machine est un brouillon ; la valider sans relecture rend les erreurs permanentes et introuvables.
  • N'ajoutez pas une locale à routing.locales sans faire le reste. Sans un messages/xx.json traduit, chaque page affiche les clés ou fait un repli silencieux.
  • Ne mélangez pas les stratégies de routage. as-needed plus un hreflang pointant vers des URL préfixées est un bug SEO auto-infligé.

Références

Lisez-les à la demande, pas à l'avance :

  • references/library-comparison.md — choisir entre next-intl, Lingui, next-i18next ; coût en bundle, déclencheurs de migration, forme de configuration par bibliothèque.
  • references/next-intl-setup.md — chaque fichier de la configuration canonique, prêt à copier-coller.
  • references/messages-and-keys.md — structure des espaces de noms, nommage des clés, syntaxe ICU, formatage des dates/nombres/listes, messages typés.
  • references/seo.md — hreflang, <html lang>, generateMetadata localisé, canonique, alternatives de sitemap, règles SEO du sélecteur de locale.
  • references/migration.md — mise à niveau d'une application existante, redirections, retour arrière.

Scripts

  • scripts/find-untranslated.sh — signale les chaînes probablement codées en dur destinées aux utilisateurs dans un projet, afin que vous puissiez évaluer le travail avant de commencer. Exécutez-le comme étape 1 d'une mise à niveau.
  • scripts/run-trigger-eval.sh — mesure si la description de cette compétence se déclenche sur les bonnes invites. Utilisez-le lors de la modification de la description.

Tester la description

La description est la seule chose que l'agent voit avant de décider de charger cette compétence. Si elle ne se déclenche pas, rien de ce qui suit n'a d'importance.

bash scripts/run-trigger-eval.sh --dry-run          # afficher le plan, pas d'appels API
bash scripts/run-trigger-eval.sh --split train     # 12 requêtes x 3 exécutions
bash scripts/run-trigger-eval.sh --split validation --out report.json

Ensemble de requêtes : evals/trigger-queries.json — 12 doivent se déclencher, 8 ne doivent pas se déclencher, répartis 60/40 en un ensemble train et un ensemble validation fixes.

Lors de l'édition de la description :

  • Corrigez les échecs en utilisant uniquement l'ensemble train. Touchez l'ensemble validation une seule fois à la fin pour vérifier que le changement se généralise — si l'ensemble train s'améliore et que l'ensemble validation se dégrade, vous avez surajusté.
  • Les échecs de type « doit se déclencher » signifient que la description est trop étroite : ajoutez le concept que la requête représente, jamais les mots-clés de la requête.
  • Les échecs de type « ne doit pas se déclencher » signifient qu'elle est trop large : dites ce que la compétence ne couvre pas.
  • Réexécutez avant d'éditer. Le déclenchement est non déterministe, et une seule exécution ne prouve rien.

N'ajoutez pas de mots-clés provenant d'une requête qui échoue — c'est ainsi que les descriptions finissent par ressembler à une liste de mots-clés et cessent de correspondre aux vraies invites.

Skills associés