mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-09 16:08:31 +00:00
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:
parent
e7b88a97ed
commit
5654a082f5
4 changed files with 101 additions and 26 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 登录。请联系管理员。",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue