Internationalisation
next-intl v4, ajout d'une langue, helpers LocalizedText et conventions.
Le boilerplate utilise next-intl v4 avec fr et en actifs par défaut.
Le CLI propose aussi es / de / pt / it à la génération.
Structure
messages/
fr.json
en.json
i18n/
routing.ts // locales, defaultLocale
request.ts // chargement messages
proxy.ts // matcher next-intl
app/[locale]/... // toutes les pages user-facingLes pages api/, maintenance/, r/[code]/, u/[username]/ sont
hors [locale] — elles n'ont pas besoin de traduction.
Ajouter une langue
- Ajoute le code locale dans
i18n/routing.ts:export const locales = ["fr", "en", "es"] as const; - Crée
messages/es.jsonavec toutes les clés (copie depuisfr.json, traduis). - Ajoute le bouton dans
components/shared/locale-switcher.tsx. - Pour les contenus DB (Plan, SiteConfig, Coupon), ajoute simplement la
clé
esaux objetsLocalizedTextexistants —pickLocalizedfait le reste avec son fallback.
LocalizedText pour les données DB
Plans, annonces, messages de maintenance utilisent ce pattern :
export type LocalizedText = Record<string, string>;Helper :
import { pickLocalized } from "@/lib/site-config";
const text = pickLocalized(plan.name, locale);
// fallback: locale → fr → en → première dispoPour Plan.features (array par locale) :
const features =
plan.features[locale]?.length > 0
? plan.features[locale]
: plan.features.fr ?? plan.features.en ?? [];Ou utilise directement localizePlan(plan, locale) qui retourne un objet
aplati prêt à rendre.
Conventions hooks
// ✅ Top du composant
"use client";
import { useTranslations } from "next-intl";
export function MyForm() {
const t = useTranslations("auth");
return <Button>{t("login")}</Button>;
}// ❌ Inline — interdit
<Button>{useTranslations("auth")("login")}</Button>
// ❌ Dans un handler async — interdit
const onClick = async () => {
toast.error(useTranslations("common")("error"));
};
// ✅ Stocké au top, utilisé dans le handler
const tCommon = useTranslations("common");
const onClick = async () => {
toast.error(tCommon("error"));
};Côté server (async), c'est getTranslations :
import { getTranslations } from "next-intl/server";
export default async function Page() {
const t = await getTranslations("dashboard");
return <h1>{t("title")}</h1>;
}Sections de clés
Les fichiers messages/*.json sont organisés par feature/page :
{
"auth": { "login": "...", "register": "..." },
"nav": { ... },
"dashboard": { ... },
"settings": { ... },
"landing": { ... },
"footer": { ... },
"errors": { ... }
}Pas de string en dur dans le JSX — règle stricte du projet.
Locale du user
User.locale (DB) prime quand le user est connecté. Sinon, next-intl
détecte via Accept-Language ou cookie.
Pour changer la locale d'un user : PATCH /api/user/profile { locale }.
Allez plus loin
- Plans tarifaires — exemple de
LocalizedText - Emails — templates par locale
- Architecture — groupes de routes
[locale]