Dieser Blog ist auf Italienisch entstanden. Als ich die sechs anderen Sprachen ergänzte – Englisch, Albanisch, Portugiesisch, Spanisch, Französisch und Deutsch –, war das Übersetzen der Texte der einfache Teil. Schwierig war alles andere: URLs, bereits geteilte Links, Suchmaschinen und eine maschinelle Übersetzung, die zu funktionieren schien, den Inhalt aber stillschweigend beschädigte.
Das Problem: eine italienische URL für sieben Sprachen
Anfangs hatte jeder Artikel nur einen Slug, den italienischen. Das Ergebnis waren Adressen wie /de/blog/perche-il-blog-non-viene-indicizzato: eine deutsche Seite mit einer italienischen URL. Unschön zum Teilen, unklar für Leser und verschenktes SEO-Potenzial, denn die Wörter in der URL zählen.
Das Datenmodell: ein Artikel, sieben Sprachen
Ich habe mich für ein einziges Dokument pro Artikel entschieden, mit Feldern pro Sprache: title_en, content_de, slug_fr und so weiter. Die Alternative wäre ein Dokument pro Übersetzung gewesen – dann hätte ich aber Tags, Aufrufe und Veröffentlichungsstatus über sieben Kopien synchron halten müssen. Mit einem Dokument bleibt der Artikel eine Einheit.
Übersetzte Slugs, die sich nie ändern
Jeder Slug pro Sprache wird genau einmal aus dem übersetzten Titel erzeugt und danach nie wieder angefasst – auch nicht, wenn ich den Titel korrigiere: Eine bereits geteilte URL muss weiter funktionieren. Außerdem muss jeder Slug über alle Felder aller Artikel hinweg eindeutig sein, damit eine Adresse immer zu genau einem Artikel führt, egal aus welcher Sprache sie stammt:
// Fill slug_xx from the translated title, only where it's missing.
// Existing slugs are never touched: they're in URLs people have shared.
for (const lang of ['en', 'sq', 'pt', 'es', 'fr', 'de']) {
if (post[`slug_${lang}`]) continue;
const title = post[`title_${lang}`];
if (title?.trim()) {
post[`slug_${lang}`] = await ensureUniqueSlug(title); // unique across ALL slug fields
}
}
Alte Links funktionieren weiter
Vor den übersetzten Slugs waren viele Links bereits in der Form /sq/blog/italienischer-slug geteilt worden. Sie kaputtgehen zu lassen hätte 404-Seiten und negative Signale für Google bedeutet. Deshalb erzeugt der Build jeden Artikel zusätzlich mit dem italienischen Slug unter jedem Sprachpräfix, wobei der Canonical auf die übersetzte Version zeigt: Wer über den alten Link kommt, sieht die richtige Seite, und Google weiß, welche Adresse die offizielle ist.
SEO für jede Sprache
- hreflang auf jeder Seite, das die sieben Versionen desselben Artikels miteinander verknüpft.
- Dynamisches og:locale, damit Social-Media-Vorschauen die richtige Sprache, den richtigen Titel und die richtige Beschreibung zeigen.
- Eine Sitemap mit allen Versionen, die bei jeder Veröffentlichung automatisch aktualisiert wird.
- Vorgerenderte Seiten pro Sprache: Ohne sie sah Google nur die Hülle der Anwendung, wie ich in diesem Artikel beschrieben habe.
Die maschinelle Übersetzung, die den Code zerstörte
Um zu übersetzen, ohne das HTML anzufassen, ersetzte ich Tags und Codeblöcke durch Platzhalter, schickte den Text an den Übersetzer und setzte danach alles wieder ein. Das klappte fast immer. Lagen jedoch mehrere Platzhalter nah beieinander – typisch für Artikel mit vielen Codeblöcken –, verdoppelte der Übersetzer manchmal einen und ließ einen anderen fallen. Das war kein Zufall: Bei einem erneuten Lauf trat exakt derselbe Fehler auf, vor allem im Albanischen.
Die Lösung: Platzhalter gar nicht mehr an den Übersetzer schicken. Ich teile den Text an den Platzhaltern auf und übersetze nur die Prosa-Stücke. Mehr Aufrufe, aber keine Chance mehr, dass ein Platzhalter verändert wird:
// Split on the placeholders: the translator never sees them
const parts = text.split(/(✦\d+✦)/);
const out = [];
for (const part of parts) {
out.push(/^✦\d+✦$/.test(part) ? part : await translate(part, lang));
}
return out.join('');
Seitdem vergleiche ich nach jeder Übersetzung die Struktur jeder Sprache mit der italienischen – gleiche Anzahl an Überschriften, Absätzen, Tabellen und Codeblöcken –, ohne mich auf das „keine Fehler“ des Skripts zu verlassen.
Fazit
Ein mehrsprachiger Blog ist kein Blog mit angeschraubtem Übersetzer: Es braucht ein klares Datenmodell, stabile URLs für jede Sprache, alte Links, die nicht brechen, und automatische Prüfungen der Übersetzung. Das sind dieselben Entscheidungen, die ich vorschlage, wenn ein Kunde eine Website in mehreren Sprachen möchte. Wenn das auf dich zutrifft, schreib mir.