6.8 KiB
6.8 KiB
12 — Internationalization (i18n)
Overview
- Default Locale:
de(Deutsch), fallbacken(English). - Supported Locales: MVP focuses on
deanden; 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_markdownin 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.
- PHP arrays:
- Routing:
- Prefixed groups:
Route::prefix('\{locale?\}')->where(['locale' => 'de|en'])->middleware('SetLocale')inroutes/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.
- Prefixed groups:
- Middleware:
SetLocale– Extracts locale from URL segment(1), setsApp::setLocale(), stores in session; defaults to 'de'. - JSON Loader Route:
Route::get('/lang/\{locale\}/\{namespace\}.json', ...)– Serves frompublic_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\}>inapp.tsx.
- Init:
- 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.
- Hook:
- Inertia Integration:
- Page Resolver:
resolvePageComponent(`./Pages/\${name}.tsx`, import.meta.glob('./Pages/**/*.tsx'))– Matches capital 'Pages' directory. - Props: Pass
localefrom middleware to pages. - Links:
<Link href={/${'{'}locale{'}'}/path}>für prefixed navigation (e.g., Header.tsx).
- Page Resolver:
- 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 viaHeadwitht(). - Header: Locale selector; dynamic links (e.g.,
/\{locale\}/loginwitht('auth.header.login')).
- Namespaces:
- 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 fort('key')and update JSON. - Validation: Missing keys log warnings; dev mode strict.
- Extraction: i18next-scanner (npm script:
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/anden/variants; updatepublic/sitemap.xml. - Robots.txt: Allow both locales; noindex for dev.
- Accessibility: ARIA labels with
t(); screen reader support for language switches.
SEO Implementation Notes (Marketing)
- Source of tags:
resources/js/layouts/mainWebsite.tsxrenders canonical + hreflang links for Inertia marketing pages. - URL building:
resources/js/lib/localizedPath.tshandles locale rewrites (e.g.,/kontakt↔/contact) and prefixing. - Data inputs:
supportedLocales,locale, andappUrlare shared viaapp/Http/Middleware/HandleInertiaRequests.php. - SSR: Inertia SSR is disabled (
config/inertia.php), so canonical/hreflang tags are client-rendered.
Validation Checklist (Search Console + Lighthouse)
- Verify canonical + hreflang output on key marketing pages (home, contact, packages, blog, occasions).
- Use Search Console URL inspection to confirm rendered HTML contains canonical + hreflang tags.
- Run Lighthouse SEO audit on both
/de/*and/en/*routes (spot-check canonical + alternate links).
Tests
- Vitest:
resources/js/layouts/__tests__/mainWebsite.seo.test.tsxvalidates canonical/hreflang output and localized slug rewrites.
Migration from PHP to JSON
- Extract keys from
resources/lang/\{locale\}/marketing.phptopublic/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).