diff --git a/src/app/auth/forgot-password.tsx b/src/app/auth/forgot-password.tsx index 94c0c44..2de4b03 100644 --- a/src/app/auth/forgot-password.tsx +++ b/src/app/auth/forgot-password.tsx @@ -9,6 +9,7 @@ import { apiAuthCaptcha, apiUserRequestPasswordReset } from "@/client/api"; import type { ResetPasswordParams } from "@/client/model"; import { getCaptcha } from "@/lib/auth-crypto"; import { AUTH_FORM } from "@/css/auth/styles"; +import { t } from "@/i18n/T"; export function ForgotPasswordPage() { const [error, setError] = useState(""); @@ -24,7 +25,7 @@ export function ForgotPasswordPage() { const result = await getCaptcha(apiAuthCaptcha, true); setCaptchaImage(result.base64); } catch { - setError("Failed to load captcha"); + setError(t("auth.login.error.failed_to_load_captcha")); } }; @@ -38,7 +39,7 @@ export function ForgotPasswordPage() { setSuccess(false); if (data.captcha !== captchaText) { - setError("Captcha verification failed"); + setError(t("auth.forgot_password.captcha_failed")); loadCaptcha(); setLoading(false); return; @@ -50,7 +51,7 @@ export function ForgotPasswordPage() { setSuccess(true); } catch (err) { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; - setError(apiErr.response?.data?.message || "Failed to send reset email"); + setError(apiErr.response?.data?.message || t("auth.forgot_password.send_failed")); } finally { setLoading(false); } @@ -61,8 +62,8 @@ export function ForgotPasswordPage() {
-

Reset your password

-

Enter your email and we'll send you a reset link

+

{t("auth.forgot_password.title")}

+

{t("auth.forgot_password.subtitle")}

@@ -75,23 +76,23 @@ export function ForgotPasswordPage() { {success && ( - If an account exists with that email, you will receive a password reset link shortly. + {t("auth.forgot_password.success_message")} )}
@@ -102,13 +103,13 @@ export function ForgotPasswordPage() {
setCaptchaText(e.target.value)} @@ -119,7 +120,7 @@ export function ForgotPasswordPage() { alt="Captcha" className={AUTH_FORM.captchaImg} onClick={loadCaptcha} - title="Click to refresh" + title={t("auth.login.captcha_title")} /> )}
@@ -133,12 +134,12 @@ export function ForgotPasswordPage() { className={AUTH_FORM.submitBtn} disabled={loading || success} > - {loading ? "Sending..." : "Send Reset Link"} + {loading ? t("auth.forgot_password.sending") : t("auth.forgot_password.submit")}
- Back to login + {t("auth.forgot_password.back_to_login")}
diff --git a/src/app/auth/login.tsx b/src/app/auth/login.tsx index 2162333..9e35583 100644 --- a/src/app/auth/login.tsx +++ b/src/app/auth/login.tsx @@ -10,6 +10,7 @@ import { useLoginMutation } from "@/hooks/useAuth"; import type { LoginParams } from "@/client/model"; import { getCaptcha, encryptPassword } from "@/lib/auth-crypto"; import { AUTH_FORM } from "@/css/auth/styles"; +import { t } from "@/i18n/T"; export function LoginPage() { const navigate = useNavigate(); @@ -28,7 +29,7 @@ export function LoginPage() { setCaptchaImage(result.base64); setPublicKey(result.publicKey || ""); } catch { - setError("Failed to load captcha"); + setError(t("auth.login.error.failed_to_load_captcha")); } }; @@ -56,11 +57,11 @@ export function LoginPage() { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; if (apiErr.response?.status === 428) { setNeeds2FA(true); - setError("Two-factor authentication required"); + setError(t("auth.login.error.two_factor_required")); } else if (apiErr.response?.status === 401) { - setError("Invalid username or password"); + setError(t("auth.login.error.invalid_credentials")); } else { - setError(apiErr.response?.data?.message || "Login failed"); + setError(apiErr.response?.data?.message || t("auth.login.error.login_failed")); } loadCaptcha(); } @@ -73,8 +74,8 @@ export function LoginPage() {
-

Welcome back!

-

We're so excited to see you again!

+

{t("auth.login.title")}

+

{t("auth.login.subtitle")}

@@ -86,12 +87,12 @@ export function LoginPage() {
@@ -102,13 +103,13 @@ export function LoginPage() {
@@ -119,13 +120,13 @@ export function LoginPage() {
@@ -135,7 +136,7 @@ export function LoginPage() { alt="Captcha" className={AUTH_FORM.captchaImg} onClick={loadCaptcha} - title="Click to refresh" + title={t("auth.login.captcha_title")} /> )}
@@ -147,12 +148,12 @@ export function LoginPage() { {needs2FA && (
- Forgot your password? + {t("auth.login.forgot_password")}
- Need an account?{" "} + {t("auth.login.need_account")}{" "} - Register + {t("auth.login.register")}
diff --git a/src/app/auth/register.tsx b/src/app/auth/register.tsx index 896a6b4..c592ae9 100644 --- a/src/app/auth/register.tsx +++ b/src/app/auth/register.tsx @@ -9,6 +9,7 @@ import { apiAuthCaptcha, apiAuthRegister } from "@/client/api"; import type { RegisterParams } from "@/client/model"; import { getCaptcha, encryptPassword } from "@/lib/auth-crypto"; import { AUTH_FORM } from "@/css/auth/styles"; +import { t } from "@/i18n/T"; export function RegisterPage() { const navigate = useNavigate(); @@ -25,7 +26,7 @@ export function RegisterPage() { setCaptchaImage(result.base64); setPublicKey(result.publicKey || ""); } catch { - setError("Failed to load captcha"); + setError(t("auth.login.error.failed_to_load_captcha")); } }; @@ -41,7 +42,7 @@ export function RegisterPage() { setError(""); if (data.password !== data.confirmPassword) { - setError("Passwords do not match"); + setError(t("auth.register.passwords_not_match")); setLoading(false); return; } @@ -60,9 +61,9 @@ export function RegisterPage() { } catch (err) { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; if (apiErr.response?.status === 409) { - setError("Username or email already exists"); + setError(t("auth.register.user_exists")); } else { - setError(apiErr.response?.data?.message || "Registration failed"); + setError(apiErr.response?.data?.message || t("auth.register.registration_failed")); } loadCaptcha(); } finally { @@ -75,8 +76,8 @@ export function RegisterPage() {
-

Create an account

-

Join us today!

+

{t("auth.register.title")}

+

{t("auth.register.subtitle")}

@@ -88,16 +89,16 @@ export function RegisterPage() {
@@ -108,16 +109,16 @@ export function RegisterPage() {
@@ -128,16 +129,16 @@ export function RegisterPage() {
@@ -148,16 +149,16 @@ export function RegisterPage() {
value === password || "Passwords do not match" + required: t("auth.register.confirm_password_required"), + validate: value => value === password || t("auth.register.passwords_not_match") })} - placeholder="Confirm your password" + placeholder={t("auth.register.confirm_password_placeholder")} disabled={loading} className={AUTH_FORM.input} /> @@ -168,13 +169,13 @@ export function RegisterPage() {
@@ -184,7 +185,7 @@ export function RegisterPage() { alt="Captcha" className={AUTH_FORM.captchaImg} onClick={loadCaptcha} - title="Click to refresh" + title={t("auth.login.captcha_title")} /> )}
@@ -198,13 +199,13 @@ export function RegisterPage() { className={AUTH_FORM.submitBtn} disabled={loading} > - {loading ? "Creating account..." : "Create Account"} + {loading ? t("auth.register.submit_loading") : t("auth.register.submit")}
- Already have an account?{" "} + {t("auth.register.already_have_account")}{" "} - Login + {t("auth.register.login")}
diff --git a/src/app/auth/reset-password.tsx b/src/app/auth/reset-password.tsx index 738aaa5..1168c9c 100644 --- a/src/app/auth/reset-password.tsx +++ b/src/app/auth/reset-password.tsx @@ -9,6 +9,7 @@ import { apiAuthCaptcha, apiUserConfirmPasswordReset } from "@/client/api"; import type { ConfirmResetPasswordParams } from "@/client/model"; import { getCaptcha, encryptPassword } from "@/lib/auth-crypto"; import { AUTH_FORM } from "@/css/auth/styles"; +import { t } from "@/i18n/T"; export function ResetPasswordPage() { const navigate = useNavigate(); @@ -33,7 +34,7 @@ export function ResetPasswordPage() { setCaptchaImage(result.base64); setPublicKey(result.publicKey || ""); } catch { - setError("Failed to load captcha"); + setError(t("auth.login.error.failed_to_load_captcha")); } }; @@ -46,7 +47,7 @@ export function ResetPasswordPage() { setError(""); if (data.new_password !== data.confirmPassword) { - setError("Passwords do not match"); + setError(t("auth.register.passwords_not_match")); setLoading(false); return; } @@ -63,9 +64,9 @@ export function ResetPasswordPage() { } catch (err) { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; if (apiErr.response?.status === 400) { - setError("Invalid or expired reset token"); + setError(t("auth.reset_password.invalid_token")); } else { - setError(apiErr.response?.data?.message || "Failed to reset password"); + setError(apiErr.response?.data?.message || t("auth.reset_password.reset_failed")); } } finally { setLoading(false); @@ -77,8 +78,8 @@ export function ResetPasswordPage() {
-

Set new password

-

Choose a strong password for your account

+

{t("auth.reset_password.title")}

+

{t("auth.reset_password.subtitle")}

@@ -92,16 +93,16 @@ export function ResetPasswordPage() {
@@ -112,16 +113,16 @@ export function ResetPasswordPage() {
value === password || "Passwords do not match" + required: t("auth.register.confirm_password_required"), + validate: value => value === password || t("auth.register.passwords_not_match") })} - placeholder="Confirm new password" + placeholder={t("auth.reset_password.confirm_password_placeholder")} disabled={loading} className={AUTH_FORM.input} /> @@ -132,13 +133,13 @@ export function ResetPasswordPage() {
@@ -148,7 +149,7 @@ export function ResetPasswordPage() { alt="Captcha" className={AUTH_FORM.captchaImg} onClick={loadCaptcha} - title="Click to refresh" + title={t("auth.login.captcha_title")} /> )}
@@ -162,12 +163,12 @@ export function ResetPasswordPage() { className={AUTH_FORM.submitBtn} disabled={loading} > - {loading ? "Resetting..." : "Reset Password"} + {loading ? t("auth.reset_password.resetting") : t("auth.reset_password.submit")}
- Back to login + {t("auth.reset_password.back_to_login")}
diff --git a/src/app/auth/two-factor.tsx b/src/app/auth/two-factor.tsx index 75a139a..3c21fc4 100644 --- a/src/app/auth/two-factor.tsx +++ b/src/app/auth/two-factor.tsx @@ -9,6 +9,7 @@ import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "@/comp import { api2faEnable, api2faVerify, api2faDisable, api2faStatus } from "@/client/api"; import type { Disable2FAParams } from "@/client/model"; import { AUTH_FORM } from "@/css/auth/styles"; +import { t } from "@/i18n/T"; export function TwoFactorPage() { const navigate = useNavigate(); @@ -27,7 +28,7 @@ export function TwoFactorPage() { const response = await api2faStatus(); setIsEnabled(response.data.is_enabled || false); } catch { - setError("Failed to load 2FA status"); + setError(t("auth.two_factor.error.load_failed")); } }; @@ -47,7 +48,7 @@ export function TwoFactorPage() { setShowSetup(true); } catch (err) { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; - setError(apiErr.response?.data?.message || "Failed to enable 2FA"); + setError(apiErr.response?.data?.message || t("auth.two_factor.error.enable_failed")); } finally { setLoading(false); } @@ -67,7 +68,7 @@ export function TwoFactorPage() { setSecret(""); } catch (err) { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; - setError(apiErr.response?.data?.message || "Invalid verification code"); + setError(apiErr.response?.data?.message || t("auth.two_factor.error.invalid_code")); } finally { setLoading(false); } @@ -82,7 +83,7 @@ export function TwoFactorPage() { setIsEnabled(false); } catch (err) { const apiErr = err as { response?: { status?: number; data?: { message?: string } } }; - setError(apiErr.response?.data?.message || "Failed to disable 2FA"); + setError(apiErr.response?.data?.message || t("auth.two_factor.error.disable_failed")); } finally { setLoading(false); } @@ -93,9 +94,9 @@ export function TwoFactorPage() {
-

Two-Factor Authentication

+

{t("auth.two_factor.title")}

- {isEnabled ? "2FA is currently enabled" : "Add an extra layer of security"} + {isEnabled ? t("auth.two_factor.enabled") : t("auth.two_factor.disabled")}

@@ -109,15 +110,14 @@ export function TwoFactorPage() { {!isEnabled && !showSetup && (

- Two-factor authentication adds an additional layer of security to your account by requiring - more than just a password to log in. + {t("auth.two_factor.description")}

)} @@ -125,14 +125,14 @@ export function TwoFactorPage() { {showSetup && (
- +
QR Code
- +
- +
@@ -160,7 +160,7 @@ export function TwoFactorPage() { disabled={loading || verificationCode.length !== 6} className={AUTH_FORM.submitBtn} > - Verify + {t("auth.two_factor.submit")}
@@ -182,18 +182,18 @@ export function TwoFactorPage() {
- Two-factor authentication is currently enabled on your account. + {t("auth.two_factor.enabled_message")}
@@ -226,7 +226,7 @@ export function TwoFactorPage() { disabled={loading} className={AUTH_FORM.submitBtnDestructive} > - {loading ? "Disabling..." : "Disable 2FA"} + {loading ? t("auth.two_factor.disabling") : t("auth.two_factor.disable")} )} @@ -237,7 +237,7 @@ export function TwoFactorPage() { className="w-full h-11 transition-colors" style={{ color: "var(--text-muted)" }} > - Back + {t("auth.two_factor.back")}
diff --git a/src/fonts.css b/src/fonts.css new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/T.tsx b/src/i18n/T.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/de.json b/src/i18n/de.json new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/en.json b/src/i18n/en.json new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/fr.json b/src/i18n/fr.json new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/jp.json b/src/i18n/jp.json new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/zh.json b/src/i18n/zh.json new file mode 100644 index 0000000..e69de29