Table des matières
Si vous développez une application Next.js qui doit prendre en charge plusieurs langues, vous avez probablement déjà été confronté à un enchevêtrement de fichiers de configuration, des chaînes de middleware cassées et des traductions qui refusent de se charger dans les server components. Ce guide vous accompagne pas à pas dans la configuration de l'internationalization (i18n) dans Next.js 15 App Router avec @better-i18n/next — de zéro à prêt pour la production en moins de 30 minutes.
Pourquoi i18n dans Next.js App Router est différent
Si vous avez utilisé i18n avec le Pages Router, oubliez la plupart de ce que vous savez. L'App Router a changé la donne :
- Les Server Components sont le standard. Vous ne pouvez pas y utiliser des hooks React comme
useTranslations— vous avez besoin degetTranslationscôté serveur. - Le Middleware gère désormais la détection de locale et le routage à la place de la configuration
i18ndenext.config.js(supprimée dans Next.js 13+). - ISR (Incremental Static Regeneration) vous permet de mettre en cache les traductions en edge et de les revalider en arrière-plan — sans reconstruction complète quand les traductions changent.
- Le Streaming signifie que votre layout peut s'afficher immédiatement pendant que les traductions se chargent de manière asynchrone.
La plupart des bibliothèques i18n ont eu du mal à s'adapter. @better-i18n/next a été conçu spécifiquement pour cette architecture, livrant les traductions depuis un CDN mondial avec mise en cache ISR et une API middleware composable.
Pourquoi i18n est important
Plus de 60 % des internautes préfèrent naviguer dans leur langue maternelle. Pour les produits SaaS, les boutiques e-commerce et les plateformes de contenu développées avec Next.js, la localisation n'est pas un simple bonus — c'est un multiplicateur de croissance. Un i18n correctement implémenté améliore :
- Le référencement SEO sur les marchés non anglophones grâce aux balises
hreflanget aux URLs localisées. Une solide localization SEO strategy multiplie la valeur de chaque page traduite. - Les taux de conversion de plus de 70 % quand les utilisateurs voient le contenu dans leur langue
- La rétention des utilisateurs grâce à une expérience qui semble native
L'essor des outils developer-first a aussi accéléré l'adoption de i18n. Why developer-first localization wins in 2026 explique les forces plus larges qui poussent les équipes à délaisser les workflows centrés sur les traducteurs au profit de configurations code-native comme celle que vous construisez ici.
Si vous venez du développement mobile, notez que les patterns ici diffèrent de React Native Expo localization — App Router utilise le chargement de messages côté serveur et la mise en cache ISR plutôt que des fichiers de locale embarqués, ce qui représente une différence architecturale significative. Pour une introduction plus large au vocabulaire de l'internationalisation avant de plonger dans le code, localization and internationalisation fundamentals est un bon point de départ.
Ce que vous allez construire
À la fin de ce guide, votre application Next.js disposera de :
- Détection automatique de locale depuis l'URL, les cookies et les en-têtes du navigateur
- Chargement de traductions côté serveur avec mise en cache ISR
- Changement de locale instantané côté client sans rechargement de page
- Routage optimisé pour le SEO avec
hreflanget URLs canoniques - Traductions typées avec namespace scoping
1. Installation
Commencez par installer @better-i18n/next et ses dépendances de pair :
npm install @better-i18n/next next-intl
@better-i18n/next requiert Next.js 15+ et next-intl 4+ comme dépendances de pair. Il s'appuie sur la gestion des requêtes éprouvée de next-intl en y ajoutant la livraison de traductions via CDN, la détection automatique de locale et une API middleware composable.
2. Créer votre configuration i18n
Créez un fichier de configuration central auquel le reste de votre application fera référence. C'est l'unique source de vérité pour votre configuration i18n.
// i18n/config.ts
import { createI18n } from "@better-i18n/next";
export const i18n = createI18n({
project: "acme/dashboard", // Votre identifiant de projet Better i18n
defaultLocale: "en", // Locale de secours
localePrefix: "as-needed", // Stratégie d'URL (voir Section 6)
timeZone: "UTC", // Formatage cohérent des dates/heures
});
La fonction createI18n retourne un objet avec tout ce dont vous avez besoin :
| Propriété | Ce qu'elle fait |
|---|---|
i18n.config | Configuration normalisée avec les valeurs par défaut appliquées |
i18n.requestConfig | Configuration de requête next-intl pour App Router |
i18n.middleware | Middleware legacy (basé sur next-intl) |
i18n.betterMiddleware() | Middleware composable moderne avec callback d'authentification |
i18n.getLocales() | Récupérer les locales disponibles depuis le CDN |
i18n.getMessages() | Récupérer les traductions pour une locale avec mise en cache ISR |
Options de configuration
L'interface I18nConfig étend la configuration principale avec des options spécifiques à Next.js :
interface I18nConfig {
project: string; // Format "org/project"
defaultLocale: string; // ex. "en"
localePrefix?: "as-needed" | "always" | "never";
cookieName?: string; // Par défaut : "locale"
manifestRevalidateSeconds?: number; // ISR pour le manifest (par défaut : 3600)
messagesRevalidateSeconds?: number; // ISR pour les traductions (par défaut : 30)
timeZone?: string; // Identifiant de fuseau horaire IANA
storage?: TranslationStorage; // Stockage de secours hors ligne
staticData?: Record<string, Messages>; // Traductions de secours embarquées
fetchTimeout?: number; // Timeout CDN en ms (par défaut : 10000)
retryCount?: number; // Tentatives de retry (par défaut : 1)
}
3. Configurer le Middleware
Le middleware gère la détection de locale et le routage des URLs. Il s'exécute à chaque requête, détecte la langue préférée de l'utilisateur et s'assure que la structure de l'URL correspond à votre stratégie de préfixe de locale.
Configuration simple
Pour la plupart des applications, une seule ligne suffit :
// middleware.ts
import { i18n } from "./i18n/config";
export default i18n.betterMiddleware();
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)`"],
};
Avec authentification (style callback Clerk)
Si vous devez combiner i18n avec l'authentification, betterMiddleware accepte un callback qui vous donne accès à la locale détectée et à la réponse i18n :
// middleware.ts
import { NextResponse } from "next/server";
import { i18n } from "./i18n/config";
export default i18n.betterMiddleware(async (request, { locale, response }) => {
const isProtected = request.nextUrl.pathname.includes("/dashboard");
const isLoggedIn = request.cookies.get("session")?.value;
if (isProtected && !isLoggedIn) {
return NextResponse.redirect(
new URL(`/${locale}/login`, request.url)
);
}
// Ne rien retourner = la réponse i18n est utilisée (en-têtes préservés !)
});
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)`"],
};
Ce pattern remplace l'approche dépréciée composeMiddleware. Le callback reçoit la locale entièrement résolue et la réponse avec tous les en-têtes i18n déjà définis, vous permettant de vous concentrer sur votre logique d'authentification sans vous soucier des conflits d'en-têtes.
Comment fonctionne la détection de locale
Le middleware détecte la locale de l'utilisateur via une chaîne de priorité :
- Chemin URL —
/fr/aboutrésout versfr - Cookie — Le cookie
locale(défini lors des visites précédentes) - En-tête du navigateur — L'en-tête
Accept-Language - Par défaut — Revient à
defaultLocale
Les locales disponibles sont récupérées depuis le CDN de Better i18n à chaque requête (mises en cache en mémoire). Lorsqu'une nouvelle locale est détectée, un cookie est automatiquement défini pour les visites futures.
4. Configurer la Request Config
La request config indique à next-intl comment charger les traductions pour chaque requête. Better i18n le gère en récupérant les messages depuis le CDN avec mise en cache ISR.
// i18n/request.ts
import { i18n } from "./config";
export default i18n.requestConfig;
Sous le capot, requestConfig effectue les opérations suivantes à chaque requête serveur :
- Résout la locale depuis l'en-tête du middleware (ou revient au cookie, puis au défaut)
- Récupère les traductions depuis le CDN avec revalidation ISR de Next.js
- Résout le fuseau horaire pour éviter les désaccords d'hydratation
- Retourne
{ locale, messages, timeZone }à next-intl
La stratégie ISR signifie que les traductions sont mises en cache sur le serveur et revalidées en arrière-plan — les données du manifest sont revalidées toutes les 3600 secondes (1 heure) et les messages de traduction toutes les 30 secondes par défaut. Vous pouvez ajuster cela avec les options de configuration manifestRevalidateSeconds et messagesRevalidateSeconds.
5. Utiliser les traductions dans les composants
Server Components
Dans les server components, utilisez la fonction getTranslations de next-intl :
// app/[locale]/page.tsx
import { getTranslations } from "next-intl/server";
export default async function HomePage() {
const t = await getTranslations("home");
return (
<main>
<h1>{t("title")}</h1>
<p>{t("description")}</p>
</main>
);
}
Client Components
Dans les client components, utilisez le hook useTranslations :
"use client";
import { useTranslations } from "next-intl";
export function WelcomeBanner() {
const t = useTranslations("home");
return (
<section>
<h2>{t("welcome")}</h2>
<p>{t("subtitle", { name: "Developer" })}</p>
</section>
);
}
Namespace scoping
Les traductions sont organisées par namespace. Quand vous appelez useTranslations("home") ou getTranslations("home"), vous limitez la portée au namespace home dans vos fichiers de traduction :
{
"home": {
"title": "Bienvenue chez Acme",
"description": "Le meilleur tableau de bord pour votre entreprise",
"welcome": "Bonjour, {name} !",
"subtitle": "Commençons"
},
"auth": {
"login": "Se connecter",
"logout": "Se déconnecter"
}
}
Cela évite les collisions de clés entre les différentes parties de votre application et maintient les fichiers de traduction gérables à mesure que votre app grandit.
6. Stratégies de préfixe de locale dans l'URL
L'option localePrefix contrôle comment les locales apparaissent dans les URLs. Choisissez la stratégie qui convient à votre application :
"as-needed" (Par défaut)
La locale par défaut n'a pas de préfixe. Les autres locales en reçoivent un.
| Locale | URL |
|---|---|
en (par défaut) | /about |
fr | /fr/about |
tr | /tr/about |
createI18n({ localePrefix: "as-needed", defaultLocale: "en" });
"always"
Chaque locale reçoit un préfixe, y compris la locale par défaut.
| Locale | URL |
|---|---|
en | /en/about |
fr | /fr/about |
tr | /tr/about |
createI18n({ localePrefix: "always", defaultLocale: "en" });
"never"
Aucune locale n'apparaît dans l'URL. La locale est déterminée entièrement par le cookie et les en-têtes du navigateur.
| Locale | URL |
|---|---|
| N'importe laquelle | /about |
createI18n({ localePrefix: "never", defaultLocale: "en" });
Avec "never", le middleware contourne entièrement la réécriture d'URL de next-intl et définit la locale via l'en-tête x-middleware-request-x-next-intl-locale. La request config revient à lire le cookie locale lorsque l'en-tête du middleware n'est pas disponible.
7. Changement de locale côté client
Better i18n propose deux approches pour changer de locale côté client, selon que vous souhaitiez un changement instantané ou une approche avec actualisation serveur.
Changement instantané avec BetterI18nProvider
Enveloppez votre layout avec BetterI18nProvider pour activer le changement de locale instantané sans rechargement de page :
// app/[locale]/layout.tsx
import { getLocale, getMessages } from "next-intl/server";
import { BetterI18nProvider } from "@better-i18n/next/client";
export default async function LocaleLayout({
children,
}: {
children: React.ReactNode;
}) {
const locale = await getLocale();
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<BetterI18nProvider
locale={locale}
messages={messages}
config={{ project: "acme/dashboard", defaultLocale: "en" }}
>
{children}
</BetterI18nProvider>
</body>
</html>
);
}
Utilisez ensuite le hook useSetLocale n'importe où dans votre arbre de composants :
"use client";
import { useSetLocale } from "@better-i18n/next/client";
export function LanguageSwitcher() {
const setLocale = useSetLocale();
return (
<div>
<button onClick={() => setLocale("en")}>English</button>
<button onClick={() => setLocale("fr")}>Français</button>
<button onClick={() => setLocale("tr")}>Türkçe</button>
</div>
);
}
Quand setLocale est appelé, il :
- Définit un cookie
localepour la persistance côté serveur lors de la prochaine navigation - Récupère les nouvelles traductions depuis le CDN côté client
- Re-rend l'intégralité de l'arbre avec la nouvelle locale et les nouveaux messages — sans rechargement de page
Créer un sélecteur de langue dynamique
Utilisez le hook useManifestLanguages pour construire un sélecteur de langue qui reflète automatiquement les langues configurées dans votre projet Better i18n :
"use client";
import { useManifestLanguages, useSetLocale } from "@better-i18n/next/client";
export function DynamicLanguagePicker() {
const { languages, isLoading, error } = useManifestLanguages({
project: "acme/dashboard",
defaultLocale: "en",
});
const setLocale = useSetLocale();
if (isLoading) return <div>Chargement des langues...</div>;
if (error) return <div>Impossible de charger les langues</div>;
return (
<select onChange={(e) => setLocale(e.target.value)}>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.nativeName || lang.name || lang.code}
</option>
))}
</select>
);
}
La liste des langues est récupérée depuis le manifest CDN avec déduplication des requêtes intégrée — plusieurs composants appelant useManifestLanguages partageront une seule requête réseau.
8. SEO : hreflang, URLs canoniques et Metadata
Une configuration SEO correcte est essentielle pour les applications Next.js multilingues. Voici comment configurer les balises hreflang et les URLs canoniques. Pour une analyse complète de la façon dont cela s'intègre dans une stratégie SEO multilingue plus large, consultez notre localization SEO strategy guide.
Lors de la planification de l'architecture de l'information globale de votre application multilingue, un multilingual website design guide couvre les patterns de navigation tenant compte de la locale, l'expansion du texte entre les langues et les considérations RTL qui impactent directement la structure que vous construisez ici.
Générer les balises hreflang
Ajoutez des liens de langues alternatives dans votre root layout ou dans les métadonnées de la page :
// app/[locale]/layout.tsx
import { i18n } from "@/i18n/config";
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: { locale: string };
}): Promise<Metadata> {
const { locale } = await params;
const locales = await i18n.getLocales();
const languages: Record<string, string> = {};
for (const loc of locales) {
languages[loc] = `https://yourdomain.com/${loc}`;
}
// Ajouter x-default pour les moteurs de recherche
languages["x-default"] = "https://yourdomain.com/en";
return {
alternates: {
canonical: `https://yourdomain.com/${locale}`,
languages,
},
};
}
Cela génère dans votre HTML :
<link rel="alternate" hreflang="en" href="https://yourdomain.com/en" /> <link rel="alternate" hreflang="fr" href="https://yourdomain.com/fr" /> <link rel="alternate" hreflang="tr" href="https://yourdomain.com/tr" /> <link rel="alternate" hreflang="x-default" href="https://yourdomain.com/en" /> <link rel="canonical" href="https://yourdomain.com/en" />
Metadata localisée
Servez des titres et descriptions de page traduits pour chaque locale :
// app/[locale]/page.tsx
import { getTranslations } from "next-intl/server";
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: { locale: string };
}): Promise<Metadata> {
const t = await getTranslations("meta");
return {
title: t("home.title"),
description: t("home.description"),
openGraph: {
title: t("home.title"),
description: t("home.description"),
},
};
}
9. Repli hors ligne et résilience
Les applications en production doivent gérer les pannes de CDN avec élégance. @better-i18n/next fournit une chaîne de repli à trois niveaux pour les données du manifest et les traductions :
- Cache mémoire — Cache TTL en processus (le plus rapide)
- Récupération CDN — Avec timeout et retry configurables
- Stockage persistant — Pour les scénarios hors ligne/dégradés
- Données statiques — Traductions embarquées en dernier recours
// i18n/config.ts
import { createI18n } from "@better-i18n/next";
export const i18n = createI18n({
project: "acme/dashboard",
defaultLocale: "en",
fetchTimeout: 5000, // Annuler la récupération CDN après 5s
retryCount: 2, // Réessayer deux fois en cas d'échec
staticData: { // Repli embarqué
en: {
common: { error: "Une erreur s'est produite" },
},
},
});
Si le CDN est inaccessible, les traductions sont servies depuis le stockage persistant (si configuré) ou reviennent aux staticData embarquées. Votre application ne montre jamais de clés cassées aux utilisateurs.
10. Traduction assistée par IA avec MCP
Better i18n inclut un serveur MCP (Model Context Protocol) qui permet aux assistants IA de gérer vos traductions directement. Au lieu d'écrire manuellement des fichiers de traduction, vous pouvez utiliser Claude, Cursor ou tout outil compatible MCP pour :
- Créer des clés de traduction avec
createKeys - Proposer de nouvelles langues avec
proposeLanguages - Mettre à jour les traductions existantes avec
updateKeys - Publier sur le CDN avec
publishTranslations
Exemple de workflow
- Vous écrivez votre composant React avec des chaînes en anglais
- Demandez à votre assistant IA : « Ajoute des traductions en français et en turc pour la page d'accueil »
- Le serveur MCP crée les clés, propose les traductions et les publie
- Votre application Next.js récupère les nouvelles traductions au prochain cycle de revalidation ISR (30 secondes par défaut)
Pas d'édition manuelle de JSON. Pas de copier-coller entre fichiers. Tout le workflow de traduction se déroule via votre assistant de codage IA. Pour voir ce workflow en action avec notre propre contenu de blog, lisez how we use AI to write our own blog.
Une fois vos traductions en ligne dans toutes les langues, il vaut la peine de lancer un i18n testing pass dédié — les vérifications automatisées détectent les clés manquantes, les interpolations cassées et les cas limites de pluralisation avant qu'ils n'atteignent les utilisateurs. Et si vos chaînes nécessitent de la nuance — des libellés d'interface qui varient selon le ton ou le contexte — notre article sur why translation context matters explique comment fournir ce contexte aux traducteurs IA de manière efficace.
Tout assembler
Voici la structure de fichiers complète pour un projet Next.js App Router avec Better i18n :
your-app/
i18n/
config.ts # Configuration createI18n
request.ts # Configuration de requête next-intl
middleware.ts # Détection de locale et routage
app/
[locale]/
layout.tsx # Wrapper BetterI18nProvider
page.tsx # Server component avec getTranslations
components/
LanguageSwitcher.tsx # Changement de locale côté client
Référence rapide
// i18n/config.ts
import { createI18n } from "@better-i18n/next";
export const i18n = createI18n({
project: "acme/dashboard",
defaultLocale: "en",
localePrefix: "as-needed",
});
// i18n/request.ts
import { i18n } from "./config";
export default i18n.requestConfig;
// middleware.ts
import { i18n } from "./i18n/config";
export default i18n.betterMiddleware();
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)`"],
};
Pièges courants (et comment les éviter)
Après avoir aidé des centaines d'équipes à configurer i18n dans Next.js, voici les problèmes les plus fréquents :
1. Désaccords d'hydratation avec le formatage des dates/heures
Si vous formatez des dates ou des heures sans définir de timeZone, le serveur et le client peuvent rendre des valeurs différentes — provoquant des erreurs d'hydratation React.
Solution : Définissez toujours timeZone dans votre configuration createI18n :
createI18n({
project: "acme/dashboard",
defaultLocale: "en",
timeZone: "UTC", // Prévient le désaccord serveur/client
});
Better i18n le définit automatiquement dans la request config et le BetterI18nProvider, revenant à Intl.DateTimeFormat().resolvedOptions().timeZone si vous n'en spécifiez pas.
2. Préfixe de locale manquant sur la locale par défaut
Avec localePrefix: "as-needed" (par défaut), votre locale par défaut n'a pas de préfixe d'URL. Cela signifie que /about sert l'anglais mais /fr/about sert le français. Si vous oubliez cela et codez en dur des segments de locale dans les liens, votre locale par défaut sera cassée.
Solution : Utilisez le composant Link de next-intl ou construisez les chemins dynamiquement :
// Faites ceci :
<Link href="/about">{t("nav.about")}</Link>
// Pas cela :
<a href="/en/about">About</a>
3. Cookie qui ne persiste pas entre les sous-domaines
Le cookie locale par défaut est défini avec path: / mais sans domain. Si votre application couvre plusieurs sous-domaines (ex. app.yourdomain.com et www.yourdomain.com), le cookie ne sera pas partagé.
Solution : Pour les configurations multi-sous-domaines, utilisez localePrefix: "always" pour que la locale soit toujours dans l'URL, ou définissez un domaine de cookie personnalisé dans votre callback middleware.
4. Matcher du middleware qui n'exclut pas les fichiers statiques
Si votre matcher de middleware est trop large, il s'exécutera à chaque requête — y compris les images, les polices et les routes API. Cela ralentit votre application et peut provoquer des redirections de locale inattendues.
Solution : Utilisez toujours le pattern de matcher recommandé :
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)`"],
};
Cela exclut /api/*, /_next/* et tout chemin contenant une extension de fichier.
Conclusion
Configurer i18n dans Next.js App Router ne doit pas être douloureux. Avec @better-i18n/next, vous obtenez :
- Détection de locale sans configuration depuis l'URL, les cookies et les en-têtes du navigateur
- Traductions via CDN avec mise en cache ISR et repli hors ligne
- Middleware composable qui s'intègre parfaitement avec l'authentification (Clerk, NextAuth, etc.)
- Changement de locale instantané côté client sans rechargement de page
- Prêt pour le SEO avec hreflang, URLs canoniques et metadata localisée
- Traduction assistée par IA grâce à l'intégration du serveur MCP
Toute la configuration nécessite cinq fichiers et moins de 50 lignes de code de configuration. Vos traductions sont servies depuis un CDN mondial, mises en cache avec ISR et gérées via votre assistant IA.
Prêt à vous lancer ? Installez @better-i18n/next et faites tourner votre première locale en quelques minutes :
npm install @better-i18n/next next-intl
Consultez la documentation complète ou explorez le dépôt GitHub pour des patterns plus avancés.
Ressources associées
- Next.js i18n Landing Page — Présentation des fonctionnalités et comparaison
- i18n for Developers — Pourquoi Better i18n est developer-first
- CLI Code Scanning — Détecter les chaînes codées en dur avant qu'elles ne soient publiées