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.
This commit is contained in:
Zheng Feng 2026-06-21 19:08:58 +08:00 committed by GitHub
parent e7b88a97ed
commit 5654a082f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 26 deletions

View file

@ -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<string, string> = {
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() {
<div className="text-center">
<h1 className="text-foreground font-serif text-3xl">DeerFlow</h1>
<p className="text-muted-foreground mt-2">
{isLogin ? "Sign in to your account" : "Create a new account"}
{isLogin ? t.login.signInTitle : t.login.createAccountTitle}
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-2">
<div className="flex flex-col space-y-1">
<label htmlFor="email" className="text-sm font-medium">
Email
{t.login.email}
</label>
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
placeholder={t.login.emailPlaceholder}
required
/>
</div>
<div className="flex flex-col space-y-1">
<label htmlFor="password" className="text-sm font-medium">
Password
{t.login.password}
</label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="•••••••"
placeholder={t.login.passwordPlaceholder}
required
minLength={isLogin ? 6 : 8}
/>
@ -223,10 +220,10 @@ export default function LoginPage() {
<Button type="submit" className="w-full" disabled={loading}>
{loading
? "Please wait..."
? t.login.pleaseWait
: isLogin
? "Sign In"
: "Create Account"}
? t.login.signIn
: t.login.createAccount}
</Button>
</form>
@ -239,15 +236,14 @@ export default function LoginPage() {
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
Or continue with
{t.login.orContinueWith}
</span>
</div>
</div>
)}
{showSsoHint && (
<p className="text-muted-foreground text-center text-sm">
If your account uses single sign-on, sign in with the option
below instead.
{t.login.ssoHint}
</p>
)}
{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)}
</Button>
))}
</div>
@ -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}
</button>
</div>
<div className="text-muted-foreground text-center text-xs">
<Link href="/" className="hover:underline">
Back to home
{t.login.backToHome}
</Link>
</div>
</div>

View file

@ -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.",
},
},
};

View file

@ -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;
};
};
}

View file

@ -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 登录。请联系管理员。",
},
},
};