Zurück zum Blog
A Contentful-style content entry card showing a filled English locale tab and an empty French locale tab with a warning icon.

Warum Ihr Contentful-Inhalt noch nicht wirklich mehrsprachig ist

By Robert

Warum Ihr Contentful-Inhalt noch nicht wirklich mehrsprachig ist

Contentful verwaltet Lokale gut. Sie können so viele definieren, wie Sie benötigen, eine Fallback-Kette einrichten, im Editor zwischen ihnen wechseln und ein Frontend erstellen, das pro Route die richtige Sprache ausliefert. Die Infrastruktur für mehrsprachige Inhalte ist wirklich gut.

Was Contentful nicht tut, ist Ihre Inhalte zu übersetzen.

Das klingt offensichtlich, wenn man es direkt sagt, aber es ist überraschend einfach, "wir haben Locale-Felder eingerichtet" mit "wir sind mehrsprachig" zu verwechseln. Die beiden sind nicht dasselbe. Locale-Felder sind ein Container. Mehrsprachig bedeutet, dass der Container Inhalte enthält.

Wenn Ihr Contentful-Space französische und deutsche Lokale konfiguriert hat, aber die Felder in diesen Lokalen leer sind, oder mit dem englischen Text als Platzhalter gefüllt sind, oder mit einem groben ersten Entwurf, den seit dem Start der Seite niemand überprüft hat — dann sind Sie nicht mehrsprachig. Sie haben das Gerüst für Mehrsprachigkeit.

Dieser Beitrag handelt davon, diese Lücke zu schließen.


Was Contentful Ihnen tatsächlich bietet

Das Locale-System von Contentful ist gut gestaltet. Jeder Inhaltseintrag kann Feldwerte pro Locale haben. Sie legen eine Standard-Locale fest, konfigurieren Fallbacks, und Ihre Delivery-API liefert die richtige Locale, wenn sie angefordert wird. Das Content-Modelling ist flexibel genug, um komplexe mehrsprachige Anforderungen über verschiedene Inhaltstypen hinweg zu bewältigen.

Das System ist jedoch völlig neutral, wie die übersetzten Inhalte dort hineingelangen. Contentful weiß nicht, ob Ihre französischen Locale-Felder professionelle Übersetzungen, maschinell übersetzten Text, versehentlich eingefügten englischen Text oder gar nichts enthalten. Es speichert und liefert einfach, was Sie eingeben.

Das Übersetzungsproblem liegt vollständig bei Ihnen.


Wie Teams es normalerweise handhaben (und wo es scheitert)

Die meisten Teams, die Contentful-Übersetzungen verwalten, fallen in eines von wenigen Mustern.

Das manuelle Exportmuster. Ein Entwickler exportiert Inhalte aus Contentful, sendet sie an eine Übersetzungsagentur oder einen Freelancer, wartet auf die Rückkehr, formatiert sie um und importiert sie. Das funktioniert für einen einmaligen Start, wird aber untragbar, wenn sich Inhalte ändern. Jede Aktualisierung der Quellsprache bedeutet, den gesamten Zyklus erneut durchlaufen zu müssen. In der Praxis hinken übersetzte Inhalte schnell der Quelle hinterher und niemand hat Zeit, sie auf den neuesten Stand zu bringen.

Das Übersetzungsmuster im Editor. Ein Redakteur öffnet jeden Eintrag, wechselt zur Zielsprache und übersetzt Feld für Feld entweder manuell oder indem er Inhalte in ein Übersetzungstool einfügt. Das ist genau, aber langsam. Es skaliert auch nicht – wenn Sie Hunderte von Einträgen über ein Dutzend Inhaltstypen haben, ist das manuelle Arbeitsvolumen erheblich.

Das "Es reicht so"-Muster. Die übersetzten Inhalte existieren, wurden aber seit ihrer ersten Erstellung nicht überprüft. Die Quellsprache wurde seitdem mehrfach aktualisiert. Die französische Version Ihrer Preisseite verweist immer noch auf einen Plan, den Sie vor acht Monaten eingestellt haben. Die deutsche Startseite hat immer noch den alten Slogan. Niemand hat es gemeldet, weil niemand nachschaut.

Alle drei Muster teilen dasselbe Grundproblem: Übersetzung wird als einmalige Aufgabe behandelt, statt als fortlaufender Teil des Content-Workflows.


Was mehrsprachig tatsächlich erfordert

Echter mehrsprachiger Inhalt in Contentful benötigt drei Dinge, die zusammenarbeiten.

Genau übersetzte Anfangsversion. Jedes Feld in jeder Ziel-Lokalisierung benötigt übersetzten Inhalt, der genau, angemessen lokalisiert ist und tatsächlich so gelesen wird, als wäre er für diesen Markt geschrieben worden, anstatt durch ein einfaches Übersetzungswerkzeug gelaufen und unüberprüft geblieben zu sein.

Ein Prozess, um Übersetzungen aktuell zu halten. Wenn sich der Quellinhalt ändert, müssen auch die übersetzten Inhalte geändert werden. Dies ist der Teil, den die meisten Teams unterschätzen. Ein Content-Team, das mehrere Updates pro Woche in einem Contentful-Bereich mit vier Lokalisierungen und fünfzig Inhaltstypen veröffentlicht, sieht sich einem erheblichen laufenden Übersetzungsaufwand gegenüber, wenn dies manuell gehandhabt wird.

Eine Möglichkeit zu wissen, wann Übersetzungen veraltet sind. Das Lokalisierungssystem von Contentful zeigt keine Veralterung an. Wenn Sie den englischen Text eines Eintrags aktualisieren und vergessen, die französische Version zu aktualisieren, wird die französische Version weiterhin stillschweigend den alten Inhalt ausliefern. Sie benötigen entweder einen Prozess oder Werkzeuge, um dies zu erkennen.


Wo PolyLingo passt

Die API von PolyLingo übersetzt strukturierte Inhalte und bewahrt dabei deren Struktur. Für Contentful ist das relevante Format JSON — die Feldwerte eines Contentful-Eintrags werden extrahiert und an die API gesendet, die übersetzt mit der gleichen Struktur zurückkommt.

Der grundlegende Ablauf in Node.js:

import PolyLingo from 'polylingo'
import { createClient } from 'contentful-management'

const poly = new PolyLingo({ apiKey: process.env.POLYLINGO_API_KEY })
const contentful = createClient({
  accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN,
})

const space = await contentful.getSpace(process.env.CONTENTFUL_SPACE_ID)
const environment = await space.getEnvironment('master')

// Hole den Eintrag, den du übersetzen möchtest
const entry = await environment.getEntry('your-entry-id')

// PolyLingo verwendet BCP-47-Codes ohne Region (fr, de).
// Ordne diese deinen Contentful-Locale-IDs zu, falls sie abweichen (z.B. fr-FR, de-DE).
const localeMap = { fr: 'fr', de: 'de' } // anpassen, um mit den Locale-IDs deines Spaces übereinzustimmen

// Extrahiere nur englische String-Felder — überspringe Referenzen, Links, Booleans und Zahlen
const sourceFields = Object.fromEntries(
  Object.entries(entry.fields)
    .filter(([, value]) => typeof value['en-US'] === 'string')
    .map(([key, value]) => [key, value['en-US']])
)

// Übersetze in einem Request ins Französische und Deutsche
const result = await poly.translate({
  content: JSON.stringify(sourceFields),
  format: 'json',
  targets: Object.keys(localeMap),
})

// Schreibe die übersetzten Werte zurück in den Eintrag mit Contentful-Locale-IDs
for (const [polyLocale, translated] of Object.entries(result.translations)) {
  const contentfulLocale = localeMap[polyLocale]
  const parsed = JSON.parse(translated)
  for (const [field, value] of Object.entries(parsed)) {
    if (!entry.fields[field]) entry.fields[field] = {}
    entry.fields[field][contentfulLocale] = value
  }
}

await entry.update()
await entry.publish()

console.log('Eintrag übersetzt und veröffentlicht.')

Ein API-Aufruf liefert sowohl Französisch als auch Deutsch. Der Eintrag wird im selben Skript aktualisiert und veröffentlicht.

Eine Anmerkung zu Locale-IDs: PolyLingo verwendet BCP-47-Codes ohne Regionsuffix (fr, de). Contentful-Spaces verwenden oft regionsqualifizierte IDs wie fr-FR oder de-DE. Das oben gezeigte localeMap-Objekt ist der Ort, an dem du sie anpasst — aktualisiere es, um mit den Locale-IDs deines Contentful-Spaces übereinzustimmen.

Das gleiche Muster funktioniert in Python:

import os, json, requests
from contentful_management import Client

poly_key = os.environ['POLYLINGO_API_KEY']
contentful = Client(os.environ['CONTENTFUL_MANAGEMENT_TOKEN'])

space = contentful.spaces().find(os.environ['CONTENTFUL_SPACE_ID'])
environment = space.environments().find('master')
entry = environment.entries().find('your-entry-id')

# Extrahiere englische Feldwerte
source_fields = {
    key: value.get('en-US')
    for key, value in entry.fields().items()
    if value.get('en-US')
}

# Übersetze ins Französische und Deutsche
r = requests.post(
    'https://api.usepolylingo.com/v1/translate',
    headers={'Authorization': f"Bearer {poly_key}"},
    json={
        'content': json.dumps(source_fields),
        'format': 'json',
        'targets': ['fr', 'de'],
    },
    timeout=120,
)
r.raise_for_status()
translations = r.json()['translations']

# Schreibe zurück in den Eintrag
for locale, translated in translations.items():
    parsed = json.loads(translated)
    for field, value in parsed.items():
        if field in entry.fields():
            entry.fields()[field][locale] = value

entry.save()
entry.publish()
print('Eintrag übersetzt und veröffentlicht.')

Übersetzen im großen Maßstab

Die obigen Beispiele behandeln einen einzelnen Eintrag. Für einen Contentful-Bereich mit vielen Einträgen über mehrere Inhaltstypen hinweg verarbeitet der Batch-Endpunkt bis zu 100 Elemente pro Anfrage:

// Mehrere Einträge abrufen und in einem Batch-Aufruf übersetzen
const entries = await environment.getEntries({
  content_type: 'blogPost',
  limit: 50,
})
 
// PolyLingo-Lokalcodes auf Contentful-Lokal-IDs abbilden
const localeMap = { fr: 'fr', de: 'de', es: 'es' } // an deinen Bereich anpassen
 
const items = entries.items.map(entry => ({
  id: entry.sys.id,
  content: JSON.stringify(
    Object.fromEntries(
      Object.entries(entry.fields)
        .filter(([, value]) => typeof value['en-US'] === 'string')
        .map(([k, v]) => [k, v['en-US']])
    )
  ),
  format: 'json',
}))
 
const batch = await poly.batch({
  items,
  targets: Object.keys(localeMap),
})
 
for (const result of batch.results) {
  const entry = entries.items.find(e => e.sys.id === result.id)
  for (const [polyLocale, translated] of Object.entries(result.translations)) {
    const contentfulLocale = localeMap[polyLocale]
    const parsed = JSON.parse(translated)
    for (const [field, value] of Object.entries(parsed)) {
      if (!entry.fields[field]) entry.fields[field] = {}
      entry.fields[field][contentfulLocale] = value
    }
  }
  await entry.update()
  await entry.publish()
}

Fünfzig Einträge, drei Sprachen, eine Batch-Anfrage.


Übersetzungen aktuell halten

Die obigen Skripte können bei Bedarf ausgeführt oder in Ihren Veröffentlichungs-Workflow eingebunden werden. Ein gängiges Muster ist es, die Übersetzung als Teil eines CI-Jobs auszuführen, der ausgelöst wird, wenn sich der Quellinhalt ändert, oder nach einem Zeitplan, der alle Einträge erfasst, die in den letzten 24 Stunden aktualisiert wurden.

Contentful unterstützt auch Webhooks — Sie können einen Webhook konfigurieren, der ausgelöst wird, wenn ein Eintrag in der Standardsprache veröffentlicht wird, was automatisch einen Übersetzungsauftrag auslösen kann. Wenn PolyLingo in diesen Webhook eingebunden wird, werden jedes Mal, wenn ein Redakteur aktualisierte englische Inhalte veröffentlicht, die übersetzten Sprachversionen ohne manuellen Schritt aktualisiert. Dies steht auf der Roadmap für eine dedizierte PolyLingo-Integration; in der Zwischenzeit bietet die API alles, was Sie benötigen, um diesen Ablauf selbst zu erstellen.


Eine Anmerkung zu Feldtypen

Der oben beschriebene Ansatz funktioniert gut für kurzen Text, langen Text und Rich-Text, der als Strings gespeichert ist. Einige Contentful-Feldtypen müssen jedoch separat behandelt werden:

Rich-Text-Felder, die im Contentful-Dokumentformat (nicht als einfache Strings) gespeichert sind, müssen vor dem Senden an PolyLingo in Klartext oder HTML serialisiert und danach wieder deserialisiert werden. Das Paket @contentful/rich-text-html-renderer übernimmt den Serialisierungsschritt, und format: "html" auf der PolyLingo-Seite bewahrt die Markup-Struktur korrekt.

Referenzfelder (Links zu anderen Einträgen oder Assets) sollten nicht übersetzt werden — sie sind IDs, keine Texte. Schließen Sie diese von den Feldern aus, die Sie an die API senden.

Zahlen-, Boolean- und Datumsfelder sind von Natur aus nicht übersetzbar. Filtern Sie diese heraus, bevor Sie Ihr Quellobjekt erstellen.


Erste Schritte

Der kostenlose Tarif von PolyLingo umfasst 50.000 Tokens pro Monat. Für einen Contentful-Space mit einer moderaten Menge an Inhalten deckt dies einen ersten Übersetzungslauf über mehrere Einträge in mehreren Sprachversionen ab und lässt noch Spielraum für laufende Aktualisierungen.

Die vollständige API-Dokumentation finden Sie unter usepolylingo.com/docs. SDK-Pakete sind für Node.js, Python, Ruby, PHP, Java und Go verfügbar.

npm install polylingo

Holen Sie sich Ihren API-Schlüssel

Warum Ihr Contentful-Inhalt noch nicht wirklich mehrsprachig ist | PolyLingo