95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import { useState } from 'react';
|
|
import { router, usePage } from '@inertiajs/react';
|
|
import { Check, Languages } from 'lucide-react';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { type SharedData } from '@/types';
|
|
import { setLocale } from '@/routes';
|
|
|
|
export function DashboardLanguageSwitcher() {
|
|
const page = usePage<SharedData>();
|
|
const { locale, supportedLocales, translations } = page.props;
|
|
const locales = supportedLocales && supportedLocales.length > 0 ? supportedLocales : ['de', 'en'];
|
|
const activeLocale = locales.includes(locale as string) && typeof locale === 'string' ? locale : locales[0];
|
|
const dashboardTranslations =
|
|
translations && typeof translations.dashboard === 'object' && translations.dashboard !== null
|
|
? (translations.dashboard as Record<string, unknown>)
|
|
: null;
|
|
const languageSwitcherTranslations =
|
|
dashboardTranslations && typeof dashboardTranslations.language_switcher === 'object'
|
|
? (dashboardTranslations.language_switcher as Record<string, string | undefined>)
|
|
: {};
|
|
const languageCopy = languageSwitcherTranslations ?? {};
|
|
const label = languageCopy.label ?? 'Sprache';
|
|
const changeLabel = languageCopy.change ?? 'Sprache wechseln';
|
|
|
|
const [pendingLocale, setPendingLocale] = useState<string | null>(null);
|
|
|
|
const handleChange = (nextLocale: string) => {
|
|
if (nextLocale === activeLocale || pendingLocale === nextLocale || !locales.includes(nextLocale)) {
|
|
return;
|
|
}
|
|
|
|
setPendingLocale(nextLocale);
|
|
|
|
router.post(
|
|
setLocale().url,
|
|
{ locale: nextLocale },
|
|
{
|
|
preserveScroll: true,
|
|
preserveState: true,
|
|
onFinish: () => setPendingLocale(null),
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="flex items-center gap-2 rounded-full border-white/30 bg-white/80 px-3 text-xs font-semibold uppercase tracking-wide text-slate-900 backdrop-blur transition hover:bg-white dark:border-white/20 dark:bg-white/10 dark:text-white"
|
|
aria-label={label}
|
|
>
|
|
<Languages className="size-4" />
|
|
<span>{label}</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="min-w-[9rem]">
|
|
<DropdownMenuLabel className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
|
|
{changeLabel}
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
{locales.map((code) => {
|
|
const isActive = code === activeLocale;
|
|
const isPending = code === pendingLocale;
|
|
|
|
return (
|
|
<DropdownMenuItem
|
|
key={code}
|
|
onSelect={(event) => {
|
|
event.preventDefault();
|
|
handleChange(code);
|
|
}}
|
|
disabled={isPending}
|
|
className="flex items-center justify-between gap-3"
|
|
>
|
|
<span className="text-sm font-medium uppercase tracking-wide">{code}</span>
|
|
{(isActive || isPending) && <Check className="size-4 text-pink-500" />}
|
|
</DropdownMenuItem>
|
|
);
|
|
})}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|