8.8 KiB
Internationalisierung (i18n) Dokumentation
Hördle unterstützt vollständige Mehrsprachigkeit (Internationalisierung) für Deutsch und Englisch.
Übersicht
Die i18n-Implementierung basiert auf next-intl und nutzt den Next.js App Router mit dynamischen [locale]-Segmenten.
Unterstützte Sprachen
- Deutsch (de) - Standardsprache
- Englisch (en)
URL-Struktur
Alle Routen sind lokalisiert:
http://localhost:3000/→ Redirect zu/de(Standard)http://localhost:3000/de→ Deutsche Versionhttp://localhost:3000/en→ Englische Versionhttp://localhost:3000/de/admin→ Admin-Dashboard (Deutsch)http://localhost:3000/de/Rock→ Rock Genre (Deutsch)http://localhost:3000/de/special/Weihnachtslieder→ Special (Deutsch)
Architektur
Verzeichnisstruktur
app/
[locale]/ # Lokalisierte Routen
layout.tsx # Root Layout mit i18n Provider
page.tsx # Homepage
admin/
page.tsx # Admin Dashboard
[genre]/
page.tsx # Genre-spezifische Seite
special/
[name]/
page.tsx # Special-Seite
i18n/
request.ts # next-intl Konfiguration
messages/
de.json # Deutsche Übersetzungen
en.json # Englische Übersetzungen
lib/
i18n.ts # Helper-Funktionen für lokalisierte DB-Werte
navigation.ts # Lokalisierte Navigation (Link, useRouter, etc.)
Übersetzungsdateien
Die Übersetzungen sind in JSON-Dateien unter messages/ organisiert:
{
"Common": {
"loading": "Laden...",
"error": "Ein Fehler ist aufgetreten"
},
"Game": {
"play": "Abspielen",
"pause": "Pause",
"won": "Gewonnen!"
},
"Home": {
"welcome": "Willkommen bei Hördle"
}
}
Datenbank-Schema
Die folgenden Modelle unterstützen mehrsprachige Felder:
Genre
name: JSON{ "de": "Rock", "en": "Rock" }subtitle: JSON{ "de": "Klassischer Rock", "en": "Classic Rock" }
Special
name: JSON{ "de": "Weihnachtslieder", "en": "Christmas Songs" }subtitle: JSON{ "de": "Festliche Musik", "en": "Festive Music" }
News
title: JSON{ "de": "Neues Feature", "en": "New Feature" }content: JSON{ "de": "Markdown Inhalt...", "en": "Markdown content..." }
Helper-Funktionen
getLocalizedValue(value, locale, fallback?)
Extrahiert den lokalisierten Wert aus einem JSON-Objekt:
import { getLocalizedValue } from '@/lib/i18n';
const genreName = getLocalizedValue(genre.name, 'de'); // "Rock"
const genreNameEn = getLocalizedValue(genre.name, 'en'); // "Rock"
Fallback-Verhalten:
- Versucht die angeforderte Locale (
deoderen) - Fallback zu
defalls nicht vorhanden - Fallback zu
enfalls nicht vorhanden - Fallback zum ersten verfügbaren Schlüssel
- Fallback zum übergebenen
fallback-Parameter
createLocalizedObject(de, en?)
Erstellt ein lokalisiertes Objekt:
import { createLocalizedObject } from '@/lib/i18n';
const name = createLocalizedObject('Rock', 'Rock');
// { de: "Rock", en: "Rock" }
Verwendung in Komponenten
Server Components
import { getTranslations } from 'next-intl/server';
import { getLocalizedValue } from '@/lib/i18n';
export default async function Page({ params }: { params: { locale: string } }) {
const { locale } = await params;
const t = await getTranslations('Home');
const genreName = getLocalizedValue(genre.name, locale);
return <h1>{t('welcome')}</h1>;
}
Client Components
'use client';
import { useTranslations } from 'next-intl';
import { useLocale } from 'next-intl';
export default function Game() {
const t = useTranslations('Game');
const locale = useLocale();
return <button>{t('play')}</button>;
}
Navigation
Verwende die lokalisierte Navigation aus lib/navigation.ts:
import { Link } from '@/lib/navigation';
// Automatisch lokalisiert
<Link href="/admin">Admin</Link>
<Link href="/Rock">Rock</Link>
Admin-Interface
Das Admin-Dashboard unterstützt mehrsprachige Eingaben:
- Sprach-Tabs: Wechsle zwischen
DEundENTabs - Genre/Special/News: Alle Felder können in beiden Sprachen bearbeitet werden
- Vorschau: Sieh dir die lokalisierte Version direkt an
Beispiel: Genre erstellen
- Öffne
/de/admin - Wähle den
DETab - Gib Name und Subtitle ein
- Wechsle zum
ENTab - Gib die englischen Übersetzungen ein
- Speichere
Migration bestehender Daten
Bestehende Daten werden automatisch migriert:
- Migration
20251128131405_add_i18n_columns: Fügt neue JSON-Spalten hinzu - Migration
20251128132806_switch_to_json_columns: Konvertiert String-Spalten zu JSON
Wichtig: Alte String-Werte werden automatisch in beide Sprachen kopiert:
"Rock"→{ "de": "Rock", "en": "Rock" }
Middleware
Die Middleware (middleware.ts) leitet Anfragen automatisch um:
/→/de(Standard)- Ungültige Locales → 404
- Validiert Locale-Parameter
Sprachumschalter
Die LanguageSwitcher-Komponente ermöglicht Nutzern, zwischen Sprachen zu wechseln:
import LanguageSwitcher from '@/components/LanguageSwitcher';
<LanguageSwitcher />
Die aktuelle Route bleibt erhalten, nur die Locale ändert sich:
/de/admin→/en/admin/de/Rock→/en/Rock
API-Endpunkte
API-Routen unterstützen einen optionalen locale-Parameter:
GET /api/genres?locale=de
GET /api/specials?locale=en
GET /api/news?locale=de
Falls kein locale angegeben wird, wird de als Standard verwendet.
Best Practices
1. Immer getLocalizedValue verwenden
❌ Falsch:
<span>{genre.name}</span> // Rendert { de: "...", en: "..." }
✅ Richtig:
<span>{getLocalizedValue(genre.name, locale)}</span>
2. Übersetzungsschlüssel konsistent benennen
Verwende Namespaces für bessere Organisation:
Common.*- Allgemeine UI-ElementeGame.*- Spiel-spezifische TexteHome.*- Homepage-TexteNavigation.*- Navigations-Elemente
3. Fallbacks definieren
Immer einen Fallback-Wert angeben:
const name = getLocalizedValue(genre.name, locale, 'Unbekannt');
4. Neue Übersetzungen hinzufügen
- Füge den Schlüssel zu
messages/de.jsonhinzu - Füge den Schlüssel zu
messages/en.jsonhinzu - Verwende
useTranslations('Namespace')odergetTranslations('Namespace')
Troubleshooting
404-Fehler auf /de oder /en
Problem: Route wird nicht gefunden.
Lösung:
- Überprüfe, ob
middleware.tskorrekt konfiguriert ist - Stelle sicher, dass
app/[locale]/layout.tsxexistiert - Prüfe die
i18n/request.tsKonfiguration
"Objects are not valid as a React child"
Problem: Ein JSON-Objekt wird direkt gerendert statt des lokalisierten Werts.
Lösung:
Verwende getLocalizedValue():
// ❌ Falsch
<span>{genre.name}</span>
// ✅ Richtig
<span>{getLocalizedValue(genre.name, locale)}</span>
Übersetzungen werden nicht angezeigt
Problem: Texte erscheinen als Schlüssel (z.B. "Game.play").
Lösung:
- Überprüfe, ob der Übersetzungsschlüssel in
messages/de.jsonundmessages/en.jsonexistiert - Stelle sicher, dass der Namespace korrekt ist:
useTranslations('Game')fürGame.play - Prüfe die JSON-Syntax auf Fehler
Admin-Interface zeigt Objekte statt Text
Problem: In Dropdowns oder Listen werden { de: "...", en: "..." } angezeigt.
Lösung:
Verwende getLocalizedValue() in allen Render-Funktionen:
// ❌ Falsch
<option value={s.id}>{s.name}</option>
// ✅ Richtig
<option value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
Erweiterung um weitere Sprachen
Um eine neue Sprache hinzuzufügen (z.B. Französisch):
-
Übersetzungsdatei erstellen:
cp messages/de.json messages/fr.json -
Übersetzungen hinzufügen: Bearbeite
messages/fr.jsonmit französischen Übersetzungen -
Locale zur Konfiguration hinzufügen:
i18n/request.ts:const locales = ['en', 'de', 'fr'];middleware.ts:locales: ['en', 'de', 'fr']lib/navigation.ts:export const locales = ['de', 'en', 'fr'] as const;
-
Layout aktualisieren:
// app/[locale]/layout.tsx if (!['en', 'de', 'fr'].includes(locale)) { notFound(); } -
LanguageSwitcher erweitern: Füge einen Button für
frhinzu -
Datenbank-Migration: Bestehende Daten behalten ihre Struktur, neue Einträge können optional
frenthalten