feat(i18n): add zh/en locale switching

This commit is contained in:
Leon-in
2026-04-29 10:36:24 +08:00
parent 437dc976fb
commit 3213f00b7b
11 changed files with 915 additions and 33 deletions
+109
View File
@@ -0,0 +1,109 @@
"use client"
import { useState, useRef, useEffect } from "react"
import { useLocale } from "next-intl"
import { useRouter } from "next/navigation"
import { locales } from "@/i18n/config"
const localeLabels: Record<string, string> = {
"zh-CN": "中",
en: "EN",
}
function GlobeIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
)
}
function ChevronDownIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<polyline points="6 9 12 15 18 9" />
</svg>
)
}
export default function LocaleSwitcher() {
const locale = useLocale()
const router = useRouter()
const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (ref.current && !ref.current.contains(event.target as Node)) {
setOpen(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => document.removeEventListener("mousedown", handleClickOutside)
}, [])
const otherLocale = locales.find((l) => l !== locale)
if (!otherLocale) return null
function setLocale(nextLocale: string) {
document.cookie = `NEXT_LOCALE=${nextLocale}; path=/; max-age=31536000; samesite=lax`
setOpen(false)
router.refresh()
}
return (
<div ref={ref} className="locale-switcher">
<button
className="locale-switcher-trigger"
onClick={() => setOpen((v) => !v)}
aria-expanded={open}
aria-haspopup="listbox"
type="button"
>
<GlobeIcon />
<span className="locale-switcher-label">{localeLabels[locale] ?? locale}</span>
<ChevronDownIcon className={open ? "rotate-180" : ""} />
</button>
{open && (
<div className="locale-switcher-dropdown" role="listbox">
{locales.map((l) => (
<button
key={l}
className={`locale-switcher-option ${l === locale ? "active" : ""}`}
role="option"
aria-selected={l === locale}
onClick={() => setLocale(l)}
type="button"
>
{localeLabels[l] ?? l}
</button>
))}
</div>
)}
</div>
)
}