IntlPull
Technical
18 min read

Traductions i18n Next.js qui ne fonctionnent pas ? Guide de dépannage complet (2026)

Résolvez les problèmes courants d'internationalisation Next.js : traductions manquantes, t() qui retourne les clés, erreurs de fichiers JSON, problèmes de middleware, erreurs d'hydratation, et plus encore. Guide de débogage pour next-intl et react-i18next.

IntlPull Team
IntlPull Team
03 Feb 2026, 11:44 AM [PST]
On this page
Summary

Résolvez les problèmes courants d'internationalisation Next.js : traductions manquantes, t() qui retourne les clés, erreurs de fichiers JSON, problèmes de middleware, erreurs d'hydratation, et plus encore. Guide de débogage pour next-intl et react-i18next.

Réponse rapide

Si vos traductions Next.js ne fonctionnent pas, vérifiez dans cet ordre : (1) La clé de traduction existe dans le fichier JSON avec la bonne imbrication, (2) Le fichier JSON est valide (pas de virgule finale), (3) La locale est correctement détectée dans le middleware, (4) Le Provider/contexte englobe vos composants, (5) Vous utilisez le bon hook (Composant Serveur vs Client). 90% des problèmes sont des fautes de frappe dans les clés ou des entrées JSON manquantes.


J'ai débogué des centaines de configurations i18n sur des projets React et Next.js. La bonne nouvelle ? Les bugs de traduction correspondent presque toujours à une dizaine de problèmes prévisibles. La mauvaise nouvelle ? Ils peuvent être incroyablement frustrants à diagnostiquer quand on ne sait pas où chercher.

Ce guide couvre tous les problèmes de traduction que j'ai rencontrés en production, organisés du plus fréquent au plus rare. Je me concentre sur Next.js 14/15 avec App Router, mais la plupart s'appliquent aussi au Pages Router.

Problème #1 : La clé de traduction s'affiche au lieu de la traduction

Symptôme : Vous voyez common.buttons.submit à l'écran au lieu de "Envoyer"

C'est le problème numéro 1, et ça signifie généralement l'une de ces choses :

La clé n'existe pas dans votre JSON

JSON
1// ❌ Votre code utilise : t('common.buttons.submit')
2// Mais votre en.json contient :
3{
4  "common": {
5    "button": {  // Notez : "button" pas "buttons"
6      "submit": "Envoyer"
7    }
8  }
9}

Solution : Vérifiez soigneusement la structure de votre JSON. Utilisez un IDE avec aperçu du chemin JSON ou un outil comme IntlPull qui valide les clés.

Vous utilisez le mauvais namespace

TSX
1// ❌ Incorrect : cherche dans le namespace par défaut
2const t = useTranslations();
3t('checkout.title');
4
5// ✅ Correct : spécifiez le namespace
6const t = useTranslations('checkout');
7t('title');

Le fichier JSON n'est pas chargé

Vérifiez votre configuration i18n :

TypeScript
1// i18n/request.ts pour next-intl
2import { getRequestConfig } from 'next-intl/server';
3
4export default getRequestConfig(async ({ locale }) => ({
5  messages: (await import(`../messages/${locale}.json`)).default
6}));

Vérifiez que le chemin du fichier est correct. Une erreur fréquente est de mettre les messages dans /public/locales alors que votre config attend /messages.


Problème #2 : Syntaxe JSON invalide

Symptôme : L'app plante avec "Unexpected token" ou les traductions échouent silencieusement

JSON est strict. Ces erreurs casseront tout :

JSON
1// ❌ Virgule finale (le plus courant)
2{
3  "welcome": "Bonjour",
4  "goodbye": "Au revoir",  // <-- Cette virgule casse tout
5}
6
7// ❌ Guillemets simples
8{
9  'welcome': 'Bonjour'  // Doit utiliser des guillemets doubles
10}
11
12// ❌ Guillemets non échappés dans les valeurs
13{
14  "message": "Cliquez "ici" pour continuer"  // Nécessite échappement
15}
16
17// ✅ Correct
18{
19  "welcome": "Bonjour",
20  "goodbye": "Au revoir",
21  "message": "Cliquez \"ici\" pour continuer"
22}

Solution : Utilisez un validateur JSON. VS Code surligne les erreurs de syntaxe. Exécutez cat en.json | python -m json.tool pour valider en ligne de commande.


Problème #3 : Traduction manquante pour une locale spécifique

Symptôme : L'anglais fonctionne, mais l'espagnol/allemand/etc. affiche les clés

Vous avez ajouté la clé dans en.json mais oublié les autres locales :

/messages
  en.json  ✅ Contient "checkout.newFeature": "Try our new feature"
  es.json  ❌ Cette clé manque complètement
  de.json  ❌ Cette clé manque complètement

Solution : Utilisez un outil de gestion de traduction comme IntlPull qui suit les traductions manquantes par locale. Ou configurez un fallback :

TypeScript
1// Configuration next-intl avec fallback
2export default getRequestConfig(async ({ locale }) => ({
3  messages: {
4    ...(await import(`../messages/en.json`)).default, // Fallback
5    ...(await import(`../messages/${locale}.json`)).default
6  }
7}));

Problème #4 : Le middleware ne détecte pas la locale

Symptôme : Affiche toujours la langue par défaut, la locale dans l'URL est ignorée

TypeScript
1// ❌ Le middleware ne s'exécute pas sur vos routes
2export const config = {
3  matcher: ['/api/:path*']  // Ne matche que les routes API !
4};
5
6// ✅ Matcher correct pour i18n
7export const config = {
8  matcher: ['/((?!api|_next|.*\\..*).*)']
9};

Vérifiez aussi l'emplacement de votre fichier middleware - il doit être à middleware.ts à la racine du projet, pas dans /app ou /src/app.

Vérifier la logique de détection de locale

TypeScript
1// middleware.ts
2import createMiddleware from 'next-intl/middleware';
3
4export default createMiddleware({
5  locales: ['en', 'es', 'de', 'fr'],
6  defaultLocale: 'en',
7  localePrefix: 'always'  // ou 'as-needed'
8});

Astuce debug : Ajoutez un console.log dans le middleware pour voir quelle locale est détectée :

TypeScript
1export default function middleware(request: NextRequest) {
2  console.log('Locale détectée :', request.nextUrl.pathname);
3  // ... reste du middleware
4}

Problème #5 : Erreurs de désynchronisation d'hydratation

Symptôme : La console affiche "Text content does not match server-rendered HTML"

Ça arrive quand le serveur et le client rendent des traductions différentes :

Cause 1 : Utiliser un hook client dans un Composant Serveur

TSX
1// ❌ Composant Serveur utilisant un hook client
2// app/[locale]/page.tsx (Composant Serveur par défaut)
3import { useTranslations } from 'next-intl';  // Ça marche en fait
4
5export default function Page() {
6  const t = useTranslations('home');
7  return <h1>{t('title')}</h1>;  // Fonctionne avec next-intl !
8}

Attendez, ça fonctionne en fait avec next-intl parce qu'il détecte le contexte. Mais avec react-i18next :

TSX
1// ❌ Avec react-i18next dans un Composant Serveur
2'use server';
3import { useTranslation } from 'react-i18next';  // Ne marchera pas
4
5// ✅ Utilisez la fonction côté serveur
6import { getTranslations } from 'next-intl/server';
7
8export default async function Page() {
9  const t = await getTranslations('home');
10  return <h1>{t('title')}</h1>;
11}

Cause 2 : Formatage date/heure sans fuseau horaire

TSX
1// ❌ Le serveur peut être en UTC, le client en fuseau local
2{formatDate(new Date())}
3
4// ✅ Spécifiez toujours le fuseau horaire
5import { format } from 'date-fns-tz';
6{format(new Date(), 'PPP', { timeZone: userTimezone })}

Problème #6 : Le Provider n'englobe pas les composants

Symptôme : "Could not find IntlProvider" ou erreurs de contexte similaires

TSX
1// ❌ Provider manquant dans le layout
2// app/[locale]/layout.tsx
3export default function Layout({ children }) {
4  return <html><body>{children}</body></html>;
5}
6
7// ✅ Avec le provider
8import { NextIntlClientProvider } from 'next-intl';
9import { getMessages } from 'next-intl/server';
10
11export default async function Layout({ 
12  children,
13  params: { locale }
14}: { 
15  children: React.ReactNode;
16  params: { locale: string };
17}) {
18  const messages = await getMessages();
19  
20  return (
21    <html lang={locale}>
22      <body>
23        <NextIntlClientProvider messages={messages}>
24          {children}
25        </NextIntlClientProvider>
26      </body>
27    </html>
28  );
29}

Problème #7 : Les clés dynamiques ne fonctionnent pas

Symptôme : t(variableName) retourne la clé, pas la traduction

TSX
1// ❌ Variable comme clé directement
2const key = `status.${order.status}`;
3t(key);  // Certains bundlers ne peuvent pas optimiser ça
4
5// ✅ Utilisez un mapping explicite
6const statusMessages = {
7  pending: t('status.pending'),
8  shipped: t('status.shipped'),
9  delivered: t('status.delivered')
10};
11return statusMessages[order.status];
12
13// ✅ Ou utilisez t.raw() pour les clés dynamiques avec next-intl
14t(`status.${order.status}`);  // Ça marche en fait avec next-intl

Problème #8 : La pluralisation ne fonctionne pas

Symptôme : Affiche "{count, plural, one {# article} other {# articles}}" littéralement

Vous utilisez le format ICU mais la librairie ne le parse pas :

JSON
1// Votre JSON
2{
3  "items": "{count, plural, one {# article} other {# articles}}"
4}
TSX
1// ❌ Variable non passée
2t('items');
3
4// ✅ Passez la variable count
5t('items', { count: 5 });  // "5 articles"

Vérifiez que votre librairie supporte ICU

  • next-intl : Support ICU complet ✅
  • react-i18next : Nécessite le plugin i18next-icu
  • next-translate : Pluralisation basique uniquement

Problème #9 : Problèmes d'environnement/build

Les clés marchent en dev, cassent en production

TypeScript
1// ❌ Les imports dynamiques peuvent échouer au build
2const messages = await import(`@/messages/${locale}.json`);
3
4// ✅ Assurez-vous que toutes les locales sont connues statiquement
5import en from '@/messages/en.json';
6import es from '@/messages/es.json';
7
8const messages = { en, es };
9export const getMessages = (locale: string) => messages[locale];

"Module not found" après ajout d'une nouvelle locale

Après avoir ajouté un nouveau fichier de locale, redémarrez votre serveur dev. Next.js met en cache la résolution des modules.

Terminal
rm -rf .next && npm run dev

Problème #10 : Les liens/navigation perdent la locale

Symptôme : Cliquer sur des liens internes remet la langue par défaut

TSX
1// ❌ Le Link standard perd la locale
2import Link from 'next/link';
3<Link href="/about">À propos</Link>
4
5// ✅ Utilisez la navigation de next-intl
6import { Link } from '@/i18n/navigation';  // Votre navigation configurée
7<Link href="/about">À propos</Link>  // Préserve la locale automatiquement
8
9// ✅ Ou incluez manuellement la locale
10import { useLocale } from 'next-intl';
11const locale = useLocale();
12<Link href={`/${locale}/about`}>À propos</Link>

Checklist de débogage

Quand les traductions cassent, passez cette liste en revue :

VérificationCommande/Action
Syntaxe JSON valide`cat messages/en.json
La clé existeRecherchez la clé exacte dans le fichier JSON
Le fichier de locale existels messages/
Le middleware s'exécuteAjoutez console.log, vérifiez la sortie serveur
Provider en placeVérifiez layout.tsx pour IntlProvider
Import correctServeur : getTranslations, Client : useTranslations
Cache vidérm -rf .next && npm run dev

Messages d'erreur courants décryptés

ErreurSignificationSolution
Missing message: "key"Clé absente du JSONAjoutez la clé à tous les fichiers de locale
Unable to find next-intl localeLe middleware ne définit pas la localeVérifiez l'emplacement et la config de middleware.ts
Hydration failedDésynchronisation serveur/clientUtilisez le bon hook selon le type de composant
Cannot read property 't' of undefinedProvider manquantEnglobez avec IntlClientProvider
ENOENT: no such fileChemin de fichier incorrectVérifiez le chemin du dossier messages dans la config

Prévention : Évitez ces problèmes dès le départ

1. Utilisez TypeScript pour des clés type-safe

TypeScript
1// Générez des types depuis votre JSON
2// Avec next-intl, créez un fichier de types :
3type Messages = typeof import('./messages/en.json');
4declare global {
5  interface IntlMessages extends Messages {}
6}

Maintenant TypeScript signalera les clés invalides.

2. Utilisez un système de gestion des traductions

Des outils comme IntlPull font automatiquement :

  • Validation de la syntaxe JSON
  • Suivi des traductions manquantes par locale
  • Prévention des fautes de frappe avec l'autocomplétion
  • Synchronisation des clés sur toutes les locales

3. Ajoutez des vérifications CI

YAML
1# .github/workflows/i18n-check.yml
2- name: Valider les fichiers JSON
3  run: |
4    for f in messages/*.json; do
5      python -m json.tool "$f" > /dev/null || exit 1
6    done
7
8- name: Vérifier les clés manquantes
9  run: npx intlpull check --config intlpull.config.json

4. Tests d'intégration pour les parcours critiques

TypeScript
1// e2e/i18n.spec.ts
2test('la page checkout se rend dans toutes les locales', async ({ page }) => {
3  for (const locale of ['en', 'es', 'de']) {
4    await page.goto(`/${locale}/checkout`);
5    // Ne doit pas contenir de clés de traduction brutes
6    await expect(page.locator('body')).not.toContainText('checkout.');
7  }
8});

Quand utiliser la gestion des traductions

Si vous rencontrez ces problèmes régulièrement, envisagez un TMS :

ScénarioFichiers JSON DIYGestion des traductions
< 50 clés, 1-2 devs✅ Ça marcheExcessif
> 200 clés, plusieurs devs❌ Conflits de merge✅ Source unique de vérité
> 3 langues❌ Difficile à synchroniser✅ Détection des clés manquantes
Traducteurs externes❌ Cauchemar d'échange JSON✅ Workflow intégré

IntlPull (pub assumée) est conçu spécifiquement pour ça. Il s'intègre à votre workflow Git, détecte les traductions manquantes en CI, et supporte les mises à jour OTA pour ne pas avoir à redéployer pour des corrections de traduction.


Questions fréquentes

Pourquoi ma fonction t() retourne-t-elle la clé au lieu de la traduction ?

Votre clé de traduction n'existe pas dans le fichier JSON ou le namespace. Vérifiez les fautes de frappe dans la clé, vérifiez que la structure JSON correspond au chemin attendu par votre code, assurez-vous d'utiliser le bon namespace, et validez que le fichier JSON est bien chargé. C'est le problème i18n le plus courant.

Pourquoi mes traductions fonctionnent en anglais mais pas dans les autres langues ?

La clé de traduction manque dans les autres fichiers de locale. Quand vous ajoutez une nouvelle clé à en.json, vous devez aussi l'ajouter à es.json, de.json, etc. Utilisez un TMS comme IntlPull pour détecter automatiquement les traductions manquantes, ou configurez des locales de fallback dans votre config.

Comment corriger les erreurs d'hydratation i18n Next.js ?

Le serveur et le client rendent un contenu différent. Ça arrive généralement avec le formatage date/heure (le serveur est en UTC, le client en local), l'utilisation de hooks client dans des Composants Serveur, ou une désynchronisation de locale entre serveur et client. Spécifiez les fuseaux horaires explicitement, utilisez les bons hooks selon le type de composant, et assurez-vous que la locale est passée correctement.

Pourquoi mon middleware Next.js ne détecte pas la locale ?

Le pattern matcher de votre middleware ne matche pas vos routes. Assurez-vous que middleware.ts est à la racine du projet (pas dans /app), que votre matcher inclut les routes nécessaires, et que le tableau locales correspond à vos langues supportées. Ajoutez un console.log dans le middleware pour débugger ce qui est détecté.

Comment débugger les erreurs "Could not find IntlProvider" ?

Votre composant n'est pas englobé par le provider i18n. Vérifiez que NextIntlClientProvider (ou le provider de votre librairie) est dans votre app/[locale]/layout.tsx et englobe tous les composants enfants. Le provider doit recevoir les messages et la locale courante.

Pourquoi mon fichier JSON provoque des erreurs "Unexpected token" ?

Votre syntaxe JSON est invalide. Problèmes courants : virgules finales, guillemets simples au lieu de doubles, guillemets non échappés dans les valeurs, ou virgules manquantes. Passez votre JSON dans un validateur comme python -m json.tool pour trouver l'emplacement exact de l'erreur.

Comment faire fonctionner les traductions dans les Composants Serveur Next.js ?

Utilisez les fonctions de traduction côté serveur. Avec next-intl, utilisez getTranslations() de next-intl/server dans les Composants Serveur. Le hook standard useTranslations() fonctionne avec next-intl grâce à la détection de contexte, mais pour react-i18next vous avez besoin de fonctions explicites côté serveur.

Pourquoi mes traductions cassent en production mais marchent en développement ?

Les imports dynamiques peuvent échouer au build. Assurez-vous que tous les fichiers de locale existent avant le build, utilisez des imports statiques ou des imports dynamiques vérifiés, et videz le cache .next avant de builder. Les builds production optimisent les imports différemment du mode dev.

Comment éviter que la locale se perde lors de la navigation ?

Utilisez le composant Link de votre librairie i18n au lieu du Link Next.js. Avec next-intl, configurez les exports de navigation dans i18n/navigation.ts et importez Link depuis là. Sinon, incluez manuellement la locale dans le href : /${locale}/about.

Comment tester que toutes les traductions existent ?

Ajoutez des vérifications CI qui valident le JSON et détectent les clés manquantes. Validez la syntaxe JSON avec un linter, comparez les clés entre les fichiers de locale, et lancez des tests d'intégration qui vérifient que les pages n'affichent pas de clés de traduction brutes. La CLI IntlPull fournit une détection automatique des traductions manquantes.


Résumé

La plupart des problèmes i18n Next.js entrent dans des catégories prévisibles :

  1. Fautes de frappe dans les clés → Utilisez les types TypeScript et l'autocomplétion
  2. JSON invalide → Validez en CI
  3. Données de locale manquantes → Utilisez un TMS avec vérification de synchronisation
  4. Mauvais hook/contexte → Suivez les règles Composant Serveur vs Client
  5. Problèmes de middleware → Vérifiez l'emplacement du fichier et la config du matcher

L'écosystème s'est beaucoup amélioré. Avec App Router et des librairies modernes comme next-intl, l'i18n est significativement plus fiable qu'avec Pages Router. Mais ça reste du code, et le code a des bugs.

Construisez défensivement : clés type-safe, validation CI, et bons outils vous épargneront des heures de débogage.


Besoin d'aide pour gérer les traductions à l'échelle ? Commencez gratuitement avec IntlPull — détection automatique des traductions manquantes, traduction IA, et intégration next-intl fluide.

Tags
nextjs
i18n
troubleshooting
debugging
next-intl
react-i18next
localization
2026
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.