feat: ContentPage bilingue (PK composite slug+lang) + seed pages EN

Migration : ContentPage.id devient PK composite (slug, lang) au lieu de slug
seul, pour stocker une version FR et une version EN du même slug. Index sur
slug seul pour les lookups.

Schema Prisma : @@id([slug, lang]).

Helpers :
- getContentPage(slug, lang) avec fallback FR si la version dans la langue
  demandée n'existe pas
- listContentPages(category?, lang?) accepte un filtre lang
- upsertContentPage : utilise le composite key

Pages publiques (a-propos, faq, comment-ca-marche, pour-comites-entreprise,
devenir-loueur, cgv, mentions-legales, politique-de-confidentialite) :
ajoutent un appel à getLocale() et le passent à getContentPage.

Seeds :
- src/lib/plugins/seeds/content-pages-en.ts : 8 pages traduites en anglais
- hook onEnable du plugin i18n-fr-en : seed EN pages au toggle on. Désactiver
  i18n n'efface pas les EN pages (elles dorment, fallback FR reprend).

Résultat : quand l'utilisateur switche vers EN, /a-propos, /faq, /cgv, etc.
basculent en anglais. Le contenu hors-DB (composants UI) bascule déjà via les
dictionnaires de la PR i18n-fr-en initiale.
This commit is contained in:
Claude Integration 2026-05-31 11:45:47 +00:00
parent 88a937f2fd
commit 87c3e7a581
13 changed files with 376 additions and 40 deletions

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("a-propos");
const page = await getContentPage("a-propos", await getLocale());
return { title: page?.title ?? "À propos" };
}
export default async function AboutPage() {
if (!(await isPluginEnabled("content-pages"))) notFound();
const page = await getContentPage("a-propos");
const page = await getContentPage("a-propos", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("cgv");
const page = await getContentPage("cgv", await getLocale());
return { title: page?.title ?? "CGV" };
}
export default async function CgvPage() {
if (!(await isPluginEnabled("legal-pages"))) notFound();
const page = await getContentPage("cgv");
const page = await getContentPage("cgv", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("comment-ca-marche");
const page = await getContentPage("comment-ca-marche", await getLocale());
return { title: page?.title ?? "Comment ça marche" };
}
export default async function HowItWorksPage() {
if (!(await isPluginEnabled("content-pages"))) notFound();
const page = await getContentPage("comment-ca-marche");
const page = await getContentPage("comment-ca-marche", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("devenir-loueur");
const page = await getContentPage("devenir-loueur", await getLocale());
return { title: page?.title ?? "Devenir loueur" };
}
export default async function OwnerOnboardingPage() {
if (!(await isPluginEnabled("content-pages"))) notFound();
const page = await getContentPage("devenir-loueur");
const page = await getContentPage("devenir-loueur", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("faq");
const page = await getContentPage("faq", await getLocale());
return { title: page?.title ?? "FAQ" };
}
export default async function FaqPage() {
if (!(await isPluginEnabled("content-pages"))) notFound();
const page = await getContentPage("faq");
const page = await getContentPage("faq", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("mentions-legales");
const page = await getContentPage("mentions-legales", await getLocale());
return { title: page?.title ?? "Mentions légales" };
}
export default async function MentionsPage() {
if (!(await isPluginEnabled("legal-pages"))) notFound();
const page = await getContentPage("mentions-legales");
const page = await getContentPage("mentions-legales", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("politique-de-confidentialite");
const page = await getContentPage("politique-de-confidentialite", await getLocale());
return { title: page?.title ?? "Politique de confidentialité" };
}
export default async function PrivacyPage() {
if (!(await isPluginEnabled("legal-pages"))) notFound();
const page = await getContentPage("politique-de-confidentialite");
const page = await getContentPage("politique-de-confidentialite", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}

View file

@ -1,18 +1,19 @@
import { notFound } from "next/navigation";
import { getContentPage } from "@/lib/content-pages";
import { getLocale } from "@/lib/i18n/server";
import { isPluginEnabled } from "@/lib/plugins/server";
import { ContentPageRenderer } from "@/components/ContentPageRenderer";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const page = await getContentPage("pour-comites-entreprise");
const page = await getContentPage("pour-comites-entreprise", await getLocale());
return { title: page?.title ?? "Pour comités d'entreprise" };
}
export default async function CEPage() {
if (!(await isPluginEnabled("content-pages"))) notFound();
const page = await getContentPage("pour-comites-entreprise");
const page = await getContentPage("pour-comites-entreprise", await getLocale());
if (!page) notFound();
return <ContentPageRenderer page={page} />;
}