# Internationalization (i18n) Guide This document describes the internationalization implementation in Syndarix. ## Overview The application supports multiple languages using [next-intl](https://next-intl-docs.vercel.app/) v4.5.3, a modern i18n library for Next.js 15 App Router. **Supported Locales:** - English (`en`) - Default - Italian (`it`) ## Architecture ### URL Structure All routes are locale-prefixed using the `[locale]` dynamic segment: ``` /en/login → English login page /it/login → Italian login page /en/settings → English settings /it/settings → Italian settings ``` ### Core Files ``` frontend/ ├── src/ │ ├── lib/ │ │ ├── i18n/ │ │ │ ├── routing.ts # Locale routing configuration │ │ │ ├── utils.ts # Locale utility functions │ │ │ └── metadata.ts # SEO metadata helpers │ ├── app/ │ │ └── [locale]/ # Locale-specific routes │ │ └── layout.tsx # Locale layout with NextIntlClientProvider │ └── components/ │ └── navigation/ │ └── LocaleSwitcher.tsx # Language switcher component ├── messages/ │ ├── en.json # English translations │ └── it.json # Italian translations └── types/ └── next-intl.d.ts # TypeScript type definitions ``` ## Usage ### Client Components Use the `useTranslations` hook from `next-intl`: ```typescript 'use client'; import { useTranslations } from 'next-intl'; export function MyComponent() { const t = useTranslations('namespace'); return (

{t('title')}

{t('description')}

); } ``` ### Server Components Use `getTranslations` from `next-intl/server`: ```typescript import { getTranslations } from 'next-intl/server'; export default async function MyPage() { const t = await getTranslations('namespace'); return (

{t('title')}

{t('description')}

); } ``` ### Navigation **Always use the locale-aware navigation utilities:** ```typescript import { Link, useRouter, usePathname } from '@/lib/i18n/routing'; // Link component (automatic locale prefix) Settings // → /en/settings // Router hooks function MyComponent() { const router = useRouter(); const pathname = usePathname(); const handleClick = () => { router.push('/dashboard'); // → /en/dashboard }; return ; } ``` **⚠️ Never import from `next/navigation` directly** - always use `@/lib/i18n/routing` ### Forms with Validation Create dynamic Zod schemas that accept translation functions: ```typescript import { useTranslations } from 'next-intl'; import { z } from 'zod'; const createLoginSchema = (t: (key: string) => string) => z.object({ email: z.string().min(1, t('validation.required')).email(t('validation.email')), password: z.string().min(1, t('validation.required')), }); export function LoginForm() { const t = useTranslations('auth.login'); const tValidation = useTranslations('validation'); const schema = createLoginSchema((key) => { if (key.startsWith('validation.')) { return tValidation(key.replace('validation.', '')); } return t(key); }); // Use schema with react-hook-form... } ``` ## Translation Files ### Structure Translations are organized hierarchically in JSON files: ```json { "common": { "loading": "Loading...", "save": "Save" }, "auth": { "login": { "title": "Sign in to your account", "emailLabel": "Email", "passwordLabel": "Password" } } } ``` ### Access Patterns ```typescript // Nested namespace access const t = useTranslations('auth.login'); t('title'); // → "Sign in to your account" // Multiple namespaces const tAuth = useTranslations('auth.login'); const tCommon = useTranslations('common'); tAuth('title'); // → "Sign in to your account" tCommon('loading'); // → "Loading..." ``` ## SEO & Metadata ### Page Metadata Use the `generatePageMetadata` helper for locale-aware metadata: ```typescript import { Metadata } from 'next'; import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; import { getTranslations } from 'next-intl/server'; export async function generateMetadata({ params, }: { params: Promise<{ locale: string }>; }): Promise { const { locale } = await params; const t = await getTranslations({ locale, namespace: 'auth.login' }); return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/login'); } ``` This automatically generates: - Open Graph tags - Twitter Card tags - Language alternates (hreflang) - Canonical URLs ### Sitemap The sitemap (`/sitemap.xml`) automatically includes all public routes for both locales with language alternates. ### Robots.txt The robots.txt (`/robots.txt`) allows crawling of public routes and references the sitemap. ## Locale Switching Users can switch languages using the `LocaleSwitcher` component in the header: ```typescript import { LocaleSwitcher } from '@/components/navigation/LocaleSwitcher'; ``` The switcher: - Displays the current locale - Lists available locales - Preserves the current path when switching - Uses the locale-aware router ## Type Safety TypeScript autocomplete for translation keys is enabled via `types/next-intl.d.ts`: ```typescript import en from '@/messages/en.json'; type Messages = typeof en; declare global { interface IntlMessages extends Messages {} } ``` This provides: - Autocomplete for translation keys - Type errors for missing keys - IntelliSense in IDEs ## Testing ### Unit Tests Mock `next-intl` in `jest.setup.js`: ```javascript jest.mock('next-intl', () => ({ useTranslations: (namespace) => { return (key) => key; // Return key as-is for tests }, useLocale: () => 'en', })); jest.mock('next-intl/server', () => ({ getTranslations: jest.fn(async () => (key) => key), })); ``` ### E2E Tests Update Playwright tests to include locale prefixes: ```typescript test('navigates to login', async ({ page }) => { await page.goto('/en/login'); // Include locale prefix await expect(page).toHaveURL('/en/login'); }); ``` ## Adding New Languages 1. **Create translation file:** ```bash cp messages/en.json messages/fr.json # Translate all values in fr.json ``` 2. **Update routing configuration:** ```typescript // src/lib/i18n/routing.ts export const routing = defineRouting({ locales: ['en', 'it', 'fr'], // Add 'fr' defaultLocale: 'en', }); ``` 3. **Update type definitions:** ```typescript // src/lib/i18n/metadata.ts export type Locale = 'en' | 'it' | 'fr'; // Add 'fr' ``` 4. **Update LocaleSwitcher:** ```typescript // src/components/navigation/LocaleSwitcher.tsx const localeNames: Record = { en: 'English', it: 'Italiano', fr: 'Français', // Add French }; ``` ## Best Practices ### DO ✅ - Use locale-aware navigation (`@/lib/i18n/routing`) - Create dynamic validation schemas - Use nested translation keys for organization - Test with multiple locales - Keep translation files in sync - Use TypeScript types for autocomplete ### DON'T ❌ - Don't hardcode text in components - Don't import from `next/navigation` directly - Don't use template strings for validation messages (use static messages) - Don't forget to update both `en.json` and `it.json` - Don't skip testing translated components ## Performance The implementation is optimized for performance: - **Server-side translation loading**: Messages loaded on server, passed to client - **Lazy loading**: Only current locale messages are loaded - **Font optimization**: `display: 'swap'` prevents layout shift - **Preloading**: Primary font preloaded, secondary font lazy-loaded - **Caching**: Next.js automatically caches translation files ## Common Issues ### "Module not found: Can't resolve 'next/navigation'" **Solution**: Use `@/lib/i18n/routing` instead: ```diff - import { useRouter } from 'next/navigation' + import { useRouter } from '@/lib/i18n/routing' ``` ### E2E tests failing with "Page not found" **Solution**: Add locale prefix to URLs: ```diff - await page.goto('/login') + await page.goto('/en/login') ``` ### TypeScript error: "Property does not exist on type" **Solution**: Ensure the key exists in `messages/en.json` and restart TypeScript server. ### ICU message format error **Solution**: Use static messages instead of templates: ```diff // ❌ Bad (ICU format not configured) - "minLength": "Must be at least {count} characters" // ✅ Good + "minLength": "Must be at least 8 characters" ``` ## Resources - [next-intl Documentation](https://next-intl-docs.vercel.app/) - [Next.js Internationalization](https://nextjs.org/docs/app/building-your-application/routing/internationalization) - [Implementation Plan](./I18N_IMPLEMENTATION_PLAN.md) ## Summary The i18n implementation provides: - ✅ Multi-language support (EN, IT) with easy extensibility - ✅ Type-safe translations with autocomplete - ✅ SEO-optimized with proper metadata and sitemaps - ✅ Performance-optimized loading - ✅ Comprehensive test coverage - ✅ Developer-friendly API For questions or issues, refer to the [next-intl documentation](https://next-intl-docs.vercel.app/) or check existing tests for examples.