Files
Felipe Cardoso 28b1cc6e48 Replace "FastNext" branding with "PragmaStack" across the project
- Updated all references, metadata, and templates to reflect the new branding, including layout files, components, and documentation.
- Replaced hardcoded color tokens like `green-600` with semantic tokens (`success`, `warning`, etc.) for improved design consistency.
- Enhanced `globals.css` with new color tokens for success, warning, and destructive states using the OKLCH color model.
- Added comprehensive branding guidelines and updated the design system documentation to align with the new identity.
- Updated tests and mocks to reflect the branding changes and ensured all visual/verbal references match "PragmaStack".
- Added new `branding/README.md` and `branding` docs for mission, values, and visual identity definition.
2025-11-20 12:55:30 +01:00

407 lines
9.5 KiB
Markdown

# Internationalization (i18n) Guide
This document describes the internationalization implementation in the PragmaStack.
## 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 (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
```
### 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 (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
```
### Navigation
**Always use the locale-aware navigation utilities:**
```typescript
import { Link, useRouter, usePathname } from '@/lib/i18n/routing';
// Link component (automatic locale prefix)
<Link href="/settings">Settings</Link> // → /en/settings
// Router hooks
function MyComponent() {
const router = useRouter();
const pathname = usePathname();
const handleClick = () => {
router.push('/dashboard'); // → /en/dashboard
};
return <button onClick={handleClick}>Go to Dashboard</button>;
}
```
**⚠️ 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<Metadata> {
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';
<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<string, string> = {
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.