UPDATE: Add muti language
This commit is contained in:
110
frontend/src/components/languageSwitcher/index.tsx
Normal file
110
frontend/src/components/languageSwitcher/index.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const languages = [
|
||||
{ code: "en", label: "EN", native: "English" },
|
||||
{ code: "vi", label: "VI", native: "Tiếng Việt" },
|
||||
{ code: "ja", label: "JA", native: "日本語" },
|
||||
{ code: "ko", label: "KO", native: "한국어" },
|
||||
{ code: "zh", label: "ZH", native: "中文" },
|
||||
];
|
||||
|
||||
const LanguageSwitcher = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const current = languages.find((l) => l.code === i18n.language) ?? languages[0];
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, []);
|
||||
|
||||
const select = (code: string) => {
|
||||
i18n.changeLanguage(code);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
{/* Trigger */}
|
||||
<button
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="
|
||||
btn btn-ghost px-2
|
||||
flex items-center gap-1
|
||||
text-sm font-semibold tracking-wider
|
||||
text-white/80+
|
||||
select-none
|
||||
tooltip tooltip-bottom
|
||||
"
|
||||
data-tip={t("header.language")}
|
||||
>
|
||||
<span className="uppercase">{current.label}</span>
|
||||
{/* Chevron */}
|
||||
<svg
|
||||
className={`w-3 h-3 text-white/50 transition-transform duration-200 ${open ? "rotate-180" : ""}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2.5}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dropdown */}
|
||||
{open && (
|
||||
<ul
|
||||
className="
|
||||
absolute right-0 mt-2 w-32
|
||||
bg-black/60 backdrop-blur-md
|
||||
border border-white/10 rounded-xl
|
||||
shadow-2xl shadow-black/50
|
||||
overflow-hidden z-999
|
||||
py-1
|
||||
animate-in fade-in slide-in-from-top-2 duration-150
|
||||
"
|
||||
>
|
||||
{languages.map((lang) => {
|
||||
const isActive = lang.code === i18n.language;
|
||||
return (
|
||||
<li key={lang.code}>
|
||||
<button
|
||||
onClick={() => select(lang.code)}
|
||||
className={`
|
||||
w-full flex items-center gap-3 px-4 py-2.5 text-sm
|
||||
transition-colors duration-150
|
||||
${isActive
|
||||
? "bg-emerald-500/20 text-emerald-400"
|
||||
: "text-white/70 hover:bg-white/8 hover:text-white"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className="flex-1 text-left">{lang.native}</span>
|
||||
{isActive && (
|
||||
<svg className="w-3.5 h-3.5 text-emerald-400 shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
Reference in New Issue
Block a user