Refactor i18n integration and update tests for improved localization
- Updated test components (`PasswordResetConfirmForm`, `PasswordChangeForm`) to use i18n keys directly, ensuring accurate validation messages. - Refined translations in `it.json` to standardize format and content. - Replaced text-based labels with localized strings in `PasswordResetRequestForm` and `RegisterForm`. - Introduced `generateLocalizedMetadata` utility and updated layout metadata generation for locale-aware SEO. - Enhanced e2e tests with locale-prefixed routes and updated assertions for consistency. - Added comprehensive i18n documentation (`I18N.md`) for usage, architecture, and testing.
This commit is contained in:
@@ -3,6 +3,10 @@ import '@testing-library/jest-dom';
|
||||
import 'whatwg-fetch'; // Polyfill fetch API
|
||||
import { Crypto } from '@peculiar/webcrypto';
|
||||
|
||||
// Mock environment variables for tests
|
||||
process.env.NEXT_PUBLIC_SITE_URL = 'http://localhost:3000';
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL = 'http://localhost:8000';
|
||||
|
||||
// Polyfill TransformStream for nock/msw
|
||||
if (typeof global.TransformStream === 'undefined') {
|
||||
const { TransformStream } = require('node:stream/web');
|
||||
@@ -114,6 +118,190 @@ if (!VERBOSE) {
|
||||
};
|
||||
}
|
||||
|
||||
// Mock next-intl/server for server-side translations
|
||||
jest.mock('next-intl/server', () => ({
|
||||
getTranslations: jest.fn(async ({ locale: _locale, namespace: _namespace }) => {
|
||||
return (key) => key;
|
||||
}),
|
||||
getMessages: jest.fn(async () => ({})),
|
||||
}));
|
||||
|
||||
// Mock next-intl for all tests
|
||||
jest.mock('next-intl', () => ({
|
||||
useTranslations: (namespace) => {
|
||||
// Return actual English translations for tests
|
||||
const translations = {
|
||||
auth: {
|
||||
login: {
|
||||
emailLabel: 'Email',
|
||||
emailPlaceholder: 'Enter your email',
|
||||
passwordLabel: 'Password',
|
||||
passwordPlaceholder: 'Enter your password',
|
||||
loginButton: 'Sign in',
|
||||
loginButtonLoading: 'Signing in...',
|
||||
forgotPassword: 'Forgot password?',
|
||||
noAccount: "Don't have an account?",
|
||||
registerLink: 'Sign up',
|
||||
successMessage: 'Login successful',
|
||||
},
|
||||
register: {
|
||||
firstNameLabel: 'First Name',
|
||||
firstNamePlaceholder: 'Enter your first name',
|
||||
lastNameLabel: 'Last Name',
|
||||
lastNamePlaceholder: 'Enter your last name',
|
||||
emailLabel: 'Email',
|
||||
emailPlaceholder: 'Enter your email',
|
||||
passwordLabel: 'Password',
|
||||
passwordPlaceholder: 'Enter your password',
|
||||
confirmPasswordLabel: 'Confirm Password',
|
||||
confirmPasswordPlaceholder: 'Confirm your password',
|
||||
registerButton: 'Create account',
|
||||
registerButtonLoading: 'Creating account...',
|
||||
hasAccount: 'Already have an account?',
|
||||
loginLink: 'Sign in',
|
||||
required: '*',
|
||||
firstNameRequired: 'First name is required',
|
||||
firstNameMinLength: 'First name must be at least 2 characters',
|
||||
firstNameMaxLength: 'First name must not exceed 50 characters',
|
||||
lastNameMaxLength: 'Last name must not exceed 50 characters',
|
||||
passwordRequired: 'Password is required',
|
||||
passwordMinLength: 'Password must be at least 8 characters',
|
||||
passwordNumber: 'Password must contain at least one number',
|
||||
passwordUppercase: 'Password must contain at least one uppercase letter',
|
||||
confirmPasswordRequired: 'Please confirm your password',
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
unexpectedError: 'An unexpected error occurred. Please try again.',
|
||||
passwordRequirements: {
|
||||
minLength: 'At least 8 characters',
|
||||
hasNumber: 'Contains a number',
|
||||
hasUppercase: 'Contains an uppercase letter',
|
||||
},
|
||||
},
|
||||
passwordReset: {
|
||||
emailLabel: 'Email',
|
||||
emailPlaceholder: 'Enter your email',
|
||||
sendResetLinkButton: 'Send reset link',
|
||||
sendResetLinkButtonLoading: 'Sending...',
|
||||
instructions:
|
||||
'Enter your email address and we will send you a link to reset your password.',
|
||||
successMessage:
|
||||
'If an account exists with that email, you will receive a password reset link.',
|
||||
unexpectedError: 'An unexpected error occurred. Please try again.',
|
||||
backToLogin: 'Back to login',
|
||||
rememberPassword: 'Remember your password?',
|
||||
},
|
||||
passwordResetConfirm: {
|
||||
newPasswordLabel: 'New Password',
|
||||
newPasswordPlaceholder: 'Enter your new password',
|
||||
confirmPasswordLabel: 'Confirm Password',
|
||||
confirmPasswordPlaceholder: 'Confirm your new password',
|
||||
resetButton: 'Reset password',
|
||||
resetButtonLoading: 'Resetting...',
|
||||
instructions: 'Enter your new password below.',
|
||||
successMessage: 'Your password has been successfully reset.',
|
||||
backToLogin: 'Back to login',
|
||||
rememberPassword: 'Remember your password?',
|
||||
required: '*',
|
||||
newPasswordRequired: 'New password is required',
|
||||
newPasswordMinLength: 'Password must be at least 8 characters',
|
||||
newPasswordNumber: 'Password must contain at least one number',
|
||||
newPasswordUppercase: 'Password must contain at least one uppercase letter',
|
||||
confirmPasswordRequired: 'Please confirm your password',
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
unexpectedError: 'An unexpected error occurred. Please try again.',
|
||||
passwordRequirements: {
|
||||
minLength: 'At least 8 characters',
|
||||
hasNumber: 'Contains a number',
|
||||
hasUppercase: 'Contains an uppercase letter',
|
||||
},
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
password: {
|
||||
title: 'Change Password',
|
||||
subtitle: 'Update your password to keep your account secure',
|
||||
currentPasswordLabel: 'Current Password',
|
||||
currentPasswordPlaceholder: 'Enter your current password',
|
||||
newPasswordLabel: 'New Password',
|
||||
newPasswordPlaceholder: 'Enter your new password',
|
||||
confirmPasswordLabel: 'Confirm New Password',
|
||||
confirmPasswordPlaceholder: 'Confirm your new password',
|
||||
updateButton: 'Update password',
|
||||
updateButtonLoading: 'Updating...',
|
||||
currentPasswordRequired: 'Current password is required',
|
||||
newPasswordRequired: 'New password is required',
|
||||
newPasswordMinLength: 'Password must be at least 8 characters',
|
||||
newPasswordNumber: 'Password must contain at least one number',
|
||||
newPasswordUppercase: 'Password must contain at least one uppercase letter',
|
||||
newPasswordLowercase: 'Password must contain at least one lowercase letter',
|
||||
newPasswordSpecial: 'Password must contain at least one special character',
|
||||
confirmPasswordRequired: 'Please confirm your new password',
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
unexpectedError: 'An unexpected error occurred. Please try again.',
|
||||
passwordRequirements: {
|
||||
minLength: 'At least 8 characters',
|
||||
hasNumber: 'Contains a number',
|
||||
hasUppercase: 'Contains an uppercase letter',
|
||||
hasLowercase: 'Contains a lowercase letter',
|
||||
hasSpecial: 'Contains a special character',
|
||||
},
|
||||
},
|
||||
profile: {
|
||||
title: 'Profile Settings',
|
||||
subtitle: 'Manage your personal information',
|
||||
firstNameLabel: 'First Name',
|
||||
firstNamePlaceholder: 'Enter your first name',
|
||||
lastNameLabel: 'Last Name',
|
||||
lastNamePlaceholder: 'Enter your last name',
|
||||
emailLabel: 'Email',
|
||||
emailDescription: 'Email cannot be changed. Contact support if you need to update it.',
|
||||
updateButton: 'Save changes',
|
||||
updateButtonLoading: 'Saving...',
|
||||
resetButton: 'Cancel',
|
||||
firstNameRequired: 'First name is required',
|
||||
firstNameMinLength: 'First name must be at least 2 characters',
|
||||
firstNameMaxLength: 'First name must not exceed 50 characters',
|
||||
lastNameMaxLength: 'Last name must not exceed 50 characters',
|
||||
emailInvalid: 'Please enter a valid email address',
|
||||
unexpectedError: 'An unexpected error occurred. Please try again.',
|
||||
},
|
||||
},
|
||||
navigation: {
|
||||
dashboard: 'Dashboard',
|
||||
settings: 'Settings',
|
||||
admin: 'Admin',
|
||||
logout: 'Logout',
|
||||
profile: 'Profile',
|
||||
password: 'Password',
|
||||
sessions: 'Sessions',
|
||||
},
|
||||
validation: {
|
||||
required: 'This field is required',
|
||||
email: 'Please enter a valid email address',
|
||||
minLength: 'Must be at least 8 characters',
|
||||
},
|
||||
errors: {
|
||||
validation: {
|
||||
required: 'This field is required',
|
||||
email: 'Please enter a valid email address',
|
||||
passwordWeak: 'Password must contain at least one number and one uppercase letter',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Helper to get nested value from object by dot notation
|
||||
const get = (obj, path) => {
|
||||
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
||||
};
|
||||
|
||||
return (key) => {
|
||||
const fullKey = namespace ? `${namespace}.${key}` : key;
|
||||
return get(translations, fullKey) || key;
|
||||
};
|
||||
},
|
||||
useLocale: () => 'en', // Default to English locale for tests
|
||||
}));
|
||||
|
||||
// Reset storage mocks before each test
|
||||
beforeEach(() => {
|
||||
// Don't use clearAllMocks - it breaks the mocks
|
||||
|
||||
Reference in New Issue
Block a user