From 385117d01a0b3609e24274c17fcdf0e7d3c24644 Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Sat, 7 Feb 2026 01:13:11 +0800 Subject: [PATCH] fix(i18n): switch language via popover menu and complete Russian Kimi translations --- src/components/layout/MainLayout.tsx | 95 ++++++++++++++++++++-------- src/i18n/locales/ru.json | 13 ++++ src/styles/layout.scss | 76 ++++++++++++++-------- 3 files changed, 134 insertions(+), 50 deletions(-) diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 233150c..6e8aaa0 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -194,26 +194,17 @@ export function MainLayout() { const language = useLanguageStore((state) => state.language); const setLanguage = useLanguageStore((state) => state.setLanguage); - const handleLanguageChange = useCallback( - (event: React.ChangeEvent) => { - const selectedLanguage = event.target.value; - if (!isSupportedLanguage(selectedLanguage)) { - return; - } - setLanguage(selectedLanguage); - }, - [setLanguage] - ); - const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [checkingVersion, setCheckingVersion] = useState(false); + const [languageMenuOpen, setLanguageMenuOpen] = useState(false); const [brandExpanded, setBrandExpanded] = useState(true); const [requestLogModalOpen, setRequestLogModalOpen] = useState(false); const [requestLogDraft, setRequestLogDraft] = useState(false); const [requestLogTouched, setRequestLogTouched] = useState(false); const [requestLogSaving, setRequestLogSaving] = useState(false); const contentRef = useRef(null); + const languageMenuRef = useRef(null); const brandCollapseTimer = useRef | null>(null); const headerRef = useRef(null); const versionTapCount = useRef(0); @@ -313,6 +304,32 @@ export function MainLayout() { }; }, []); + useEffect(() => { + if (!languageMenuOpen) { + return; + } + + const handlePointerDown = (event: MouseEvent) => { + if (!languageMenuRef.current?.contains(event.target as Node)) { + setLanguageMenuOpen(false); + } + }; + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setLanguageMenuOpen(false); + } + }; + + document.addEventListener('mousedown', handlePointerDown); + document.addEventListener('keydown', handleEscape); + + return () => { + document.removeEventListener('mousedown', handlePointerDown); + document.removeEventListener('keydown', handleEscape); + }; + }, [languageMenuOpen]); + const handleBrandClick = useCallback(() => { if (!brandExpanded) { setBrandExpanded(true); @@ -332,6 +349,21 @@ export function MainLayout() { setRequestLogModalOpen(true); }, [requestLogEnabled]); + const toggleLanguageMenu = useCallback(() => { + setLanguageMenuOpen((prev) => !prev); + }, []); + + const handleLanguageSelect = useCallback( + (nextLanguage: string) => { + if (!isSupportedLanguage(nextLanguage)) { + return; + } + setLanguage(nextLanguage); + setLanguageMenuOpen(false); + }, + [setLanguage] + ); + const handleRequestLogClose = useCallback(() => { setRequestLogModalOpen(false); setRequestLogTouched(false); @@ -580,22 +612,35 @@ export function MainLayout() { > {headerIcons.update} -
- - + {headerIcons.language} + + {languageMenuOpen && ( +
+ {LANGUAGE_ORDER.map((lang) => ( + + ))} +
+ )}