110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
"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>
|
|
)
|
|
}
|