Files
NextEdu/src/shared/components/locale-switcher.tsx
SpecialX 224740ad98 feat(shared): update permissions system, i18n, hooks, and components
- Update permission-bitmap.ts, permissions.ts, resolve-action-error.ts

- Update types/permissions.ts

- Update rbac i18n messages (en, zh-CN)

- Update locale-switcher.tsx

- Update use-action-with-toast.ts hook
2026-07-07 16:17:53 +08:00

76 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import * as React from "react"
import { useTransition } from "react"
import { useLocale, useTranslations } from "next-intl"
import { Check, Globe } from "lucide-react"
import { notify } from "@/shared/lib/notify"
import { Button } from "@/shared/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/shared/components/ui/dropdown-menu"
import { LOCALES, type Locale } from "@/shared/i18n/locale"
import { setLocaleAction } from "@/i18n/actions"
import { cn } from "@/shared/lib/utils"
/**
* 语言切换组件v3 i18n 体系)。
*
* 设计:
* - 不使用 URL 路由段,通过 cookie 持久化用户偏好
* - 切换时调用 setLocaleAction 写入 cookie + revalidatePath
* - 用 useTransition 保证切换过程不阻塞 UI
*/
export function LocaleSwitcher({ compact = false }: { compact?: boolean }) {
const locale = useLocale()
const t = useTranslations("common.locale")
const [isPending, startTransition] = useTransition()
function handleSelect(next: Locale) {
if (next === locale) return
startTransition(async () => {
const result = await setLocaleAction(next)
if (result.success) {
notify.success(t("switch"))
} else {
notify.error(t("switch"))
}
})
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size={compact ? "icon" : "sm"}
disabled={isPending}
aria-label={t("switch")}
className={cn(compact && "h-9 w-9")}
>
<Globe className="h-4 w-4" />
{!compact ? (
<span className="ml-1.5 text-sm">{t(locale)}</span>
) : null}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{LOCALES.map((l) => (
<DropdownMenuItem
key={l}
onClick={() => handleSelect(l)}
className="flex items-center justify-between gap-2"
>
<span>{t(l)}</span>
{l === locale ? <Check className="h-4 w-4" /> : null}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}