- 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>
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
import { useState } from "react"
|
|
import { Button } from "@workspace/ui/components/button"
|
|
import { useRoomStore } from "@/store/room"
|
|
import { Avatar } from "@/components/avatar"
|
|
import { useI18n } from "@/i18n/context"
|
|
|
|
const inputClass =
|
|
"border-input bg-background h-10 w-full rounded-md border px-3 py-2 text-center text-sm focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none"
|
|
|
|
/** Choix du pseudo dans la room, avec aperçu de l'avatar en temps réel. */
|
|
export function PseudoScreen({ code }: { code: string }) {
|
|
const setName = useRoomStore((s) => s.setName)
|
|
const { t } = useI18n()
|
|
const [name, setName_] = useState("")
|
|
const canSubmit = name.trim().length > 0
|
|
|
|
return (
|
|
<div className="flex min-h-svh items-center justify-center p-6">
|
|
<div className="flex w-full max-w-xs flex-col items-center gap-5">
|
|
<p className="text-muted-foreground text-xs uppercase">
|
|
{t.pseudo.room(code)}
|
|
</p>
|
|
<Avatar
|
|
seed={name.trim() || "?"}
|
|
className="size-28 ring-2 ring-primary/30"
|
|
/>
|
|
<p className="font-heading text-lg font-bold">{t.pseudo.choose}</p>
|
|
<input
|
|
className={inputClass}
|
|
placeholder={t.pseudo.placeholder}
|
|
value={name}
|
|
maxLength={24}
|
|
autoFocus
|
|
onChange={(e) => setName_(e.target.value)}
|
|
onKeyDown={(e) => e.key === "Enter" && canSubmit && setName(name)}
|
|
/>
|
|
<Button
|
|
className="w-full"
|
|
disabled={!canSubmit}
|
|
onClick={() => setName(name)}
|
|
>
|
|
{t.pseudo.enter}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|