tokenFactory/web/src/components/layout/headerbar/LanguageSelector.jsx

94 lines
3.4 KiB
JavaScript

/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useState, useRef, useEffect } from "react";
import { Languages } from "lucide-react";
import { useTranslation } from "react-i18next";
import {
normalizeLanguage,
supportedLanguages,
LANGUAGE_NATIVE_LABELS,
} from "../../../i18n/language";
const nativeLabel = (code) => LANGUAGE_NATIVE_LABELS[code] || code;
const LanguageSelector = ({ currentLang, onLanguageChange }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const ref = useRef(null);
const normalized = normalizeLanguage(currentLang) || "zh-CN";
useEffect(() => {
if (!open) return;
const handler = (e) => {
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [open]);
return (
<div className="relative" ref={ref}>
<button
type="button"
aria-label={`${t("切换语言")}: ${nativeLabel(normalized)}`}
onClick={() => setOpen(!open)}
className="inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<Languages size={16} />
<span className="hidden sm:inline truncate max-w-[6rem]">
{nativeLabel(normalized)}
</span>
</button>
{open && (
<div className="absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-lg border border-border bg-popover p-1 shadow-md animate-fade-in">
{supportedLanguages.map((code) => {
const active = normalized === code;
return (
<button
key={code}
type="button"
onClick={() => {
onLanguageChange(code);
setOpen(false);
}}
className={`flex w-full items-center rounded-md px-2.5 py-1.5 text-sm transition-colors ${
active
? "bg-accent text-accent-foreground font-medium"
: "text-popover-foreground hover:bg-accent hover:text-accent-foreground"
}`}
>
{nativeLabel(code)}
{active && (
<span className="ml-auto flex h-3.5 w-3.5 items-center justify-center">
<span className="h-1.5 w-1.5 rounded-full bg-primary"></span>
</span>
)}
</button>
);
})}
</div>
)}
</div>
);
};
export default LanguageSelector;