From d6a5fdd911b8db6b795eaa3b68beacc046772440 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Tue, 12 Aug 2025 21:02:40 +0800 Subject: [PATCH] perf: complete sidebar menu --- .../components/home-sidebar/HomeSidebar.tsx | 125 +++++++++++++----- web/src/app/login/page.tsx | 77 +---------- web/src/app/register/page.tsx | 79 +---------- web/src/components/ui/language-selector.tsx | 98 ++++++++++++++ web/src/i18n/locales/en-US.ts | 4 +- web/src/i18n/locales/ja-JP.ts | 2 + web/src/i18n/locales/zh-Hans.ts | 2 + web/src/i18n/locales/zh-Hant.ts | 2 + 8 files changed, 210 insertions(+), 179 deletions(-) create mode 100644 web/src/components/ui/language-selector.tsx diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 816d50bb..d690d9d5 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -11,16 +11,17 @@ import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConf import langbotIcon from '@/app/assets/langbot-logo.webp'; import { systemInfo } from '@/app/infra/http/HttpClient'; import { useTranslation } from 'react-i18next'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; +import { Moon, Sun, Monitor } from 'lucide-react'; +import { useTheme } from 'next-themes'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Button } from '@/components/ui/button'; import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; +import { LanguageSelector } from '@/components/ui/language-selector'; // TODO 侧边导航栏要加动画 export default function HomeSidebar({ @@ -37,8 +38,10 @@ export default function HomeSidebar({ }, [pathname]); const [selectedChild, setSelectedChild] = useState(); - + const { theme, setTheme } = useTheme(); const { t } = useTranslation(); + const [popoverOpen, setPopoverOpen] = useState(false); + const [languageSelectorOpen, setLanguageSelectorOpen] = useState(false); useEffect(() => { initSelect(); @@ -178,9 +181,32 @@ export default function HomeSidebar({ } name={t('common.helpDocs')} /> + {/* { + handleLogout(); + }} + isSelected={false} + icon={ + + + + } + name={t('common.logout')} + /> */} - - + { + // 防止语言选择器打开时关闭popover + if (!open && languageSelectorOpen) return; + setPopoverOpen(open); + }} + > + {}} isSelected={false} @@ -195,26 +221,65 @@ export default function HomeSidebar({ } name={t('common.accountOptions')} /> - - - {/* My Account */} - {/* 主题颜色切换 */} - { - handleLogout(); - }} - > - + +
+ {t('common.theme')} + { + if (value) setTheme(value); + }} + className="justify-start" > - - - {t('common.logout')} - - - + + + + + + + + + + +
+ +
+ + {t('common.language')} + + +
+ +
+ {t('common.account')} + +
+ + ); diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx index 7f06e887..42c1653e 100644 --- a/web/src/app/login/page.tsx +++ b/web/src/app/login/page.tsx @@ -8,13 +8,7 @@ import { CardTitle, CardDescription, } from '@/components/ui/card'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; +import { LanguageSelector } from '@/components/ui/language-selector'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; @@ -26,14 +20,13 @@ import { FormLabel, FormMessage, } from '@/components/ui/form'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { useRouter } from 'next/navigation'; -import { Mail, Lock, Globe } from 'lucide-react'; +import { Mail, Lock } from 'lucide-react'; import langbotIcon from '@/app/assets/langbot-logo.webp'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; -import i18n from '@/i18n'; import Link from 'next/link'; import { ThemeToggle } from '@/components/ui/theme-toggle'; @@ -46,7 +39,6 @@ const formSchema = (t: (key: string) => string) => export default function Login() { const router = useRouter(); const { t } = useTranslation(); - const [currentLanguage, setCurrentLanguage] = useState(i18n.language); const form = useForm>>({ resolver: zodResolver(formSchema(t)), @@ -57,57 +49,10 @@ export default function Login() { }); useEffect(() => { - judgeLanguage(); getIsInitialized(); checkIfAlreadyLoggedIn(); }, []); - const judgeLanguage = () => { - if (i18n.language === 'zh-CN' || i18n.language === 'zh-Hans') { - setCurrentLanguage('zh-Hans'); - localStorage.setItem('langbot_language', 'zh-Hans'); - } else if (i18n.language === 'zh-TW' || i18n.language === 'zh-Hant') { - setCurrentLanguage('zh-Hant'); - localStorage.setItem('langbot_language', 'zh-Hant'); - } else if (i18n.language === 'ja' || i18n.language === 'ja-JP') { - setCurrentLanguage('ja-JP'); - localStorage.setItem('langbot_language', 'ja-JP'); - } else { - setCurrentLanguage('en-US'); - localStorage.setItem('langbot_language', 'en-US'); - } - // check if the language is already set - const lang = localStorage.getItem('langbot_language'); - if (lang) { - i18n.changeLanguage(lang); - setCurrentLanguage(lang); - return; - } else { - const language = navigator.language; - if (language) { - let lang = 'zh-Hans'; - if (language === 'zh-CN') { - lang = 'zh-Hans'; - } else if (language === 'zh-TW') { - lang = 'zh-Hant'; - } else if (language === 'ja' || language === 'ja-JP') { - lang = 'ja-JP'; - } else { - lang = 'en-US'; - } - i18n.changeLanguage(lang); - setCurrentLanguage(lang); - localStorage.setItem('langbot_language', lang); - } - } - }; - - const handleLanguageChange = (value: string) => { - i18n.changeLanguage(value); - setCurrentLanguage(value); - localStorage.setItem('langbot_language', value); - }; - function getIsInitialized() { httpClient .checkIfInited() @@ -161,21 +106,7 @@ export default function Login() {
- +
string) => @@ -45,7 +38,6 @@ const formSchema = (t: (key: string) => string) => export default function Register() { const router = useRouter(); const { t } = useTranslation(); - const [currentLanguage, setCurrentLanguage] = useState(i18n.language); const form = useForm>>({ resolver: zodResolver(formSchema(t)), @@ -56,58 +48,9 @@ export default function Register() { }); useEffect(() => { - judgeLanguage(); getIsInitialized(); }, []); - const judgeLanguage = () => { - if (i18n.language === 'zh-CN' || i18n.language === 'zh-Hans') { - setCurrentLanguage('zh-Hans'); - localStorage.setItem('langbot_language', 'zh-Hans'); - } else if (i18n.language === 'zh-TW' || i18n.language === 'zh-Hant') { - setCurrentLanguage('zh-Hant'); - localStorage.setItem('langbot_language', 'zh-Hant'); - } else if (i18n.language === 'ja' || i18n.language === 'ja-JP') { - setCurrentLanguage('ja-JP'); - localStorage.setItem('langbot_language', 'ja-JP'); - } else { - setCurrentLanguage('en-US'); - localStorage.setItem('langbot_language', 'en-US'); - } - // check if the language is already set - const lang = localStorage.getItem('langbot_language'); - console.log('lang: ', lang); - if (lang) { - i18n.changeLanguage(lang); - setCurrentLanguage(lang); - } else { - const language = navigator.language; - if (language) { - let lang = 'zh-Hans'; - if (language === 'zh-CN') { - lang = 'zh-Hans'; - } else if (language === 'zh-TW') { - lang = 'zh-Hant'; - } else if (language === 'ja' || language === 'ja-JP') { - lang = 'ja-JP'; - } else { - lang = 'en-US'; - } - console.log('language: ', lang); - i18n.changeLanguage(lang); - setCurrentLanguage(lang); - localStorage.setItem('langbot_language', lang); - } - } - }; - - const handleLanguageChange = (value: string) => { - console.log('handleLanguageChange: ', value); - i18n.changeLanguage(value); - setCurrentLanguage(value); - localStorage.setItem('langbot_language', value); - }; - function getIsInitialized() { httpClient .checkIfInited() @@ -145,21 +88,7 @@ export default function Register() {
- +
void; +} + +export function LanguageSelector({ + triggerClassName, + onOpenChange, +}: LanguageSelectorProps) { + const { t } = useTranslation(); + const [currentLanguage, setCurrentLanguage] = useState(i18n.language); + + useEffect(() => { + initializeLanguage(); + }, []); + + const initializeLanguage = () => { + if (i18n.language === 'zh-CN' || i18n.language === 'zh-Hans') { + setCurrentLanguage('zh-Hans'); + localStorage.setItem('langbot_language', 'zh-Hans'); + } else if (i18n.language === 'zh-TW' || i18n.language === 'zh-Hant') { + setCurrentLanguage('zh-Hant'); + localStorage.setItem('langbot_language', 'zh-Hant'); + } else if (i18n.language === 'ja' || i18n.language === 'ja-JP') { + setCurrentLanguage('ja-JP'); + localStorage.setItem('langbot_language', 'ja-JP'); + } else { + setCurrentLanguage('en-US'); + localStorage.setItem('langbot_language', 'en-US'); + } + + const savedLanguage = localStorage.getItem('langbot_language'); + if (savedLanguage) { + i18n.changeLanguage(savedLanguage); + setCurrentLanguage(savedLanguage); + } else { + const browserLanguage = navigator.language; + if (browserLanguage) { + let detectedLanguage = 'zh-Hans'; + if (browserLanguage === 'zh-CN') { + detectedLanguage = 'zh-Hans'; + } else if (browserLanguage === 'zh-TW') { + detectedLanguage = 'zh-Hant'; + } else if (browserLanguage === 'ja' || browserLanguage === 'ja-JP') { + detectedLanguage = 'ja-JP'; + } else { + detectedLanguage = 'en-US'; + } + i18n.changeLanguage(detectedLanguage); + setCurrentLanguage(detectedLanguage); + localStorage.setItem('langbot_language', detectedLanguage); + } + } + }; + + const handleLanguageChange = (value: string) => { + i18n.changeLanguage(value); + setCurrentLanguage(value); + localStorage.setItem('langbot_language', value); + + // 刷新页面以应用新的语言设置 + window.location.reload(); + }; + + return ( + + ); +} diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 3cf12202..71b305a2 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -2,7 +2,8 @@ const enUS = { common: { login: 'Login', logout: 'Logout', - accountOptions: 'Account Options', + accountOptions: 'Account', + account: 'Account', email: 'Email', password: 'Password', welcome: 'Welcome back to LangBot 👋', @@ -42,6 +43,7 @@ const enUS = { test: 'Test', forgotPassword: 'Forgot Password?', loading: 'Loading...', + theme: 'Theme', }, notFound: { title: 'Page not found', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index cd9f0aa4..84a04ce1 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -3,6 +3,7 @@ const jaJP = { login: 'ログイン', logout: 'ログアウト', accountOptions: 'アカウントオプション', + account: 'アカウント', email: 'メールアドレス', password: 'パスワード', welcome: 'LangBot へおかえりなさい 👋', @@ -43,6 +44,7 @@ const jaJP = { test: 'テスト', forgotPassword: 'パスワードを忘れた?', loading: '読み込み中...', + theme: 'テーマ', }, notFound: { title: 'ページが見つかりません', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index ac5b685d..9e878453 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -3,6 +3,7 @@ const zhHans = { login: '登录', logout: '退出登录', accountOptions: '账户选项', + account: '账户', email: '邮箱', password: '密码', welcome: '欢迎回到 LangBot 👋', @@ -42,6 +43,7 @@ const zhHans = { test: '测试', forgotPassword: '忘记密码?', loading: '加载中...', + theme: '主题', }, notFound: { title: '页面不存在', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 74dbced2..313c1fe5 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -3,6 +3,7 @@ const zhHant = { login: '登入', logout: '登出', accountOptions: '帳戶選項', + account: '帳戶', email: '電子郵件', password: '密碼', welcome: '歡迎回到 LangBot 👋', @@ -42,6 +43,7 @@ const zhHant = { test: '測試', forgotPassword: '忘記密碼?', loading: '載入中...', + theme: '主題', }, notFound: { title: '頁面不存在',