Added language and theme switching function to login page and homepage

This commit is contained in:
choizhang
2025-03-12 00:42:12 +08:00
parent e5214f1a70
commit 7bf2d51bd0
9 changed files with 131 additions and 27 deletions

View File

@@ -1,5 +1,4 @@
import { useState, useCallback } from 'react'
import ThemeProvider from '@/components/ThemeProvider'
import MessageAlert from '@/components/MessageAlert'
import ApiKeyAlert from '@/components/ApiKeyAlert'
import StatusIndicator from '@/components/graph/StatusIndicator'
@@ -52,7 +51,6 @@ function App() {
}, [message, setApiKeyInvalid])
return (
<ThemeProvider>
<main className="flex h-screen w-screen overflow-x-hidden">
<Tabs
defaultValue={currentTab}
@@ -79,7 +77,6 @@ function App() {
{message !== null && !apiKeyInvalid && <MessageAlert />}
{apiKeyInvalid && <ApiKeyAlert />}
</main>
</ThemeProvider>
)
}

View File

@@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { Toaster } from 'sonner'
import App from './App'
import LoginPage from '@/features/LoginPage'
import ThemeProvider from '@/components/ThemeProvider'
interface ProtectedRouteProps {
children: React.ReactNode
@@ -20,20 +21,22 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const AppRouter = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/*"
element={
<ProtectedRoute>
<App />
</ProtectedRoute>
}
/>
</Routes>
<Toaster position="top-center" />
</BrowserRouter>
<ThemeProvider>
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/*"
element={
<ProtectedRoute>
<App />
</ProtectedRoute>
}
/>
</Routes>
<Toaster position="top-center" />
</BrowserRouter>
</ThemeProvider>
)
}

View File

@@ -0,0 +1,49 @@
import Button from '@/components/ui/Button'
import { useCallback } from 'react'
import { controlButtonVariant } from '@/lib/constants'
import { useTranslation } from 'react-i18next'
import { useSettingsStore } from '@/stores/settings'
/**
* Component that toggles the language between English and Chinese.
*/
export default function LanguageToggle() {
const { i18n } = useTranslation()
const currentLanguage = i18n.language
const setLanguage = useSettingsStore.use.setLanguage()
const setEnglish = useCallback(() => {
i18n.changeLanguage('en')
setLanguage('en')
}, [i18n, setLanguage])
const setChinese = useCallback(() => {
i18n.changeLanguage('zh')
setLanguage('zh')
}, [i18n, setLanguage])
if (currentLanguage === 'zh') {
return (
<Button
onClick={setEnglish}
variant={controlButtonVariant}
tooltip="Switch to English"
size="icon"
side="bottom"
>
</Button>
)
}
return (
<Button
onClick={setChinese}
variant={controlButtonVariant}
tooltip="切换到中文"
size="icon"
side="bottom"
>
EN
</Button>
)
}

View File

@@ -3,15 +3,19 @@ import { useNavigate } from 'react-router-dom'
import { useAuthStore } from '@/stores/state'
import { loginToServer } from '@/api/lightrag'
import { toast } from 'sonner'
import { useTranslation } from 'react-i18next'
import { Card, CardContent, CardHeader } from '@/components/ui/Card'
import Input from '@/components/ui/Input'
import Button from '@/components/ui/Button'
import { ZapIcon } from 'lucide-react'
import ThemeToggle from '@/components/ThemeToggle'
import LanguageToggle from '@/components/LanguageToggle'
const LoginPage = () => {
const navigate = useNavigate()
const { login } = useAuthStore()
const { t } = useTranslation()
const [loading, setLoading] = useState(false)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
@@ -19,7 +23,7 @@ const LoginPage = () => {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!username || !password) {
toast.error('Please enter your username and password')
toast.error(t('login.errorEmptyFields'))
return
}
@@ -28,10 +32,10 @@ const LoginPage = () => {
const response = await loginToServer(username, password)
login(response.access_token)
navigate('/')
toast.success('Login succeeded')
toast.success(t('login.successMessage'))
} catch (error) {
console.error('Login failed...', error)
toast.error('Login failed, please check username and password')
toast.error(t('login.errorInvalidCredentials'))
} finally {
setLoading(false)
}
@@ -39,6 +43,10 @@ const LoginPage = () => {
return (
<div className="flex h-screen w-screen items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800">
<div className="absolute top-4 right-4 flex items-center gap-2">
<LanguageToggle />
<ThemeToggle />
</div>
<Card className="w-full max-w-[480px] shadow-lg mx-4">
<CardHeader className="flex items-center justify-center space-y-2 pb-8 pt-6">
<div className="flex flex-col items-center space-y-4">
@@ -49,7 +57,7 @@ const LoginPage = () => {
<div className="text-center space-y-2">
<h1 className="text-3xl font-bold tracking-tight">LightRAG</h1>
<p className="text-muted-foreground text-sm">
Please enter your account and password to log in to the system
{t('login.description')}
</p>
</div>
</div>
@@ -58,11 +66,11 @@ const LoginPage = () => {
<form onSubmit={handleSubmit} className="space-y-6">
<div className="flex items-center gap-4">
<label htmlFor="username" className="text-sm font-medium w-16 shrink-0">
username
{t('login.username')}
</label>
<Input
id="username"
placeholder="Please input a username"
placeholder={t('login.usernamePlaceholder')}
value={username}
onChange={(e) => setUsername(e.target.value)}
required
@@ -71,12 +79,12 @@ const LoginPage = () => {
</div>
<div className="flex items-center gap-4">
<label htmlFor="password" className="text-sm font-medium w-16 shrink-0">
password
{t('login.password')}
</label>
<Input
id="password"
type="password"
placeholder="Please input a password"
placeholder={t('login.passwordPlaceholder')}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
@@ -88,7 +96,7 @@ const LoginPage = () => {
className="w-full h-11 text-base font-medium mt-2"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
{loading ? t('login.loggingIn') : t('login.loginButton')}
</Button>
</form>
</CardContent>

View File

@@ -1,6 +1,7 @@
import Button from '@/components/ui/Button'
import { SiteInfo } from '@/lib/constants'
import ThemeToggle from '@/components/ThemeToggle'
import LanguageToggle from '@/components/LanguageToggle'
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
import { useSettingsStore } from '@/stores/settings'
import { useAuthStore } from '@/stores/state'
@@ -82,6 +83,7 @@ export default function SiteHeader() {
<GithubIcon className="size-4" aria-hidden="true" />
</a>
</Button>
<LanguageToggle />
<ThemeToggle />
<Button
variant="ghost"

View File

@@ -1,9 +1,23 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { useSettingsStore } from "./stores/settings";
import en from "./locales/en.json";
import zh from "./locales/zh.json";
const getStoredLanguage = () => {
try {
const settingsString = localStorage.getItem('settings-storage');
if (settingsString) {
const settings = JSON.parse(settingsString);
return settings.state?.language || 'en';
}
} catch (e) {
console.error('Failed to get stored language:', e);
}
return 'en';
};
i18n
.use(initReactI18next)
.init({
@@ -11,7 +25,7 @@ i18n
en: { translation: en },
zh: { translation: zh }
},
lng: "en", // default
lng: getStoredLanguage(), // 使用存储的语言设置
fallbackLng: "en",
interpolation: {
escapeValue: false

View File

@@ -10,6 +10,18 @@
"switchToDark": "Switch to dark theme"
}
},
"login": {
"description": "Please enter your account and password to log in to the system",
"username": "Username",
"usernamePlaceholder": "Please input a username",
"password": "Password",
"passwordPlaceholder": "Please input a password",
"loginButton": "Login",
"loggingIn": "Logging in...",
"successMessage": "Login succeeded",
"errorEmptyFields": "Please enter your username and password",
"errorInvalidCredentials": "Login failed, please check username and password"
},
"documentPanel": {
"clearDocuments": {
"button": "Clear",

View File

@@ -10,6 +10,18 @@
"switchToDark": "切换到暗色主题"
}
},
"login": {
"description": "请输入您的账号和密码登录系统",
"username": "用户名",
"usernamePlaceholder": "请输入用户名",
"password": "密码",
"passwordPlaceholder": "请输入密码",
"loginButton": "登录",
"loggingIn": "登录中...",
"successMessage": "登录成功",
"errorEmptyFields": "请输入您的用户名和密码",
"errorInvalidCredentials": "登录失败,请检查用户名和密码"
},
"documentPanel": {
"clearDocuments": {
"button": "清除",

View File

@@ -6,6 +6,7 @@ import { Message, QueryRequest } from '@/api/lightrag'
type Theme = 'dark' | 'light' | 'system'
type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
type Language = 'en' | 'zh'
interface SettingsState {
// Graph viewer settings
@@ -46,6 +47,9 @@ interface SettingsState {
theme: Theme
setTheme: (theme: Theme) => void
language: Language
setLanguage: (language: Language) => void
enableHealthCheck: boolean
setEnableHealthCheck: (enable: boolean) => void
@@ -57,6 +61,7 @@ const useSettingsStoreBase = create<SettingsState>()(
persist(
(set) => ({
theme: 'system',
language: 'en',
showPropertyPanel: true,
showNodeSearchBar: true,
@@ -99,6 +104,8 @@ const useSettingsStoreBase = create<SettingsState>()(
setTheme: (theme: Theme) => set({ theme }),
setLanguage: (language: Language) => set({ language }),
setGraphLayoutMaxIterations: (iterations: number) =>
set({
graphLayoutMaxIterations: iterations