Files
dyolink/frontend/src/components/ui/shared/LanguageToggle.tsx

95 lines
3.2 KiB
TypeScript

'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Globe, Check } from 'lucide-react';
import { useLocale, useTranslations } from 'next-intl';
import { usePathname, useRouter } from '@/i18n/navigation';
import { useAuth } from '@/lib/hooks/useAuth';
import { authApi } from '@/lib/api/auth';
import { locales, type AppLocale } from '@/i18n/routing';
const LOCALE_OPTIONS: AppLocale[] = [...locales];
export function LanguageToggle() {
const t = useTranslations('language');
const locale = useLocale() as AppLocale;
const router = useRouter();
const pathname = usePathname();
const { user, setUserLanguage } = useAuth();
const [open, setOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const onDocClick = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setOpen(false);
}
};
document.addEventListener('mousedown', onDocClick);
return () => document.removeEventListener('mousedown', onDocClick);
}, []);
const switchLocale = useCallback(
async (next: AppLocale) => {
if (next === locale) {
setOpen(false);
return;
}
if (user) {
setUserLanguage(next);
try {
await authApi.updateLanguage(next);
} catch {
/* keep optimistic locale in client state */
}
}
router.replace(pathname, { locale: next });
setOpen(false);
},
[locale, pathname, router, setUserLanguage, user],
);
return (
<div className="relative" ref={menuRef}>
<button
type="button"
onClick={() => setOpen((value) => !value)}
className="inline-flex h-9 shrink-0 items-center justify-center gap-1.5 rounded-[var(--radius-md)] border border-border/60 bg-background-secondary/80 px-2.5 text-text-primary hover:border-border-strong hover:bg-background-card/80 transition-colors"
aria-label={t('selectLanguage')}
aria-expanded={open}
aria-haspopup="listbox"
title={t('label')}
>
<Globe className="h-[18px] w-[18px] icon-flat" />
<span className="text-xs font-medium uppercase">{locale}</span>
</button>
{open && (
<ul
role="listbox"
aria-label={t('selectLanguage')}
className="absolute right-0 z-[200] mt-2 min-w-[10rem] rounded-[var(--radius-md)] border border-border bg-background-secondary/95 py-1 shadow-lg backdrop-blur-sm"
>
{LOCALE_OPTIONS.map((option) => {
const selected = option === locale;
return (
<li key={option} role="option" aria-selected={selected}>
<button
type="button"
className="flex w-full items-center justify-between gap-3 px-3 py-2 text-sm text-text-primary hover:bg-background-card/70"
onClick={() => void switchLocale(option)}
>
<span>{t(option)}</span>
{selected && <Check className="h-4 w-4 text-primary shrink-0" />}
</button>
</li>
);
})}
</ul>
)}
</div>
);
}