From df872823c1ff0dccce5125593e62e04c4c1b14c2 Mon Sep 17 00:00:00 2001 From: samanhappy Date: Sat, 26 Jul 2025 21:46:14 +0800 Subject: [PATCH] Implement language and theme switchers in Header (#247) --- .../src/components/icons/LanguageIcon.tsx | 24 ++++++ frontend/src/components/layout/Header.tsx | 30 ++++---- frontend/src/components/ui/LanguageSwitch.tsx | 77 +++++++++++++++++++ frontend/src/components/ui/ThemeSwitch.tsx | 49 +++--------- frontend/src/pages/LoginPage.tsx | 4 +- frontend/src/pages/SettingsPage.tsx | 40 +--------- 6 files changed, 132 insertions(+), 92 deletions(-) create mode 100644 frontend/src/components/icons/LanguageIcon.tsx create mode 100644 frontend/src/components/ui/LanguageSwitch.tsx diff --git a/frontend/src/components/icons/LanguageIcon.tsx b/frontend/src/components/icons/LanguageIcon.tsx new file mode 100644 index 0000000..9d6b53e --- /dev/null +++ b/frontend/src/components/icons/LanguageIcon.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export const LanguageIcon: React.FC> = (props) => { + return ( + + Language + + + + + ); +}; + +export default LanguageIcon; diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index a53aa66..0d6e454 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 { useTranslation } from 'react-i18next'; import ThemeSwitch from '@/components/ui/ThemeSwitch'; +import LanguageSwitch from '@/components/ui/LanguageSwitch'; import GitHubIcon from '@/components/icons/GitHubIcon'; -import SponsorIcon from '@/components/icons/SponsorIcon'; -import WeChatIcon from '@/components/icons/WeChatIcon'; -import DiscordIcon from '@/components/icons/DiscordIcon'; import SponsorDialog from '@/components/ui/SponsorDialog'; import WeChatDialog from '@/components/ui/WeChatDialog'; @@ -13,7 +11,7 @@ interface HeaderProps { } const Header: React.FC = ({ onToggleSidebar }) => { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const [sponsorDialogOpen, setSponsorDialogOpen] = useState(false); const [wechatDialogOpen, setWechatDialogOpen] = useState(false); @@ -36,26 +34,27 @@ const Header: React.FC = ({ onToggleSidebar }) => {

{t('app.title')}

- {/* Theme Switch and Version */} -
- + {/* Theme Switch and Language Switcher and Version */} +
+ {import.meta.env.PACKAGE_VERSION === 'dev' ? import.meta.env.PACKAGE_VERSION : `v${import.meta.env.PACKAGE_VERSION}`} + - {i18n.language === 'zh' ? ( + {/* {i18n.language === 'zh' ? ( + */} +
diff --git a/frontend/src/components/ui/LanguageSwitch.tsx b/frontend/src/components/ui/LanguageSwitch.tsx new file mode 100644 index 0000000..c4cbd96 --- /dev/null +++ b/frontend/src/components/ui/LanguageSwitch.tsx @@ -0,0 +1,77 @@ +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import LanguageIcon from '@/components/icons/LanguageIcon'; + +const LanguageSwitch: React.FC = () => { + const { i18n } = useTranslation(); + const [languageDropdownOpen, setLanguageDropdownOpen] = useState(false); + const [currentLanguage, setCurrentLanguage] = useState(i18n.language); + + // Update current language when it changes + useEffect(() => { + setCurrentLanguage(i18n.language); + }, [i18n.language]); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest('.language-dropdown')) { + setLanguageDropdownOpen(false); + } + }; + + if (languageDropdownOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [languageDropdownOpen]); + + const handleLanguageChange = (lang: string) => { + localStorage.setItem('i18nextLng', lang); + setLanguageDropdownOpen(false); + window.location.reload(); + }; + + return ( +
+ + + {languageDropdownOpen && ( +
+
+ + +
+
+ )} +
+ ); +}; + +export default LanguageSwitch; diff --git a/frontend/src/components/ui/ThemeSwitch.tsx b/frontend/src/components/ui/ThemeSwitch.tsx index f975ae7..b0f2d3b 100644 --- a/frontend/src/components/ui/ThemeSwitch.tsx +++ b/frontend/src/components/ui/ThemeSwitch.tsx @@ -7,44 +7,19 @@ const ThemeSwitch: React.FC = () => { const { t } = useTranslation(); const { theme, setTheme } = useTheme(); + const toggleTheme = () => { + setTheme(theme === 'light' ? 'dark' : 'light'); + }; + return ( -
-
- - - {/* */} -
-
+ ); }; diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 57cd9b1..86704e1 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../contexts/AuthContext'; import ThemeSwitch from '@/components/ui/ThemeSwitch'; +import LanguageSwitch from '@/components/ui/LanguageSwitch'; const LoginPage: React.FC = () => { const { t } = useTranslation(); @@ -41,8 +42,9 @@ const LoginPage: React.FC = () => { return (
-
+
+
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index c2c37d0..d4dcee6 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -10,15 +10,9 @@ import { PermissionChecker } from '@/components/PermissionChecker'; import { PERMISSIONS } from '@/constants/permissions'; const SettingsPage: React.FC = () => { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const navigate = useNavigate(); const { showToast } = useToast(); - const [currentLanguage, setCurrentLanguage] = useState(i18n.language); - - // Update current language when it changes - useEffect(() => { - setCurrentLanguage(i18n.language); - }, [i18n.language]); const [installConfig, setInstallConfig] = useState<{ pythonIndexUrl: string; @@ -197,42 +191,10 @@ const SettingsPage: React.FC = () => { }, 2000); }; - const handleLanguageChange = (lang: string) => { - localStorage.setItem('i18nextLng', lang); - window.location.reload(); - }; - return (

{t('pages.settings.title')}

- {/* Language Settings */} -
-
-

{t('pages.settings.language')}

-
- - -
-
-
- {/* Smart Routing Configuration Settings */}