Hintergrund und Anforderungen
Wir möchten mehrsprachige Unterstützung unter folgenden Einschränkungen implementieren:
- Next.js 15 App Router mit TypeScript
- Static Export (
output: 'export') – Deployment über S3 + CloudFront - Kein Server-Side Rendering – Dynamische APIs und Middleware können nicht verwendet werden
- SEO erforderlich – Suchmaschinen müssen geeignete Sprachinhalte bereitgestellt werden
Architekturübersicht
1. Routing-Design
/ # Root-Seite (Standard: en)
/en/ # Englische Seite
/ja/ # Japanische Seite
Verwendet Dynamic Segment [locale], um Seiten für jede Sprache während der statischen Generierung vor zu rendern.
2. Übersetzungsdaten-Management
Übersetzungsdaten werden im JSON-Format verwaltet:
{
"title": "title"
}
{
"title": "タイトル"
}
3. Zustandsverwaltung mit React Context API
interface I18nContextType {
messages: Record<string, any>;
locale: string;
}
const I18nContext = createContext<I18nContextType | undefined>(undefined);
export function I18nProvider({
children,
messages,
locale
}: I18nProviderProps) {
return (
<I18nContext.Provider value={{ messages, locale }}>
{children}
</I18nContext.Provider>
);
}
export function useTranslations() {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useTranslations must be used within I18nProvider');
}
const t = (key: string): string => {
// Verschachtelte Schlüssel unterstützen
const keys = key.split('.');
let value: any = context.messages;
for (const k of keys) {
value = value?.[k];
}
return typeof value === 'string' ? value : key;
};
return {
t,
locale: context.locale,
messages: context.messages,
isLoaded: true
};
}
4. Context-Bereitstellung in der Layout-Hierarchie
export default async function RootLayout({ children }: Props) {
const locale = defaultLocale;
const messages = await getMessages(locale);
return (
<html lang={locale}>
<body>
<I18nProvider messages={messages} locale={locale}>
<LanguageSwitcherClient />
{children}
</I18nProvider>
</body>
</html>
);
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
const messages = await getMessages(locale);
return (
<div lang={locale}>
<I18nProvider messages={messages} locale={locale}>
{children}
</I18nProvider>
</div>
);
}
Standardsprache und clientseitige Weiterleitung
Serverseitig (Während der statischen Generierung)
Die Root-Seite (/) wird immer in Englisch (en) generiert:
export default async function RootPage() {
const locale = defaultLocale; // 'en'
const messages = await getMessages(locale);
return (
<I18nProvider messages={messages} locale={locale}>
<ClientLocaleRedirect />
<PageContent />
</I18nProvider>
);
}
Begründung:
- SEO-Strategie: Stabile Inhalte für Suchmaschinen-Crawler bereitstellen
- Static-Export-Einschränkungen: Benutzersprache kann serverseitig nicht bestimmt werden
Clientseitig (Browser)
Beim ersten Zugriff wird nach Erkennung der Browser-Spracheinstellungen automatisch weitergeleitet:
'use client';
export default function ClientLocaleRedirect() {
useEffect(() => {
// Prüfen, ob Weiterleitung bereits durchgeführt wurde
if (typeof window === 'undefined' ||
sessionStorage.getItem('localeRedirectDone')) {
return;
}
const supportedLocales = ['en', 'ja'];
const pathname = window.location.pathname;
// Nur für Root-Pfad weiterleiten
if (pathname === '/') {
// Browser-Spracheinstellungen abrufen
const browserLocales = navigator.languages || [navigator.language];
// Unterstützte Sprache finden
const preferredLocale = browserLocales
.map(lang => lang.split('-')[0]) // 'ja-JP' -> 'ja'
.find(lang => supportedLocales.includes(lang));
// Weiterleiten, wenn sich von Standard unterscheidet
if (preferredLocale && preferredLocale !== defaultLocale) {
sessionStorage.setItem('localeRedirectDone', 'true');
window.location.replace(`/${preferredLocale}`);
}
}
}, []);
return null;
}
Verhaltensszenarien
| Situation | Während SSG | Client-Verhalten |
|---|---|---|
| Suchmaschinen-Crawler | / auf Englisch generieren |
Keine Weiterleitung |
| Japanischer Browser-Nutzer | / auf Englisch generieren |
Weiterleitung zu /ja |
| Englischer Browser-Nutzer | / auf Englisch generieren |
Keine Weiterleitung (wie angezeigt) |
| Nutzer mit nicht unterstützter Sprache | / auf Englisch generieren |
Keine Weiterleitung (auf Englisch anzeigen) |
Verwendung in Komponenten
'use client';
function NetworkInfoClient() {
const { t } = useTranslations();
return (
<div>
<h2>{t('title')}</h2>
</div>
);
}
Technische Vorteile
- Typsicherheit: TypeScript + Context API ermöglicht Prüfungen auf das Vorhandensein von Übersetzungsschlüsseln
- Klare Abhängigkeiten: Komponenten, die
useTranslationsverwenden, müssen innerhalb des Providers platziert werden, was das Design ausdrücklich macht - Performance: Schnelle Seitenauslieferung durch statische Generierung
- SEO: Jede Sprachseite hat eine unabhängige URL, optimiert für Suchmaschinen
- UX: Automatische Weiterleitung basierend auf Browser-Spracheinstellungen reduziert den Aufwand für Benutzer
Zusammenfassung
Wir haben mehrsprachige Unterstützung mit der React Context API in einer Next.js App Router Static-Export-Umgebung implementiert. Wir ergänzen serverseitige Einschränkungen durch clientseitige Logik und erreichen so bessere UX.
Das Ändern der Standardsprache erfordert nur das Bearbeiten von defaultLocale in config.ts und gewährleistet Erweiterbarkeit.
