Internationalisation d'une application Next.js

Publié le 01/10/2024 - Ecrit par Antoine Bourin

5 minutes

La gestion de l'internationalisation vous permet d'exposer les contenus votre application sous plusieurs langues différentes, autrement appelées locales.

i18n avec Next.js

Ces locales sont souvent composées de deux parties, la première étant la langue de l'utilisateur et potentiellement, en seconde partie, la région. Par exemple, la locale fr-CA correspondrait donc aux utilisateurs parlant français situés au Canada.

Il existe deux possibilités principales pour la traduction : ajouter un segment de route supplémentaire /fr/produits ou par extension de domaine avec par exemple monsite.fr, monsite.nl.

Mise en place du routage pour l'internationalisation

Avant de commencer à gérer l'i18n, vous devriez commencer à préparer le routage de l'application Next.js.

Une des fonctionnalités de Next.js est le middleware, qui permet d'intercepter certaines requêtes de votre application et de consulter, modifier ou encore rediriger la réponse.

Nous allons utiliser ce middleware pour rediriger nos utilisateurs si nécessaire vers les pages avec la locale dans le chemin d'URL.

Création du middleware

Créez un fichier middleware.js à la racine de votre projet.

Lors de l'interception d'une requête, l'accès à l'objet Request permettra de récupérer le header accept-language.

Ce header est automatiquement défini par les navigateurs et permet de connaître les langues préférées d'un utilisateur suivant ses habitudes de navigation.

En exemple, le header suivant : Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7 permet de comprendre que la locale préférée de l'utilisateur est fr-FR mais également en-US.

Negotiator est un premier paquet à installer qui permet de traduire ce header en un tableau de langues disponible. La fonction match du paquet @formatjs/intl-localematcher nous permettra quant à elle de définir la locale de l'utilisateur selon les locales disponibles dans l'application et celles préférées dans Accept-Language.

La fonction créée devrait ressembler donc à :

// middleware.js
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
 
const locales = ['fr', 'en'];
const defaultLocale = 'fr';

function getLocale(request) {
  const headers = { "accept-language": request.headers.get("accept-language") };
  const languages = new Negotiator({ headers }).languages();

  return match(languages, locales, defaultLocale);
}

Il ne reste plus qu'à créer le middleware en lui-même qui récupère cette locale préférée et redirige l'utilisateur (si aucune locale n'est déjà présente dans l'URL) :

// middleware.js
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
import { NextResponse } from "next/server";
 
const locales = ['fr', 'en'];
const defaultLocale = 'fr';

function getLocale(request) {
  const headers = { "accept-language": request.headers.get("accept-language") };
  const languages = new Negotiator({ headers }).languages();

  return match(languages, locales, defaultLocale);
}

export function middleware(request) {
  // Récupération du chemin actuel et check si une locale est déjà présente
  const { pathname } = request.nextUrl
  const pathnameHasLocale = locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  )
 
  // Pas de redirection si une locale est déjà détectée
  if (pathnameHasLocale) return
 
  // Récupération de locale préférée
  const locale = getLocale(request)
  request.nextUrl.pathname = `/${locale}${pathname}`
  
  // Redirection vers la nouvelle URL avec locale
  return NextResponse.redirect(request.nextUrl)
}
 
export const config = {
  matcher: [
    // Interception de toutes les URLs sauf celles internes à Next
    '/((?!_next).*)',
  ],
}

Si vous souhaitez utiliser l'i18n sous la forme de redirection de domaine plutôt que chemin d'URL, modifiez simplement la redirection effectuée dans ce middleware.

Adaptation du routage

Pour vous assurer que les redirections ciblent correctement vos pages, créez un nouveau segment à la racine du dossier app

Ce segment dynamique devrait ressembler à un dossier [locale]. Vous pouvez y inclure tous les segments de votre application à l'intérieur.

Chacune des pages et layouts dans ce segment aura désormais accès au paramètre de segment locale dans les props :

// page /app/[locale]/page.tsx

export default async function Page({ params: { locale } }) {

  return (
    // ...
  );

}

Ca y est ! Votre routage est mis en place.

Essayez d'accéder au chemin / de votre application, il devrait être redirigé vers la locale préférée disponible, par exemple /fr. La page correspondante récupère la valeur de la locale pour la mise en place de la traduction.

Une idée ? Un projet ? Besoin d'aide ?

Trouvez un créneau dans mon calendrier et échangeons simplement.

Traduction des contenus

Pour commencer la mise en place des traductions, commençons par créer les dictionnaires correspondant. Ceux-ci stockeront l'ensemble des traductions disponibles pour l'application sous forme de clés.

Création des dictionnaires de traduction

Vous pouvez créer les dictionnaires sous format JSON. Pour nos deux locales disponibles, voilà ce que pourrait être leur contenu :

// dictionaries/fr.json
{
  "home": {
    "welcome": "Bienvenue sur cette application"
  }
}

Et pour la version anglaise :

// dictionaries/en.json
{
  "home": {
    "welcome": "Welcome on this app"
  }
}

Et une fonction getDictionary qui nous permettra depuis les composants de l'application de récupérer les traductions pour une locale :

// utils/dictionaries.js
const dictionaries = {
  en: () => import("../dictionaries/en.json").then((module) => module.default),
  fr: () => import("../dictionaries/fr.json").then((module) => module.default),
};

export const getDictionary = async (locale) => dictionaries[locale]();

Récupération de la traduction

Vous pouvez maintenant récupérer les traductions depuis une page, par exemple la page d'accueil de l'application :

// app/page.js
import { getDictionary } from "@/utils/dictionaries";

export default function Home({ params: { locale } }) {
  const i18n = await getDictionary(locale);

  return (
    <div>
      <h1>{i18n.home.welcome}</h1>
    </div>
  );

}

Ce paramètre locale peut également vous permettre d'insérer des liens avec Next.js Link tout en utilisant la même locale.

Génération statique des pages

Puisque vos différentes pages sont désormais sous un segment dynamique [locale], vous pouvez générer des pages statiques avec les différentes locales disponibles grâce à la fonction generateStaticParams.

Cette génération peut être appliquée pour une page ou plus globalement, dans le RootLayout, pour la génération de l'ensemble des pages de segments enfants :

// app/[locale]/layout.js

export async function generateStaticParams() {
  return [{ lang: 'fr' }, { lang: 'en' }];
}
 
export default function RootLayout({ children, params }) {

  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  );
}

Librairies disponibles pour l'i18n avec Next.js

Certaines librairies ont été créées pour faciliter l'internationalisation. next-intl peut notamment être une option.

Envie d'en apprendre plus sur Next.js ?

Découvrez une formation Next.js complète :

  • Comprendre et créer un système de routage complet
  • Créer des routes statiques et dynamique
  • Maîtriser le rendu serveur et client
  • Utiliser tous les atouts majeurs proposés par le framework

Plus de 35 heures de contenu vidéo

Plus de 80 cours écrits