Contexte et exigences
Nous voulons implémenter le support multilingue avec les contraintes suivantes :
- Next.js 15 App Router avec TypeScript
- Static Export (
output: 'export') - Déploiement via S3 + CloudFront - Pas de rendu côté serveur - Les APIs dynamiques et les middlewares ne peuvent pas être utilisés
- SEO requis - Doit fournir le contenu en langue appropriée aux moteurs de recherche
Aperçu de l'architecture
1. Conception du routage
/ # Page racine (par défaut : en)
/en/ # Page en anglais
/ja/ # Page en japonais
Utilise le segment dynamique [locale] pour pré-rendre les pages pour chaque langue lors de la génération statique.
2. Gestion des données de traduction
Les données de traduction sont gérées au format JSON :
{
"title": "title"
}
{
"title": "タイトル"
}
3. Gestion d'état avec l'API React Context
interface I18nContextType {
messages: Record<string, any>;
locale: string;
}
const I18nContext = createContext<I18nContextType | undefined>(undefined);
export function I18nProvider({
children,
messages,
locale
}: I18nProviderProps) {
return (
<I18nContext.Provider value={{ messages, locale }}>
{children}
</I18nContext.Provider>
);
}
export function useTranslations() {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useTranslations must be used within I18nProvider');
}
const t = (key: string): string => {
// Support des clés imbriquées
const keys = key.split('.');
let value: any = context.messages;
for (const k of keys) {
value = value?.[k];
}
return typeof value === 'string' ? value : key;
};
return {
t,
locale: context.locale,
messages: context.messages,
isLoaded: true
};
}
4. Fourniture du contexte au niveau de la hiérarchie de layout
export default async function RootLayout({ children }: Props) {
const locale = defaultLocale;
const messages = await getMessages(locale);
return (
<html lang={locale}>
<body>
<I18nProvider messages={messages} locale={locale}>
<LanguageSwitcherClient />
{children}
</I18nProvider>
</body>
</html>
);
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
const messages = await getMessages(locale);
return (
<div lang={locale}>
<I18nProvider messages={messages} locale={locale}>
{children}
</I18nProvider>
</div>
);
}
Langue par défaut et redirection côté client
Côté serveur (lors de la génération statique)
La page racine (/) est toujours générée en anglais (en) :
export default async function RootPage() {
const locale = defaultLocale; // 'en'
const messages = await getMessages(locale);
return (
<I18nProvider messages={messages} locale={locale}>
<ClientLocaleRedirect />
<PageContent />
</I18nProvider>
);
}
Raisonnement :
- Stratégie SEO : Fournir un contenu stable aux robots des moteurs de recherche
- Contraintes d'export statique : Impossible de déterminer la langue de l'utilisateur côté serveur
Côté client (navigateur)
Lors du premier accès, redirige automatiquement après avoir détecté les paramètres de langue du navigateur :
'use client';
export default function ClientLocaleRedirect() {
useEffect(() => {
// Vérifier si la redirection a déjà été effectuée
if (typeof window === 'undefined' ||
sessionStorage.getItem('localeRedirectDone')) {
return;
}
const supportedLocales = ['en', 'ja'];
const pathname = window.location.pathname;
// Rediriger uniquement pour le chemin racine
if (pathname === '/') {
// Obtenir les paramètres de langue du navigateur
const browserLocales = navigator.languages || [navigator.language];
// Trouver la langue supportée
const preferredLocale = browserLocales
.map(lang => lang.split('-')[0]) // 'ja-JP' -> 'ja'
.find(lang => supportedLocales.includes(lang));
// Rediriger si différent de la langue par défaut
if (preferredLocale && preferredLocale !== defaultLocale) {
sessionStorage.setItem('localeRedirectDone', 'true');
window.location.replace(`/${preferredLocale}`);
}
}
}, []);
return null;
}
Scénarios de comportement
| Situation | Pendant SSG | Comportement client |
|---|---|---|
| Robot de moteur de recherche | Générer / en anglais |
Pas de redirection |
| Utilisateur avec navigateur japonais | Générer / en anglais |
Redirection vers /ja |
| Utilisateur avec navigateur anglais | Générer / en anglais |
Pas de redirection (afficher tel quel) |
| Utilisateur avec langue non supportée | Générer / en anglais |
Pas de redirection (afficher en anglais) |
Utilisation dans les composants
'use client';
function NetworkInfoClient() {
const { t } = useTranslations();
return (
<div>
<h2>{t('title')}</h2>
</div>
);
}
Avantages techniques
- Sécurité des types : TypeScript + API Context permet de vérifier l'existence des clés de traduction
- Dépendances claires : Les composants utilisant
useTranslationsdoivent être placés dans le Provider, rendant la conception explicite - Performance : Livraison rapide des pages via la génération statique
- SEO : Chaque page de langue a une URL indépendante, optimisée pour les moteurs de recherche
- UX : Redirection automatique basée sur les paramètres de langue du navigateur réduit l'effort de l'utilisateur
Résumé
Nous avons implémenté le support multilingue en utilisant l'API React Context dans un environnement d'export statique Next.js App Router. Nous complétons les contraintes côté serveur par une logique côté client, réalisant ainsi une bonne UX.
La modification de la langue par défaut nécessite uniquement la modification de defaultLocale dans config.ts, assurant l'extensibilité.
