feat: add i18n support (FR/EN) to backoffice with English as default

Implement full internationalization for the Next.js backoffice:
- i18n infrastructure: type-safe dictionaries (fr.ts/en.ts), cookie-based locale detection, React Context for client components, server-side translation helper
- Language selector in Settings page (General tab) with cookie + DB persistence
- All ~35 pages and components translated via t() / useTranslation()
- Default locale set to English, French available via settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 19:39:01 +01:00
parent 055c376222
commit d4f87c4044
43 changed files with 2024 additions and 693 deletions

View File

@@ -2,10 +2,12 @@
import { useState } from "react";
import Image from "next/image";
import { useTranslation } from "../../lib/i18n/context";
const PAGE_SIZE = 5;
export function BookPreview({ bookId, pageCount }: { bookId: string; pageCount: number }) {
const { t } = useTranslation();
const [offset, setOffset] = useState(0);
const pages = Array.from({ length: PAGE_SIZE }, (_, i) => offset + i + 1).filter(
@@ -16,9 +18,9 @@ export function BookPreview({ bookId, pageCount }: { bookId: string; pageCount:
<div className="bg-card rounded-xl border border-border p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-foreground">
Aperçu
{t("bookPreview.preview")}
<span className="ml-2 text-sm font-normal text-muted-foreground">
pages {offset + 1}{Math.min(offset + PAGE_SIZE, pageCount)} / {pageCount}
{t("bookPreview.pages", { start: offset + 1, end: Math.min(offset + PAGE_SIZE, pageCount), total: pageCount })}
</span>
</h2>
<div className="flex gap-2">
@@ -27,14 +29,14 @@ export function BookPreview({ bookId, pageCount }: { bookId: string; pageCount:
disabled={offset === 0}
className="px-3 py-1.5 text-sm rounded-lg border border-border bg-muted/50 text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
Préc.
{t("bookPreview.prev")}
</button>
<button
onClick={() => setOffset((o) => Math.min(o + PAGE_SIZE, pageCount - 1))}
disabled={offset + PAGE_SIZE >= pageCount}
className="px-3 py-1.5 text-sm rounded-lg border border-border bg-muted/50 text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
Suiv.
{t("bookPreview.next")}
</button>
</div>
</div>