Mehrsprachigkeit (i18n)
Postbox ist zweisprachig: Deutsch (primär) und Englisch. Deutsch ist die führende Sprache — bestehende URLs ohne Prefix leiten per 301 auf /de/ um.
Architektur
URL-Strategie
| Bereich | Locale-Quelle | Beispiel |
|---|---|---|
| Öffentliche Seiten | URL-Prefix (/de/, /en/) | /de/explorer, /en/explorer |
| App-Bereich (eingeloggt) | users.locale Column (DB) | /dashboard (kein Prefix) |
| Admin-Bereich | Deutsch (nicht übersetzt) | /admin/* |
| API / Webhooks | Kein Locale | /api/*, /webhooks/* |
Middleware-Stack
| Middleware | Bereich | Funktion |
|---|---|---|
RedirectToLocalizedUrl | Global (GET) | 301-Redirect von /explorer → /de/explorer |
SetLocaleFromUrl | Öffentliche Routen | Liest {locale} aus URL, setzt app()->setLocale() |
SetLocaleFromUser | Auth-Routen | Liest users.locale aus DB, setzt Locale |
Fallback-Kette
- Öffentlich: URL-Prefix →
/de/(Default per 301) - Eingeloggt:
users.locale→de(Default) - Übersetzungen: EN-Key vorhanden → EN, sonst Fallback auf DE
Translation-Dateien
lang/
├── de.json → ~280 UI-Strings (Deutsch = Key-Sprache)
├── en.json → ~450 EN-Übersetzungen
├── de/
│ ├── validation.php → Validierungs-Meldungen
│ ├── auth.php → Auth-Meldungen
│ ├── passwords.php → Passwort-Reset
│ └── pagination.php → Pagination
└── en/
├── validation.php → EN Validierung
├── auth.php → EN Auth
├── passwords.php → EN Passwort-Reset
├── pagination.php → EN Pagination
└── mail.php → EN Mail-Strings
String-Pattern
Blade:
<flux:heading>{{ __('Übersicht') }}</flux:heading>
<flux:button>{{ __('Speichern') }}</flux:button>
PHP (Toast/Flash):
Flux::toast(__('Profil erfolgreich hinzugefügt.'));
Parametrisiert:
__('Zu viele Anfragen. Bitte warte :minutes Minuten.', ['minutes' => $minutes])
Locale-Verwaltung
Registrierung
Bei der Account-Erstellung wird die Browser-Sprache (Accept-Language Header) einmalig in users.locale geschrieben:
// app/Actions/Fortify/CreateNewUser.php
$browserLocale = request()->getPreferredLanguage(User::SUPPORTED_LOCALES) ?? 'de';
Einstellungen
User können ihre Sprache in den Profil-Einstellungen (/settings/profile) ändern. Ein flux:select-Dropdown bietet Deutsch und English zur Auswahl.
Unterstützte Locales
// app/Models/User.php
public const SUPPORTED_LOCALES = ['de', 'en'];
SEO
hreflang-Tags
Alle öffentlichen Seiten erhalten automatisch hreflang-Tags im <head>:
<link rel="alternate" hreflang="de" href="https://example.com/de/explorer" />
<link rel="alternate" hreflang="en" href="https://example.com/en/explorer" />
<link rel="alternate" hreflang="x-default" href="https://example.com/de/explorer" />
x-default zeigt auf /de/ (Deutsch als Default für unbekannte Sprachen).
Open Graph
<meta property="og:locale" content="de_DE"> <!-- oder en_US -->
<meta property="og:locale:alternate" content="en_US"> <!-- oder de_DE -->
Sitemap
Die Sitemap enthält für jede URL bidirektionale hreflang-Annotationen:
<url>
<loc>https://example.com/de/explorer/username_y123</loc>
<xhtml:link rel="alternate" hreflang="de" href="...de..." />
<xhtml:link rel="alternate" hreflang="en" href="...en..." />
<xhtml:link rel="alternate" hreflang="x-default" href="...de..." />
</url>
301-Redirects
Alle bestehenden URLs ohne Locale-Prefix leiten permanent auf /de/ um. Kein Browser-Sniffing — Google-Empfehlung wird befolgt.
CMS-Pages
Datenbank-Design
Deutsch bleibt in den bestehenden Feldern (Rückwärtskompatibilität), Englisch in _en-Spalten:
| Feld (DE, primär) | Feld (EN) |
|---|---|
title | title_en |
content | content_en |
meta_description | meta_description_en |
slug | slug_en |
AI-Übersetzung
Im Admin-Editor (/admin/pages/{id}/edit) gibt es einen "KI-Übersetzung generieren"-Button, der den PageTranslator-Service aufruft. Dieser übersetzt die deutschen Inhalte per Gemini AI ins Englische — HTML-Struktur wird beibehalten.
Locale-Methoden (Page Model)
$page->getLocalizedTitle('en'); // EN-Titel oder DE-Fallback
$page->getLocalizedContent('en'); // EN-Content oder DE-Fallback
$page->getLocalizedMetaDescription('en');
$page->hasEnglishTranslation(); // true wenn title_en + content_en gesetzt
AI-Beschreibungen
Profil-AI-Beschreibungen werden locale-aware ausgespielt — sowohl auf SocialProfile als auch auf PublicExplorerProfile:
$profile->getAiDescriptionForLocale(app()->getLocale());
SocialProfile (social_profiles Tabelle):
| Locale | Feld | Fallback |
|---|---|---|
de | ai_description_de | ai_description (EN) |
en | ai_description | — |
PublicExplorerProfile (public_explorer_profiles Tabelle):
| Locale | Feld | Fallback |
|---|---|---|
de | ai_description_de | ai_description (EN) |
en | ai_description | — |
Beim public-explorer:refresh werden beide Sprachversionen aus dem SocialProfile in die denormalisierte Tabelle synchronisiert.
Fortify-Integration
- Auth-Views (Login, Register, etc.) laufen unter
/{locale}/login,/{locale}/register - POST-Actions bleiben auf Root-Level (
POST /login) — werden NICHT redirected - Post-Login: Redirect auf
/dashboard(Locale aus DB viaSetLocaleFromUser) - Post-Logout: Redirect auf
/{user.locale}/viaLogoutResponse+CaptureLogoutLocaleListener
Helper
localized_route()
Generiert URLs mit Locale-Prefix für öffentliche Routen:
localized_route('public-explorer.index')
// → /de/explorer (wenn Locale = de)
localized_route('public-explorer.show', ['path' => 'user_y123'])
// → /en/explorer/user_y123 (wenn Locale = en)
Abgedeckte Bereiche
| Bereich | Status | Anmerkung |
|---|---|---|
| Öffentliche Seiten (Explorer, Landing, CMS) | ✅ Vollständig | URL-Prefix + hreflang |
| Auth (Login, Register, 2FA, Passwort) | ✅ Vollständig | Fortify-Integration |
| Dashboard | ✅ Vollständig | |
| Watcher (Index + Detail + Charts) | ✅ Vollständig | Inkl. Video Performance, Tooltips, Admin-Diagnose |
| Settings (Profil, Workspaces) | ✅ Vollständig | |
| Compare | ✅ Vollständig | |
| Discover | ✅ Vollständig | |
| Video-Trends | ✅ Vollständig | |
| Tops/Flops | ✅ Vollständig | |
| Feedback | ✅ Vollständig | |
| Shared Components (Slider-Cards, Workspace-Modal) | ✅ Vollständig | |
| Mail-Templates | ✅ Vollständig | lang/en/mail.php |
Admin-Bereich (/admin/*) | ❌ Bewusst nicht | Nur interne Nutzung |
Admin-Bereich
Admin-Seiten (/admin/*) werden nicht übersetzt. Sie bleiben auf Deutsch, da nur interne Nutzung (1-2 Admins, beide deutschsprachig).
Dateien
| Datei | Beschreibung |
|---|---|
app/Http/Middleware/SetLocaleFromUrl.php | Locale aus URL für öffentliche Seiten |
app/Http/Middleware/SetLocaleFromUser.php | Locale aus DB für eingeloggte User |
app/Http/Middleware/RedirectToLocalizedUrl.php | 301-Redirect für alte URLs |
app/helpers.php | localized_route() Helper |
app/Services/AI/PageTranslator.php | CMS-Page AI-Übersetzung (DE→EN) |
app/Http/Responses/LogoutResponse.php | Locale-aware Logout-Redirect |
app/Listeners/CaptureLogoutLocale.php | Locale vor Session-Invalidierung sichern |
resources/views/components/language-switcher.blade.php | Sprachwechsler UI-Component |
resources/views/components/layouts/public.blade.php | hreflang + og:locale Tags |
lang/en.json | Englische UI-Übersetzungen |
lang/en/mail.php | Englische Mail-Strings |