From 9b40f7e10114fc81db6d7c76cd3ed706bcdba8b6 Mon Sep 17 00:00:00 2001 From: samanhappy Date: Sat, 26 Jul 2025 22:58:01 +0800 Subject: [PATCH] feat: enhance LanguageSwitch component with language toggle functionality and improve dropdown behavior; update UserProfileMenu styles (#248) --- .../src/components/icons/LanguageIcon.tsx | 5 +- frontend/src/components/layout/Header.tsx | 34 +--------- frontend/src/components/ui/LanguageSwitch.tsx | 62 ++++++++++++------- .../src/components/ui/UserProfileMenu.tsx | 58 ++++++++++++++++- frontend/src/locales/en.json | 3 +- frontend/src/locales/zh.json | 3 +- 6 files changed, 105 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/icons/LanguageIcon.tsx b/frontend/src/components/icons/LanguageIcon.tsx index 9d6b53e..cce57f5 100644 --- a/frontend/src/components/icons/LanguageIcon.tsx +++ b/frontend/src/components/icons/LanguageIcon.tsx @@ -1,6 +1,9 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; export const LanguageIcon: React.FC> = (props) => { + const { t } = useTranslation(); + return ( > = (props) => strokeWidth={2} {...props} > - Language + {t('common.language')} diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index 0d6e454..10c62b8 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -1,10 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import ThemeSwitch from '@/components/ui/ThemeSwitch'; import LanguageSwitch from '@/components/ui/LanguageSwitch'; import GitHubIcon from '@/components/icons/GitHubIcon'; -import SponsorDialog from '@/components/ui/SponsorDialog'; -import WeChatDialog from '@/components/ui/WeChatDialog'; interface HeaderProps { onToggleSidebar: () => void; @@ -12,8 +10,6 @@ interface HeaderProps { const Header: React.FC = ({ onToggleSidebar }) => { const { t } = useTranslation(); - const [sponsorDialogOpen, setSponsorDialogOpen] = useState(false); - const [wechatDialogOpen, setWechatDialogOpen] = useState(false); return (
@@ -51,38 +47,10 @@ const Header: React.FC = ({ onToggleSidebar }) => { > - {/* {i18n.language === 'zh' ? ( - - ) : ( - - - - )} */} - {/* */} - -
); }; diff --git a/frontend/src/components/ui/LanguageSwitch.tsx b/frontend/src/components/ui/LanguageSwitch.tsx index c4cbd96..9a88237 100644 --- a/frontend/src/components/ui/LanguageSwitch.tsx +++ b/frontend/src/components/ui/LanguageSwitch.tsx @@ -7,13 +7,21 @@ const LanguageSwitch: React.FC = () => { const [languageDropdownOpen, setLanguageDropdownOpen] = useState(false); const [currentLanguage, setCurrentLanguage] = useState(i18n.language); + // Available languages + const availableLanguages = [ + { code: 'en', label: 'English' }, + { code: 'zh', label: '中文' } + ]; + // Update current language when it changes useEffect(() => { setCurrentLanguage(i18n.language); }, [i18n.language]); - // Close dropdown when clicking outside + // Close dropdown when clicking outside (only needed when more than 2 languages) useEffect(() => { + if (availableLanguages.length <= 2) return; + const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; if (!target.closest('.language-dropdown')) { @@ -28,7 +36,7 @@ const LanguageSwitch: React.FC = () => { return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, [languageDropdownOpen]); + }, [languageDropdownOpen, availableLanguages.length]); const handleLanguageChange = (lang: string) => { localStorage.setItem('i18nextLng', lang); @@ -36,37 +44,47 @@ const LanguageSwitch: React.FC = () => { window.location.reload(); }; + // Toggle between languages when only two options are available + const handleLanguageToggle = () => { + if (availableLanguages.length === 2) { + // Direct toggle between two languages + const currentLangCode = currentLanguage.startsWith('zh') ? 'zh' : 'en'; + const nextLang = availableLanguages.find(lang => lang.code !== currentLangCode); + if (nextLang) { + handleLanguageChange(nextLang.code); + } + } else { + // Show dropdown for multiple languages + setLanguageDropdownOpen(!languageDropdownOpen); + } + }; + return (
- {languageDropdownOpen && ( + {/* Only show dropdown if there are more than 2 languages or if explicitly opened */} + {languageDropdownOpen && availableLanguages.length > 2 && (
- - + {availableLanguages.map((lang) => ( + + ))}
)} diff --git a/frontend/src/components/ui/UserProfileMenu.tsx b/frontend/src/components/ui/UserProfileMenu.tsx index cffdb6c..a22f06e 100644 --- a/frontend/src/components/ui/UserProfileMenu.tsx +++ b/frontend/src/components/ui/UserProfileMenu.tsx @@ -4,6 +4,11 @@ import { useNavigate } from 'react-router-dom'; import { useAuth } from '@/contexts/AuthContext'; import { User, Settings, LogOut, Info } from 'lucide-react'; import AboutDialog from './AboutDialog'; +import SponsorDialog from './SponsorDialog'; +import WeChatDialog from './WeChatDialog'; +import WeChatIcon from '@/components/icons/WeChatIcon'; +import DiscordIcon from '@/components/icons/DiscordIcon'; +import SponsorIcon from '@/components/icons/SponsorIcon'; import { checkLatestVersion, compareVersions } from '@/utils/version'; interface UserProfileMenuProps { @@ -12,12 +17,14 @@ interface UserProfileMenuProps { } const UserProfileMenu: React.FC = ({ collapsed, version }) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const navigate = useNavigate(); const { auth, logout } = useAuth(); const [isOpen, setIsOpen] = useState(false); const [showNewVersionInfo, setShowNewVersionInfo] = useState(false); const [showAboutDialog, setShowAboutDialog] = useState(false); + const [sponsorDialogOpen, setSponsorDialogOpen] = useState(false); + const [wechatDialogOpen, setWechatDialogOpen] = useState(false); const menuRef = useRef(null); // Check for new version on login and component mount @@ -65,6 +72,16 @@ const UserProfileMenu: React.FC = ({ collapsed, version }) setIsOpen(false); }; + const handleSponsorClick = () => { + setSponsorDialogOpen(true); + setIsOpen(false); + }; + + const handleWeChatClick = () => { + setWechatDialogOpen(true); + setIsOpen(false); + }; + return (
{isOpen && ( -
+
+ + + {i18n.language === 'zh' ? ( + + ) : ( + + + {t('discord.label')} + + )} + + +
+
); }; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 72e450b..1824d25 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -186,7 +186,8 @@ "copySuccess": "Copied to clipboard", "copyFailed": "Copy failed", "close": "Close", - "confirm": "Confirm" + "confirm": "Confirm", + "language": "Language" }, "nav": { "dashboard": "Dashboard", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index a246d1b..2a9d0a2 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -187,7 +187,8 @@ "copySuccess": "已复制到剪贴板", "copyFailed": "复制失败", "close": "关闭", - "confirm": "确认" + "confirm": "确认", + "language": "语言" }, "nav": { "dashboard": "仪表盘",