- lightweight, dependency-free i18n: typed fr/en dictionaries (fr is the source of truth, en must match its shape → compile error on drift), I18nProvider + useI18n hook, browser detection + localStorage persistence - FR/EN language switcher (home + room header) - translated the whole player-facing flow: home, join, pseudo, room shell, lobby (incl. settings, categories, bots, track submission, history), quiz, blindtest, player-cards, round transitions, end screen (podium, awards, tracks, rounds recap), and server-error messages (mapped by code) - history dates localized to the active language Content (questions/categories) intentionally left as-is. Back-office (admin) not translated yet — internal, token-gated tool. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
25 lines
700 B
TypeScript
25 lines
700 B
TypeScript
import { useI18n, type Lang } from "./context"
|
|
|
|
const LANGS: Lang[] = ["fr", "en"]
|
|
|
|
/** Petit sélecteur FR / EN. */
|
|
export function LanguageToggle({ className = "" }: { className?: string }) {
|
|
const { lang, setLang } = useI18n()
|
|
return (
|
|
<div className={`flex gap-1 ${className}`}>
|
|
{LANGS.map((l) => (
|
|
<button
|
|
key={l}
|
|
onClick={() => setLang(l)}
|
|
className={`rounded-md px-2 py-1 text-xs font-semibold uppercase transition-colors ${
|
|
lang === l
|
|
? "bg-primary/10 text-foreground"
|
|
: "text-muted-foreground hover:text-foreground"
|
|
}`}
|
|
>
|
|
{l}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|