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:
@@ -1,4 +1,7 @@
|
||||
import { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
// Code-split LoginForm - heavy with react-hook-form + validation
|
||||
const LoginForm = dynamic(
|
||||
@@ -15,6 +18,17 @@ const LoginForm = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'auth.login' });
|
||||
|
||||
return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/login');
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -3,9 +3,28 @@
|
||||
* Users set a new password using the token from their email
|
||||
*/
|
||||
|
||||
import { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import PasswordResetConfirmContent from './PasswordResetConfirmContent';
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'auth.passwordResetConfirm' });
|
||||
|
||||
return generatePageMetadata(
|
||||
locale as Locale,
|
||||
t('title'),
|
||||
t('instructions'),
|
||||
'/password-reset/confirm'
|
||||
);
|
||||
}
|
||||
|
||||
export default function PasswordResetConfirmPage() {
|
||||
return (
|
||||
<Suspense
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
* Users enter their email to receive reset instructions
|
||||
*/
|
||||
|
||||
import { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
// Code-split PasswordResetRequestForm
|
||||
const PasswordResetRequestForm = dynamic(
|
||||
@@ -22,6 +25,17 @@ const PasswordResetRequestForm = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'auth.passwordReset' });
|
||||
|
||||
return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/password-reset');
|
||||
}
|
||||
|
||||
export default function PasswordResetPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
// Code-split RegisterForm (313 lines)
|
||||
const RegisterForm = dynamic(
|
||||
@@ -15,6 +18,17 @@ const RegisterForm = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'auth.register' });
|
||||
|
||||
return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/register');
|
||||
}
|
||||
|
||||
export default function RegisterPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -3,16 +3,28 @@
|
||||
* Displayed when users try to access resources they don't have permission for
|
||||
*/
|
||||
|
||||
/* istanbul ignore next - Next.js type import for metadata */
|
||||
import type { Metadata } from 'next';
|
||||
import { Link } from '@/lib/i18n/routing';
|
||||
import { ShieldAlert } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export const metadata: Metadata = /* istanbul ignore next */ {
|
||||
title: '403 - Forbidden',
|
||||
description: 'You do not have permission to access this resource',
|
||||
};
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'errors' });
|
||||
|
||||
return generatePageMetadata(
|
||||
locale as Locale,
|
||||
t('unauthorized'),
|
||||
t('unauthorizedDescription'),
|
||||
'/forbidden'
|
||||
);
|
||||
}
|
||||
|
||||
export default function ForbiddenPage() {
|
||||
return (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { notFound } from 'next/navigation';
|
||||
import { routing } from '@/lib/i18n/routing';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getMessages } from 'next-intl/server';
|
||||
import { generateLocalizedMetadata, type Locale } from '@/lib/i18n/metadata';
|
||||
import '../globals.css';
|
||||
import { Providers } from '../providers';
|
||||
import { AuthProvider } from '@/lib/auth/AuthContext';
|
||||
@@ -23,10 +24,14 @@ const geistMono = Geist_Mono({
|
||||
preload: false, // Only preload primary font
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'FastNext Template',
|
||||
description: 'FastAPI + Next.js Template',
|
||||
};
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
return generateLocalizedMetadata(locale as Locale);
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
|
||||
Reference in New Issue
Block a user