Pourquoi pas next-intl ?
Pour un portfolio, je ne voulais pas payer le coût d'une librairie i18n complète : routing localisé, middleware, segments dynamiques. Trois langues, du contenu statique-ish, et la plupart de la traduction stockée en base — un React.Context fait le job.
const messages: Record<Locale, Messages> = { fr, en, mg };
export function useI18n() {
return React.useContext(I18nContext);
}
Le pattern "champ suffixé"
Plutôt qu'une table de traductions normalisée (Translation { entityId, lang, field, value }), je suffixe les colonnes :
model Project {
descFr String
descEn String
descMg String
}
Avantages :
- Une seule requête pour récupérer un projet en n'importe quelle langue
- Pas de
JOINni de cache busting par langue - TypeScript voit toutes les colonnes — pas de cast
Inconvénient : ajouter une langue = migration. À 3 langues sur un portfolio perso, c'est OK.
Le hook useLocalized()
export function useLocalized() {
const { locale } = useI18n();
return useCallback((row, base) => {
const suffix = locale === "fr" ? "Fr" : locale === "en" ? "En" : "Mg";
return row[`${base}${suffix}`] ?? row[`${base}Fr`];
}, [locale]);
}
Dans un composant : const t = useLocalized(); t(project, "desc").
Bilan
3 langues, 0 ms de runtime i18n, 0 dépendance externe. Le SEO ? Une seule URL canonique en FR pour le moment ; je rajouterai /en et /mg quand le contenu mérite l'investissement.