IntlPull
Tutorial
20 min read

Next.js 15 i18n : Guide Complet de l'Internationalisation 2026

Maîtrisez l'internationalisation Next.js 15 avec ce guide complet. App Router, Server Components, next-intl, routage et meilleures pratiques de production.

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

Maîtrisez l'internationalisation Next.js 15 avec ce guide complet. App Router, Server Components, next-intl, routage et meilleures pratiques de production.

Réponse Rapide

Pour ajouter l'internationalisation (i18n) à Next.js 15, utilisez next-intl avec l'App Router. Installez avec npm install next-intl, créez un dossier [locale] dans votre répertoire app, configurez le middleware pour la détection de locale, et utilisez getTranslations() dans les Server Components ou useTranslations() dans les Client Components. Les Server Components de Next.js 15 éliminent le gonflement du bundle de traduction côté client en rendant les traductions sur le serveur.


J'ai passé la majeure partie du mois dernier à migrer le site e-commerce d'un client de Next.js 13 Pages Router vers Next.js 15 App Router. Le site devait supporter 12 langues à travers l'Europe et l'Amérique Latine. Ce que j'ai appris en cours de route, c'est que l'i18n dans Next.js 15 est vraiment mieux qu'avant, mais il y a encore plein de façons de se tirer une balle dans le pied.

C'est le guide que j'aurais aimé avoir quand j'ai commencé.

Ce Qui a Vraiment Changé dans Next.js 15

Soyons honnêtes : si vous venez du Pages Router, l'App Router ressemble à un cadre différent. La bonne nouvelle ? Les Server Components changent tout sur la façon dont nous gérons les traductions.

Voici ce qui a fait tilt pour moi :

Avec l'ancienne approche, chaque clé de traduction devait être envoyée au client. Vos utilisateurs japonais téléchargeaient des chaînes espagnoles, allemandes et françaises qu'ils ne verraient jamais. Avec les Server Components, les traductions peuvent rester entièrement sur le serveur. Le client reçoit seulement le HTML rendu.

Ce Que Vous ObtenezPages RouterApp Router
Traductions rendues serveurEn quelque sorteOui, correctement
StreamingBricoléIntégré
Layouts imbriqués par localeTravail manuelFonctionne simplement
Taille bundle traductionToutes les languesLangue actuelle seulement

La partie streaming m'a surpris. J'avais une page avec 200+ clés de traduction, et au lieu d'attendre tout, la coquille rend immédiatement tandis que les traductions arrivent en flux continu.

Choisir une Bibliothèque (J'ai des Avis)

J'ai utilisé les trois options majeures en production. Voici mon avis honnête :

next-intl est ce que je prends maintenant. Il a été conçu spécifiquement pour l'App Router dès le départ. L'expérience développeur est excellente, le support TypeScript est de premier ordre, et le bundle est minuscule. Le mainteneur, Jan Amann, est incroyablement réactif aux problèmes.

react-i18next est la norme de l'industrie pour une raison. Si votre équipe le connaît déjà, il y a de la valeur là-dedans. Mais le faire fonctionner correctement avec les Server Components nécessite une configuration supplémentaire qui donne toujours l'impression de se battre contre le framework.

next-translate est l'option la plus légère. Je la considérerais pour un site marketing avec une interactivité minimale. Pour tout ce qui a des formulaires, du contenu dynamique ou une pluralisation complexe, vous atteindrez rapidement les limites.

next-intlreact-i18nextnext-translate
Support Server ComponentNatifNécessite wrapperNatif
Courbe d'apprentissageFaibleMoyenneFaible
PluralisationFormat ICUICU avec pluginBasique seulement
Taille bundle~2KB~8KB~1.5KB

Pour ce guide, j'utilise next-intl. Si vous commencez à neuf, je recommanderais la même chose.

Configurer les Choses (La Bonne Façon)

Installation

Terminal
npm install next-intl

Structure de Dossier

C'est là que j'ai merdé initialement. J'ai essayé d'être malin avec la structure de dossier et ça s'est retourné contre moi. Tenez-vous en à ceci :

/messages
  /en.json
  /es.json
  /de.json
/app
  /[locale]
    /layout.tsx
    /page.tsx
    /(auth)
      /login/page.tsx
/i18n
  /config.ts
  /request.ts
/middleware.ts

Fichiers de Traduction

Gardez vos fichiers JSON organisés par fonctionnalité, pas par page. J'ai appris cela à la dure quand nous avions 40 pages et que les fichiers sont devenus ingérables.

JSON
1{
2  "common": {
3    "buttons": {
4      "submit": "Soumettre",
5      "cancel": "Annuler",
6      "save": "Sauvegarder les changements"
7    },
8    "errors": {
9      "required": "Ce champ est requis",
10      "network": "Quelque chose s'est mal passé. Veuillez réessayer."
11    }
12  },
13  "checkout": {
14    "title": "Complétez votre commande",
15    "shipping": "Adresse de livraison",
16    "payment": "Méthode de paiement"
17  }
18}

Les Fichiers de Config

Créez i18n/config.ts:

TypeScript
export const locales = ['en', 'es', 'de', 'fr', 'ja'] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = 'en';

Et i18n/request.ts:

TypeScript
1import { getRequestConfig } from 'next-intl/server';
2import { notFound } from 'next/navigation';
3import { locales } from './config';
4
5export default getRequestConfig(async ({ locale }) => {
6  if (!locales.includes(locale as any)) notFound();
7
8  return {
9    messages: (await import(`../messages/${locale}.json`)).default
10  };
11});

Middleware

C'est là que la détection de locale se produit. Le middleware s'exécute à chaque requête et détermine quelle langue montrer.

TypeScript
1import createMiddleware from 'next-intl/middleware';
2import { locales, defaultLocale } from './i18n/config';
3
4export default createMiddleware({
5  locales,
6  defaultLocale,
7  localePrefix: 'always'
8});
9
10export const config = {
11  matcher: ['/((?!api|_next|.*\\..*).*)']
12};

Une chose qui m'a fait trébucher : cette regex matcher. Si vous avez un dossier /public avec des images, assurez-vous que votre matcher n'essaie pas de localiser ces chemins. La partie .*\\. gère les fichiers avec extensions, mais vérifiez bien si vous avez des ressources sans extension.

Configuration du Layout

Votre layout racine à app/[locale]/layout.tsx:

TypeScript
1import { NextIntlClientProvider } from 'next-intl';
2import { getMessages } from 'next-intl/server';
3import { notFound } from 'next/navigation';
4import { locales } from '@/i18n/config';
5
6export default async function LocaleLayout({
7  children,
8  params: { locale }
9}: {
10  children: React.ReactNode;
11  params: { locale: string };
12}) {
13  if (!locales.includes(locale as any)) {
14    notFound();
15  }
16
17  const messages = await getMessages();
18
19  return (
20    <html lang={locale}>
21      <body>
22        <NextIntlClientProvider messages={messages}>
23          {children}
24        </NextIntlClientProvider>
25      </body>
26    </html>
27  );
28}

Utiliser Réellement les Traductions

Server Components

C'est la partie la plus propre. Pas de hooks, pas de contexte, juste async/await :

TypeScript
1import { getTranslations } from 'next-intl/server';
2
3export default async function ProductPage() {
4  const t = await getTranslations('product');
5
6  return (
7    <div>
8      <h1>{t('title')}</h1>
9      <p>{t('description')}</p>
10    </div>
11  );
12}

Client Components

Quand vous avez besoin d'interactivité :

TypeScript
1'use client';
2
3import { useTranslations } from 'next-intl';
4
5export function AddToCartButton() {
6  const t = useTranslations('product');
7
8  return (
9    <button onClick={handleAddToCart}>
10      {t('addToCart')}
11    </button>
12  );
13}

Variables et Pluralisation

C'est là que le format ICU brille. Dans votre JSON :

JSON
1{
2  "cart": {
3    "items": "{count, plural, =0 {Votre panier est vide} one {# article dans le panier} other {# articles dans le panier}}",
4    "total": "Total : {price, number, ::currency/USD}"
5  }
6}

Et dans votre composant :

TypeScript
t('cart.items', { count: 3 })  // "3 articles dans le panier"
t('cart.total', { price: 99.99 })  // "Total : 99,99 $"

La syntaxe plurielle ICU semble intimidante au début, mais elle gère des cas limites auxquels vous ne penseriez jamais. Le russe a différentes formes plurielles pour les nombres finissant par 1, 2-4, et 5-20. ICU gère cela automatiquement.

Astuce : Utilisez notre Éditeur de Messages ICU gratuit pour construire et tester visuellement la syntaxe de pluralisation ICU avant de l'ajouter à vos fichiers de traduction.

Choses Que J'aurais Aimé Qu'on Me Dise Plus Tôt

Le Problème d'Inadéquation d'Hydratation

Si vous voyez des erreurs d'hydratation avec des dates ou des nombres, c'est parce que le serveur a rendu avec une locale mais le client a initialisé avec une autre. La correction :

TypeScript
1// Dans layout.tsx
2<NextIntlClientProvider
3  messages={messages}
4  timeZone="Europe/Berlin"  // Soyez explicite
5  now={new Date()}          // Passez l'heure serveur
6>

N'oubliez Pas generateStaticParams

Sans cela, vos pages locale ne seront pas générées statiquement :

TypeScript
export function generateStaticParams() {
  return locales.map((locale) => ({ locale }));
}

J'ai manqué cela sur un déploiement et je me demandais pourquoi mon site était lent. Chaque page était rendue par le serveur à la demande.

Les Trucs SEO Qui Comptent Vraiment

Les balises Hreflang sont cruciales. Google a besoin de connaître vos versions linguistiques :

TypeScript
1export async function generateMetadata({ params: { locale } }) {
2  const t = await getTranslations('meta');
3
4  return {
5    title: t('title'),
6    alternates: {
7      canonical: `https://example.com/${locale}`,
8      languages: {
9        'en': 'https://example.com/en',
10        'es': 'https://example.com/es',
11        'de': 'https://example.com/de',
12        'x-default': 'https://example.com/en'
13      }
14    }
15  };
16}

Ce x-default est facile à oublier mais important pour les utilisateurs dont vous ne supportez pas la langue.

Construire un Sélecteur de Langue

TypeScript
1'use client';
2
3import { useLocale } from 'next-intl';
4import { usePathname, useRouter } from 'next-intl/client';
5
6export function LanguageSwitcher() {
7  const locale = useLocale();
8  const pathname = usePathname();
9  const router = useRouter();
10
11  const switchLocale = (newLocale: string) => {
12    router.replace(pathname, { locale: newLocale });
13  };
14
15  return (
16    <select value={locale} onChange={(e) => switchLocale(e.target.value)}>
17      <option value="en">English</option>
18      <option value="es">Espanol</option>
19      <option value="de">Deutsch</option>
20    </select>
21  );
22}

Un piège : usePathname de next-intl/client retourne le chemin sans le préfixe locale. Celui de next/navigation l'inclut. J'ai mélangé ces deux plus de fois que je ne voudrais l'admettre.

Stratégies d'URL

Vous avez trois options :

Toujours montrer le préfixe locale (/en/about, /es/about) : Le plus simple à implémenter, le plus clair pour les utilisateurs et moteurs de recherche. C'est ce que je recommande pour la plupart des sites.

Cacher la locale par défaut (/about pour Anglais, /es/about pour Espagnol) : Semble plus propre mais ajoute de la complexité. Vous devez gérer les redirections soigneusement.

Basé sur le domaine (example.com, example.es) : Meilleur pour le SEO dans différents pays, mais nécessite plus d'infrastructure.

Pour l'approche domaine, vous configureriez next-intl comme ceci :

TypeScript
1export default createMiddleware({
2  locales,
3  defaultLocale,
4  domains: [
5    { domain: 'example.com', defaultLocale: 'en' },
6    { domain: 'example.es', defaultLocale: 'es' },
7    { domain: 'example.de', defaultLocale: 'de' }
8  ]
9});

Quand les Fichiers JSON Deviennent une Douleur

Autour de 500 clés de traduction, gérer les fichiers JSON manuellement commence à faire mal. Quelques problèmes que j'ai rencontrés :

  • Traducteurs cassant accidentellement la syntaxe JSON
  • Pas de moyen de voir ce qui manque à travers les langues
  • Copier-coller de nouvelles clés dans 12 fichiers
  • Oublier de supprimer les clés effacées des autres langues

Solution rapide pour les problèmes JSON : Notre outil gratuit Comparateur de JSON vous aide à comparer les fichiers de traduction entre les langues et détecter instantanément les clés manquantes.

C'est là qu'un système de gestion de traduction aide. J'utilise IntlPull pour mes projets récents—il est conçu spécifiquement pour les workflows i18n Next.js. Le flux de travail ressemble à :

Terminal
1# Poussez vos chaînes sources
2npx @intlpullhq/cli upload
3
4# Obtenez des traductions IA (ou envoyez aux traducteurs humains)
5npx @intlpullhq/cli translate --languages es,de,fr
6
7# Récupérez tout
8npx @intlpullhq/cli download

La commande npx @intlpullhq/cli watch est pratique pendant le développement. Elle synchronise les changements au fur et à mesure que vous codez.

Pourquoi IntlPull pour les projets Next.js :

  • Intégration native next-intl - Fonctionne avec votre structure de dossiers existante
  • Traductions alimentées par IA - Obtenez des traductions instantanées avec conscience du contexte
  • Workflow compatible Git - Téléchargez les traductions directement dans votre dépôt
  • Mises à jour OTA - Mettez à jour les traductions sans redéployer
  • Niveau gratuit - Couvre la plupart des projets personnels et startups

Pour les applications qui ont besoin de mettre à jour les traductions sans redéployer, IntlPull a une fonctionnalité OTA qui récupère les traductions à l'exécution. Mais pour la plupart des projets, le workflow CLI est plus simple.

Notes sur la Performance

Les Server Components vous donnent déjà un énorme gain en gardant les traductions hors du bundle client. Au-delà de ça :

Divisez par espace de noms. Ne chargez pas toutes les 2000 clés quand une page a besoin seulement de 50. Dans votre appel getMessages:

TypeScript
const messages = await getMessages({ namespace: 'checkout' });

Utilisez Suspense pour les pages lourdes. Si vous avez une page avec beaucoup de contenu traduit :

TypeScript
<Suspense fallback={<ProductSkeleton />}>
  <ProductDetails />
</Suspense>

Ne sur-optimisez pas. J'ai vu des équipes passer des jours à raser des kilo-octets sur les bundles de traduction quand leurs images faisaient des méga-octets. Concentrez-vous sur ce qui compte.

Questions Courantes

Puis-je mélanger App Router et Pages Router pendant la migration ?

Oui, et je le recommande. Migrez page par page. next-intl peut fonctionner dans les deux, bien que la configuration diffère.

Qu'en est-il des langues RTL comme l'Arabe et l'Hébreu ?

Définissez l'attribut dir basé sur la locale :

TypeScript
1const rtlLocales = ['ar', 'he', 'fa'];
2const dir = rtlLocales.includes(locale) ? 'rtl' : 'ltr';
3
4return <html lang={locale} dir={dir}>

Vous aurez aussi besoin de CSS conscient du RTL. Des bibliothèques comme Tailwind ont des variantes RTL, ou vous pouvez utiliser des propriétés logiques (margin-inline-start au lieu de margin-left).

Comment gérer les clés de traduction dans un monorepo ?

Gardez les traductions partagées dans un paquet et importez-les. Pour les traductions spécifiques au projet, je les garde généralement dans chaque app.

Tester les composants traduits ?

Enveloppez votre composant avec le provider dans les tests :

TypeScript
1render(
2  <NextIntlClientProvider locale="en" messages={messages}>
3    <MyComponent />
4  </NextIntlClientProvider>
5);

Pour Conclure

L'App Router de Next.js 15 rend vraiment l'i18n meilleur. Les Server Components étaient la pièce manquante qui rend les traductions natives plutôt que rajoutées.

Si vous débutez juste :

  1. Configurez next-intl avec la structure de dossier ci-dessus
  2. Faites fonctionner votre routage avec le middleware
  3. Ajoutez des traductions incrémentales au fur et à mesure que vous construisez des fonctionnalités
  4. Quand vous dépassez les fichiers JSON, regardez un TMS

La configuration initiale prend peut-être une heure. Après cela, ajouter de nouvelles langues est simple.

Si vous voulez essayer IntlPull pour gérer les traductions, il y a un niveau gratuit qui devrait couvrir la plupart des projets parallèles. Mais honnêtement, les fichiers JSON fonctionnent bien jusqu'à ce qu'ils ne le fassent plus. Vous saurez quand vous aurez besoin de quelque chose de plus.

Foire Aux Questions

Quelle est la meilleure bibliothèque i18n pour Next.js 15 ?

next-intl est la meilleure bibliothèque i18n pour Next.js 15 car elle est construite spécifiquement pour l'App Router et les Server Components. Elle offre un support natif des Server Components sans wrappers, une taille de bundle minuscule de 2KB, un support TypeScript complet, et le format ICU pour la pluralisation. react-i18next fonctionne mais nécessite une configuration supplémentaire pour les Server Components.

Next.js 15 a-t-il un support i18n intégré ?

Next.js 15 fournit une infrastructure de routage pour l'i18n (URL basées sur la locale via dossiers [locale] et middleware) mais n'inclut pas de fonctionnalité de traduction. Vous avez besoin d'une bibliothèque comme next-intl, react-i18next ou next-translate pour gérer les traductions réelles. Le segment dynamique [locale] de l'App Router est la fondation pour le routage i18n.

Comment ajouter plusieurs langues à Next.js ?

Pour ajouter plusieurs langues à Next.js 15 : (1) Créez un dossier [locale] dans votre répertoire app, (2) Ajoutez des fichiers JSON de traduction dans un dossier messages (en.json, es.json, etc.), (3) Configurez le middleware pour la détection de locale et le routage, (4) Utilisez les hooks getTranslations() ou useTranslations() pour afficher les chaînes traduites. IntlPull peut automatiser la gestion de traduction et fournir des traductions IA pour les nouvelles langues.

Comment les Server Components améliorent-ils l'i18n dans Next.js ?

Les Server Components éliminent le gonflement du bundle de traduction côté client. Dans le Pages Router, tous les fichiers de traduction étaient envoyés au client (les utilisateurs téléchargeaient chaque langue). Avec les Server Components, les traductions sont rendues sur le serveur et seul le HTML est envoyé aux clients. Une page avec 200+ clés de traduction se charge maintenant instantanément car les chaînes ne s'ajoutent pas au bundle JavaScript.

Quelle structure d'URL devrais-je utiliser pour les apps Next.js multilingues ?

Utilisez le préfixe locale dans les URL (/en/about, /es/about) pour la plupart des sites—c'est le plus clair pour les utilisateurs et le meilleur pour le SEO. Configurez le middleware next-intl avec localePrefix: 'always'. Pour le ciblage spécifique par pays, considérez le routage basé sur domaine (example.com, example.es) qui nécessite une configuration DNS et hébergement mais fournit les signaux SEO locaux les plus forts.

Comment gérer les traductions manquantes dans Next.js ?

Configurez une langue de repli dans votre configuration i18n pour afficher le texte par défaut quand les traductions manquent. Avec next-intl, définissez fallbackLocale: 'en' dans votre config. Pour les apps en production, utilisez la détection de traduction manquante d'IntlPull pour identifier les lacunes avant déploiement et auto-remplir avec la traduction IA pendant le développement.

Tags
nextjs
nextjs-15
i18n
next-intl
app-router
react
2026
2024
IntlPull Team
IntlPull Team
Engineering

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