From 15514fe5dd44cb6008346113c74fe9351014732f Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Sat, 11 Jul 2026 12:41:09 +0300 Subject: [PATCH] fix: localize the login pages --- apps/backoffice/src/app/i18n/locales/am.ts | 105 ++++++++++++++++ apps/backoffice/src/app/i18n/locales/en.ts | 112 ++++++++++++++++++ apps/backoffice/src/app/router/index.tsx | 4 +- apps/portal/src/app/i18n/locales/am.ts | 99 +++++++++++++++- apps/portal/src/app/i18n/locales/en.ts | 104 ++++++++++++++++ .../portal/src/app/providers/AppProviders.tsx | 6 +- apps/portal/src/app/router.tsx | 12 +- libs/auth/src/lib/components/AuthShell.tsx | 39 ++++-- libs/auth/src/lib/pages/FaydaCallbackPage.tsx | 22 ++-- .../auth/src/lib/pages/ForgotPasswordPage.tsx | 51 ++++---- libs/auth/src/lib/pages/LoginPage.tsx | 36 +++--- .../src/lib/pages/OTPVerificationPage.tsx | 42 +++---- libs/auth/src/lib/pages/SetPasswordPage.tsx | 41 ++++--- libs/auth/src/lib/pages/SignupPage.tsx | 81 +++++++------ 14 files changed, 595 insertions(+), 159 deletions(-) diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts index 38ca00e8c..2ab906155 100644 --- a/apps/backoffice/src/app/i18n/locales/am.ts +++ b/apps/backoffice/src/app/i18n/locales/am.ts @@ -48,6 +48,111 @@ export const am: Translations = { collapse: 'ሰብስብ', expand: 'ዘርጋ', toggleTheme: 'የብርሃን / ጨለማ ሁነታን ይቀይሩ', + or: 'ወይም', + somethingWentWrong: 'የሆነ ስህተት ተከስቷል', + }, + + auth: { + login: 'ግባ', + logout: 'ውጣ', + signup: 'ይመዝገቡ', + account: 'መለያ', + guest: 'እንግዳ ተጠቃሚ', + welcomeTo: 'እንኳን ወደ {{appName}} በደህና መጡ', + signInToAccess: 'መለያዎን ለመድረስ ይግቡ።', + emailOrPhone: 'ኢሜይል ወይም ስልክ', + emailPlaceholder: 'you@example.com', + password: 'የይለፍ ቃል', + passwordPlaceholder: 'የይለፍ ቃልዎ', + rememberMe: 'አስታውሰኝ', + forgotPassword: 'የይለፍ ቃል ረሱት?', + signIn: 'ግባ', + signInWithPhone: 'በስልክ ቁጥር ይግቡ', + noAccount: 'መለያ የለዎትም?', + createOne: 'አዲስ ይፍጠሩ', + forgotYourPassword: 'የይለፍ ቃልዎን ረሱት?', + forgotDescription: + 'ከመለያዎ ጋር የተገናኘውን ኢሜይል ያስገቡ፤ የይለፍ ቃልዎን እንደገና ለማስተካከል አገናኝ እንልክልዎታለን።', + emailAddress: 'የኢሜይል አድራሻ', + sendResetLink: 'የማስተካከያ አገናኝ ይላኩ', + backToSignIn: 'ወደ መግቢያ ተመለስ', + checkYourEmail: 'ኢሜይልዎን ያረጋግጡ', + resetLinkSent: + 'የይለፍ ቃል ማስተካከያ አገናኝ ወደ {{email}} ልከናል። አዲስ የይለፍ ቃል ለመምረጥ በኢሜይሉ ውስጥ ያለውን አገናኝ ይከተሉ።', + openEmailApp: 'የኢሜይል መተግበሪያ ይክፈቱ', + didNotGetEmail: 'ኢሜይሉ አልደረሰዎትም?', + resendLink: 'አገናኙን እንደገና ይላኩ', + resetLinkSentAgain: 'የማስተካከያ አገናኝ እንደገና ተልኳል', + createAccount: 'መለያ ይፍጠሩ', + signupDescription: 'ለመጀመር አንድ ደቂቃ ብቻ ይወስዳል።', + nameEn: 'ስም (እንግሊዝኛ)', + nameEnPlaceholder: 'አበበ በቀለ', + nameAm: 'ስም (አማርኛ)', + nameAmPlaceholder: 'ሙሉ ስም', + username: 'የተጠቃሚ ስም', + usernamePlaceholder: 'የተጠቃሚ ስም ይምረጡ', + phoneNumber: 'ስልክ ቁጥር', + phonePlaceholder: '+251 911 234 567', + confirmPassword: 'የይለፍ ቃል ያረጋግጡ', + confirmPasswordPlaceholder: 'የይለፍ ቃሉን እንደገና ያስገቡ', + agreeToTerms: 'እኔ እስማማለሁ በ', + termsAndPrivacy: 'ደንቦች እና የግላዊነት ፖሊሲ', + signupWithFayda: 'በብሔራዊ መታወቂያ (ፋይዳ) ይመዝገቡ', + alreadyHaveAccount: 'መለያ አለዎት?', + faydaFailed: 'የብሔራዊ መታወቂያ ማረጋገጫ መጀመር አልተቻለም', + resetPasswordBrandTitle: 'የይለፍ ቃልዎን በአስተማማኝ ሁኔታ ያስተካክሉ።', + resetPasswordBrandSubtitle: + 'አይጨነቁ \u2014 አዲስ የይለፍ ቃል ለመፍጠር አስተማማኝ አገናኝ እንልክልዎታለን።', + signupBrandTitle: 'ጉዞዎ እዚህ ይጀምራል።', + signupBrandSubtitle: 'መለያ ይፍጠሩ እና የማይቻል የሚመስሉ ነገሮችን ይክፈቱ።', + verifyAccount: 'መለያዎን ያረጋግጡ', + otpSent: 'የማረጋገጫ ኮድ ወደ {{email}} ተልኳል። ለመቀጠል ከዚህ በታች ያስገቡት።', + verify: 'አረጋግጥ', + havingTrouble: 'ችግር አለብዎት?', + skipVerification: 'ለአሁን ማረጋገጫውን ዘልለው', + noCode: 'ኮድ አልደረሰዎትም?', + resendIn: 'እንደገና ይላኩ በ{{seconds}}ሰ', + resendCode: 'ኮዱን እንደገና ይላኩ', + otpDigitCode: 'የ{{length}}-አሃዝ ኮዱን ያስገቡ', + phoneVerified: 'ስልክ ቁጥር በተሳካ ሁኔታ ተረጋግጧል', + codeResent: 'የማረጋገጫ ኮድ እንደገና ወደ ኢሜይልዎ ተልኳል', + otpBrandTitle: 'መለያዎን ለማስጠበቅ የመጨረሻው እርምጃ።', + otpBrandSubtitle: 'መዳረሻ ከመስጠታችን በፊት እርስዎ መሆንዎን ለማረጋገጥ የአንድ ጊዜ ኮድ እንጠቀማለን።', + setPassword: 'የይለፍ ቃልዎን ያዘጋጁ', + newPassword: 'አዲስ የይለፍ ቃል', + setPasswordBrandTitle: 'የመለያ ይለፍ ቃልዎን ያዘጋጁ።', + setPasswordBrandSubtitle: 'መለያዎን ለማስጠበቅ እና ሁሉንም የፖርታል ተግባራት ለመጠቀም ጠንካራ የይለፍ ቃል ይምረጡ።', + welcomeNationalId: 'እንኳን ደህና መጡ፣ {{name}}። የብሔራዊ መታወቂያዎ በተሳካ ሁኔታ ተረጋግጧል። የመለያ ዝግጅትዎን ለማጠናቀቅ የይለፍ ቃል ይምረጡ።', + setPasswordAndContinue: 'የይለፍ ቃል ያዘጋጁ እና ይቀጥሉ', + passwordSet: 'የይለፍ ቃል በተሳካ ሁኔታ ተዘጋጅቷል', + sessionExpired: 'ክፍለ ጊዜ ጊዜው አልፏል። እባክዎ እንደገና ይመዝገቡ።', + faydaBrandTitle: 'የብሔራዊ መታወቂያዎን በማረጋገጥ ላይ...', + faydaBrandSubtitle: 'እባክዎ ማንነትዎን ከብሔራዊ መታወቂያ አገልግሎት ደህንነቱ በተጠበቀ ሁኔታ ስናረጋግጥ ይጠብቁ።', + faydaVerifying: 'ማንነትዎን በማረጋገጥ ላይ', + faydaFailedTitle: 'ማረጋገጫ አልተሳካም', + faydaProcessing: 'የብሔራዊ መታወቂያ መረጃዎን በደህንነት እያስኬድን ነው። ይህ ጥቂት ጊዜ ብቻ ይወስዳል።', + faydaTryAgain: 'እባክዎ እንደገና ይሞክሩ ወይም መደበኛውን የምዝገባ ዘዴ ይጠቀሙ።', + faydaInvalidResponse: 'ከብሔራዊ መታወቂያ አገልግሎት የተሳሳተ ምላሽ። ኮድ ወይም ሁኔታ የለም።', + faydaSessionExpired: 'ክፍለ ጊዜ ጊዜው አልፏል። እባክዎ እንደገና ለመመዝገብ ይሞክሩ።', + faydaVerifyFailed: 'የብሔራዊ መታወቂያ ማረጋገጫን ማጠናቀቅ አልተቻለም', + faydaVerified: 'ብሔራዊ መታወቂያ በተሳካ ሁኔታ ተረጋግጧል', + hero: { + brandTitle: 'የባሕር ፍቃድ አሰጣጥ፣ ቀላል ተደረገ።', + brandSubtitle: + 'ለመርከብ እና ለመርከበኛ ፈቃዶች ያመልክቱ፣ ሰነዶችን ይስቀሉ እና እያንዳንዱን ማመልከቻ በአንድ ደህንነቱ በተጠበቀ ፖርታል ይከታተሉ።', + featureOnline: 'ማመልከቻዎችን በመስመር ያቅርቡ፣ ቀንና ሌሊት', + featureTracking: 'በእውነተኛ ጊዜ የሁኔታ ክትትል እና ማሳወቂያዎች', + featureBilingual: 'በእንግሊዝኛ እና በአማርኛ ይገኛል', + }, + validation: { + emailInvalid: 'ትክክለኛ ኢሜይል ያስገቡ', + passwordMin: 'የይለፍ ቃል ቢያንስ {{min}} ቁምፊዎች መሆን አለበት', + usernameMin: 'የተጠቃሚ ስም ቢያንስ 3 ቁምፊዎች መሆን አለበት', + phoneRequired: 'ስልክ ቁጥር ያስፈልጋል', + nameEnRequired: 'ስም (እንግሊዝኛ) ያስፈልጋል', + confirmRequired: 'የይለፍ ቃልዎን ያረጋግጡ', + passwordMismatch: 'የይለፍ ቃላት አይዛመዱም', + }, }, breadcrumbs: { diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts index 06411c3d6..ff31cf44b 100644 --- a/apps/backoffice/src/app/i18n/locales/en.ts +++ b/apps/backoffice/src/app/i18n/locales/en.ts @@ -46,6 +46,118 @@ export const en = { collapse: 'Collapse', expand: 'Expand', toggleTheme: 'Toggle light / dark mode', + or: 'or', + somethingWentWrong: 'Something went wrong', + }, + + auth: { + login: 'Log in', + logout: 'Log out', + signup: 'Sign up', + account: 'Account', + guest: 'Guest user', + welcomeTo: 'Welcome to {{appName}}', + signInToAccess: 'Sign in to access your account.', + emailOrPhone: 'Email or phone', + emailPlaceholder: 'you@example.com', + password: 'Password', + passwordPlaceholder: 'Your password', + rememberMe: 'Remember me', + forgotPassword: 'Forgot password?', + signIn: 'Sign in', + signInWithPhone: 'Sign in with phone number', + noAccount: 'Don\u2019t have an account?', + createOne: 'Create one', + forgotYourPassword: 'Forgot your password?', + forgotDescription: + 'Enter the email linked to your account and we\u2019ll send you a link to reset your password.', + emailAddress: 'Email address', + sendResetLink: 'Send reset link', + backToSignIn: 'Back to sign in', + checkYourEmail: 'Check your email', + resetLinkSent: + 'We\u2019ve sent a password reset link to {{email}}. Follow the link in that email to choose a new password.', + openEmailApp: 'Open email app', + didNotGetEmail: 'Didn\u2019t get the email?', + resendLink: 'Resend link', + resetLinkSentAgain: 'Reset link sent again', + createAccount: 'Create account', + signupDescription: 'It only takes a minute to get started.', + nameEn: 'Name (English)', + nameEnPlaceholder: 'Abebe Bekele', + nameAm: 'Name (Amharic)', + nameAmPlaceholder: '\u1235\u121d', + username: 'Username', + usernamePlaceholder: 'Choose a username', + phoneNumber: 'Phone number', + phonePlaceholder: '+251 911 234 567', + confirmPassword: 'Confirm password', + confirmPasswordPlaceholder: 'Re-enter password', + agreeToTerms: 'I agree to the', + termsAndPrivacy: 'Terms & Privacy Policy', + signupWithFayda: 'Sign up with National ID (Fayda)', + alreadyHaveAccount: 'Already have an account?', + faydaFailed: 'Failed to initiate National ID verification', + resetPasswordBrandTitle: 'Reset your password securely.', + resetPasswordBrandSubtitle: + 'No worries \u2014 we\u2019ll send you a secure link to create a new password and get you back on track.', + signupBrandTitle: 'Your journey begins here.', + signupBrandSubtitle: 'Create an account and unlock a world of possibilities.', + verifyAccount: 'Verify your account', + otpSent: 'A verification code has been sent to {{email}}. Enter it below to continue.', + verify: 'Verify', + havingTrouble: 'Having trouble?', + skipVerification: 'Skip verification for now', + noCode: 'Didn\u2019t receive a code?', + resendIn: 'Resend in {{seconds}}s', + resendCode: 'Resend code', + otpDigitCode: 'Enter the {{length}}-digit code', + phoneVerified: 'Phone number verified successfully', + codeResent: 'Verification code resent to your email', + otpBrandTitle: 'One last step to secure your account.', + otpBrandSubtitle: + 'We use a one-time code to confirm it\u2019s really you before granting access.', + setPassword: 'Set your password', + newPassword: 'New password', + setPasswordBrandTitle: 'Set your account password.', + setPasswordBrandSubtitle: + 'Choose a strong password to secure your account and access all Portal features.', + welcomeNationalId: + 'Welcome, {{name}}. Your National ID has been verified successfully. Choose a password to complete your account setup.', + setPasswordAndContinue: 'Set password & continue', + passwordSet: 'Password set successfully', + sessionExpired: 'Session expired. Please sign up again.', + faydaBrandTitle: 'Verifying your National ID...', + faydaBrandSubtitle: + 'Please wait while we securely verify your identity with the National ID service.', + faydaVerifying: 'Verifying your identity', + faydaFailedTitle: 'Verification failed', + faydaProcessing: + 'We are securely processing your National ID information. This should only take a moment.', + faydaTryAgain: + 'Please try again or use the standard sign-up method instead.', + faydaInvalidResponse: + 'Invalid response from National ID service. Missing code or state.', + faydaSessionExpired: 'Session expired. Please try signing up again.', + faydaVerifyFailed: 'Failed to complete National ID verification', + faydaVerified: 'National ID verified successfully', + hero: { + brandTitle: 'Maritime licensing, made simple.', + brandSubtitle: + 'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.', + featureOnline: 'Submit applications online, 24/7', + featureTracking: 'Real-time status tracking & alerts', + featureBilingual: 'Available in English & አማርኛ', + }, + validation: { + emailInvalid: 'Enter a valid email', + passwordMin: 'Password must be at least {{min}} characters', + usernameMin: 'Username must be at least 3 characters', + phoneRequired: 'Phone number is required', + nameEnRequired: 'Name (English) is required', + confirmRequired: 'Confirm your password', + passwordMismatch: 'Passwords do not match', + }, }, breadcrumbs: { diff --git a/apps/backoffice/src/app/router/index.tsx b/apps/backoffice/src/app/router/index.tsx index 06bf91cd4..7393d5501 100644 --- a/apps/backoffice/src/app/router/index.tsx +++ b/apps/backoffice/src/app/router/index.tsx @@ -41,8 +41,8 @@ const router = createBrowserRouter([ { element: , children: [ - { path: '/login', element: }, - { path: '/forgot-password', element: }, + { path: '/login', element: }, + { path: '/forgot-password', element: }, { path: '/otp-verify', element: }, ], }, diff --git a/apps/portal/src/app/i18n/locales/am.ts b/apps/portal/src/app/i18n/locales/am.ts index c49bb7926..4a2626c0d 100644 --- a/apps/portal/src/app/i18n/locales/am.ts +++ b/apps/portal/src/app/i18n/locales/am.ts @@ -2,7 +2,7 @@ import type { Translations } from './en'; export const am: Translations = { app: { - name: 'ኢባባ ፖርታል', + name: 'ኢማ ፖርታል', authority: 'የኢትዮጵያ ባሕር ጉዳዮች ባለሥልጣን', tagline: 'የባሕር ፍቃድና የምስክር ወረቀት አገልግሎቶች', }, @@ -58,6 +58,8 @@ export const am: Translations = { learnMore: 'ተጨማሪ ይወቁ', toggleTheme: 'ብርሃን / ጨለማ ገጽታ ቀይር', welcome: 'እንኳን ደህና መጡ', + or: 'ወይም', + somethingWentWrong: 'የሆነ ስህተት ተከስቷል', }, auth: { @@ -66,6 +68,101 @@ export const am: Translations = { signup: 'ይመዝገቡ', account: 'መለያ', guest: 'እንግዳ ተጠቃሚ', + welcomeTo: 'እንኳን ወደ {{appName}} በደህና መጡ', + signInToAccess: 'መለያዎን ለመድረስ ይግቡ።', + emailOrPhone: 'ኢሜይል ወይም ስልክ', + emailPlaceholder: 'you@example.com', + password: 'የይለፍ ቃል', + passwordPlaceholder: 'የይለፍ ቃልዎ', + rememberMe: 'አስታውሰኝ', + forgotPassword: 'የይለፍ ቃል ረሱት?', + signIn: 'ግባ', + signInWithPhone: 'በስልክ ቁጥር ይግቡ', + noAccount: 'መለያ የለዎትም?', + createOne: 'አዲስ ይፍጠሩ', + forgotYourPassword: 'የይለፍ ቃልዎን ረሱት?', + forgotDescription: + 'ከመለያዎ ጋር የተገናኘውን ኢሜይል ያስገቡ፤ የይለፍ ቃልዎን እንደገና ለማስተካከል አገናኝ እንልክልዎታለን።', + emailAddress: 'የኢሜይል አድራሻ', + sendResetLink: 'የማስተካከያ አገናኝ ይላኩ', + backToSignIn: 'ወደ መግቢያ ተመለስ', + checkYourEmail: 'ኢሜይልዎን ያረጋግጡ', + resetLinkSent: + 'የይለፍ ቃል ማስተካከያ አገናኝ ወደ {{email}} ልከናል። አዲስ የይለፍ ቃል ለመምረጥ በኢሜይሉ ውስጥ ያለውን አገናኝ ይከተሉ።', + openEmailApp: 'የኢሜይል መተግበሪያ ይክፈቱ', + didNotGetEmail: 'ኢሜይሉ አልደረሰዎትም?', + resendLink: 'አገናኙን እንደገና ይላኩ', + resetLinkSentAgain: 'የማስተካከያ አገናኝ እንደገና ተልኳል', + createAccount: 'መለያ ይፍጠሩ', + signupDescription: 'ለመጀመር አንድ ደቂቃ ብቻ ይወስዳል።', + nameEn: 'ስም (እንግሊዝኛ)', + nameEnPlaceholder: 'Abebe Bekila', + nameAm: 'ስም (አማርኛ)', + nameAmPlaceholder: 'አበበ በቀለ', + username: 'የተጠቃሚ ስም', + usernamePlaceholder: 'የተጠቃሚ ስም ይምረጡ', + phoneNumber: 'ስልክ ቁጥር', + phonePlaceholder: '+251 911 234 567', + confirmPassword: 'የይለፍ ቃል ያረጋግጡ', + confirmPasswordPlaceholder: 'የይለፍ ቃሉን እንደገና ያስገቡ', + agreeToTerms: 'እኔ እስማማለሁ በ', + termsAndPrivacy: 'ደንቦች እና የግላዊነት ፖሊሲ', + signupWithFayda: 'በብሔራዊ መታወቂያ (ፋይዳ) ይመዝገቡ', + alreadyHaveAccount: 'መለያ አለዎት?', + faydaFailed: 'የብሔራዊ መታወቂያ ማረጋገጫ መጀመር አልተቻለም', + resetPasswordBrandTitle: 'የይለፍ ቃልዎን በአስተማማኝ ሁኔታ ያስተካክሉ።', + resetPasswordBrandSubtitle: + 'አይጨነቁ \u2014 አዲስ የይለፍ ቃል ለመፍጠር አስተማማኝ አገናኝ እንልክልዎታለን።', + signupBrandTitle: 'ጉዞዎ እዚህ ይጀምራል።', + signupBrandSubtitle: 'መለያ ይፍጠሩ እና የማይቻል የሚመስሉ ነገሮችን ይክፈቱ።', + verifyAccount: 'መለያዎን ያረጋግጡ', + otpSent: 'የማረጋገጫ ኮድ ወደ {{email}} ተልኳል። ለመቀጠል ከዚህ በታች ያስገቡት።', + verify: 'አረጋግጥ', + havingTrouble: 'ችግር አለብዎት?', + skipVerification: 'ለአሁን ማረጋገጫውን ዘልለው', + noCode: 'ኮድ አልደረሰዎትም?', + resendIn: 'እንደገና ይላኩ በ{{seconds}}ሰ', + resendCode: 'ኮዱን እንደገና ይላኩ', + otpDigitCode: 'የ{{length}}-አሃዝ ኮዱን ያስገቡ', + phoneVerified: 'ስልክ ቁጥር በተሳካ ሁኔታ ተረጋግጧል', + codeResent: 'የማረጋገጫ ኮድ እንደገና ወደ ኢሜይልዎ ተልኳል', + otpBrandTitle: 'መለያዎን ለማስጠበቅ የመጨረሻው እርምጃ።', + otpBrandSubtitle: 'መዳረሻ ከመስጠታችን በፊት እርስዎ መሆንዎን ለማረጋገጥ የአንድ ጊዜ ኮድ እንጠቀማለን።', + setPassword: 'የይለፍ ቃልዎን ያዘጋጁ', + newPassword: 'አዲስ የይለፍ ቃል', + setPasswordBrandTitle: 'የመለያ ይለፍ ቃልዎን ያዘጋጁ።', + setPasswordBrandSubtitle: 'መለያዎን ለማስጠበቅ እና ሁሉንም የፖርታል ተግባራት ለመጠቀም ጠንካራ የይለፍ ቃል ይምረጡ።', + welcomeNationalId: 'እንኳን ደህና መጡ፣ {{name}}። የብሔራዊ መታወቂያዎ በተሳካ ሁኔታ ተረጋግጧል። የመለያ ዝግጅትዎን ለማጠናቀቅ የይለፍ ቃል ይምረጡ።', + setPasswordAndContinue: 'የይለፍ ቃል ያዘጋጁ እና ይቀጥሉ', + passwordSet: 'የይለፍ ቃል በተሳካ ሁኔታ ተዘጋጅቷል', + sessionExpired: 'ክፍለ ጊዜ ጊዜው አልፏል። እባክዎ እንደገና ይመዝገቡ።', + faydaBrandTitle: 'የብሔራዊ መታወቂያዎን በማረጋገጥ ላይ...', + faydaBrandSubtitle: 'እባክዎ ማንነትዎን ከብሔራዊ መታወቂያ አገልግሎት ደህንነቱ በተጠበቀ ሁኔታ ስናረጋግጥ ይጠብቁ።', + faydaVerifying: 'ማንነትዎን በማረጋገጥ ላይ', + faydaFailedTitle: 'ማረጋገጫ አልተሳካም', + faydaProcessing: 'የብሔራዊ መታወቂያ መረጃዎን በደህንነት እያስኬድን ነው። ይህ ጥቂት ጊዜ ብቻ ይወስዳል።', + faydaTryAgain: 'እባክዎ እንደገና ይሞክሩ ወይም መደበኛውን የምዝገባ ዘዴ ይጠቀሙ።', + faydaInvalidResponse: 'ከብሔራዊ መታወቂያ አገልግሎት የተሳሳተ ምላሽ። ኮድ ወይም ሁኔታ የለም።', + faydaSessionExpired: 'ክፍለ ጊዜ ጊዜው አልፏል። እባክዎ እንደገና ለመመዝገብ ይሞክሩ።', + faydaVerifyFailed: 'የብሔራዊ መታወቂያ ማረጋገጫን ማጠናቀቅ አልተቻለም', + faydaVerified: 'ብሔራዊ መታወቂያ በተሳካ ሁኔታ ተረጋግጧል', + hero: { + brandTitle: 'የባሕር ፍቃድ አሰጣጥ፣ ቀላል ተደረገ።', + brandSubtitle: + 'ለመርከብ እና ለመርከበኛ ፈቃዶች ያመልክቱ፣ ሰነዶችን ይስቀሉ እና እያንዳንዱን ማመልከቻ በአንድ ደህንነቱ በተጠበቀ ፖርታል ይከታተሉ።', + featureOnline: 'ማመልከቻዎችን በመስመር ያቅርቡ፣ ቀንና ሌሊት', + featureTracking: 'በእውነተኛ ጊዜ የሁኔታ ክትትል እና ማሳወቂያዎች', + featureBilingual: 'በእንግሊዝኛ እና በአማርኛ ይገኛል', + }, + validation: { + emailInvalid: 'ትክክለኛ ኢሜይል ያስገቡ', + passwordMin: 'የይለፍ ቃል ቢያንስ {{min}} ቁምፊዎች መሆን አለበት', + usernameMin: 'የተጠቃሚ ስም ቢያንስ 3 ቁምፊዎች መሆን አለበት', + phoneRequired: 'ስልክ ቁጥር ያስፈልጋል', + nameEnRequired: 'ስም (እንግሊዝኛ) ያስፈልጋል', + confirmRequired: 'የይለፍ ቃልዎን ያረጋግጡ', + passwordMismatch: 'የይለፍ ቃላት አይዛመዱም', + }, }, dashboard: { diff --git a/apps/portal/src/app/i18n/locales/en.ts b/apps/portal/src/app/i18n/locales/en.ts index aedabca1e..3f9a3a8bd 100644 --- a/apps/portal/src/app/i18n/locales/en.ts +++ b/apps/portal/src/app/i18n/locales/en.ts @@ -56,6 +56,8 @@ export const en = { learnMore: 'Learn more', toggleTheme: 'Toggle light / dark mode', welcome: 'Welcome', + or: 'or', + somethingWentWrong: 'Something went wrong', }, auth: { @@ -64,6 +66,108 @@ export const en = { signup: 'Sign up', account: 'Account', guest: 'Guest user', + welcomeTo: 'Welcome to {{appName}}', + signInToAccess: 'Sign in to access your account.', + emailOrPhone: 'Email or phone', + emailPlaceholder: 'you@example.com', + password: 'Password', + passwordPlaceholder: 'Your password', + rememberMe: 'Remember me', + forgotPassword: 'Forgot password?', + signIn: 'Sign in', + signInWithPhone: 'Sign in with phone number', + noAccount: 'Don\u2019t have an account?', + createOne: 'Create one', + forgotYourPassword: 'Forgot your password?', + forgotDescription: + 'Enter the email linked to your account and we\u2019ll send you a link to reset your password.', + emailAddress: 'Email address', + sendResetLink: 'Send reset link', + backToSignIn: 'Back to sign in', + checkYourEmail: 'Check your email', + resetLinkSent: + 'We\u2019ve sent a password reset link to {{email}}. Follow the link in that email to choose a new password.', + openEmailApp: 'Open email app', + didNotGetEmail: 'Didn\u2019t get the email?', + resendLink: 'Resend link', + resetLinkSentAgain: 'Reset link sent again', + createAccount: 'Create account', + signupDescription: 'It only takes a minute to get started.', + nameEn: 'Name (English)', + nameEnPlaceholder: 'Abebe Bekele', + nameAm: 'Name (Amharic)', + nameAmPlaceholder: 'አበበ በቂላ', + username: 'Username', + usernamePlaceholder: 'Choose a username', + phoneNumber: 'Phone number', + phonePlaceholder: '+251 911 234 567', + confirmPassword: 'Confirm password', + confirmPasswordPlaceholder: 'Re-enter password', + agreeToTerms: 'I agree to the', + termsAndPrivacy: 'Terms & Privacy Policy', + signupWithFayda: 'Sign up with National ID (Fayda)', + alreadyHaveAccount: 'Already have an account?', + faydaFailed: 'Failed to initiate National ID verification', + resetPasswordBrandTitle: 'Reset your password securely.', + resetPasswordBrandSubtitle: + 'No worries \u2014 we\u2019ll send you a secure link to create a new password and get you back on track.', + signupBrandTitle: 'Your journey begins here.', + signupBrandSubtitle: 'Create an account and unlock a world of possibilities.', + verifyAccount: 'Verify your account', + otpSent: 'A verification code has been sent to {{email}}. Enter it below to continue.', + verify: 'Verify', + havingTrouble: 'Having trouble?', + skipVerification: 'Skip verification for now', + noCode: 'Didn\u2019t receive a code?', + resendIn: 'Resend in {{seconds}}s', + resendCode: 'Resend code', + otpDigitCode: 'Enter the {{length}}-digit code', + phoneVerified: 'Phone number verified successfully', + codeResent: 'Verification code resent to your email', + otpBrandTitle: 'One last step to secure your account.', + otpBrandSubtitle: + 'We use a one-time code to confirm it\u2019s really you before granting access.', + setPassword: 'Set your password', + newPassword: 'New password', + setPasswordBrandTitle: 'Set your account password.', + setPasswordBrandSubtitle: + 'Choose a strong password to secure your account and access all Portal features.', + welcomeNationalId: + 'Welcome, {{name}}. Your National ID has been verified successfully. Choose a password to complete your account setup.', + setPasswordAndContinue: 'Set password & continue', + passwordSet: 'Password set successfully', + sessionExpired: 'Session expired. Please sign up again.', + faydaBrandTitle: 'Verifying your National ID...', + faydaBrandSubtitle: + 'Please wait while we securely verify your identity with the National ID service.', + faydaVerifying: 'Verifying your identity', + faydaFailedTitle: 'Verification failed', + faydaProcessing: + 'We are securely processing your National ID information. This should only take a moment.', + faydaTryAgain: + 'Please try again or use the standard sign-up method instead.', + faydaInvalidResponse: + 'Invalid response from National ID service. Missing code or state.', + faydaSessionExpired: 'Session expired. Please try signing up again.', + faydaVerifyFailed: 'Failed to complete National ID verification', + faydaVerified: 'National ID verified successfully', + hero: { + brandTitle: 'Maritime licensing, made simple.', + brandSubtitle: + 'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.', + featureOnline: 'Submit applications online, 24/7', + featureTracking: 'Real-time status tracking & alerts', + featureBilingual: 'Available in English & አማርኛ', + }, + validation: { + emailInvalid: 'Enter a valid email', + passwordMin: 'Password must be at least {{min}} characters', + usernameMin: 'Username must be at least 3 characters', + phoneRequired: 'Phone number is required', + nameEnRequired: 'Name (English) is required', + confirmRequired: 'Confirm your password', + passwordMismatch: 'Passwords do not match', + }, }, dashboard: { diff --git a/apps/portal/src/app/providers/AppProviders.tsx b/apps/portal/src/app/providers/AppProviders.tsx index b907bae8d..d38889494 100644 --- a/apps/portal/src/app/providers/AppProviders.tsx +++ b/apps/portal/src/app/providers/AppProviders.tsx @@ -1,7 +1,9 @@ +import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { ReactNode } from 'react'; import { store } from '../store'; +import { i18n } from '../i18n/config'; import { MantineThemeProvider } from './MantineThemeProvider'; import { ErrorBoundary } from '../components/ErrorBoundary'; @@ -14,7 +16,9 @@ export function AppProviders({ children }: { children: ReactNode }) { - {children} + + {children} + diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx index 424f08514..a42382429 100644 --- a/apps/portal/src/app/router.tsx +++ b/apps/portal/src/app/router.tsx @@ -1,6 +1,4 @@ import { createBrowserRouter, Navigate } from 'react-router-dom'; -import { I18nextProvider } from 'react-i18next'; -import { i18n } from './i18n/config'; import { PortalLayout } from './layouts/PortalLayout'; import { ProtectedRoute } from './components/ProtectedRoute'; import { ProfileGuard } from './components/ProfileGuard'; @@ -45,11 +43,11 @@ import { VesselOwnerDashboardPage } from './features/vessel-owner/pages/VesselOw export const router = createBrowserRouter([ // Public auth pages - { path: '/login', element: }, - { path: '/signup', element: }, + { path: '/login', element: }, + { path: '/signup', element: }, // Public auth pages - { path: '/forgot-password', element: }, + { path: '/forgot-password', element: }, // Fayda / National ID callback { path: '/callback', element: }, @@ -72,9 +70,7 @@ export const router = createBrowserRouter([ element: ( - - - + ), diff --git a/libs/auth/src/lib/components/AuthShell.tsx b/libs/auth/src/lib/components/AuthShell.tsx index 6696cea3a..941370df0 100644 --- a/libs/auth/src/lib/components/AuthShell.tsx +++ b/libs/auth/src/lib/components/AuthShell.tsx @@ -15,6 +15,8 @@ import { type BoxProps, } from '@mantine/core'; import { IconCheck, IconMoon, IconSun } from '@tabler/icons-react'; +import { useTranslation } from 'react-i18next'; +import { LanguageSwitcher } from '@ema-platform/ui'; const LOGO_URL = '/brand/ema-white.png'; @@ -60,23 +62,26 @@ function ThemeToggle() { ); } -const FEATURES = [ - 'Submit applications online, 24/7', - 'Real-time status tracking & alerts', - 'Available in English & አማርኛ', +const FEATURE_KEYS = [ + 'auth.hero.featureOnline', + 'auth.hero.featureTracking', + 'auth.hero.featureBilingual', ]; interface AuthShellProps { children: ReactNode; brandTitle?: string; brandSubtitle?: string; + supportedLanguages?: readonly string[]; } export function AuthShell({ children, - brandTitle = 'Maritime licensing, made simple.', - brandSubtitle = 'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.', + brandTitle, + brandSubtitle, + supportedLanguages = ['en', 'am'], }: AuthShellProps) { + const { t } = useTranslation(); const theme = useMantineTheme(); const heroGradient = theme.other.heroGradient as string; const computed = useComputedColorScheme('light'); @@ -91,6 +96,18 @@ export function AuthShell({ }} p="md" > + {/* Language switcher — top-left corner */} + + + + {/* Theme toggle — top-right corner */} - {brandTitle} + {brandTitle ?? t('auth.hero.brandTitle')} - {brandSubtitle} + {t('auth.hero.brandSubtitle')} - {FEATURES.map((feature) => ( - + {FEATURE_KEYS.map((key) => ( +
- {feature} + {t(key)}
))} diff --git a/libs/auth/src/lib/pages/FaydaCallbackPage.tsx b/libs/auth/src/lib/pages/FaydaCallbackPage.tsx index 16d2a9c26..d30bffe83 100644 --- a/libs/auth/src/lib/pages/FaydaCallbackPage.tsx +++ b/libs/auth/src/lib/pages/FaydaCallbackPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { Alert, Center, Loader, Stack, Text, Title } from '@mantine/core'; import { IconAlertCircle, IconShieldCheck } from '@tabler/icons-react'; +import { useTranslation } from 'react-i18next'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; @@ -18,6 +19,7 @@ interface FaydaUserInfo { } export function FaydaCallbackPage() { + const { t } = useTranslation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const calledRef = useRef(false); @@ -33,7 +35,7 @@ export function FaydaCallbackPage() { const state = searchParams.get('state'); if (!code || !state) { - const msg = 'Invalid response from National ID service. Missing code or state.'; + const msg = t('auth.faydaInvalidResponse'); setError(msg); notify.error(msg); return; @@ -43,7 +45,7 @@ export function FaydaCallbackPage() { sessionStorage.removeItem('fayda_authId'); if (!authId) { - const msg = 'Session expired. Please try signing up again.'; + const msg = t('auth.faydaSessionExpired'); setError(msg); notify.error(msg); return; @@ -60,12 +62,12 @@ export function FaydaCallbackPage() { sessionStorage.setItem('fayda_userId', userInfo.sub); sessionStorage.setItem('fayda_userInfo', JSON.stringify(userInfo)); - notify.success('National ID verified successfully'); + notify.success(t('auth.faydaVerified')); navigate('/set-password', { replace: true }); } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Failed to complete National ID verification'); + (err instanceof Error ? err.message : t('auth.faydaVerifyFailed')); setError(msg); notify.error(msg); } @@ -76,19 +78,17 @@ export function FaydaCallbackPage() { return (
- {error ? 'Verification failed' : 'Verifying your identity'} + {error ? t('auth.faydaFailedTitle') : t('auth.faydaVerifying')} - {error - ? error - : 'We are securely processing your National ID information. This should only take a moment.'} + {error ? error : t('auth.faydaProcessing')}
@@ -100,7 +100,7 @@ export function FaydaCallbackPage() { {error} - Please try again or use the standard sign-up method instead. + {t('auth.faydaTryAgain')} )} diff --git a/libs/auth/src/lib/pages/ForgotPasswordPage.tsx b/libs/auth/src/lib/pages/ForgotPasswordPage.tsx index 39e3e2216..b7279a8c3 100644 --- a/libs/auth/src/lib/pages/ForgotPasswordPage.tsx +++ b/libs/auth/src/lib/pages/ForgotPasswordPage.tsx @@ -17,6 +17,7 @@ import { IconMailForward, IconSend, } from '@tabler/icons-react'; +import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -31,11 +32,8 @@ const schema = z.object({ type FormValues = z.infer; -interface ForgotPasswordPageProps { - appName?: string; -} - -export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPageProps) { +export function ForgotPasswordPage() { + const { t } = useTranslation(); const [forgotTrigger, { isLoading }] = useApiMutation(); const [sentTo, setSentTo] = useState(null); const [serverError, setServerError] = useState(null); @@ -63,7 +61,7 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } @@ -73,11 +71,11 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro if (!sentTo || isLoading) return; try { await sendResetLink(sentTo); - notify.success('Reset link sent again'); + notify.success(t('auth.resetLinkSentAgain')); } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } @@ -93,7 +91,7 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }} > - Back to sign in + {t('auth.backToSignIn')} ); @@ -101,8 +99,8 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro if (sentTo) { return ( @@ -111,14 +109,10 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro
- Check your email + {t('auth.checkYourEmail')} - We've sent a password reset link to{' '} - - {sentTo} - - . Follow the link in that email to choose a new password. + {t('auth.resetLinkSent', { email: sentTo })}
@@ -131,12 +125,12 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro size="md" leftSection={} > - Open email app + {t('auth.openEmailApp')} - Didn't get the email? + {t('auth.didNotGetEmail')} - Resend link + {t('auth.resendLink')} @@ -156,17 +150,16 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro return (
- Forgot your password? + {t('auth.forgotYourPassword')} - Enter the email linked to your account and we'll send you a link - to reset your password. + {t('auth.forgotDescription')}
@@ -179,11 +172,11 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro
} - error={errors.email?.message} + error={errors.email ? t('auth.validation.emailInvalid') : undefined} {...register('email')} /> @@ -194,7 +187,7 @@ export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPagePro size="md" rightSection={} > - Send reset link + {t('auth.sendResetLink')}
diff --git a/libs/auth/src/lib/pages/LoginPage.tsx b/libs/auth/src/lib/pages/LoginPage.tsx index 063738a9e..c5bd9a1bc 100644 --- a/libs/auth/src/lib/pages/LoginPage.tsx +++ b/libs/auth/src/lib/pages/LoginPage.tsx @@ -21,6 +21,7 @@ import { import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; +import { useTranslation } from 'react-i18next'; import { useNavigate, Link } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { useApiMutation } from '@ema-platform/api'; @@ -44,10 +45,11 @@ interface LoginPageProps { } export function LoginPage({ - appName = 'Portal', + appName, enableSignup = true, enableForgotPassword = true, }: LoginPageProps) { + const { t } = useTranslation(); const navigate = useNavigate(); const dispatch = useDispatch(); const [isLoading, setIsLoading] = useState(false); @@ -118,7 +120,7 @@ export function LoginPage({ } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } finally { @@ -131,10 +133,10 @@ export function LoginPage({
- Welcome to {appName} + {t('auth.welcomeTo', { appName: appName ?? t('app.name') })} - Sign in to access your account. + {t('auth.signInToAccess')}
@@ -147,25 +149,25 @@ export function LoginPage({
} - error={errors.email?.message} + error={errors.email ? t('auth.validation.emailInvalid') : undefined} {...register('email')} /> } - error={errors.password?.message} + error={errors.password ? t('auth.validation.passwordMin', { min: 6 }) : undefined} {...register('password')} /> setRememberMe(e.currentTarget.checked)} @@ -177,7 +179,7 @@ export function LoginPage({ size="sm" fw={600} > - Forgot password? + {t('auth.forgotPassword')} )} @@ -189,12 +191,12 @@ export function LoginPage({ size="md" rightSection={} > - Sign in + {t('auth.signIn')}
- + {enableSignup && ( - Don't have an account?{' '} + {t('auth.noAccount')}{' '} - Create one + {t('auth.createOne')} )} diff --git a/libs/auth/src/lib/pages/OTPVerificationPage.tsx b/libs/auth/src/lib/pages/OTPVerificationPage.tsx index e59593f88..517b0176d 100644 --- a/libs/auth/src/lib/pages/OTPVerificationPage.tsx +++ b/libs/auth/src/lib/pages/OTPVerificationPage.tsx @@ -16,6 +16,7 @@ import { IconArrowRight, IconShieldLock } from '@tabler/icons-react'; import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; +import { useTranslation } from 'react-i18next'; import { useNavigate, useLocation } from 'react-router-dom'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; @@ -25,14 +26,13 @@ const CODE_LENGTH = 6; const RESEND_SECONDS = 30; const schema = z.object({ - verificationCode: z - .string() - .length(CODE_LENGTH, { message: `Enter the ${CODE_LENGTH}-digit code` }), + verificationCode: z.string().length(CODE_LENGTH), }); type FormValues = z.infer; export function OTPVerificationPage() { + const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const state = location.state as @@ -70,12 +70,12 @@ export function OTPVerificationPage() { body: { email, phoneNumber, verificationCode: values.verificationCode }, }).unwrap(); - notify.success('Phone number verified successfully'); + notify.success(t('auth.phoneVerified')); navigate(needsProfile ? '/profile-setup' : '/dashboard'); } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } @@ -90,13 +90,13 @@ export function OTPVerificationPage() { body: { email, phoneNumber, type: 'verify-phone-number' }, }).unwrap(); - notify.success('Verification code resent to your email'); + notify.success(t('auth.codeResent')); setSecondsLeft(RESEND_SECONDS); setServerError(null); } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } @@ -104,8 +104,8 @@ export function OTPVerificationPage() { return ( @@ -114,14 +114,10 @@ export function OTPVerificationPage() {
- Verify your account + {t('auth.verifyAccount')} - A verification code has been sent to{' '} - - {email || 'your email'} - - . Enter it below to continue. + {t('auth.otpSent', { email: email || t('auth.emailPlaceholder') })}
@@ -150,9 +146,9 @@ export function OTPVerificationPage() { onChange={field.onChange} onComplete={() => handleSubmit(onSubmit)()} /> - {errors.verificationCode?.message && ( + {errors.verificationCode && ( - {errors.verificationCode.message} + {t('auth.otpDigitCode', { length: CODE_LENGTH })} )}
@@ -165,13 +161,13 @@ export function OTPVerificationPage() { size="md" rightSection={} > - Verify + {t('auth.verify')}
@@ -182,17 +178,17 @@ export function OTPVerificationPage() { size="md" onClick={() => navigate('/dashboard')} > - Skip verification for now + {t('auth.skipVerification')}
- Didn't receive a code? + {t('auth.noCode')} {secondsLeft > 0 ? ( - Resend in {secondsLeft}s + {t('auth.resendIn', { seconds: secondsLeft })} ) : ( - Resend code + {t('auth.resendCode')} )} diff --git a/libs/auth/src/lib/pages/SetPasswordPage.tsx b/libs/auth/src/lib/pages/SetPasswordPage.tsx index 50681af11..abb68d397 100644 --- a/libs/auth/src/lib/pages/SetPasswordPage.tsx +++ b/libs/auth/src/lib/pages/SetPasswordPage.tsx @@ -4,6 +4,7 @@ import { IconArrowRight, IconLock, IconLockOpen } from '@tabler/icons-react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; +import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; @@ -11,8 +12,8 @@ import { AuthShell } from '../components/AuthShell'; const schema = z .object({ - newPassword: z.string().min(8, { message: 'Password must be at least 8 characters' }), - confirmPassword: z.string().min(8, { message: 'Confirm your password' }), + newPassword: z.string().min(8), + confirmPassword: z.string().min(8), }) .refine((data) => data.newPassword === data.confirmPassword, { message: 'Passwords do not match', @@ -22,6 +23,7 @@ const schema = z type FormValues = z.infer; export function SetPasswordPage() { + const { t } = useTranslation(); const navigate = useNavigate(); const [serverError, setServerError] = useState(null); const [setPasswordTrigger, { isLoading }] = useApiMutation(); @@ -49,7 +51,7 @@ export function SetPasswordPage() { const onSubmit = async (values: FormValues) => { if (!userId) { - notify.error('Session expired. Please sign up again.'); + notify.error(t('auth.sessionExpired')); navigate('/signup', { replace: true }); return; } @@ -68,12 +70,12 @@ export function SetPasswordPage() { sessionStorage.removeItem('fayda_userId'); sessionStorage.removeItem('fayda_userInfo'); - notify.success('Password set successfully'); + notify.success(t('auth.passwordSet')); navigate('/login', { replace: true }); } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } @@ -81,8 +83,8 @@ export function SetPasswordPage() { return ( @@ -91,11 +93,10 @@ export function SetPasswordPage() {
- Set your password + {t('auth.setPassword')} - Welcome, {userName}. Your National ID has been verified successfully. - Choose a password to complete your account setup. + {t('auth.welcomeNationalId', { name: userName })}
@@ -108,17 +109,23 @@ export function SetPasswordPage() {
} - error={errors.newPassword?.message} + error={errors.newPassword ? t('auth.validation.passwordMin', { min: 8 }) : undefined} {...register('newPassword')} /> } - error={errors.confirmPassword?.message} + error={ + errors.confirmPassword?.message === 'Passwords do not match' + ? t('auth.validation.passwordMismatch') + : errors.confirmPassword + ? t('auth.validation.confirmRequired') + : undefined + } {...register('confirmPassword')} /> @@ -129,7 +136,7 @@ export function SetPasswordPage() { size="md" rightSection={} > - Set password & continue + {t('auth.setPasswordAndContinue')}
diff --git a/libs/auth/src/lib/pages/SignupPage.tsx b/libs/auth/src/lib/pages/SignupPage.tsx index 195e64fa9..111c32ad8 100644 --- a/libs/auth/src/lib/pages/SignupPage.tsx +++ b/libs/auth/src/lib/pages/SignupPage.tsx @@ -25,6 +25,7 @@ import { import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; +import { useTranslation } from 'react-i18next'; import { useNavigate, Link } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { useApiMutation } from '@ema-platform/api'; @@ -64,11 +65,8 @@ interface SignupPayload { confirmPassword: string; } -interface SignupPageProps { - appName?: string; -} - -export function SignupPage({ appName = 'Portal' }: SignupPageProps) { +export function SignupPage() { + const { t } = useTranslation(); const navigate = useNavigate(); const dispatch = useDispatch(); const [agreed, setAgreed] = useState(false); @@ -101,7 +99,7 @@ export function SignupPage({ appName = 'Portal' }: SignupPageProps) { } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Failed to initiate National ID verification'); + (err instanceof Error ? err.message : t('auth.faydaFailed')); setServerError(msg); notify.error(msg); } @@ -150,7 +148,7 @@ export function SignupPage({ appName = 'Portal' }: SignupPageProps) { } catch (err: unknown) { const msg = (err as { data?: { message?: string } })?.data?.message ?? - (err instanceof Error ? err.message : 'Something went wrong'); + (err instanceof Error ? err.message : t('common.somethingWentWrong')); setServerError(msg); notify.error(msg); } @@ -158,16 +156,16 @@ export function SignupPage({ appName = 'Portal' }: SignupPageProps) { return (
- Create account + {t('auth.createAccount')} - It only takes a minute to get started. + {t('auth.signupDescription')}
@@ -181,59 +179,64 @@ export function SignupPage({ appName = 'Portal' }: SignupPageProps) { } - error={errors.nameEn?.message} + error={errors.nameEn ? t('auth.validation.nameEnRequired') : undefined} {...register('nameEn')} /> } - error={errors.nameAm?.message} {...register('nameAm')} /> } - error={errors.email?.message} + error={errors.email ? t('auth.validation.emailInvalid') : undefined} {...register('email')} /> } - error={errors.username?.message} + error={errors.username ? t('auth.validation.usernameMin') : undefined} {...register('username')} /> } - error={errors.phoneNumber?.message} + error={errors.phoneNumber ? t('auth.validation.phoneRequired') : undefined} {...register('phoneNumber')} /> } - error={errors.password?.message} + error={errors.password ? t('auth.validation.passwordMin', { min: 8 }) : undefined} {...register('password')} /> } - error={errors.confirmPassword?.message} + error={ + errors.confirmPassword?.message === 'Passwords do not match' + ? t('auth.validation.passwordMismatch') + : errors.confirmPassword + ? t('auth.validation.confirmRequired') + : undefined + } {...register('confirmPassword')} /> @@ -244,13 +247,13 @@ export function SignupPage({ appName = 'Portal' }: SignupPageProps) { onChange={(e) => setAgreed(e.currentTarget.checked)} label={ - I agree to the{' '} + {t('auth.agreeToTerms')}{' '} e.preventDefault()} > - Terms & Privacy Policy + {t('auth.termsAndPrivacy')} } @@ -264,10 +267,10 @@ export function SignupPage({ appName = 'Portal' }: SignupPageProps) { size="md" rightSection={} > - Create account + {t('auth.createAccount')} - + - Already have an account?{' '} + {t('auth.alreadyHaveAccount')}{' '} - Sign in + {t('auth.signIn')}