Internationalisation avec next-intl¶
Pourquoi next-intl ?¶
Je vous ai montré dans la première partie du cours que pour rendre multilingue une application React, le module react-intl est très pratique. Cependant, react-intl n'est pas idéal dans l'environnement Next.JS. C'est pour ça que je vous parle maintenant de son équivalent, next-intl.
Manuel
Installation¶
Structure du projet¶
Voici la structure de fichiers à mettre en place :
├── message/
│ ├── fr.json
│ └── en.json
├── i18n/
│ ├── routing.ts
│ └── request.ts
├── proxy.ts
├── app/
│ └── [locale]/
│ ├── layout.tsx
│ └── page.tsx
Étapes de configuration¶
1. Créer les fichiers de traductions¶
{
"app": {
"titre": "Mon application",
"bienvenue": "Bonjour, {nom} !",
"clics": "{nombreclics, plural, =0 {Aucun clic} one {Un clic} other {{nombreclics} clics}}"
}
}
{
"app": {
"titre": "My application",
"bienvenue": "Hello, {nom}!",
"clics": "{nombreclics, plural, =0 {No clicks} one {One click} other {{nombreclics} clicks}}"
}
}
Pensez à la structure JSON du fichier de message comme étant la structure de l'application. Par exemple, si vous avez une composante qui liste les billets vous pourrez avoir ceci dans votre fichier de traduction :
{
"listeBillets": {
"numero": "Numéro du billet",
"desc": "Description",
"demandeur": "Demandeur"
}
}
2. Configurer le routage i18n¶
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['fr', 'en'],
defaultLocale: 'fr',
});
3. Configurer le chargement des messages¶
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as 'fr' | 'en')) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../lang/${locale}.json`)).default,
};
});
4. Configurer le proxy¶
Le proxy détecte la langue préférée de l'utilisateur et redirige automatiquement vers la bonne URL :
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
};
5. Configurer next.config.ts¶
import { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
const nextConfig: NextConfig = {};
export default withNextIntl(nextConfig);
6. Déplacer les pages sous [locale]¶
Toutes vos pages doivent être dans app/[locale]/ :
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import { notFound } from 'next/navigation';
interface Props {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
if (!routing.locales.includes(locale as 'fr' | 'en')) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Utilisation dans les composants¶
Dans un Server Component¶
import { useTranslations } from 'next-intl';
export default function Page() {
const t = useTranslations();
return <h1>{t('app.titre')}</h1>;
}
Dans un Client Component¶
'use client';
import { useTranslations } from 'next-intl';
export default function MonComposant() {
const t = useTranslations();
return <p>{t('app.titre')}</p>;
}
Message avec variable¶
Message avec pluriel¶
{
"app":{
"clics": "{nombreclics, plural, =0 {Aucun clic} one {Un clic} other {{nombreclics} clics}}"
}
}
Manuel
Formater une date¶
import { useFormatter } from 'next-intl';
const format = useFormatter();
const maintenant = new Date();
<p>{format.dateTime(maintenant, { year: 'numeric', month: 'long', day: '2-digit' })}</p>
Formater une devise (monétaire)¶
import { useFormatter } from 'next-intl';
const format = useFormatter();
const prix = 12.35;
<p>{format.number(prix, { style: 'currency', currency: 'CAD' })}</p>
Formater un pourcentage¶
import { useFormatter } from 'next-intl';
const format = useFormatter();
const rabais = 0.15;
<p>{format.number(rabais, { style: 'percent' })}</p>
Sélecteur de langue¶
Pour changer de langue, next-intl fournit des composants de navigation qui préservent le chemin courant tout en changeant le segment [locale] dans l'URL :
'use client';
import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
export default function SelecteurLangue() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
function changerLangue(nouvelleLangue: string) {
const segments = pathname.split('/');
segments[1] = nouvelleLangue;
router.push(segments.join('/'));
}
return (
<select value={locale} onChange={(e) => changerLangue(e.target.value)}>
<option value="fr">Français</option>
<option value="en">English</option>
</select>
);
}