调整 AdminShell 组件的子组件顺序,提升代码可读性。更新 admin-breadcrumb 组件,简化导航标签翻译逻辑,确保多语言支持的一致性。重构 admin-language-switcher 组件,优化语言切换的用户体验,增强界面交互性。更新多语言配置,新增登录界面的副标题,提升用户体验。
103 lines
3.0 KiB
TypeScript
103 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { CheckIcon, ChevronDownIcon, GlobeIcon } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuGroup,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import {
|
|
ADMIN_API_LOCALES,
|
|
ADMIN_LOCALE_LABELS,
|
|
applyAdminUiLocale,
|
|
getAdminRequestLocale,
|
|
type AdminApiLocale,
|
|
} from "@/lib/admin-locale";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export function AdminLanguageSwitcher() {
|
|
const { i18n, t } = useTranslation("common");
|
|
// Match SSR: do not read document/localStorage until after mount.
|
|
const [locale, setLocale] = useState<AdminApiLocale>("en");
|
|
|
|
useEffect(() => {
|
|
const syncLocale = () => {
|
|
setLocale(getAdminRequestLocale());
|
|
};
|
|
|
|
syncLocale();
|
|
i18n.on("languageChanged", syncLocale);
|
|
|
|
return () => {
|
|
i18n.off("languageChanged", syncLocale);
|
|
};
|
|
}, [i18n]);
|
|
|
|
async function onSelectLocale(next: AdminApiLocale) {
|
|
applyAdminUiLocale(next);
|
|
await i18n.changeLanguage(next);
|
|
setLocale(next);
|
|
toast.success(
|
|
t("language.changed", {
|
|
language: ADMIN_LOCALE_LABELS[next],
|
|
}),
|
|
);
|
|
}
|
|
|
|
const currentLabel = t(`language.${locale}`);
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
aria-label={t("language.title")}
|
|
className="inline-flex h-9 max-w-[9rem] items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground outline-none transition-colors hover:bg-muted/80 focus-visible:ring-2 focus-visible:ring-ring"
|
|
>
|
|
<GlobeIcon
|
|
className="size-4 shrink-0 text-muted-foreground"
|
|
aria-hidden
|
|
/>
|
|
<span className="min-w-0 truncate">{currentLabel}</span>
|
|
<ChevronDownIcon
|
|
className="size-3.5 shrink-0 text-muted-foreground/80"
|
|
aria-hidden
|
|
/>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="min-w-[10.5rem]">
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
{t("language.title")}
|
|
</DropdownMenuLabel>
|
|
{ADMIN_API_LOCALES.map((code) => {
|
|
const active = locale === code;
|
|
|
|
return (
|
|
<DropdownMenuItem
|
|
key={code}
|
|
className={cn(
|
|
"flex items-center justify-between gap-2",
|
|
active && "bg-accent text-accent-foreground",
|
|
)}
|
|
onClick={() => void onSelectLocale(code)}
|
|
>
|
|
<span className="truncate font-medium">
|
|
{ADMIN_LOCALE_LABELS[code]}
|
|
</span>
|
|
{active ? (
|
|
<CheckIcon className="size-4 shrink-0 text-primary" />
|
|
) : null}
|
|
</DropdownMenuItem>
|
|
);
|
|
})}
|
|
</DropdownMenuGroup>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|