feat(plugin): i18n FR + EN (Phase 4.2)

Infrastructure i18n légère, sans deps externe :

- lib/i18n/types.ts : LOCALES, DEFAULT_LOCALE, cookie name
- lib/i18n/server.ts : getLocale (cookie > Accept-Language > FR),
  t(key) async server-side, dict(locale)
- lib/i18n/client.tsx : LocaleProvider + useLocale + useT
- messages/fr.json + messages/en.json : ~50 clés pour landing + header + footer
- LocaleSwitcher component (cookie + router.refresh)

Plugin gated :
- Quand i18n-fr-en désactivé, getLocale() force FR. Le switcher ne s'affiche
  pas dans le hero. Pas d'impact sur le rendu existant.
- Quand activé, switcher visible coin haut-droit du hero. Les composants
  landing/header/footer rendent en FR ou EN selon le cookie utilisateur.

Composants i18n-isés :
- HeroSection (eyebrow, titre, CTA)
- ExperiencesSection (route/fleuve vs expédition, tous les bullets)
- HowItWorksSection (3 étapes)
- CESection (KPIs + body + CTA)
- TestimonialsSection (eyebrow + titre, citations restent en VO)
- Footer (taglines, colonnes)
- SeasonBanner (3 saisons + messages)
- AccessTypeBadge (labels + tooltips)

Pour les ContentPage, le champ lang existait déjà. Une suite (PR ultérieure)
ajoutera le filtre lang dans getContentPage + seed pages EN.
This commit is contained in:
Claude Integration 2026-05-31 11:38:39 +00:00
parent efeea16467
commit cf9da94bb5
15 changed files with 454 additions and 116 deletions

View file

@ -4,6 +4,8 @@ import "./globals.css";
import { PluginProvider } from "@/lib/plugins/client";
import { getEnabledPluginKeys, syncPluginsFromRegistry } from "@/lib/plugins/server";
import { SeasonBanner } from "@/components/SeasonBanner";
import { LocaleProvider } from "@/lib/i18n/client";
import { dict, getLocale } from "@/lib/i18n/server";
// Le layout interroge la DB Plugin à chaque request → rendu dynamique forcé.
// Sans ça, le layout (et donc data-theme + enabledKeys passés au client) est
@ -67,10 +69,12 @@ export default async function RootLayout({
}
const themeGuyane = enabledKeys.includes("theme-guyane");
const locale = await getLocale();
const messages = await dict(locale);
return (
<html
lang="fr"
lang={locale}
className={`${geistSans.variable} ${geistMono.variable} ${cormorant.variable} h-full antialiased`}
>
<body
@ -78,8 +82,10 @@ export default async function RootLayout({
className="min-h-full flex flex-col font-sans"
>
<PluginProvider enabledKeys={enabledKeys}>
<SeasonBanner />
{children}
<LocaleProvider locale={locale} messages={messages}>
<SeasonBanner />
{children}
</LocaleProvider>
</PluginProvider>
</body>
</html>