返回 CodeWhale
locale-switcher.tsx
根目录 / web / components / locale-switcher.tsx
1 "use client";
2
3 import { useRouter, usePathname } from "next/navigation";
4 import { ALL_LOCALES, locales } from "@/lib/i18n/config";
5 import { fill, getChrome } from "@/lib/i18n/dictionaries";
6
7 /** Labels for the dropdown. Keyed by locale code, displayed in native script. */
8 const LOCALE_LABELS: Record<string, string> = {};
9 for (const l of ALL_LOCALES) {
10 LOCALE_LABELS[l.code] = l.label;
11 }
12
13 /** Routed locales that appear in the switcher (shipped + partial). */
14 const ROUTED = ALL_LOCALES.filter((l) => l.status === "shipped" || l.status === "partial");
15
16 export function LocaleSwitcher({ current }: { current: string }) {
17 const router = useRouter();
18 const pathname = usePathname();
19 const chrome = getChrome(current);
20
21 const switchLocale = (code: string) => {
22 if (code === current) return;
23 const segments = pathname.split("/");
24 if ((locales as readonly string[]).includes(segments[1])) {
25 segments[1] = code;
26 } else {
27 segments.splice(1, 0, code);
28 }
29 const newPath = segments.join("/") || `/${code}`;
30 document.cookie = `NEXT_LOCALE=${code};path=/;max-age=${60 * 60 * 24 * 365}`;
31 router.push(newPath);
32 };
33
34 // If only 1 routed locale, no switcher needed.
35 if (ROUTED.length <= 1) return null;
36
37 // If exactly 2 routed locales, show a simple toggle.
38 if (ROUTED.length === 2) {
39 const other = ROUTED.find((l) => l.code !== current);
40 if (!other) return null;
41 return (
42 <button
43 onClick={() => switchLocale(other.code)}
44 className="font-mono text-[0.72rem] uppercase text-ink-mute hover:text-indigo transition-colors px-2 py-1"
45 aria-label={fill(chrome.switcherSwitchTo, { label: other.label })}
46 >
47 {other.label}
48 </button>
49 );
50 }
51
52 // 3+ routed locales: show a dropdown. Partial packs carry a visible
53 // badge so the incomplete scope is honest at the point of selection.
54 return (
55 <select
56 value={current}
57 onChange={(e) => switchLocale(e.target.value)}
58 className="font-mono text-[0.72rem] uppercase text-ink-mute bg-transparent hairline-t hairline-b hairline-l hairline-r px-2 py-1 cursor-pointer hover:text-indigo transition-colors"
59 aria-label={chrome.switcherLabel}
60 >
61 {ROUTED.map((l) => (
62 <option key={l.code} value={l.code}>
63 {l.status === "partial" ? `${l.label} ${chrome.partialBadge}` : l.label}
64 </option>
65 ))}
66 </select>
67 );
68 }
69
69 lines Plain Text