feat: 增加多语言

This commit is contained in:
2026-05-09 13:54:41 +08:00
parent 9805f56d3a
commit 38d40f3a8b
4 changed files with 195 additions and 2 deletions

View File

@@ -2,11 +2,14 @@
import {
BellIcon,
CheckIcon,
ChevronDownIcon,
GlobeIcon,
LogOutIcon,
UserRoundIcon,
} from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
@@ -21,6 +24,13 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Separator } from "@/components/ui/separator";
import {
ADMIN_API_LOCALES,
ADMIN_LOCALE_LABELS,
applyAdminUiLocale,
getAdminRequestLocale,
type AdminApiLocale,
} from "@/lib/admin-locale";
import {
useAdminProfile,
useAdminSessionStore,
@@ -68,6 +78,17 @@ export function ShellToolbar() {
const router = useRouter();
const adminProfile = useAdminProfile();
const clearSession = useAdminSessionStore((s) => s.clearSession);
const [locale, setLocale] = useState<AdminApiLocale>(() =>
typeof document !== "undefined"
? getAdminRequestLocale()
: "zh",
);
useEffect(() => {
queueMicrotask(() => {
setLocale(getAdminRequestLocale());
});
}, []);
const displayName =
adminProfile?.nickname?.trim() ||
@@ -81,6 +102,13 @@ export function ShellToolbar() {
router.refresh();
}
function onSelectLocale(next: AdminApiLocale) {
applyAdminUiLocale(next);
setLocale(next);
toast.success(`语言:${ADMIN_LOCALE_LABELS[next]}`);
router.refresh();
}
return (
<div className="flex items-center gap-2 sm:gap-3">
<Button
@@ -102,6 +130,39 @@ export function ShellToolbar() {
<Separator orientation="vertical" className="mx-0.5 h-7" />
<DropdownMenu>
<DropdownMenuTrigger className="inline-flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring">
<GlobeIcon className="size-5 stroke-[1.75]" aria-hidden />
<span className="sr-only"></span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[10rem]">
<DropdownMenuGroup>
<DropdownMenuLabel className="text-xs text-muted-foreground">
/ Language
</DropdownMenuLabel>
{ADMIN_API_LOCALES.map((code) => (
<DropdownMenuItem
key={code}
className="gap-2"
onClick={() => onSelectLocale(code)}
>
{locale === code ? (
<CheckIcon className="size-4 opacity-100" />
) : (
<span className="size-4 shrink-0" aria-hidden />
)}
<span className="flex-1">{ADMIN_LOCALE_LABELS[code]}</span>
<span className="text-xs text-muted-foreground uppercase">
{code}
</span>
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<Separator orientation="vertical" className="mx-0.5 h-7" />
<DropdownMenu>
<DropdownMenuTrigger className="flex max-w-[min(100vw-8rem,14rem)] items-center gap-2 rounded-lg px-1.5 py-1 text-left outline-none hover:bg-muted/80 focus-visible:ring-2 focus-visible:ring-ring sm:max-w-[16rem]">
<Avatar size="sm" className="ring-1 ring-border">