i18n
Jedes Feld ist standardmäßig mehrsprachig. Wie Locales URLs formen, zu welchem ein Request auflöst und was passiert, wenn eine Übersetzung fehlt.
In cmssy modellierst du keine Übersetzungen. Jedes Feld ist bereits mehrsprachig: Inhalte werden nach Sprachcode verschlüsselt gespeichert, eine Seite trägt also alle ihre Sprachen und eine Block-Instanz alle ihre.
Das beseitigt das übliche Duplikationsproblem - es gibt keine „englische Seite“ und „deutsche Seite“, die synchron bleiben müssen - und ersetzt es durch zwei kleinere Fragen. Zu welchem Locale löst ein Request auf, und was erscheint, wenn ein Feld noch nicht übersetzt ist?
Der Locale-Satz
Locales kommen aus deiner Site-Config, nicht aus deinem Code:
const { defaultLocale, locales } = await resolveSiteLocales();Eine Sprache hinzuzufügen ist eine Workspace-Einstellung. Deine App greift sie beim nächsten Request auf - ein neues Locale braucht also keinen Deploy, nur Übersetzungen.
URLs: das Standard-Locale hat kein Präfix
Das ist die Regel, aus der alles Weitere folgt:
localizedPath("/pricing", "de", "de"); // "/pricing" Standard: nackt
localizedPath("/pricing", "en", "de"); // "/en/pricing" andere: mit Präfix
localizedPath("/", "en", "de"); // "/en"Das Standard-Locale ohne Präfix zu lassen bedeutet, dass deine kanonischen URLs sich nicht ändern, wenn du eine zweite Sprache hinzufügst - bestehende Links, Shares und Rankings überleben.
Einen Request auflösen
Die Gegenrichtung liest das erste Pfadsegment und ist bewusst streng:
splitLocaleFromPath(["en", "pricing"], { defaultLocale: "de", locales: ["de", "en"] });
// -> { locale: "en", path: ["pricing"] }
splitLocaleFromPath(["pricing"], { defaultLocale: "de", locales: ["de", "en"] });
// -> { locale: "de", path: ["pricing"] }Ein erstes Segment zählt nur dann als Locale, wenn es in locales steht und nicht das Standard-Locale ist. /de/pricing ist also nicht die deutsche Preisseite - es ist eine Seite, deren Slug zufällig mit de beginnt. Eine kanonische URL pro Seite pro Sprache, keine Duplikate.
Die Catch-all-Route tut das, bevor sie einen Slug nachschlägt:
const { path: strippedPath } = splitLocaleFromPath(path, await resolveSiteLocales());
const slug = "/" + (strippedPath ?? []).join("/");Fehlende Übersetzungen
Ein Feld fällt zurück, statt zu verschwinden:
pickLocalized(value, locale, defaultLocale);
// value[locale] ?? value[defaultLocale] ?? erster vorhandener WertDrei Stufen: die angefragte Sprache, dann die Standardsprache, dann was auch immer existiert. Die letzte zählt - ein nur ins Französische übersetztes Feld rendert auf einer deutschen Seite trotzdem, statt ein Loch zu lassen.
Die Konsequenz für Block-Autoren: eine teilweise übersetzte Seite ist ein normaler Zustand, kein Fehler. Schreibe Komponenten, die sinnvoll rendern, wenn ein Feld in der falschen Sprache zurückkommt, und teste mit einem Locale, das du nicht übersetzt hast - darauf treffen deine Besucher in einer neuen Sprache zuerst.
Middleware
export const proxy = createCmssyProxy(cmssy, {
// Nur wenn deine URLs die Sprache tragen UND deine Routen statische Pfade
// sind statt einer Catch-all-Route.
stripLocalePrefix: true,
});Das SDK sagt es ausdrücklich: bei einer Catch-all-App, die die Sprache selbst aus dem Pfad liest, ausgeschaltet lassen. Schaltest du es trotzdem ein, bekommt die Route einen Pfad ohne Präfix - splitLocaleFromPath findet nichts zu entfernen und fällt auf das Standard-Locale zurück. Jede Sprache rendert dann als die Standardsprache.
Die Middleware löst die Sprache weiterhin auf und reicht sie in einem Header weiter, eine App kann sie also von dort lesen. Leitet deine Route das Locale aber aus dem Pfad ab, wie bei Catch-all üblich, sagen ihr diese beiden Quellen jetzt Unterschiedliches.
Die Sprache im HTML deklarieren
Setze <html lang> aus dem aufgelösten Locale. Das lesen Screenreader und Übersetzungstools, und es ist ein Vertrag - deshalb prüft der Editor-Smoke-Test dagegen, statt nach einem Wort in deinen Texten zu suchen. Texte sind Inhalt; eine Redaktion kann sie jederzeit ändern, und dann lügt der Test.
Nächste Schritte
- SEO - hreflang-Alternativen aus demselben Locale-Satz.
- Testen - die
<html lang>-Zusicherung. - Datenmodelle - Records sind ebenfalls mehrsprachig.