From 5654a082f5586e2ab3b0cd45e1341c042ef8e3ca Mon Sep 17 00:00:00 2001 From: Zheng Feng Date: Sun, 21 Jun 2026 19:08:58 +0800 Subject: [PATCH] feat(frontend): internationalize login page (en-US, zh-CN) (#3677) The login page used hardcoded English strings. Add a `login` section to the i18n Translations interface and both locale files, then wire the login page to `useI18n()` so all titles, labels, placeholders, buttons, SSO hints, and error messages resolve from the active locale. --- frontend/src/app/(auth)/login/page.tsx | 46 +++++++++++-------------- frontend/src/core/i18n/locales/en-US.ts | 28 +++++++++++++++ frontend/src/core/i18n/locales/types.ts | 27 +++++++++++++++ frontend/src/core/i18n/locales/zh-CN.ts | 26 ++++++++++++++ 4 files changed, 101 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx index 33964fa44..7971331b4 100644 --- a/frontend/src/app/(auth)/login/page.tsx +++ b/frontend/src/app/(auth)/login/page.tsx @@ -10,6 +10,7 @@ import { FlickeringGrid } from "@/components/ui/flickering-grid"; import { Input } from "@/components/ui/input"; import { useAuth } from "@/core/auth/AuthProvider"; import { parseAuthError } from "@/core/auth/types"; +import { useI18n } from "@/core/i18n/hooks"; /** * Validate next parameter @@ -49,6 +50,7 @@ export default function LoginPage() { const searchParams = useSearchParams(); const { isAuthenticated } = useAuth(); const { theme, resolvedTheme } = useTheme(); + const { t } = useI18n(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); @@ -59,16 +61,11 @@ export default function LoginPage() { // Extract error from query params (e.g., ?error=sso_failed) const errorParam = searchParams.get("error"); - const errorMessages: Record = { - sso_failed: "SSO login failed. Please try again or use email login.", - sso_cancelled: "SSO login was cancelled.", - sso_account_exists: - "An account with this email already exists. Please sign in with your password or contact your administrator.", - sso_not_allowed: - "SSO login is not allowed for your account. Contact your administrator.", - }; const [error, setError] = useState( - errorParam ? (errorMessages[errorParam] ?? "Authentication failed.") : "", + errorParam + ? (t.login.errors[errorParam as keyof typeof t.login.errors] ?? + t.login.authFailed) + : "", ); // Soft hint shown after a failed login when SSO is configured: an SSO-only // account has no local password, so the backend returns a generic @@ -164,7 +161,7 @@ export default function LoginPage() { // Both login and register set a cookie — redirect to workspace router.push(redirectPath); } catch { - setError("Network error. Please try again."); + setError(t.login.networkError); } finally { setLoading(false); } @@ -186,34 +183,34 @@ export default function LoginPage() {

DeerFlow

- {isLogin ? "Sign in to your account" : "Create a new account"} + {isLogin ? t.login.signInTitle : t.login.createAccountTitle}

setEmail(e.target.value)} - placeholder="you@example.com" + placeholder={t.login.emailPlaceholder} required />
setPassword(e.target.value)} - placeholder="•••••••" + placeholder={t.login.passwordPlaceholder} required minLength={isLogin ? 6 : 8} /> @@ -223,10 +220,10 @@ export default function LoginPage() { @@ -239,15 +236,14 @@ export default function LoginPage() {
- Or continue with + {t.login.orContinueWith}
)} {showSsoHint && (

- If your account uses single sign-on, sign in with the option - below instead. + {t.login.ssoHint}

)} {ssoProviders.map((provider) => ( @@ -261,7 +257,7 @@ export default function LoginPage() { window.location.href = `/api/v1/auth/oauth/${provider.id}?next=${encodeURIComponent(redirectPath)}`; }} > - Continue with {provider.display_name} + {t.login.continueWith(provider.display_name)} ))} @@ -277,15 +273,13 @@ export default function LoginPage() { }} className="text-blue-500 hover:underline" > - {isLogin - ? "Don't have an account? Sign up" - : "Already have an account? Sign in"} + {isLogin ? t.login.noAccountSignUp : t.login.haveAccountSignIn}
- ← Back to home + {t.login.backToHome}
diff --git a/frontend/src/core/i18n/locales/en-US.ts b/frontend/src/core/i18n/locales/en-US.ts index 00295954e..60031263f 100644 --- a/frontend/src/core/i18n/locales/en-US.ts +++ b/frontend/src/core/i18n/locales/en-US.ts @@ -555,4 +555,32 @@ export const enUS: Translations = { emptyDescription: "Credits and acknowledgements will show here.", }, }, + login: { + signInTitle: "Sign in to your account", + createAccountTitle: "Create a new account", + email: "Email", + emailPlaceholder: "you@example.com", + password: "Password", + passwordPlaceholder: "•••••••", + pleaseWait: "Please wait...", + signIn: "Sign In", + createAccount: "Create Account", + orContinueWith: "Or continue with", + ssoHint: + "If your account uses single sign-on, sign in with the option below instead.", + continueWith: (provider: string) => `Continue with ${provider}`, + noAccountSignUp: "Don't have an account? Sign up", + haveAccountSignIn: "Already have an account? Sign in", + backToHome: "← Back to home", + networkError: "Network error. Please try again.", + authFailed: "Authentication failed.", + errors: { + sso_failed: "SSO login failed. Please try again or use email login.", + sso_cancelled: "SSO login was cancelled.", + sso_account_exists: + "An account with this email already exists. Please sign in with your password or contact your administrator.", + sso_not_allowed: + "SSO login is not allowed for your account. Contact your administrator.", + }, + }, }; diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts index d16868243..a3b2664c7 100644 --- a/frontend/src/core/i18n/locales/types.ts +++ b/frontend/src/core/i18n/locales/types.ts @@ -459,4 +459,31 @@ export interface Translations { emptyDescription: string; }; }; + + // Login / Auth + login: { + signInTitle: string; + createAccountTitle: string; + email: string; + emailPlaceholder: string; + password: string; + passwordPlaceholder: string; + pleaseWait: string; + signIn: string; + createAccount: string; + orContinueWith: string; + ssoHint: string; + continueWith: (provider: string) => string; + noAccountSignUp: string; + haveAccountSignIn: string; + backToHome: string; + networkError: string; + authFailed: string; + errors: { + sso_failed: string; + sso_cancelled: string; + sso_account_exists: string; + sso_not_allowed: string; + }; + }; } diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts index 4ccce09ab..1f0e0ed59 100644 --- a/frontend/src/core/i18n/locales/zh-CN.ts +++ b/frontend/src/core/i18n/locales/zh-CN.ts @@ -534,4 +534,30 @@ export const zhCN: Translations = { emptyDescription: "相关的致谢信息会展示在这里。", }, }, + login: { + signInTitle: "登录你的账号", + createAccountTitle: "创建新账号", + email: "邮箱", + emailPlaceholder: "you@example.com", + password: "密码", + passwordPlaceholder: "•••••••", + pleaseWait: "请稍候...", + signIn: "登录", + createAccount: "创建账号", + orContinueWith: "或使用以下方式登录", + ssoHint: "如果你的账号使用单点登录(SSO),请改用下方的选项登录。", + continueWith: (provider: string) => `使用 ${provider} 登录`, + noAccountSignUp: "还没有账号?立即注册", + haveAccountSignIn: "已有账号?立即登录", + backToHome: "← 返回首页", + networkError: "网络错误,请重试。", + authFailed: "身份验证失败。", + errors: { + sso_failed: "SSO 登录失败,请重试或使用邮箱登录。", + sso_cancelled: "SSO 登录已取消。", + sso_account_exists: + "该邮箱对应的账号已存在。请使用密码登录或联系管理员。", + sso_not_allowed: "你的账号不允许使用 SSO 登录。请联系管理员。", + }, + }, };