Tippe, um Seiten und Abschnitte zu finden.

Zum Inhalt
Ginko

Sprachen

Mehrsprachige Seiten mit ginko-i18n. Sprachen, Seitenpfade, Übersetzungstexte und Sprachwechsel.

Sprachen festlegen

Die Sprachen einer Seite stehen in locales in der ginko.config.ts. Jeder Eintrag ist ein Sprach-Code. Der Code ist das Präfix in der Adresse (/pt-BR/…) und der Name der Übersetzungsdatei (i18n/pt-BR.json). Jedes kanonische Sprach-Tag kann ein Code sein, zum Beispiel 'es', 'pt-BR' oder 'zh-Hant'. Die erste Sprache ist die Hauptsprache. Ihre Seiten haben kein Präfix, die anderen Sprachen bekommen eines:

ts
export default defineSite({
  site: 'restaurant',
  locales: ['de', 'en'],   // /speisekarte (de) und /en/menu (en)
  // …
})

Der Code ist auch das Sprach-Tag für lang, hreflang und Formate. Für ein regionales Tag ohne längere Adresse setzt du languages: Mit languages: { de: 'de-AT' } bleibt die Adresse /… und die Datei i18n/de.json, aber die Seiten sagen lang="de-AT".

Du richtest kein eigenes i18n-Modul ein; der Starter führt es auf, damit sein CLI verfügbar ist. Das Ginko-Modul richtet ginko-i18n (@lupinum/ginko-i18n) ein und gibt ihm die Sprachen aus der Konfiguration. ginko-i18n nennt die Hauptsprache defaultLocale. Du darfst @nuxtjs/i18n nicht zusätzlich installieren.

Wo jede Option steht

OptionDateiBedeutung
localesginko.config.tsDie Sprach-Codes. Der erste ist die Hauptsprache.
languagesginko.config.tsDas Sprach-Tag pro Code, zum Beispiel { de: 'de-AT' }.
urlginko.config.tsDie Basis für Canonical- und Hreflang-Links.
timeZoneginko.config.tsDie Zeitzone für Datum und Uhrzeit.
strategynuxt.config.ts, unter i18n'prefix_except_default' (Standard): Die Hauptsprache hat kein Präfix. 'prefix': Jede Sprache hat eines.
excludenuxt.config.ts, unter i18nPfade ohne Präfix, zum Beispiel ['/studio'].
userLocalenuxt.config.ts, unter i18nPfade, deren Sprache dem Benutzer folgt (siehe unten).
paramsnuxt.config.ts, unter i18nParameter, die jede Nachricht nutzen kann.
seonuxt.config.ts, unter i18nfalse schaltet die Head-Tags von ginko-i18n ab.

locales, defaultLocale, baseUrl und timeZone gibt Ginko selbst an ginko-i18n. Setzt du eine davon unter i18n in nuxt.config.ts, stoppt der Build mit einer Meldung.

ginko-i18n nimmt die Sprache aus der Adresse. Kein Cookie und kein Browser-Header ändert die Sprache einer Seite mit einer lokalisierten Adresse. Eine Ausnahme gibt es für Apps hinter einem Login: Die Pfade in userLocale folgen stattdessen dem Benutzer (Cookie ginko_locale, dann der Header Accept-Language), und /api/** immer. userLocale ist eine Option von ginko-i18n. Du setzt sie unter i18n in nuxt.config.ts; das Ginko-Modul reicht die übrigen i18n-Optionen weiter:

ts
// nuxt.config.ts
export default defineNuxtConfig({
  i18n: { userLocale: ['/app/**'] }, // diese Pfade haben kein Sprachpräfix
})

ginko-i18n setzt für jede Seite lang, dir, canonical, hreflang und og:locale. Fehlerseiten (404, 503) bekommen kein canonical und kein hreflang, dafür noindex.

Jede Seite hat genau eine Adresse, und sie endet nie auf / (außer / selbst). Andere Schreibweisen (//blog, /blog/, /Blog) leiten mit 301 dorthin um. Auf statischem Hosting (nuxt generate) gibt es keinen 301: Dort korrigiert der Browser die Adresse, und das canonical nennt die eine Adresse.

Die Texte der Inhalte sind pro Sprache gespeichert. Fehlt eine Übersetzung, zeigt das Bundle den Text der Hauptsprache. Siehe „Seiten lesen“, Abschnitt „Fallback“.

Seitenpfade pro Sprache

Eine Seitendatei legt den Pfad der Hauptsprache fest. Datei app/pages/speisekarte.vue ist /speisekarte. Für andere Sprachen setzt du mit definePageMeta einen eigenen Pfad:

vue
<!-- app/pages/speisekarte.vue -->
<script setup lang="ts">
definePageMeta({ i18n: { paths: { en: '/menu' } } })
</script>

<template><PageMenu /></template>

Die englische Seite hat den Pfad /en/menu. ginko-i18n setzt das Präfix /en selbst davor. Ohne Angabe heißt der englische Pfad /en/speisekarte.

Trage jeden festen Pfad auch in pages in der ginko.config.ts ein, mit dem Präfix:

ts
pages: {
  home: { de: '/', en: '/en' },
  menu: { de: '/speisekarte', en: '/en/menu' },
},

Ginko nutzt pages für die Sitemap und für das sofortige Neuladen von Seiten.

useI18n

useI18n() liefert die Werkzeuge für Texte und Sprachwechsel:

NameBedeutung
t(key, params)Gibt einen Text aus den Übersetzungsdateien zurück.
parts(key, params)Gibt denselben Text in Teilen zurück, damit du seine Tags selbst darstellst. <I18nT> (unten) erledigt das für dich.
has(key)Sagt, ob es den Schlüssel gibt.
localeDer Code der aktuellen Sprache, zum Beispiel 'en'.
languageDas Sprach-Tag der aktuellen Sprache, zum Beispiel 'en-GB'.
localesAlle Sprachen, jede mit code, language, name und dir.
defaultLocaleDer Code der Hauptsprache.
localePath(pfad, code?)Gibt den Pfad einer Seite in einer Sprache zurück. Ohne code gilt die aktuelle Sprache.
alternatesDie aktuelle Seite in jeder Sprache, für den Sprachwechsel.
switchLocale(code)Wechselt zur aktuellen Seite in einer anderen Sprache.
formatFormatiert Datum, Zahlen, Beträge und Listen in der aktuellen Sprache, zum Beispiel format.date('2026-10-08'). format.currency nimmt den Betrag in der Hauptwährungseinheit (12.5 für 12,50 €). Für ein f.money-Feld nutzt du formatMoney aus @lupinum/ginko.

locale und language sind Computed Refs: Im Script liest du locale.value.

localePath nimmt den Pfad der Hauptsprache: localePath('/speisekarte') ergibt in der Hauptsprache /speisekarte, in der englischen Version /en/menu. Nutze localePath nur für Seiten deiner Seite. Für Detailseiten von Einträgen nimmst du die Adressen aus dem Bundle (item.url, oder item.urls für jede Sprache).

Ein Sprachwechsel aus alternates:

vue
<script setup lang="ts">
const { alternates } = useI18n()
</script>

<template>
  <nav aria-label="Sprache">
    <NuxtLink
      v-for="a in alternates"
      :key="a.code"
      :to="a.path"
      :hreflang="a.language"
      :aria-current="a.current ? 'page' : undefined"
    >
      {{ a.code.toUpperCase() }}
    </NuxtLink>
  </nav>
</template>

Übersetzungsdateien

Feste Texte der Seite, zum Beispiel Navigation und Beschriftungen, stehen in i18n/<code>.json neben der nuxt.config.ts. Das sind keine Inhalte aus Ginko-Bausteinen. Du legst eine Datei pro Sprach-Code an:

i18n/de.json
{
  "nav": { "menu": "Speisekarte" },
  "weekOf": "Woche ab {date}"
}
i18n/en.json
{
  "nav": { "menu": "Menu" },
  "weekOf": "Week of {date}"
}
vue
<script setup lang="ts">
const { t } = useI18n()
</script>

<template>
  <h1>{{ t('nav.menu') }}</h1>
  <p>{{ t('weekOf', { date: '5. Oktober' }) }}</p>
</template>

Die Texte nutzen das ICU-Format. Damit schreibst du zum Beispiel Mehrzahl-Regeln: {count, plural, one {# Gericht} other {# Gerichte}}. Schlüssel sind Namen, und die Verschachtelung trennt sie: t('nav.menu') liest "menu" in "nav". Ein Schlüssel darf darum keinen Punkt enthalten. Fehlt ein Schlüssel in einer Sprache, zeigt die Seite den Text der Hauptsprache. Prüfe die Dateien mit npx ginko-i18n check. Der Starter führt @lupinum/ginko-i18n in package.json auf; in einer anderen Seite trägst du es zuerst in die Abhängigkeiten ein, damit der Befehl das lokale CLI findet.

Ein Text mit Link oder Hervorhebung schreibt Tags in die Nachricht, zum Beispiel "Ich akzeptiere die <link>Datenschutzerklärung</link>.". <I18nT> füllt jedes Tag über einen Slot mit dem gleichen Namen:

vue
<script setup lang="ts">
const { localePath } = useI18n()
</script>

<template>
  <I18nT k="legal.consent">
    <template #link="{ text }"><NuxtLink :to="localePath('/datenschutz')">{{ text }}</NuxtLink></template>
  </I18nT>
</template>

Alle Funktionen von ginko-i18n (Formate für Datum und Zahl, getI18n in Server-Routen, die Regeln für Links) stehen im README von ginko-i18n.

Detailseiten

Ein Eintrag kann in jeder Sprache eine andere Adresse haben. Das Bundle liefert diese Adressen in urls. Die Seitendatei hat nur einen Pfad pro Sprache mit Platzhalter :slug():

vue
<!-- app/pages/docs/[slug].vue -->
<script setup lang="ts">
definePageMeta({ i18n: { paths: { de: '/doku/:slug()', en: '/docs/:slug()' } } })
const route = useRoute()
const doc = useGinkoEntry('docs', () => route.params.slug)
</script>

Der Pfad muss zur route des Bausteins passen. Dort steht /doku/[slug] für Deutsch und /en/docs/[slug] für Englisch.

Du musst für Detailseiten keinen i18nAlternates-Resolver schreiben. Das Ginko-Modul meldet die Adressen eines Eintrags aus dem Bundle an ginko-i18n. Darum zeigt der Sprachwechsel auf die richtige Adresse in der anderen Sprache, und hreflang stimmt. Hat ein Eintrag in einer Sprache keine Seite, gilt diese Seite dort als nicht übersetzt.

Mit untranslated im Baustein steuerst du, was bei fehlender Übersetzung geschieht:

  • 'hide' (Standard bei Bausteinen mit route) lässt einen Eintrag in einer Sprache weg, solange dort ein übersetzbares Pflichtfeld fehlt: keine Seite (404), nicht in Listen, Sitemap und hreflang.
  • 'fallback' (Standard ohne route) zeigt den Eintrag mit dem Text der Hauptsprache. Hat der Baustein eine route, verlinkt er bis zur Übersetzung auf die Seite in der Hauptsprache. Veraltete Übersetzungen blenden nichts aus; das Studio markiert sie.

Ein fehlender Slug oder Bildtext zählt nicht als fehlende Übersetzung. Dort gilt der Wert der Hauptsprache.

Ein Link auf einen Eintrag ($<baustein>.<name>) in einem übersetzten Text führt auf die Seite in der Hauptsprache, solange der Eintrag in dieser Sprache keine eigene Seite hat.

Für Datei-Bausteine gilt: Eine vorhandene Übersetzung ist immer aktuell. Eine fehlende Datei löst den Fallback aus oder blendet den Eintrag aus, je nach untranslated.