- 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>
43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
import { useEffect, useRef, useState } from "react"
|
|
import { Link, useLocation } from "wouter"
|
|
import { Button } from "@workspace/ui/components/button"
|
|
import { useRoomStore } from "@/store/room"
|
|
import { useI18n } from "@/i18n/context"
|
|
import { errorMessage } from "@/i18n/error"
|
|
|
|
/** Lien d'invitation /join/:code → rejoint la room puis redirige vers le lobby. */
|
|
export function JoinPage({ code }: { code: string }) {
|
|
const [, navigate] = useLocation()
|
|
const { t } = useI18n()
|
|
const joinRoom = useRoomStore((s) => s.joinRoom)
|
|
const connected = useRoomStore((s) => s.connected)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const tried = useRef(false)
|
|
|
|
useEffect(() => {
|
|
if (!connected || tried.current) {
|
|
return
|
|
}
|
|
tried.current = true
|
|
joinRoom(code)
|
|
.then(({ roomCode }) => navigate(`/room/${roomCode}`, { replace: true }))
|
|
.catch((err) => setError(errorMessage(t, err)))
|
|
}, [connected, code, joinRoom, navigate, t])
|
|
|
|
return (
|
|
<div className="flex min-h-svh flex-col items-center justify-center gap-4 p-6 text-center">
|
|
{error ? (
|
|
<>
|
|
<p className="text-muted-foreground text-sm">{error}</p>
|
|
<Link href="/">
|
|
<Button variant="secondary">{t.common.back}</Button>
|
|
</Link>
|
|
</>
|
|
) : (
|
|
<p className="text-muted-foreground text-sm">
|
|
{t.joinPage.connecting(code)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|