Die Lingo.dev CLI übersetzt Markdoc-Dateien und JSON-Kataloge mit UI-Strings über eine konfigurierte Lokalisierungs-Engine. Markdoc ist ein Markdown-basiertes Authoring-Format mit typisierten, React-gestützten Custom Tags – ideal für Next.js App Router-Websites, die umfangreiche Inhalte mit interaktiven Komponenten verbinden.
Diese Anleitung zeigt Ihnen Schritt für Schritt, wie Sie eine Next.js App Router-Website vollständig lokalisieren: von der Konfiguration der CLI über die Organisation sprachspezifischer Inhalte und das Rendern von Markdoc in dynamischen Routen bis zur Automatisierung mit GitHub Actions.
Demo-Repository
Klonen oder forken Sie lingodotdev/markdoc-nextjs-localization-example, um direkt mitzumachen. Das Repository enthält eine funktionierende Next.js App Router-App mit Markdoc-Inhalten, einer Lingo.dev CLI-Konfiguration und einem GitHub Actions-Workflow.
So funktioniert die Lokalisierung mit Next.js + Markdoc#
Die meisten Next.js App Router-Websites unterteilen lokalisierte Inhalte in zwei Ebenen:
| Ebene | Inhalt | Beispieldatei |
|---|---|---|
| Umfangreiche Inhalte | Marketingseiten, Dokumentation, Blogbeiträge | src/content/[locale]/pages/home.md |
| UI-Strings | Navbar-Labels, CTAs, Button-Status | src/content/[locale]/ui.json |
Die Routen liegen unter src/app/[lang]/ und lesen zur Laufzeit die Dateien der passenden Sprache. Eine Middleware wählt anhand des Accept-Language-Headers des Browsers eine Standardsprache aus und leitet einfache Pfade wie / zu /en weiter – oder zur jeweils besten Übereinstimmung.
Der Bucket markdoc der CLI verarbeitet Markdoc-Dateien, ohne Frontmatter oder Custom Tags zu verändern, während der Bucket json den Katalog mit UI-Strings übernimmt. Beide übersetzen nur das Delta über Ihre Lokalisierungs-Engine und schreiben sprachspezifische Dateien direkt neben die Quelldateien.
Voraussetzungen#
Eine Lokalisierungs-Engine erstellen
Jeder CLI-Durchlauf schickt Inhalte durch eine Lokalisierungs-Engine – also die Konfiguration, die festlegt, welches LLM-Modell, Glossar, Markenstimme und welche Anweisungen gelten. Erstellen Sie sie im Lingo.dev dashboard und generieren Sie einen API-Schlüssel.
Node.js prüfen
Die CLI setzt Node.js 18 oder höher voraus:
node -vIhr Next.js-Projekt einrichten
Ihr Projekt benötigt den App Router (src/app/) und ein Inhaltsverzeichnis pro Sprache. Das Demo-Repository verwendet src/content/[locale]/ mit zwei Unterordnern (pages/ und blog/) sowie einer Datei ui.json. Die Grundlagen zum Routing finden Sie unter Next.js internationalization.
Inhalte organisieren#
Teilen Sie Inhalte nach ihrer Funktion auf. Umfangreiche Seiten und Beiträge werden in Markdoc verfasst, kurze UI-Strings liegen in JSON, damit Komponenten sie direkt laden können.
src/content/
en/ # Source locale
pages/home.md # Long-form Markdoc
blog/hello.md
ui.json # UI strings (navbar, CTAs, button states)
es/ # Target locales – generated by Lingo.dev
fr/
de/Markdoc-Dateien unterstützen Frontmatter für seitenbezogene Metadaten wie Titel, Beschreibung, Datum und Autor sowie Custom Tags, die als React-Komponenten gerendert werden. Eine minimale Seite sieht so aus:
---
title: Author once in Markdoc, ship in every language.
description: An example Next.js App Router app that localizes Markdoc with Lingo.
---
{% inline-callout type="info" %}
This page is authored in Markdoc and translated by Lingo.dev.
{% /inline-callout %}
## Built from three pieces
Markdoc custom tags render as React components – even interactive ones.Die CLI konfigurieren#
Erstellen Sie im Stammverzeichnis Ihres Projekts eine Datei i18n.json. Deklarieren Sie darin zwei Buckets – einen für Markdoc-Inhalte und einen für den UI-String-Katalog:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.15",
"locale": {
"source": "en",
"targets": ["es", "fr", "de"]
},
"buckets": {
"markdoc": {
"include": [
"src/content/[locale]/pages/*.md",
"src/content/[locale]/blog/*.md"
]
},
"json": {
"include": ["src/content/[locale]/ui.json"]
}
}
}Der Platzhalter [locale] wird jeweils zum konfigurierten Sprachcode aufgelöst. Mit source: "en" liest die CLI aus src/content/en/ und schreibt übersetzte Dateien nach src/content/es/, src/content/fr/ und src/content/de/.
Einzeldatei-Kataloge
Wenn Ihre UI-Strings in einer einzelnen mehrsprachigen JSON-Datei statt in separaten Dateien pro Sprache liegen, verwenden Sie den Bucket-Typ json-per-locale. Die vollständige Liste der Bucket-Typen finden Sie unter Static Content Localization.
Markdoc im App Router rendern#
Eine typische dynamische Route lädt ein Dokument und rendert den transformierten Baum. Das Demo-Repository stellt dafür einen kleinen Helper bereit:
// src/lib/markdoc.ts
export async function loadDoc(
locale: Locale,
collection: "pages" | "blog",
slug: string,
) {
const raw = await fs.readFile(
path.join(process.cwd(), "src/content", locale, collection, `${slug}.md`),
"utf8",
);
const ast = Markdoc.parse(raw);
const frontmatter = ast.attributes.frontmatter
? parseFrontmatter(ast.attributes.frontmatter)
: {};
const content = Markdoc.transform(ast, { ...schema, variables: { frontmatter } });
return { frontmatter, content };
}Die App Router-Seite ist ein schlanker Wrapper, der das Dokument mit sprachspezifischen UI-Strings kombiniert:
// src/app/[lang]/page.tsx
export default async function Home({ params }: PageProps<"/[lang]">) {
const { lang } = await params;
const doc = await loadDoc(lang, "pages", "home");
const { home } = await getMessages(lang);
return (
<main>
<h1>{doc.frontmatter.title}</h1>
{renderMarkdoc(doc.content)}
</main>
);
}Custom Tags in Markdoc (callout, bento, blog-hero usw.) werden in markdoc.schema.ts deklariert und mit React-Komponenten unter src/components/markdoc/ verknüpft. Die vollständige API finden Sie in den Markdoc schema docs.
Sprache in der Middleware erkennen#
Die Next.js-Middleware prüft die Anfrage, bevor eine Route gerendert wird. Verwenden Sie sie, um einfache Pfade anhand des Accept-Language-Headers zur am besten passenden Sprache weiterzuleiten:
// src/middleware.ts
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const hasLocale = locales.some(
(locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`),
);
if (hasLocale) return;
const locale = pickLocale(request); // parses Accept-Language
const url = request.nextUrl.clone();
url.pathname = `/${locale}${pathname === "/" ? "" : pathname}`;
return NextResponse.redirect(url);
}
export const config = {
matcher: ["/((?!_next|api|.*\\..*).*)", ],
};Besucher landen auf /en, /es, /fr oder /de, ohne das Präfix selbst eingeben zu müssen.
Lokal übersetzen#
Setzen Sie Ihren API-Schlüssel und führen Sie die CLI aus:
export LINGO_API_KEY="your-api-key"
npx lingo.dev@latest runDie CLI liest jede Datei, die zu Ihren Bucket-Mustern passt, identifiziert mithilfe der lockfile noch nicht übersetzte Einträge, übersetzt nur das Delta über Ihre Lokalisierungs-Engine und schreibt die Ergebnisse in das Verzeichnis der jeweiligen Zielsprache. Frontmatter-Schlüssel, Markdoc-Custom-Tags und JSON-Strukturen bleiben dabei unverändert – nur der übersetzbare Text ändert sich.
So legen Sie während der Entwicklung eine bestimmte Zielsprache fest:
npx lingo.dev@latest run --target-locale esMit GitHub Actions automatisieren#
Fügen Sie unter .github/workflows/translate.yml eine Workflow-Datei hinzu, um bei jedem Push zu übersetzen:
Übersetzungen werden direkt in main committet – ohne Reibungsverluste und ideal für kleine Teams:
name: Translate
on:
push:
branches: [main]
permissions:
contents: write
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lingo.dev
uses: lingodotdev/lingo.dev@main
with:
api-key: ${{ secrets.LINGODOTDEV_API_KEY }}Speichern Sie Ihren API-Schlüssel in Ihrem GitHub-Repository unter Settings > Secrets and variables > Actions als LINGODOTDEV_API_KEY.
Vor dem Deployment prüfen#
Verwenden Sie das Flag --frozen als Deployment-Gate, damit keine unübersetzten Inhalte in Produktion gehen. Die CLI endet mit einem Status ungleich null, wenn noch Einträge übersetzt werden müssen:
npx lingo.dev@latest run --frozenFügen Sie dies vor Ihrem Next.js-Build als separaten CI-Schritt hinzu:
- name: Verify translations
run: npx lingo.dev@latest run --frozen
- name: Build
run: pnpm build