Blog 18ways

Comment ajouter plusieurs langues à une application Next.js

Comment ajouter l’i18n multilingue et la localisation à une application Next.js existante sans nuire au SEO ni tout reconstruire à partir de zéro.

L’i18n (internationalisation), la l10n (localisation), le support multilingue… peu importe le terme, vous devrez tôt ou tard mettre à jour votre application Next.js pour prendre en charge plus d’une langue.

La difficulté n’est pas de traduire une seule phrase. C’est d’ajouter plusieurs langues à une vraie application Next.js sans nuire au SEO, sans embrouiller votre base de code, ni vous créer un problème de maintenance.

Si vous voulez aller directement au code, consultez les exemples GitHub de 18ways-next.

Mettez d’abord votre infra en place

Avant toute chose, nous devons installer nos paquets. Nous allons mettre en place les bibliothèques 18ways, mais des outils comme i18next fonctionnent bien si votre projet est simple et ne contient que du contenu statique.

bash
npm install @18ways/next @18ways/react

Créez votre fichier de configuration :

js
// 18ways.config.js
module.exports = {
  apiKey: 'pk_dummy_demo_token',
  baseLocale: 'en-GB',
  router: 'app', // 'app', or 'path' depending on which Next.js router you are using
};

Enveloppez votre configuration Next.js :

js
// next.config.js
const { withWays } = require('@18ways/next/config');
 
const nextConfig = {
  /*
   * your normal Next.js config here
   */ 
};
 
module.exports = withWays(nextConfig);

Ajoutez un proxy racine pour que / puisse rediriger vers la bonne langue :

js
// proxy.js
export { default, config } from '@18ways/next/proxy';

Ajoutez ensuite une mise en page localisée :

jsx
// app/layout.jsx
import './styles.css';
import { WaysRoot } from '@18ways/next/server';
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="next-demo-body">
        <WaysRoot>{children}</WaysRoot>
      </body>
    </html>
  );
}

Et voilà, c’est fait ! Vous avez maintenant :

Traduisez votre première page

Vous pouvez maintenant traduire votre première page. Des outils comme i18next vous demanderont d’extraire tout votre texte dans des clés de traduction, puis d’y faire référence dans votre code. L’apparence exacte de cette étape dépendra de la bibliothèque que vous avez choisie.

Si vous utilisez 18ways, vous pouvez simplement envelopper le texte que vous souhaitez traduire dans un composant <T> :

jsx
// src/components/MyExampleComponent.jsx
import { useT, T } from '@18ways/react';
 
export default function MyExampleComponent() {
  const t = useT();
 
  return (
    <div>
      <T>Hello world!</T>
      <img
        src="https://example.com/image.png"
        alt={t('Example image')}
      />
    </div>
  );
}

Vous voudrez sans doute permettre aux utilisateurs de changer de langue :

jsx
// src/components/Footer.jsx
import { T, LanguageSwitcher } from '@18ways/react';
 
export default function Footer() {
  return (
    <footer>
      <T>My footer content</T>
      <LanguageSwitcher />
    </footer>
  );
}

Attention aux pièges

Une fois la plomberie en place, il y a plusieurs façons dont ces projets tournent généralement mal.

Rendu côté serveur

Le rendu côté serveur (SSR) permet à votre application Next.js de servir du HTML pré-rendu. C’est essentiel à la fois pour le SEO et pour éviter que l’utilisateur ne voie des flashs de contenu incorrect ou de langue erronée.

Si vous utilisez une bibliothèque comme i18next, vous devez faire très attention à ce que vos traductions soient chargées et renseignées pendant le rendu côté serveur. Vous pouvez le vérifier en consultant le view-source: de votre page, par exemple view-source:http://localhost:3000/. Vous devriez aussi vérifier cela en production, pour vous assurer que cela fonctionne également dans votre build de production.

Si vous utilisez 18ways, ne vous inquiétez pas de cela. Tout est géré pour vous.

Clés de traduction

Si vous utilisez 18ways, vous n’avez pas du tout à vous en soucier. 18ways n’a pas besoin de clés de traduction : vous pouvez laisser votre texte en place, comme d’habitude.

Beaucoup de systèmes d’i18n vous obligent à découper votre code en clés de traduction. Il est important de faire attention à la manière dont vous nommez ces clés.

Les mauvaises clés sont vagues :

js
// bad-keys.en-GB.js
module.exports = {
  title: 'Continue',
  button: 'Pay now',
  label: 'Home',
};

Ces clés n’indiquent presque rien aux traducteurs et aux développeurs sur l’endroit où le texte apparaît.

Les meilleures clés incluent du contexte :

js
// better-keys.en-GB.js
module.exports = {
  'checkout.payment.primaryButton': 'Pay now',
  'checkout.payment.stepTitle': 'Complete your payment',
  'account.sidebar.homeLink': 'Home',
};

Évitez aussi de générer des clés de façon dynamique :

js
// bad-dynamic-keys.js
const key = `checkout.${status}.${buttonType}`;
const translatedText = t(key);

Cela cassera les outils d’IDE qui essaient de rendre les clés de traduction moins indigeste. Cela rendra aussi extrêmement difficile la recherche et le nettoyage des anciennes clés de traduction.

js
// better-dynamic-keys.js
const keyMap = {
  success: {
    primary: t('checkout.success.primary'),
    default: t('checkout.success.default'),
  },
  error: {
    primary: t('checkout.error.primary'),
    default: t('checkout.error.default'),
  },
};
 
const translatedText = keyMap[status][buttonType];

C’est moins DRY, mais c’est la meilleure façon d’éviter que les clés de traduction deviennent ingérables.

Le mieux encore est d’utiliser un outil comme 18ways : vous n’avez alors plus besoin de clés de traduction du tout :

jsx
// app/[lang]/checkout/page.jsx
<T>Pay now</T>

Détection de la locale

La détection de la locale consiste à décider quelle langue un utilisateur doit voir avant qu’il ne la change explicitement.

Cela implique généralement une combinaison de :

Dans le middleware Next.js classique, cela ressemble souvent à ceci :

js
// middleware.js
 
/**
 * You don't need any of this if you're using 18ways 
 */
 
import { NextResponse } from 'next/server';
 
const acceptedLocales = ['en-GB', 'fr-FR'];
 
export function middleware(request) {
  const savedLocale =
    request.cookies.get('preferred-locale')?.value;
  const browserLocale =
    request.headers
      .get('accept-language')
      ?.split(',')[0] || 'en-GB';
 
  const locale = acceptedLocales.includes(savedLocale)
    ? savedLocale
    : acceptedLocales.includes(browserLocale)
      ? browserLocale
      : 'en-GB';
 
  if (request.nextUrl.pathname === '/') {
    return NextResponse.redirect(
      new URL(`/${locale}`, request.url)
    );
  }
 
  return NextResponse.next();
}

Certaines bibliothèques proposent des aides pour faciliter cela, à des degrés divers. Avec 18ways, la détection initiale et la couche de redirection sont gérées pour vous.

Dates et primitives spécifiques à la locale

Les différentes locales formatent les dates, les nombres et la monnaie différemment.

Par exemple :

En JavaScript brut, vous gérez cela vous-même avec Intl :

js
// formatting-dates-and-currency.js
const myLocale = getCurrentLocale(); // depends on your lib
 
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const dateLabel = new Intl.DateTimeFormat('fr-FR', {
  dateStyle: 'long',
}).format(someTimestamp);
 
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
const moneyLabel = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: someMoney.currency,
}).format(someMoney.amount);
 
const translatedText = t(
  'my.translation.key',
  { dateLabel, moneyLabel }
);

Si vous utilisez 18ways, cela est pris en charge pour vous :

jsx
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
 
<T>My text with {{ someTimestamp }} and {{ someMoney }}</T>

Assembler des chaînes

N’assemblez pas une interface traduite en cousant des fragments ensemble :

jsx
// bad-string-joining.jsx
const clickHereText = t('click.here');
const toGetStartedText = t('to.get.started')
<p><a href="#">{clickHereText}</a> {toGetStartedText}.</p>

Cela ne fonctionnera pas dans plusieurs langues.

Vous pourriez dire « 👉👉Cliquez ici👈👈 pour commencer » en anglais, mais en français il est plus naturel de dire « Pour commencer, 👉👉cliquez ici👈👈 ». Certaines langues, comme le japonais, auront même besoin de mots à la fois avant et après, comme « 始めるには👉👉こちら👈👈をクリックしてください ».

La structure de la phrase peut changer, donc la découper en morceaux rend la bonne traduction beaucoup plus difficile.

Les phrases complètes se traduisent mieux, car les traducteurs peuvent réorganiser les mots naturellement et voir le sens comme une unité complète.

Si vous traduisez du JSX comme ceci :

jsx
// rich-text-message.jsx
<p>
  Something <strong>that we want to be bold</strong>
</p>

vous devrez consulter la documentation de votre bibliothèque d’internationalisation pour savoir comment gérer cela, car la prise en charge varie beaucoup d’une bibliothèque à l’autre.

Si vous utilisez 18ways, vous pouvez simplement traduire le bloc JSX entier comme d’habitude.

jsx
// rich-text-message.jsx
<p>
  <T>Something <strong>that we want to be bold</strong></T>
</p>

Variables

Les variables vous permettent de garder la phrase intacte tout en insérant des valeurs à l’exécution.

La plupart des bibliothèques d’internationalisation prennent cela en charge d’une manière ou d’une autre. Avec 18ways :

jsx
// app/[lang]/page.jsx
<T>Hello {{ name: 'Ada' }}</T>

Ce modèle fonctionne bien pour :

La phrase reste lisible, et la valeur reste explicite.

Si vous devez aussi faire traduire la variable elle-même, veillez à l’envelopper dans un t(...) :

jsx
const animal = t('dog');
<T>Favourite animal: {{ animal }}</T>

Pluriels

Les règles de pluriel varient selon la langue. L’anglais a une forme de pluriel (1 year, 2 years, etc.). Le polonais a plusieurs formes de pluriel (1 rok, 2 lata, 5 lat). Le japonais n’a aucune forme de pluriel (1 年, 2 年, 3 年).

La plupart des bibliothèques d’internationalisation vous permettent de spécifier une syntaxe de type ICU pour gérer les pluriels :

jsx
// app/[lang]/page.jsx
<T>
  {{
    unreadCount,
    format:
      'plural, =0{No unread messages} =1{One unread message} other{{unreadCount} unread messages}',
  }}
</T>

Ce qui précède fonctionnera de 18 manières, mais dans la plupart des cas, vous pouvez aussi simplement faire ceci :

jsx
// app/[lang]/page.jsx
<T>{{ unreadCount }} unread messages</T>

18ways gérera les pluriels pour vous !

Commencer maintenant

Ajouter plusieurs langues à une application Next.js ne doit pas se transformer en projet de réécriture.

Si vous n’utilisez pas une solution comme 18ways, assurez-vous que votre routage et votre SSR fonctionnent correctement, puis passez à l’extraction de votre contenu vers des clés de traduction, en veillant à éviter les pièges courants de l’internationalisation.

Si vous utilisez 18ways, tout cela est géré pour vous, et il vous suffit de commencer à envelopper votre texte dans des blocs <T> !

Changement de langue
Comment ajouter plusieurs langues à une application Next.js | Blog 18ways