diff --git a/apps/console/src/components/AuthPageLayout.tsx b/apps/console/src/components/AuthPageLayout.tsx new file mode 100644 index 000000000..7e56c4d1f --- /dev/null +++ b/apps/console/src/components/AuthPageLayout.tsx @@ -0,0 +1,45 @@ +/** + * Shared layout for authentication pages (login, register, forgot password). + * Provides a widescreen-optimized split-panel design with branding on the left + * and form content on the right, inspired by enterprise platforms like Airtable and Salesforce. + */ + +import type React from 'react'; + +export function AuthPageLayout({ children }: { children: React.ReactNode }) { + return ( +
+ {/* Left branding panel - hidden on mobile, shown on lg+ */} +
+
+
+ + + + ObjectStack +
+

+ Build powerful business applications, faster. +

+

+ The universal platform for enterprise data management, workflows, and analytics. +

+
+
+ + {/* Right form panel */} +
+ {children} +
+
+ ); +} diff --git a/apps/console/src/pages/ForgotPasswordPage.tsx b/apps/console/src/pages/ForgotPasswordPage.tsx index 5fb9053de..4dd508682 100644 --- a/apps/console/src/pages/ForgotPasswordPage.tsx +++ b/apps/console/src/pages/ForgotPasswordPage.tsx @@ -2,12 +2,37 @@ * Forgot Password Page for ObjectStack Console */ -import { ForgotPasswordForm } from '@object-ui/auth'; +import { Link } from 'react-router-dom'; +import { ForgotPasswordForm, type AuthLinkComponentProps } from '@object-ui/auth'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { AuthPageLayout } from '../components/AuthPageLayout'; + +const RouterLink = ({ href, className, children }: AuthLinkComponentProps) => ( + {children} +); export function ForgotPasswordPage() { + const { t } = useObjectTranslation(); + return ( -
- -
+ + + ); } diff --git a/apps/console/src/pages/LoginPage.tsx b/apps/console/src/pages/LoginPage.tsx index ef0c60b79..a8167f9ba 100644 --- a/apps/console/src/pages/LoginPage.tsx +++ b/apps/console/src/pages/LoginPage.tsx @@ -2,19 +2,40 @@ * Login Page for ObjectStack Console */ -import { useNavigate } from 'react-router-dom'; -import { LoginForm } from '@object-ui/auth'; +import { useNavigate, Link } from 'react-router-dom'; +import { LoginForm, type AuthLinkComponentProps } from '@object-ui/auth'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { AuthPageLayout } from '../components/AuthPageLayout'; + +const RouterLink = ({ href, className, children }: AuthLinkComponentProps) => ( + {children} +); export function LoginPage() { const navigate = useNavigate(); + const { t } = useObjectTranslation(); return ( -
+ navigate('/')} registerUrl="/register" forgotPasswordUrl="/forgot-password" + title={t('auth.login.title')} + description={t('auth.login.description')} + linkComponent={RouterLink} + labels={{ + emailLabel: t('auth.login.emailLabel'), + emailPlaceholder: t('auth.login.emailPlaceholder'), + passwordLabel: t('auth.login.passwordLabel'), + passwordPlaceholder: t('auth.login.passwordPlaceholder'), + forgotPasswordText: t('auth.login.forgotPasswordText'), + submitButton: t('auth.login.submitButton'), + submittingButton: t('auth.login.submittingButton'), + noAccountText: t('auth.login.noAccountText'), + signUpText: t('auth.login.signUpText'), + }} /> -
+ ); } diff --git a/apps/console/src/pages/RegisterPage.tsx b/apps/console/src/pages/RegisterPage.tsx index b52147114..8da765cdf 100644 --- a/apps/console/src/pages/RegisterPage.tsx +++ b/apps/console/src/pages/RegisterPage.tsx @@ -2,18 +2,44 @@ * Register Page for ObjectStack Console */ -import { useNavigate } from 'react-router-dom'; -import { RegisterForm } from '@object-ui/auth'; +import { useNavigate, Link } from 'react-router-dom'; +import { RegisterForm, type AuthLinkComponentProps } from '@object-ui/auth'; +import { useObjectTranslation } from '@object-ui/i18n'; +import { AuthPageLayout } from '../components/AuthPageLayout'; + +const RouterLink = ({ href, className, children }: AuthLinkComponentProps) => ( + {children} +); export function RegisterPage() { const navigate = useNavigate(); + const { t } = useObjectTranslation(); return ( -
+ navigate('/')} loginUrl="/login" + title={t('auth.register.title')} + description={t('auth.register.description')} + linkComponent={RouterLink} + labels={{ + nameLabel: t('auth.register.nameLabel'), + namePlaceholder: t('auth.register.namePlaceholder'), + emailLabel: t('auth.register.emailLabel'), + emailPlaceholder: t('auth.register.emailPlaceholder'), + passwordLabel: t('auth.register.passwordLabel'), + passwordPlaceholder: t('auth.register.passwordPlaceholder'), + confirmPasswordLabel: t('auth.register.confirmPasswordLabel'), + confirmPasswordPlaceholder: t('auth.register.confirmPasswordPlaceholder'), + passwordMismatchError: t('auth.register.passwordMismatchError'), + passwordTooShortError: t('auth.register.passwordTooShortError'), + submitButton: t('auth.register.submitButton'), + submittingButton: t('auth.register.submittingButton'), + hasAccountText: t('auth.register.hasAccountText'), + signInText: t('auth.register.signInText'), + }} /> -
+ ); } diff --git a/packages/auth/src/ForgotPasswordForm.tsx b/packages/auth/src/ForgotPasswordForm.tsx index 4ca480a82..8d0c7f872 100644 --- a/packages/auth/src/ForgotPasswordForm.tsx +++ b/packages/auth/src/ForgotPasswordForm.tsx @@ -8,6 +8,20 @@ import React, { useState } from 'react'; import { useAuth } from './useAuth'; +import type { AuthLinkComponentProps } from './types'; + +/** Translatable labels for the ForgotPasswordForm */ +export interface ForgotPasswordFormLabels { + emailLabel?: string; + emailPlaceholder?: string; + submitButton?: string; + submittingButton?: string; + successTitle?: string; + successDescription?: string; + backToSignInText?: string; + rememberPasswordText?: string; + signInText?: string; +} export interface ForgotPasswordFormProps { /** Callback on successful submission */ @@ -20,8 +34,16 @@ export interface ForgotPasswordFormProps { title?: string; /** Custom description */ description?: string; + /** Custom link component for SPA navigation (e.g. React Router's Link) */ + linkComponent?: React.ComponentType; + /** Override default labels for i18n */ + labels?: ForgotPasswordFormLabels; } +const DefaultLink = ({ href, className, children }: AuthLinkComponentProps) => ( + {children} +); + /** * Forgot password form component. * Sends a password reset email to the user. @@ -40,12 +62,26 @@ export function ForgotPasswordForm({ loginUrl = '/login', title = 'Reset your password', description = 'Enter your email address and we\'ll send you a link to reset your password', + linkComponent: LinkComp = DefaultLink, + labels = {}, }: ForgotPasswordFormProps) { const { forgotPassword, isLoading } = useAuth(); const [email, setEmail] = useState(''); const [error, setError] = useState(null); const [submitted, setSubmitted] = useState(false); + const l = { + emailLabel: labels.emailLabel ?? 'Email', + emailPlaceholder: labels.emailPlaceholder ?? 'name@example.com', + submitButton: labels.submitButton ?? 'Send Reset Link', + submittingButton: labels.submittingButton ?? 'Sending...', + successTitle: labels.successTitle ?? 'Check your email', + successDescription: labels.successDescription ?? "We've sent a password reset link to {{email}}. Please check your inbox.", + backToSignInText: labels.backToSignInText ?? 'Back to sign in', + rememberPasswordText: labels.rememberPasswordText ?? 'Remember your password?', + signInText: labels.signInText ?? 'Sign in', + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); @@ -62,20 +98,20 @@ export function ForgotPasswordForm({ }; if (submitted) { + const successMsg = l.successDescription.includes('{{email}}') + ? l.successDescription.replace('{{email}}', email) + : `${l.successDescription} ${email}`; return ( -
+
-

Check your email

-

- We've sent a password reset link to {email}. - Please check your inbox and follow the instructions. -

+

{l.successTitle}

+

{successMsg}

{loginUrl && (

- - Back to sign in - + + {l.backToSignInText} +

)}
@@ -83,7 +119,7 @@ export function ForgotPasswordForm({ } return ( -
+

{title}

{description}

@@ -98,12 +134,12 @@ export function ForgotPasswordForm({
setEmail(e.target.value)} required @@ -118,16 +154,16 @@ export function ForgotPasswordForm({ disabled={isLoading} className="inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" > - {isLoading ? 'Sending...' : 'Send Reset Link'} + {isLoading ? l.submittingButton : l.submitButton} {loginUrl && (

- Remember your password?{' '} - - Sign in - + {l.rememberPasswordText}{' '} + + {l.signInText} +

)}
diff --git a/packages/auth/src/LoginForm.tsx b/packages/auth/src/LoginForm.tsx index f725b2625..9545dd372 100644 --- a/packages/auth/src/LoginForm.tsx +++ b/packages/auth/src/LoginForm.tsx @@ -8,6 +8,20 @@ import React, { useState } from 'react'; import { useAuth } from './useAuth'; +import type { AuthLinkComponentProps } from './types'; + +/** Translatable labels for the LoginForm */ +export interface LoginFormLabels { + emailLabel?: string; + emailPlaceholder?: string; + passwordLabel?: string; + passwordPlaceholder?: string; + forgotPasswordText?: string; + submitButton?: string; + submittingButton?: string; + noAccountText?: string; + signUpText?: string; +} export interface LoginFormProps { /** Callback on successful login */ @@ -22,8 +36,16 @@ export interface LoginFormProps { title?: string; /** Custom description */ description?: string; + /** Custom link component for SPA navigation (e.g. React Router's Link) */ + linkComponent?: React.ComponentType; + /** Override default labels for i18n */ + labels?: LoginFormLabels; } +const DefaultLink = ({ href, className, children }: AuthLinkComponentProps) => ( + {children} +); + /** * Login form component with email/password authentication. * Uses Tailwind CSS utility classes for styling. @@ -44,12 +66,26 @@ export function LoginForm({ forgotPasswordUrl = '/forgot-password', title = 'Sign in to your account', description = 'Enter your email and password to continue', + linkComponent: LinkComp = DefaultLink, + labels = {}, }: LoginFormProps) { const { signIn, isLoading } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); + const l = { + emailLabel: labels.emailLabel ?? 'Email', + emailPlaceholder: labels.emailPlaceholder ?? 'name@example.com', + passwordLabel: labels.passwordLabel ?? 'Password', + passwordPlaceholder: labels.passwordPlaceholder ?? 'Enter your password', + forgotPasswordText: labels.forgotPasswordText ?? 'Forgot password?', + submitButton: labels.submitButton ?? 'Sign In', + submittingButton: labels.submittingButton ?? 'Signing in...', + noAccountText: labels.noAccountText ?? "Don't have an account?", + signUpText: labels.signUpText ?? 'Sign up', + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); @@ -65,7 +101,7 @@ export function LoginForm({ }; return ( -
+

{title}

{description}

@@ -80,12 +116,12 @@ export function LoginForm({
setEmail(e.target.value)} required @@ -98,21 +134,21 @@ export function LoginForm({
{forgotPasswordUrl && ( - - Forgot password? - + {l.forgotPasswordText} + )}
setPassword(e.target.value)} required @@ -127,16 +163,16 @@ export function LoginForm({ disabled={isLoading} className="inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" > - {isLoading ? 'Signing in...' : 'Sign In'} + {isLoading ? l.submittingButton : l.submitButton} {registerUrl && (

- Don't have an account?{' '} - - Sign up - + {l.noAccountText}{' '} + + {l.signUpText} +

)}
diff --git a/packages/auth/src/RegisterForm.tsx b/packages/auth/src/RegisterForm.tsx index eac9c8ec0..a2d7d9beb 100644 --- a/packages/auth/src/RegisterForm.tsx +++ b/packages/auth/src/RegisterForm.tsx @@ -8,6 +8,25 @@ import React, { useState } from 'react'; import { useAuth } from './useAuth'; +import type { AuthLinkComponentProps } from './types'; + +/** Translatable labels for the RegisterForm */ +export interface RegisterFormLabels { + nameLabel?: string; + namePlaceholder?: string; + emailLabel?: string; + emailPlaceholder?: string; + passwordLabel?: string; + passwordPlaceholder?: string; + confirmPasswordLabel?: string; + confirmPasswordPlaceholder?: string; + passwordMismatchError?: string; + passwordTooShortError?: string; + submitButton?: string; + submittingButton?: string; + hasAccountText?: string; + signInText?: string; +} export interface RegisterFormProps { /** Callback on successful registration */ @@ -20,8 +39,16 @@ export interface RegisterFormProps { title?: string; /** Custom description */ description?: string; + /** Custom link component for SPA navigation (e.g. React Router's Link) */ + linkComponent?: React.ComponentType; + /** Override default labels for i18n */ + labels?: RegisterFormLabels; } +const DefaultLink = ({ href, className, children }: AuthLinkComponentProps) => ( + {children} +); + /** * Registration form component with name, email, and password fields. * Uses Tailwind CSS utility classes for styling. @@ -40,6 +67,8 @@ export function RegisterForm({ loginUrl = '/login', title = 'Create an account', description = 'Enter your information to get started', + linkComponent: LinkComp = DefaultLink, + labels = {}, }: RegisterFormProps) { const { signUp, isLoading } = useAuth(); const [name, setName] = useState(''); @@ -48,17 +77,34 @@ export function RegisterForm({ const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(null); + const l = { + nameLabel: labels.nameLabel ?? 'Name', + namePlaceholder: labels.namePlaceholder ?? 'John Doe', + emailLabel: labels.emailLabel ?? 'Email', + emailPlaceholder: labels.emailPlaceholder ?? 'name@example.com', + passwordLabel: labels.passwordLabel ?? 'Password', + passwordPlaceholder: labels.passwordPlaceholder ?? 'Create a password (min. 8 characters)', + confirmPasswordLabel: labels.confirmPasswordLabel ?? 'Confirm Password', + confirmPasswordPlaceholder: labels.confirmPasswordPlaceholder ?? 'Confirm your password', + passwordMismatchError: labels.passwordMismatchError ?? 'Passwords do not match', + passwordTooShortError: labels.passwordTooShortError ?? 'Password must be at least 8 characters', + submitButton: labels.submitButton ?? 'Create Account', + submittingButton: labels.submittingButton ?? 'Creating account...', + hasAccountText: labels.hasAccountText ?? 'Already have an account?', + signInText: labels.signInText ?? 'Sign in', + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); if (password !== confirmPassword) { - setError('Passwords do not match'); + setError(l.passwordMismatchError); return; } if (password.length < 8) { - setError('Password must be at least 8 characters'); + setError(l.passwordTooShortError); return; } @@ -73,7 +119,7 @@ export function RegisterForm({ }; return ( -
+

{title}

{description}

@@ -88,12 +134,12 @@ export function RegisterForm({
setName(e.target.value)} required @@ -105,12 +151,12 @@ export function RegisterForm({
setEmail(e.target.value)} required @@ -122,12 +168,12 @@ export function RegisterForm({
setPassword(e.target.value)} required @@ -140,12 +186,12 @@ export function RegisterForm({
setConfirmPassword(e.target.value)} required @@ -161,16 +207,16 @@ export function RegisterForm({ disabled={isLoading} className="inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" > - {isLoading ? 'Creating account...' : 'Create Account'} + {isLoading ? l.submittingButton : l.submitButton} {loginUrl && (

- Already have an account?{' '} - - Sign in - + {l.hasAccountText}{' '} + + {l.signInText} +

)}
diff --git a/packages/auth/src/__tests__/ForgotPasswordForm.test.tsx b/packages/auth/src/__tests__/ForgotPasswordForm.test.tsx index d06296c6d..98770e49a 100644 --- a/packages/auth/src/__tests__/ForgotPasswordForm.test.tsx +++ b/packages/auth/src/__tests__/ForgotPasswordForm.test.tsx @@ -90,4 +90,30 @@ describe('ForgotPasswordForm', () => { }); expect(onError).toHaveBeenCalled(); }); + + it('uses custom linkComponent for login link', () => { + const CustomLink = ({ href, className, children }: { href: string; className?: string; children: React.ReactNode }) => ( + {children} + ); + renderWithAuth(); + const link = screen.getByTestId('custom-link'); + expect(link.getAttribute('data-href')).toBe('/login'); + }); + + it('renders custom labels when provided', () => { + renderWithAuth( + + ); + expect(screen.getByLabelText('E-Mail')).toBeTruthy(); + expect(screen.getByRole('button', { name: 'Link senden' })).toBeTruthy(); + expect(screen.getByText('Passwort eingefallen?')).toBeTruthy(); + expect(screen.getByText('Anmelden')).toBeTruthy(); + }); }); diff --git a/packages/auth/src/__tests__/LoginForm.test.tsx b/packages/auth/src/__tests__/LoginForm.test.tsx index 5565020c4..fdd9c99b7 100644 --- a/packages/auth/src/__tests__/LoginForm.test.tsx +++ b/packages/auth/src/__tests__/LoginForm.test.tsx @@ -120,4 +120,38 @@ describe('LoginForm', () => { expect(screen.getByRole('alert')).toBeTruthy(); }); }); + + it('uses custom linkComponent for links', () => { + const CustomLink = ({ href, className, children }: { href: string; className?: string; children: React.ReactNode }) => ( + {children} + ); + renderWithAuth( + + ); + const links = screen.getAllByTestId('custom-link'); + expect(links).toHaveLength(2); + expect(links[0].getAttribute('data-href')).toBe('/forgot'); + expect(links[1].getAttribute('data-href')).toBe('/register'); + }); + + it('renders custom labels when provided', () => { + renderWithAuth( + + ); + expect(screen.getByLabelText('Correo')).toBeTruthy(); + expect(screen.getByLabelText('Contraseña')).toBeTruthy(); + expect(screen.getByRole('button', { name: 'Iniciar sesión' })).toBeTruthy(); + expect(screen.getByText('¿Olvidaste tu contraseña?')).toBeTruthy(); + expect(screen.getByText('¿No tienes cuenta?')).toBeTruthy(); + expect(screen.getByText('Regístrate')).toBeTruthy(); + }); }); diff --git a/packages/auth/src/__tests__/RegisterForm.test.tsx b/packages/auth/src/__tests__/RegisterForm.test.tsx index 54f60ddf4..bac4a83b0 100644 --- a/packages/auth/src/__tests__/RegisterForm.test.tsx +++ b/packages/auth/src/__tests__/RegisterForm.test.tsx @@ -132,4 +132,36 @@ describe('RegisterForm', () => { }); expect(onError).toHaveBeenCalled(); }); + + it('uses custom linkComponent for login link', () => { + const CustomLink = ({ href, className, children }: { href: string; className?: string; children: React.ReactNode }) => ( + {children} + ); + renderWithAuth(); + const link = screen.getByTestId('custom-link'); + expect(link.getAttribute('data-href')).toBe('/login'); + }); + + it('renders custom labels when provided', () => { + renderWithAuth( + + ); + expect(screen.getByLabelText('名前')).toBeTruthy(); + expect(screen.getByLabelText('メール')).toBeTruthy(); + expect(screen.getByLabelText('パスワード')).toBeTruthy(); + expect(screen.getByLabelText('パスワード確認')).toBeTruthy(); + expect(screen.getByRole('button', { name: 'アカウント作成' })).toBeTruthy(); + expect(screen.getByText('アカウントをお持ちですか?')).toBeTruthy(); + expect(screen.getByText('ログイン')).toBeTruthy(); + }); }); diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 114ced788..003eb0752 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -24,9 +24,9 @@ export { AuthProvider, type AuthProviderProps } from './AuthProvider'; export { useAuth } from './useAuth'; export { AuthGuard, type AuthGuardProps } from './AuthGuard'; -export { LoginForm, type LoginFormProps } from './LoginForm'; -export { RegisterForm, type RegisterFormProps } from './RegisterForm'; -export { ForgotPasswordForm, type ForgotPasswordFormProps } from './ForgotPasswordForm'; +export { LoginForm, type LoginFormProps, type LoginFormLabels } from './LoginForm'; +export { RegisterForm, type RegisterFormProps, type RegisterFormLabels } from './RegisterForm'; +export { ForgotPasswordForm, type ForgotPasswordFormProps, type ForgotPasswordFormLabels } from './ForgotPasswordForm'; export { UserMenu, type UserMenuProps } from './UserMenu'; export { PreviewBanner, type PreviewBannerProps } from './PreviewBanner'; export { createAuthClient } from './createAuthClient'; @@ -40,6 +40,7 @@ export type { AuthState, AuthClient, AuthClientConfig, + AuthLinkComponentProps, AuthProviderConfig, PreviewModeOptions, SignInCredentials, diff --git a/packages/auth/src/types.ts b/packages/auth/src/types.ts index f034d936c..3066f0f25 100644 --- a/packages/auth/src/types.ts +++ b/packages/auth/src/types.ts @@ -6,6 +6,8 @@ * LICENSE file in the root directory of this source tree. */ +import type React from 'react'; + /** * Authentication types for @object-ui/auth */ @@ -130,6 +132,16 @@ export interface PreviewModeOptions { bannerMessage?: string; } +/** Props for custom link components used in auth forms (e.g. React Router's Link) */ +export interface AuthLinkComponentProps { + /** Target URL */ + href: string; + /** CSS class names */ + className?: string; + /** Link content */ + children: React.ReactNode; +} + /** Auth provider configuration */ export interface AuthProviderConfig { /** Authentication server URL */ diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts index 208f630c6..13348bb7b 100644 --- a/packages/i18n/src/locales/ar.ts +++ b/packages/i18n/src/locales/ar.ts @@ -481,6 +481,52 @@ const ar = { label: 'اللغة', }, }, + auth: { + login: { + title: 'تسجيل الدخول إلى حسابك', + description: 'أدخل بريدك الإلكتروني وكلمة المرور للمتابعة', + emailLabel: 'البريد الإلكتروني', + emailPlaceholder: 'name@example.com', + passwordLabel: 'كلمة المرور', + passwordPlaceholder: 'أدخل كلمة المرور', + forgotPasswordText: 'نسيت كلمة المرور؟', + submitButton: 'تسجيل الدخول', + submittingButton: 'جارٍ تسجيل الدخول...', + noAccountText: 'ليس لديك حساب؟', + signUpText: 'إنشاء حساب', + }, + register: { + title: 'إنشاء حساب', + description: 'أدخل معلوماتك للبدء', + nameLabel: 'الاسم', + namePlaceholder: 'محمد أحمد', + emailLabel: 'البريد الإلكتروني', + emailPlaceholder: 'name@example.com', + passwordLabel: 'كلمة المرور', + passwordPlaceholder: 'إنشاء كلمة مرور (8 أحرف على الأقل)', + confirmPasswordLabel: 'تأكيد كلمة المرور', + confirmPasswordPlaceholder: 'أكّد كلمة المرور', + passwordMismatchError: 'كلمتا المرور غير متطابقتين', + passwordTooShortError: 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل', + submitButton: 'إنشاء حساب', + submittingButton: 'جارٍ إنشاء الحساب...', + hasAccountText: 'لديك حساب بالفعل؟', + signInText: 'تسجيل الدخول', + }, + forgotPassword: { + title: 'إعادة تعيين كلمة المرور', + description: 'أدخل عنوان بريدك الإلكتروني وسنرسل لك رابطًا لإعادة تعيين كلمة المرور', + emailLabel: 'البريد الإلكتروني', + emailPlaceholder: 'name@example.com', + submitButton: 'إرسال رابط إعادة التعيين', + submittingButton: 'جارٍ الإرسال...', + successTitle: 'تحقق من بريدك الإلكتروني', + successDescription: 'لقد أرسلنا رابط إعادة تعيين كلمة المرور إلى {{email}}. يرجى التحقق من بريدك الوارد.', + backToSignInText: 'العودة إلى تسجيل الدخول', + rememberPasswordText: 'تتذكر كلمة المرور؟', + signInText: 'تسجيل الدخول', + }, + }, errors: { networkError: 'خطأ في الشبكة. يرجى التحقق من اتصالك.', serverError: 'خطأ في الخادم. يرجى المحاولة مرة أخرى لاحقاً.', diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index e4bdd33bf..cf03843c1 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -485,6 +485,52 @@ const de = { label: 'Sprache', }, }, + auth: { + login: { + title: 'In Ihr Konto einloggen', + description: 'Geben Sie Ihre E-Mail-Adresse und Ihr Passwort ein, um fortzufahren', + emailLabel: 'E-Mail', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Passwort', + passwordPlaceholder: 'Passwort eingeben', + forgotPasswordText: 'Passwort vergessen?', + submitButton: 'Anmelden', + submittingButton: 'Anmeldung läuft...', + noAccountText: 'Noch kein Konto?', + signUpText: 'Registrieren', + }, + register: { + title: 'Konto erstellen', + description: 'Geben Sie Ihre Daten ein, um loszulegen', + nameLabel: 'Name', + namePlaceholder: 'Max Mustermann', + emailLabel: 'E-Mail', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Passwort', + passwordPlaceholder: 'Passwort erstellen (mind. 8 Zeichen)', + confirmPasswordLabel: 'Passwort bestätigen', + confirmPasswordPlaceholder: 'Passwort bestätigen', + passwordMismatchError: 'Passwörter stimmen nicht überein', + passwordTooShortError: 'Das Passwort muss mindestens 8 Zeichen lang sein', + submitButton: 'Konto erstellen', + submittingButton: 'Konto wird erstellt...', + hasAccountText: 'Bereits ein Konto?', + signInText: 'Anmelden', + }, + forgotPassword: { + title: 'Passwort zurücksetzen', + description: 'Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts', + emailLabel: 'E-Mail', + emailPlaceholder: 'name@example.com', + submitButton: 'Link zum Zurücksetzen senden', + submittingButton: 'Wird gesendet...', + successTitle: 'Überprüfen Sie Ihre E-Mails', + successDescription: 'Wir haben einen Link zum Zurücksetzen des Passworts an {{email}} gesendet. Bitte überprüfen Sie Ihren Posteingang.', + backToSignInText: 'Zurück zur Anmeldung', + rememberPasswordText: 'Passwort doch bekannt?', + signInText: 'Anmelden', + }, + }, errors: { networkError: 'Netzwerkfehler. Bitte überprüfen Sie Ihre Verbindung.', serverError: 'Serverfehler. Bitte versuchen Sie es später erneut.', diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index 2247dc148..50ee2e312 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -543,6 +543,52 @@ const en = { label: 'Language', }, }, + auth: { + login: { + title: 'Sign in to your account', + description: 'Enter your email and password to continue', + emailLabel: 'Email', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Password', + passwordPlaceholder: 'Enter your password', + forgotPasswordText: 'Forgot password?', + submitButton: 'Sign In', + submittingButton: 'Signing in...', + noAccountText: "Don't have an account?", + signUpText: 'Sign up', + }, + register: { + title: 'Create an account', + description: 'Enter your information to get started', + nameLabel: 'Name', + namePlaceholder: 'John Doe', + emailLabel: 'Email', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Password', + passwordPlaceholder: 'Create a password (min. 8 characters)', + confirmPasswordLabel: 'Confirm Password', + confirmPasswordPlaceholder: 'Confirm your password', + passwordMismatchError: 'Passwords do not match', + passwordTooShortError: 'Password must be at least 8 characters', + submitButton: 'Create Account', + submittingButton: 'Creating account...', + hasAccountText: 'Already have an account?', + signInText: 'Sign in', + }, + forgotPassword: { + title: 'Reset your password', + description: "Enter your email address and we'll send you a link to reset your password", + emailLabel: 'Email', + emailPlaceholder: 'name@example.com', + submitButton: 'Send Reset Link', + submittingButton: 'Sending...', + successTitle: 'Check your email', + successDescription: "We've sent a password reset link to {{email}}. Please check your inbox.", + backToSignInText: 'Back to sign in', + rememberPasswordText: 'Remember your password?', + signInText: 'Sign in', + }, + }, errors: { networkError: 'Network error. Please check your connection.', serverError: 'Server error. Please try again later.', diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts index ce7f9234e..bfffe206e 100644 --- a/packages/i18n/src/locales/es.ts +++ b/packages/i18n/src/locales/es.ts @@ -480,6 +480,52 @@ const es = { label: 'Idioma', }, }, + auth: { + login: { + title: 'Inicia sesión en tu cuenta', + description: 'Ingresa tu correo electrónico y contraseña para continuar', + emailLabel: 'Correo electrónico', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Contraseña', + passwordPlaceholder: 'Ingresa tu contraseña', + forgotPasswordText: '¿Olvidaste tu contraseña?', + submitButton: 'Iniciar sesión', + submittingButton: 'Iniciando sesión...', + noAccountText: '¿No tienes una cuenta?', + signUpText: 'Regístrate', + }, + register: { + title: 'Crear una cuenta', + description: 'Ingresa tu información para comenzar', + nameLabel: 'Nombre', + namePlaceholder: 'Juan Pérez', + emailLabel: 'Correo electrónico', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Contraseña', + passwordPlaceholder: 'Crear una contraseña (mín. 8 caracteres)', + confirmPasswordLabel: 'Confirmar contraseña', + confirmPasswordPlaceholder: 'Confirma tu contraseña', + passwordMismatchError: 'Las contraseñas no coinciden', + passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres', + submitButton: 'Crear cuenta', + submittingButton: 'Creando cuenta...', + hasAccountText: '¿Ya tienes una cuenta?', + signInText: 'Iniciar sesión', + }, + forgotPassword: { + title: 'Restablecer tu contraseña', + description: 'Ingresa tu dirección de correo electrónico y te enviaremos un enlace para restablecer tu contraseña', + emailLabel: 'Correo electrónico', + emailPlaceholder: 'name@example.com', + submitButton: 'Enviar enlace de restablecimiento', + submittingButton: 'Enviando...', + successTitle: 'Revisa tu correo electrónico', + successDescription: 'Hemos enviado un enlace para restablecer la contraseña a {{email}}. Por favor revisa tu bandeja de entrada.', + backToSignInText: 'Volver a iniciar sesión', + rememberPasswordText: '¿Recuerdas tu contraseña?', + signInText: 'Iniciar sesión', + }, + }, errors: { networkError: 'Error de red. Por favor, compruebe su conexión.', serverError: 'Error del servidor. Por favor, inténtelo de nuevo más tarde.', diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts index 4206eecdd..722d52b05 100644 --- a/packages/i18n/src/locales/fr.ts +++ b/packages/i18n/src/locales/fr.ts @@ -485,6 +485,52 @@ const fr = { label: 'Langue', }, }, + auth: { + login: { + title: 'Connectez-vous à votre compte', + description: 'Entrez votre e-mail et votre mot de passe pour continuer', + emailLabel: 'E-mail', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Mot de passe', + passwordPlaceholder: 'Entrez votre mot de passe', + forgotPasswordText: 'Mot de passe oublié ?', + submitButton: 'Se connecter', + submittingButton: 'Connexion en cours...', + noAccountText: "Vous n'avez pas de compte ?", + signUpText: "S'inscrire", + }, + register: { + title: 'Créer un compte', + description: 'Entrez vos informations pour commencer', + nameLabel: 'Nom', + namePlaceholder: 'Jean Dupont', + emailLabel: 'E-mail', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Mot de passe', + passwordPlaceholder: 'Créer un mot de passe (min. 8 caractères)', + confirmPasswordLabel: 'Confirmer le mot de passe', + confirmPasswordPlaceholder: 'Confirmez votre mot de passe', + passwordMismatchError: 'Les mots de passe ne correspondent pas', + passwordTooShortError: 'Le mot de passe doit contenir au moins 8 caractères', + submitButton: 'Créer un compte', + submittingButton: 'Création du compte...', + hasAccountText: 'Vous avez déjà un compte ?', + signInText: 'Se connecter', + }, + forgotPassword: { + title: 'Réinitialiser votre mot de passe', + description: 'Entrez votre adresse e-mail et nous vous enverrons un lien pour réinitialiser votre mot de passe', + emailLabel: 'E-mail', + emailPlaceholder: 'name@example.com', + submitButton: 'Envoyer le lien de réinitialisation', + submittingButton: 'Envoi en cours...', + successTitle: 'Vérifiez vos e-mails', + successDescription: 'Nous avons envoyé un lien de réinitialisation du mot de passe à {{email}}. Veuillez vérifier votre boîte de réception.', + backToSignInText: 'Retour à la connexion', + rememberPasswordText: 'Vous vous souvenez de votre mot de passe ?', + signInText: 'Se connecter', + }, + }, errors: { networkError: 'Erreur réseau. Veuillez vérifier votre connexion.', serverError: 'Erreur serveur. Veuillez réessayer plus tard.', diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts index 6f4a738f0..7fd2e67dc 100644 --- a/packages/i18n/src/locales/ja.ts +++ b/packages/i18n/src/locales/ja.ts @@ -480,6 +480,52 @@ const ja = { label: '言語', }, }, + auth: { + login: { + title: 'アカウントにサインイン', + description: 'メールアドレスとパスワードを入力してください', + emailLabel: 'メールアドレス', + emailPlaceholder: 'name@example.com', + passwordLabel: 'パスワード', + passwordPlaceholder: 'パスワードを入力', + forgotPasswordText: 'パスワードをお忘れですか?', + submitButton: 'サインイン', + submittingButton: 'サインイン中...', + noAccountText: 'アカウントをお持ちでないですか?', + signUpText: '新規登録', + }, + register: { + title: 'アカウントを作成', + description: '情報を入力して始めましょう', + nameLabel: '名前', + namePlaceholder: '田中太郎', + emailLabel: 'メールアドレス', + emailPlaceholder: 'name@example.com', + passwordLabel: 'パスワード', + passwordPlaceholder: 'パスワードを作成(8文字以上)', + confirmPasswordLabel: 'パスワード確認', + confirmPasswordPlaceholder: 'パスワードを再入力', + passwordMismatchError: 'パスワードが一致しません', + passwordTooShortError: 'パスワードは8文字以上必要です', + submitButton: 'アカウント作成', + submittingButton: 'アカウント作成中...', + hasAccountText: 'すでにアカウントをお持ちですか?', + signInText: 'サインイン', + }, + forgotPassword: { + title: 'パスワードをリセット', + description: 'メールアドレスを入力してください。パスワードリセットリンクをお送りします', + emailLabel: 'メールアドレス', + emailPlaceholder: 'name@example.com', + submitButton: 'リセットリンクを送信', + submittingButton: '送信中...', + successTitle: 'メールをご確認ください', + successDescription: '{{email}} にパスワードリセットリンクを送信しました。受信箱をご確認ください。', + backToSignInText: 'サインインに戻る', + rememberPasswordText: 'パスワードを覚えていますか?', + signInText: 'サインイン', + }, + }, errors: { networkError: 'ネットワークエラーです。接続を確認してください。', serverError: 'サーバーエラーです。後でもう一度お試しください。', diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts index 0c0a2e278..481d4fb73 100644 --- a/packages/i18n/src/locales/ko.ts +++ b/packages/i18n/src/locales/ko.ts @@ -480,6 +480,52 @@ const ko = { label: '언어', }, }, + auth: { + login: { + title: '계정에 로그인', + description: '이메일과 비밀번호를 입력하세요', + emailLabel: '이메일', + emailPlaceholder: 'name@example.com', + passwordLabel: '비밀번호', + passwordPlaceholder: '비밀번호를 입력하세요', + forgotPasswordText: '비밀번호를 잊으셨나요?', + submitButton: '로그인', + submittingButton: '로그인 중...', + noAccountText: '계정이 없으신가요?', + signUpText: '회원가입', + }, + register: { + title: '계정 만들기', + description: '정보를 입력하여 시작하세요', + nameLabel: '이름', + namePlaceholder: '홍길동', + emailLabel: '이메일', + emailPlaceholder: 'name@example.com', + passwordLabel: '비밀번호', + passwordPlaceholder: '비밀번호 생성 (최소 8자)', + confirmPasswordLabel: '비밀번호 확인', + confirmPasswordPlaceholder: '비밀번호를 다시 입력하세요', + passwordMismatchError: '비밀번호가 일치하지 않습니다', + passwordTooShortError: '비밀번호는 최소 8자 이상이어야 합니다', + submitButton: '계정 만들기', + submittingButton: '계정 생성 중...', + hasAccountText: '이미 계정이 있으신가요?', + signInText: '로그인', + }, + forgotPassword: { + title: '비밀번호 재설정', + description: '이메일 주소를 입력하시면 비밀번호 재설정 링크를 보내드립니다', + emailLabel: '이메일', + emailPlaceholder: 'name@example.com', + submitButton: '재설정 링크 보내기', + submittingButton: '전송 중...', + successTitle: '이메일을 확인하세요', + successDescription: '{{email}}(으)로 비밀번호 재설정 링크를 전송했습니다. 수신함을 확인해주세요.', + backToSignInText: '로그인으로 돌아가기', + rememberPasswordText: '비밀번호가 기억나시나요?', + signInText: '로그인', + }, + }, errors: { networkError: '네트워크 오류입니다. 연결을 확인해주세요.', serverError: '서버 오류입니다. 나중에 다시 시도해주세요.', diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts index 5faf0bb4f..203ee3412 100644 --- a/packages/i18n/src/locales/pt.ts +++ b/packages/i18n/src/locales/pt.ts @@ -480,6 +480,52 @@ const pt = { label: 'Idioma', }, }, + auth: { + login: { + title: 'Entre na sua conta', + description: 'Digite seu e-mail e senha para continuar', + emailLabel: 'E-mail', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Senha', + passwordPlaceholder: 'Digite sua senha', + forgotPasswordText: 'Esqueceu a senha?', + submitButton: 'Entrar', + submittingButton: 'Entrando...', + noAccountText: 'Não tem uma conta?', + signUpText: 'Cadastre-se', + }, + register: { + title: 'Criar uma conta', + description: 'Digite suas informações para começar', + nameLabel: 'Nome', + namePlaceholder: 'João Silva', + emailLabel: 'E-mail', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Senha', + passwordPlaceholder: 'Criar uma senha (mín. 8 caracteres)', + confirmPasswordLabel: 'Confirmar senha', + confirmPasswordPlaceholder: 'Confirme sua senha', + passwordMismatchError: 'As senhas não coincidem', + passwordTooShortError: 'A senha deve ter pelo menos 8 caracteres', + submitButton: 'Criar conta', + submittingButton: 'Criando conta...', + hasAccountText: 'Já tem uma conta?', + signInText: 'Entrar', + }, + forgotPassword: { + title: 'Redefinir sua senha', + description: 'Digite seu endereço de e-mail e enviaremos um link para redefinir sua senha', + emailLabel: 'E-mail', + emailPlaceholder: 'name@example.com', + submitButton: 'Enviar link de redefinição', + submittingButton: 'Enviando...', + successTitle: 'Verifique seu e-mail', + successDescription: 'Enviamos um link para redefinição de senha para {{email}}. Por favor verifique sua caixa de entrada.', + backToSignInText: 'Voltar para o login', + rememberPasswordText: 'Lembra da sua senha?', + signInText: 'Entrar', + }, + }, errors: { networkError: 'Erro de rede. Por favor, verifique sua conexão.', serverError: 'Erro no servidor. Por favor, tente novamente mais tarde.', diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts index af6e5b8dd..75bd563e5 100644 --- a/packages/i18n/src/locales/ru.ts +++ b/packages/i18n/src/locales/ru.ts @@ -480,6 +480,52 @@ const ru = { label: 'Язык', }, }, + auth: { + login: { + title: 'Войдите в свой аккаунт', + description: 'Введите электронную почту и пароль для продолжения', + emailLabel: 'Электронная почта', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Пароль', + passwordPlaceholder: 'Введите пароль', + forgotPasswordText: 'Забыли пароль?', + submitButton: 'Войти', + submittingButton: 'Вход...', + noAccountText: 'Нет аккаунта?', + signUpText: 'Зарегистрироваться', + }, + register: { + title: 'Создать аккаунт', + description: 'Введите свои данные, чтобы начать', + nameLabel: 'Имя', + namePlaceholder: 'Иван Иванов', + emailLabel: 'Электронная почта', + emailPlaceholder: 'name@example.com', + passwordLabel: 'Пароль', + passwordPlaceholder: 'Создайте пароль (мин. 8 символов)', + confirmPasswordLabel: 'Подтвердите пароль', + confirmPasswordPlaceholder: 'Подтвердите свой пароль', + passwordMismatchError: 'Пароли не совпадают', + passwordTooShortError: 'Пароль должен содержать не менее 8 символов', + submitButton: 'Создать аккаунт', + submittingButton: 'Создание аккаунта...', + hasAccountText: 'Уже есть аккаунт?', + signInText: 'Войти', + }, + forgotPassword: { + title: 'Сбросить пароль', + description: 'Введите адрес электронной почты, и мы отправим вам ссылку для сброса пароля', + emailLabel: 'Электронная почта', + emailPlaceholder: 'name@example.com', + submitButton: 'Отправить ссылку для сброса', + submittingButton: 'Отправка...', + successTitle: 'Проверьте почту', + successDescription: 'Мы отправили ссылку для сброса пароля на {{email}}. Пожалуйста, проверьте свою почту.', + backToSignInText: 'Вернуться к входу', + rememberPasswordText: 'Помните свой пароль?', + signInText: 'Войти', + }, + }, errors: { networkError: 'Ошибка сети. Проверьте подключение к интернету.', serverError: 'Ошибка сервера. Попробуйте позже.', diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts index 950799950..cb71c0b52 100644 --- a/packages/i18n/src/locales/zh.ts +++ b/packages/i18n/src/locales/zh.ts @@ -543,6 +543,52 @@ const zh = { label: '语言', }, }, + auth: { + login: { + title: '登录您的账户', + description: '输入您的邮箱和密码以继续', + emailLabel: '邮箱', + emailPlaceholder: 'name@example.com', + passwordLabel: '密码', + passwordPlaceholder: '输入您的密码', + forgotPasswordText: '忘记密码?', + submitButton: '登录', + submittingButton: '登录中...', + noAccountText: '还没有账户?', + signUpText: '注册', + }, + register: { + title: '创建账户', + description: '输入您的信息以开始使用', + nameLabel: '姓名', + namePlaceholder: '张三', + emailLabel: '邮箱', + emailPlaceholder: 'name@example.com', + passwordLabel: '密码', + passwordPlaceholder: '创建密码(至少8个字符)', + confirmPasswordLabel: '确认密码', + confirmPasswordPlaceholder: '确认您的密码', + passwordMismatchError: '两次输入的密码不一致', + passwordTooShortError: '密码至少需要8个字符', + submitButton: '创建账户', + submittingButton: '创建中...', + hasAccountText: '已有账户?', + signInText: '登录', + }, + forgotPassword: { + title: '重置密码', + description: '输入您的邮箱地址,我们将发送重置密码链接', + emailLabel: '邮箱', + emailPlaceholder: 'name@example.com', + submitButton: '发送重置链接', + submittingButton: '发送中...', + successTitle: '请查看您的邮箱', + successDescription: '我们已将密码重置链接发送至 {{email}},请检查您的收件箱。', + backToSignInText: '返回登录', + rememberPasswordText: '记住密码了?', + signInText: '登录', + }, + }, errors: { networkError: '网络错误,请检查网络连接。', serverError: '服务器错误,请稍后重试。',