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

@@ -4,11 +4,12 @@ import { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { BookDto, ReadingStatus } from "../../lib/api";
import { useTranslation } from "../../lib/i18n/context";
const readingStatusOverlay: Record<ReadingStatus, { label: string; className: string } | null> = {
const readingStatusOverlayClasses: Record<ReadingStatus, string | null> = {
unread: null,
reading: { label: "En cours", className: "bg-amber-500/90 text-white" },
read: { label: "Lu", className: "bg-green-600/90 text-white" },
reading: "bg-amber-500/90 text-white",
read: "bg-green-600/90 text-white",
};
interface BookCardProps {
@@ -57,9 +58,15 @@ function BookImage({ src, alt }: { src: string; alt: string }) {
}
export function BookCard({ book, readingStatus }: BookCardProps) {
const { t } = useTranslation();
const coverUrl = book.coverUrl || `/api/books/${book.id}/thumbnail`;
const status = readingStatus ?? book.reading_status;
const overlay = status ? readingStatusOverlay[status] : null;
const overlayClass = status ? readingStatusOverlayClasses[status] : null;
const statusLabels: Record<ReadingStatus, string> = {
unread: t("status.unread"),
reading: t("status.reading"),
read: t("status.read"),
};
const isRead = status === "read";
@@ -71,11 +78,11 @@ export function BookCard({ book, readingStatus }: BookCardProps) {
<div className="relative">
<BookImage
src={coverUrl}
alt={`Couverture de ${book.title}`}
alt={t("books.coverOf", { name: book.title })}
/>
{overlay && (
<span className={`absolute bottom-2 left-2 px-2 py-0.5 rounded-full text-[10px] font-bold tracking-wide ${overlay.className}`}>
{overlay.label}
{overlayClass && status && (
<span className={`absolute bottom-2 left-2 px-2 py-0.5 rounded-full text-[10px] font-bold tracking-wide ${overlayClass}`}>
{statusLabels[status]}
</span>
)}
</div>