Die Nitro CMS Integration bildet mehrsprachige Websites vollständig über die API ab. Eine Integration besitzt eine Hauptsprache (Primary Language) und beliebig viele weitere Sprachen. Inhalte werden pro Sprache angefragt: Dieselbe Seite oder Entität wird in genau der Sprache ausgeliefert, die die Anfrage verlangt.
Es gibt keinen separaten Übersetzungs-Endpunkt — jeder Inhalts-Endpunkt ist sprachbewusst. Die Sprachen werden in der Integrationskonfiguration definiert, wobei eine Sprache als Hauptsprache festgelegt wird.
Grundprinzip in einem Satz
Seiten tragen die Sprache als Präfix im Slug (global eindeutig, kein lang nötig), Entitäten haben pro Sprache einen eigenen, lokalisierten Slug (nicht prefixiert, lang ist erforderlich), und beide liefern über translation[] die vollständig aufgelösten Sprach-Alternativen.
Der lang-Query-Parameter
Jeder Nitro-Endpunkt akzeptiert den optionalen Query-Parameter lang mit dem Sprachkürzel (z.B. de, en, fr):
GET /nitro/v1/config?token=…&lang=en
GET /nitro/v1/pages?slug=de/erleben&token=…
GET /nitro/v1/entities/slug/public-viewings?token=…&lang=en- Es handelt sich um einen Query-Parameter, nicht um einen HTTP-Header — es gibt keine Auswertung von
Accept-Language. - Wird
langweggelassen, greift automatisch die Hauptsprache.
Hauptsprache und die Liste der verfügbaren Sprachen
Der Config-Endpunkt beschreibt den Sprachkontext im nitro-Objekt:
"nitro": {
"primary_language": "de",
"language": "de"
}primary_language→ die Hauptsprache der Integration, die verwendet wird, wenn keinlangangegeben ist.language→ die Sprache, in der diese konkrete Antwort aufgelöst wurde.
Die API liefert keine vollständige Sprachliste
Die API gibt nicht die Liste aller verfügbaren Sprachen zurück. Eine Integration, die alle Sprachen kennen muss (z.B. für Locale-Erkennung oder einen Sprachumschalter), muss diese Sprachen selbst konfigurieren (z.B. als locales-Array in der Integrationskonfiguration). Das ist so gewollt.
Seiten — adressiert über einen eindeutigen, vollständigen Slug
Seiten-Slugs sind mit dem Sprachkürzel prefixiert und global eindeutig. Das Array config.pages[] ist die Routing-Tabelle aller gültigen Seiten-Slugs und ist für jede Sprache identisch — es wird nicht nach Sprache gefiltert:
// GET /config und GET /config?lang=en liefern dasselbe pages[]:
"pages": [
"", // Home (Hauptsprache)
"de/erleben",
"de/erleben/kultur",
"de/business",
// …
"en", // Home (Englisch)
"en/experience",
"en/experience/culture",
"en/business"
// …
]- Die Homepage der Hauptsprache ist am Root (
""bzw./) erreichbar. - Für weitere Sprachen ist die Homepage unter dem Sprachkürzel erreichbar (z.B.
en→/en).
Weil das Präfix jeden Slug eindeutig macht, wird eine Seite allein über ihren Slug abgerufen — der lang-Parameter ist zur Unterscheidung nicht nötig:
GET /nitro/v1/pages?slug=de/erleben → die deutsche Seite "Erleben"
GET /nitro/v1/pages?slug=en/experience → die englische Seite "Experience"Die Seiten-Antwort enthält ihre eigene Sprache und ihre Alternativen:
{
"slug": "de/erleben",
"href": "/de/erleben",
"language": "de",
"translation": [ /* siehe Abschnitt "translation[]" */ ]
}Konsequenz fürs Frontend
Eine einzige Catch-all-Route (/:path*) kann jede Seite in jeder Sprache ausliefern, weil der eingehende URL-Pfad bereits der eindeutige Slug ist. Details dazu im Routing-Kapitel.
Was lang bei Seiten tatsächlich lokalisiert
Da bei Seiten die Sprache im Slug steckt, stellt sich die Frage: Was ändert lang dann? Die sprachabhängigen Teile der Config-Antwort:
containers→ Navigationen (Hauptnavigation, Footer usw.). Labels und aufgelöstehrefs werden in der angefragten Sprache zurückgegeben.globals→ globale Inhaltsblöcke (Popups, geteilte Inhalte usw.).
// GET /config → Nav-Item: { "label": "Erleben", "href": "/de/erleben" }
// GET /config?lang=en → Nav-Item: { "label": "Experience", "href": "/en/experience" }Eine lokalisierte Website lädt die Config also einmal pro aktiver Sprache, um Navigation und Globals korrekt darzustellen.
Entitäten — lokalisierte Slugs, lang erforderlich
Entitäten verhalten sich anders als Seiten. Der Slug einer Entität ist pro Sprache unterschiedlich (lokalisiert) und trägt kein Sprachpräfix. Dieselbe Story liegt in Deutsch z.B. unter oeffentliche-uebertragungen, in Englisch unter public-viewings. Weil der Slug allein die Sprache nicht verrät, ist der lang-Parameter erforderlich und muss zur Sprache des Slugs passen — ohne ihn wird die Hauptsprache angenommen:
GET /entities/slug/oeffentliche-uebertragungen → deutscher Inhalt (Hauptsprache)
GET /entities/slug/public-viewings?lang=en → englischer InhaltDie Entitäts-Antwort trägt die aufgelöste Sprache sowie ihre kanonischen, lokalisierten URLs:
{
"language": "en",
"primary_language": "de",
"entity": {
"entity_slug": "public-viewings",
"language": "en",
"href": "/en/story/public-viewings",
"routes": { "detail": "/en/story/public-viewings" }
},
"translation": [ /* siehe Abschnitt "translation[]" */ ]
}Konsequenz fürs Frontend
Entitäts-Detailrouten müssen die aktive Sprache kennen und als lang mitgeben. In einem URL-präfixierten Setup bildet sich das natürlich auf eine Locale-Segment-Route wie /:lang/story/:slug ab — das Locale kommt direkt aus dem Pfad und wird an die Entitäts-Abfrage weitergereicht. Mehr zum Entities-Endpunkt.
Seiten vs. Entitäten im Überblick
translation[] — die Sprach-Alternativen
Sowohl Seiten- als auch Entitäts-Antworten enthalten ein translation-Array, das denselben Inhalt in jeder verfügbaren Sprache beschreibt. Jeder Eintrag ist vollständig aufgelöst:
"translation": [
{
"language": { "shortcode": "de", "name": "Deutsch" },
"slug": "oeffentliche-uebertragungen",
"title": "Die besten öffentlichen Übertragungen …",
"href": "/de/story/oeffentliche-uebertragungen"
},
{
"language": { "shortcode": "en", "name": "Englisch" },
"slug": "public-viewings",
"title": "The best public viewing spots …",
"href": "/en/story/public-viewings"
}
]language.shortcode/language.name→ das Sprachkürzel und der Name der Alternative.href→ die vollständig aufgelöste, lokalisierte URL (direkt verlinkbar).title→ der lokalisierte Titel (nützlich als Label/Tooltip im Sprachumschalter).
translation[] ist die einzige verlässliche Quelle für einen Sprachumschalter und für hreflang-Tags.
Nur vorhandene Übersetzungen
translation[] listet nur die Sprachen, für die tatsächlich eine Übersetzung existiert. Eine Seite oder Entität, die in eine bestimmte Sprache nicht übersetzt ist, hat schlicht keinen Eintrag dafür — so kann das Frontend gezielt einen Fallback rendern.
Feldname
In früheren Versionen dieser Dokumentation war dieses Array als linked_translations beschrieben. Der tatsächliche Feldname in der API-Antwort ist translation — siehe Pages-Endpunkt und Entities-Endpunkt.
SEO
meta_json(Seiten) → Titel/Beschreibung/Bild der Seite in ihrer Sprache.jsonld(Entitäten) → ein schema.org-Objekt für strukturierte Daten.translation[]→hreflang: Gib für jeden Eintrag ein<link rel="alternate" hreflang="<shortcode>" href="<href>">aus und setze zusätzlich das Canonical für die aktuelle Sprache.
<!-- Beispiel für die englische Detailseite von "public-viewings" -->
<link rel="canonical" href="https://example.com/en/story/public-viewings" />
<link rel="alternate" hreflang="de" href="https://example.com/de/story/public-viewings" />
<link rel="alternate" hreflang="en" href="https://example.com/en/story/public-viewings" />Empfohlenes Integrationsmuster im Frontend
Das folgende Muster ist framework-agnostisch und entspricht der Funktionsweise der Laravel- und Next.js-Adapter:
- Sprachen konfigurieren — die verfügbaren Sprachen und die Haupt-/Standardsprache festlegen (die API liefert die Liste nicht, siehe oben).
- Aktive Sprache erkennen — aus dem ersten URL-Pfadsegment, wenn es einer konfigurierten Sprache entspricht, sonst Fallback auf die Hauptsprache. (In Laravel z.B.
request()->segment(1); in Next.js setzt eine Middleware einen Request-Header.) - Config mit diesem
langladen, um Navigation und Globals in der richtigen Sprache zu rendern. - Seiten über ihren vollständigen (prefixierten) Slug auflösen — kein
langnötig. - Entitäten mit der aktiven
langauflösen — ihr Slug ist pro Sprache lokalisiert und wird zusammen mitlangübergeben. - Sprachumschalter aus
translation[]aufbauen (vorhandene Einträge rendern, für fehlende einen Fallback). hreflangaustranslation[]ableiten.
Endpunkt-Referenz — Verhalten von lang
| Endpunkt | lang | Verhalten |
|---|---|---|
GET /config | optional | pages[] ist sprachunabhängig; containers + globals werden lokalisiert. |
GET /pages (per Slug) | optional | Seite wird über ihren eindeutigen, prefixierten Slug gewählt; lang ist zur Unterscheidung nicht nötig. |
GET /pages/home | optional | Homepage der angefragten Sprache. |
GET /entities/slug/{slug} | optional | In der Praxis erforderlich — der Slug ist pro Sprache lokalisiert und muss zur lang-Sprache passen; ohne lang greift die Hauptsprache. |
GET /entities/uniqueid/{uniqueid} | optional | Wie oben. |
GET /sitemap | optional | Liefert alle Sprachvarianten unabhängig von lang (vollständige SEO-Abdeckung). |
GET /search | optional | Ergebnisse werden auf die angefragte Sprache gefiltert. |
Praxisbeispiele
Config — identisches pages[], lokalisierte Navigation
GET /config → nitro.language = "de", Nav "Erleben" → /de/erleben
GET /config?lang=en → nitro.language = "en", Nav "Experience" → /en/experiencepages[] ist in beiden Antworten byte-identisch (alle de/… und en/… Slugs plus "" und en für die beiden Homepages).
Seite — aufgelöst über den Slug
GET /pages?slug=de/erleben
→ { slug: "de/erleben", language: "de",
translation: [ de → /de/erleben, en → /en/experience ] }Entität — lokalisierter Slug + lang
GET /entities/slug/oeffentliche-uebertragungen
→ entity.language = "de", entity.href = "/de/story/oeffentliche-uebertragungen"
GET /entities/slug/public-viewings?lang=en
→ entity.language = "en", entity.href = "/en/story/public-viewings"Beide Antworten tragen dasselbe translation[] (de ↔ en) mit vollständig aufgelösten hrefs — ein Sprachumschalter auf der deutschen Seite verlinkt direkt auf die englische und umgekehrt.

