Implémenter l'i18n avec Claude Code : guide Next.js pratique
Méthode concrète pour ajouter l'i18n à Next.js avec Claude Code, next-intl, contrôles de traduction et pièges courants.
L’i18n commence par une décision d’architecture
i18n signifie internationalisation : préparer une application pour plusieurs langues et régions. En production, ce n’est pas seulement une liste de fichiers JSON traduits. Il faut définir les URL, la détection de langue, les metadata SEO, les formats de dates et de devises, les règles de pluriel, les liens internes et le contrôle des traductions manquantes. Claude Code est utile parce qu’il peut lire le dépôt, modifier plusieurs fichiers, lancer des commandes et expliquer les risques restants. Mais il faut lui donner une cible claire.
L’erreur que Masa a souvent vue sur des sites de contenu consiste à demander trop vite « traduis tout ». Le résultat a l’air complet, puis on découvre que les CTA, les prix, les descriptions et les URL ne suivent pas la même règle. Une approche plus robuste consiste à demander à Claude Code le flux complet : base de routage, fichiers de messages, migration des pages, script de vérification et notes de revue.
Ce guide utilise Next.js App Router avec next-intl. Pendant la revue, gardez les références officielles ouvertes : documentation Claude Code, configuration du routage next-intl, guide d’internationalisation Next.js et référence Intl de MDN. Elles évitent de copier un exemple ancien, notamment autour de proxy.ts et middleware.ts.
flowchart LR
A[Besoins] --> B[Routage]
B --> C[Messages JSON]
C --> D[Pages et metadata]
D --> E[Contrôles]
E --> F[SEO et publication]
Base Next.js App Router avec next-intl
L’exemple utilise ja comme langue par défaut et ajoute en et de. Avec Next.js 16, le fichier de proxy est proxy.ts. Dans un projet plus ancien, le même rôle peut être joué par middleware.ts; Claude Code doit vérifier la version installée avant de modifier le dépôt.
src/
app/
[locale]/
layout.tsx
page.tsx
i18n/
navigation.ts
request.ts
routing.ts
messages/
ja.json
en.json
de.json
proxy.ts
Le fichier routing.ts centralise les langues et les chemins. Cela rend les changements d’URL lisibles en pull request.
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['ja', 'en', 'de'],
defaultLocale: 'ja',
pathnames: {
'/': '/',
'/pricing': {
ja: '/pricing',
en: '/pricing',
de: '/preise',
},
'/docs': {
ja: '/docs',
en: '/docs',
de: '/dokumentation',
},
},
});
export type Locale = (typeof routing.locales)[number];
Les wrappers de navigation évitent de mélanger des liens conscients de la locale avec des liens bruts.
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
La configuration de requête lit la locale demandée, vérifie qu’elle est supportée et charge les messages correspondants.
// src/i18n/request.ts
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
Relier les clés aux vraies pages
Ne mettez pas tous les textes dans common. Ce fichier doit rester réservé aux éléments réellement partagés : navigation, sélecteur de langue, boutons génériques. Les textes d’une page doivent aller dans un namespace comme HomePage, PricingPage ou DocsPage. Un namespace est simplement un groupe de clés liées au même écran.
{
"common": {
"language": {
"label": "Langue d'affichage",
"ja": "日本語",
"en": "English",
"de": "Deutsch"
},
"nav": {
"docs": "Documentation",
"pricing": "Tarifs"
}
},
"HomePage": {
"title": "Diffusez les connaissances de l'équipe en plusieurs langues",
"lead": "Affichez {count} articles dans la langue du lecteur.",
"cta": "Réserver un échange"
}
}
Dans une page App Router, getTranslations fonctionne bien dans un Server Component et prépare aussi la traduction de generateMetadata.
// src/app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server';
type Props = {
params: Promise<{ locale: string }>;
};
export default async function HomePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: 'HomePage' });
return (
<main>
<h1>{t('title')}</h1>
<p>{t('lead', { count: 42 })}</p>
<a href={`/${locale}/pricing`}>{t('cta')}</a>
</main>
);
}
Le sélecteur de langue doit conserver le chemin courant et changer uniquement la locale. Une substitution manuelle comme remplacer /en par /fr peut casser une URL qui contient ces lettres ailleurs.
Bloquer les traductions manquantes en CI
Quand Claude Code génère des messages, les humains repèrent souvent le style maladroit mais pas toujours une clé absente. Le script suivant compare la langue de référence avec les autres fichiers.
// scripts/check-translations.mjs
import { readdir, readFile } from 'node:fs/promises';
const messagesDir = new URL('../src/messages/', import.meta.url);
const baseLocale = 'ja';
function flattenKeys(value, prefix = '') {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return [prefix];
}
return Object.entries(value).flatMap(([key, child]) => {
const nextPrefix = prefix ? `${prefix}.${key}` : key;
return flattenKeys(child, nextPrefix);
});
}
async function readMessages(locale) {
const file = new URL(`${locale}.json`, messagesDir);
return JSON.parse(await readFile(file, 'utf8'));
}
const files = await readdir(messagesDir);
const locales = files.filter((file) => file.endsWith('.json')).map((file) => file.replace(/\.json$/, ''));
const baseKeys = new Set(flattenKeys(await readMessages(baseLocale)));
let hasError = false;
for (const locale of locales.filter((item) => item !== baseLocale)) {
const targetKeys = new Set(flattenKeys(await readMessages(locale)));
const missing = [...baseKeys].filter((key) => !targetKeys.has(key));
const extra = [...targetKeys].filter((key) => !baseKeys.has(key));
if (missing.length || extra.length) {
hasError = true;
console.error(`\n${locale}.json has translation key drift`);
if (missing.length) console.error('Missing:', missing.join(', '));
if (extra.length) console.error('Extra:', extra.join(', '));
}
}
if (hasError) process.exit(1);
console.log(`Translation keys are aligned for ${locales.length} locales.`);
Ce contrôle ne juge pas la qualité littéraire. Il garantit simplement que le contrat de clés est complet. C’est suffisant pour éviter qu’un bouton ou un titre disparaisse dans une locale au moment de publier.
Trois cas d’usage concrets
Le premier cas est une page de tarifs SaaS. Elle mélange devise, taxes, période de facturation et noms de plans. Une traduction littérale ne suffit pas. Demandez à Claude Code d’utiliser Intl.NumberFormat ou les formatters de next-intl, puis relisez l’ordre naturel de la phrase pour chaque marché.
Le deuxième cas est une documentation technique. Les termes middleware, proxy, locale et namespace ne doivent pas tous être traduits. Expliquez-les simplement à la première occurrence, puis conservez le terme original pour que le lecteur puisse chercher dans la documentation officielle.
Le troisième cas est une interface d’administration. Le risque principal est l’action irréversible. Les libellés “supprimer”, “désactiver” ou “révoquer une invitation” doivent être précis. Incluez boutons, modales de confirmation, notifications et journaux d’audit dans le même système de clés.
Pour un blog multilingue, traduire le corps de l’article ne suffit pas. Title, description, canonical, liens alternatifs, image OGP et liens internes doivent être vérifiés ensemble. Documentez cette règle dans CLAUDE.md best practices pour que Claude Code applique le même processus à chaque mise à jour.
Pièges courants et résultat testé
| Piège | Effet | Solution |
|---|---|---|
| Décider les URL trop tard | Redirections confuses après publication | Choisir prefix, sous-domaine ou domaine au départ |
| Clés trop vagues | title2 et text3 deviennent impossibles à relire | Nommer par écran et par sens |
| Trop compter sur le fallback | Les traductions absentes sont masquées | Faire échouer la CI en cas de clé manquante |
| Concaténer dates et montants | Ordre et séparateurs incorrects | Utiliser Intl ou les formatters |
| Oublier metadata | Les résultats de recherche restent dans la langue par défaut | Traduire generateMetadata |
J’ai vérifié le script sur un petit jeu de messages en supprimant HomePage.cta du fichier anglais. Le script a échoué et a indiqué la clé manquante. Il ne peut pas décider si “Réserver un échange” convient culturellement à tous les marchés. La bonne répartition est donc claire : Claude Code et la CI détectent les oublis mécaniques, les humains relisent CTA, prix, juridique et nuances culturelles. Pour aller plus loin, combinez ce flux avec la checklist de revue Claude Code ou la page de consultation ClaudeCodeLab.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.