# 12 — Internationalization (i18n) ## Overview - **Default Locale**: `de` (Deutsch), fallback `en` (English). - **Supported Locales**: MVP focuses on `de` and `en`; expandable via config/app.php locales array. - **Strategy**: Hybrid approach – Laravel PHP for backend/Blade views, react-i18next for React/Vite PWAs (Marketing, Tenant Admin, Guest). - **Translatable Fields**: JSON columns in DB for dynamic content (e.g., `name`, `description`, `title`, `body_markdown` in models like Package, EventType, LegalPage). - **Namespaces**: Organized by feature (e.g., `marketing`, `auth`, `guest`, `admin`) to avoid key collisions; glossary terms centralized in a shared namespace if needed. - **Date/Number Formatting**: Locale-aware via Laravel's Carbon/Intl; PWAs use Intl API or date-fns with locale bundles. - **RTL Support**: Not in MVP; future via CSS classes and i18next RTL plugin. - **SEO**: Multilingual URLs with prefixes (/de/, /en/), hreflang tags, canonical links, translated meta (title, description, og:). ## Backend (Laravel/PHP) - **Config**: `config/app.php` – `locale => 'de'`, `fallback_locale => 'en'`, `available_locales => ['de', 'en']`. - **Translation Files**: - PHP arrays: `resources/lang/\{locale\}/\{group\}.php` (e.g., `marketing.php`, `auth.php`, `legal.php`) for Blade and API responses. - JSON for PWAs: `public/lang/\{locale\}/\{namespace\}.json` (e.g., `public/lang/de/marketing.json`) – migrated from PHP where possible; loaded via dedicated route. - **Routing**: - Prefixed groups: `Route::prefix('\{locale?\}')->where(['locale' => 'de|en'])->middleware('SetLocale')` in `routes/web.php`. - Fallbacks: Non-prefixed routes redirect to `/de/\{path\}` (e.g., `/login` → `/de/login`). - Auth routes (login, register, logout): Prefixed and named (e.g., `Route::get('/login', ...)->name('login')`). - API routes: Locale from header/session; no URL prefix for `/api/v1`. - **Middleware**: `SetLocale` – Extracts locale from URL segment(1), sets `App::setLocale()`, stores in session; defaults to 'de'. - **JSON Loader Route**: `Route::get('/lang/\{locale\}/\{namespace\}.json', ...)` – Serves from `public_path('lang/\{locale\}/\{namespace\}.json')`; Vite proxy forwards requests. - **DB Translations**: Use JSON fields with spatie/laravel-translatable or native casts; admin UI (Filament) for editing per locale. - **Legal Pages**: Dynamic via LegalPage model; rendered with `__($key)` or JSON for PWAs. ## Frontend (React/Vite PWAs) - **Library**: react-i18next with i18next-http-backend for async JSON loads. - **Setup** (`resources/js/i18n.js`): - Init: `i18n.use(Backend).use(LanguageDetector).init({ lng: 'de', fallbackLng: 'en', ns: ['marketing', 'auth'], backend: { loadPath: '/lang/\{\{lng\}\}/\{\{ns\}\}.json' } })`. - Detection: Path-based (`order: ['path']`, `lookupFromPathIndex: 0`) for prefixed URLs; cookie/session fallback. - Provider: Wrap `<App>` in `<I18nextProvider i18n=\{i18n\}>` in `app.tsx`. - **Usage**: - Hook: `const { t } = useTranslation('namespace');` in components (e.g., `t('marketing.home.title')`). - Interpolation: Placeholders `\{count\}`; pluralization via i18next rules. - Dynamic Keys: Avoid; use namespaces for organization. - **Inertia Integration**: - Page Resolver: `` resolvePageComponent(`./Pages/\${name}.tsx`, import.meta.glob('./Pages/**/*.tsx')) `` – Matches capital 'Pages' directory. - Props: Pass `locale` from middleware to pages. - Links: `<Link href={`/${'{'}locale{'}'}/path`}>` für prefixed navigation (e.g., Header.tsx). - **Marketing Frontend**: - Namespaces: `marketing` (Home, Packages, Blog, Features), `auth` (Login, Register). - Components: All hard-coded strings replaced (e.g., Home.tsx: `t('marketing.hero.title')`); SEO meta via `Head` with `t()`. - Header: Locale selector; dynamic links (e.g., `/\{locale\}/login` with `t('auth.header.login')`). - **Guest/Tenant PWAs**: - Similar setup; load JSON on app init. - Guest: Anonymous, locale from URL or default 'de'; strings for UI (e.g., gallery, upload). - Tenant Admin: User-preferred locale from profile; sync with backend. - **Automation**: - Extraction: i18next-scanner (npm script: `i18next-scanner --config i18next-scanner.config.js`) to scan TSX for `t('key')` and update JSON. - Validation: Missing keys log warnings; dev mode strict. ## SEO & Accessibility - **Multilingual URLs**: `/de/home`, `/en/home`; 301 redirects for non-prefixed. - **Hreflang**: `<link rel="alternate" hreflang="de" href="/de/home">` in `<Head>`. - **Canonical**: `<link rel="canonical" href=\{currentUrl\}>` based on detected locale. - **Meta**: Translated via `t('seo.title')`; og:locale='de_DE'. - **Sitemap**: Generate with `de/` and `en/` variants; update `public/sitemap.xml`. - **Robots.txt**: Allow both locales; noindex for dev. - **Accessibility**: ARIA labels with `t()`; screen reader support for language switches. ## Migration from PHP to JSON - Extract keys from `resources/lang/\{locale\}/marketing.php` to `public/lang/\{locale\}/marketing.json`. - Consolidate: Remove duplicates; use nested objects (e.g., `{ "header": { "login": "Anmelden" } }`). - Fallback: PHP arrays remain for backend; JSON for PWAs. ## Testing & Maintenance - **Tests**: PHPUnit for backend (`__('key')`); Vitest/Jest for frontend (`t('key')` renders correctly). - **Linting**: ESLint rule for missing translations; i18next-scanner in pre-commit. - **Deployment**: JSON files in public; cache-bust via Vite hashes. - **Expansion**: Add locales via config; migrate more namespaces (e.g., `guest`, `admin`). ## Decisions & Trade-offs - Path-based detection over query params for SEO/clean URLs. - JSON over PHP for PWAs: Faster async loads, no server roundtrips. - No auto-redirect on locale mismatch in MVP; user chooses via selector. - ADR: Use react-i18next over next-intl (Inertia compatibility).