Topics

Mehrsprachigen Blog mit EmDash erstellt – Übersetzungs-API-Kosten mit SSR reduzieren

  • column

Unser Blog „Yuru Log" – wir haben kürzlich heimlich eine englische Version erstellt. Diesmal schreibe ich über die Hintergründe, insbesondere darüber, wie wir das Problem gelöst haben: „Führt Mehrsprachigkeit auf einer SSR-Website zu explodierenden API-Kosten?"

Voraussetzung: Wir nutzen das CMS EmDash

Dieser Blog läuft auf EmDash, einem Astro-basierten CMS. Artikel werden über die Admin-Oberfläche geschrieben, und das Frontend wird mit Astro server-side rendering (SSR) bereitgestellt.

Das intuitive Bedienungsgefühl ähnelt WordPress, und es wird gesagt, dass es als „geistiger Nachfolger von WordPress" gilt.

EmDash verfügt bereits über einen integrierten Mechanismus für „mehrsprachige Inhalte", mit dem Artikel, Seiten, Menüs und Taxonomien (Kategorien und Tags) als separate Datensätze pro Gebietsschema gespeichert werden können. Mit dem Feld translationOf können Sie einfach kennzeichnen, dass ein englischer Artikel eine Übersetzung eines japanischen Artikels ist – und der Artikel wird dann als separate Sprachversion desselben Inhalts behandelt.

Migration von microCMS zu Emdash + Cloudflare

Multilingualisierung ist mit SSG eigentlich ganz einfach, nicht wahr?

In der Regel ist es recht einfach, wenn man „die Übersetzung der Mehrsprachunterstützung über eine API automatisieren" möchte – zumindest bei SSG (Static Site Generation).

  • Die Übersetzungs-API wird während des Builds nur einmal aufgerufen
  • Ergebnisse als statische Dateien zwischenspeichern
  • Dann wird einfach das HTML bereitgestellt

Daher entstehen API-Kosten, egal wie viele Artikel hinzukommen, nur "bei jedem Build". Die Häufigkeit lässt sich leicht kontrollieren, und das Caching funktioniert reibungslos.

Aber Yuru Log ist SSR. Also wird die API bei jeder Anfrage aufgerufen?

Das ist das Problem. EmDash CMS-Seiten werden nach Spezifikation vollständig mit SSR ((output: "server")) gerendert. Es ist kein System, das Seiten durch statisches Bauen vorkonfiguriert, sondern ein Mechanismus, der bei jeder Anfrage die neuesten Inhalte von der CMS API und Datenbank abruft und darstellt.

Was würde also passieren, wenn wir einen einfachen Ansatz implementieren würden: "Bei der Anzeige einer englischen Seite die Übersetzung sofort von Gemini durchführen und ausgeben"?

  • Bei jeder Anfrage fallen Gebühren für die Übersetzungs-API an
  • Die Latenz nimmt zu (wir müssen auf die Antwort des LLM warten)
  • Möglicherweise liefert dieselbe Artikel jedes Mal leicht unterschiedliche Übersetzungsergebnisse zurück (keine Reproduzierbarkeit)

Das ist völlig inakzeptabel. Wenn man bei SSR den Ansatz "API bei jeder Anfrage aufrufen" wählt, scheitern sowohl die Kosten als auch die Benutzererfahrung.

Lösung: Übersetzung von „bei Anfrage" zu „bei der Inhaltserstellung" verschieben

Die Antwort ist einfach: Die Übersetzung nicht zum Zeitpunkt der Anfrage durchführen.

Konkret haben wir die Pipeline so gestaltet.

  1. Japanische Artikel in der EmDash-Verwaltungsoberfläche schreiben und veröffentlichen
  2. Das Übersetzungsskript (scripts/translate.mjs) nur einmal ausführen
    • Strukturierte Rich-Text-Daten (Portable Text) von japanischen Artikeln aus der EmDash-API abrufen
    • Extrahieren Sie nur den zu übersetzenden Textabschnitt und geben Sie ihn an die Gemini API ((gemini-3.6-flash)) weiter.
    • Blöcke wie Bilder und Amazon-Produktkarten werden unverändert durchgeleitet und nicht übersetzt
  3. Übersetzungsergebnis als neuen englischen Artikel in der EmDash-Datenbank speichern
    • Geben Sie translationOf die ID des ursprünglichen japanischen Artikels an, um ihn als englische Version derselben Artikelgruppe zu verlinken
    • Sie wird als Entwurf gespeichert, damit Sie sie überprüfen und veröffentlichen können.

Mit anderen Worten: Das Übersetzungsergebnis wird nicht „spontan generiert", sondern als „ein weiterer Sprachinhalt, der im CMS gespeichert ist" behandelt.

Auf diese Weise unterscheidet sich die SSR-Anfrageverarbeitung selbst nicht von der normalen Artikelanzeige. Mit EmDashs standardmäßiger Locale-Abfrage (getEmDashEntry(..., { locale: "en" })) werden einfach die bereits in der Datenbank gespeicherten englischen Inhalte abgerufen. Die Kommunikation mit der Gemini API erfolgt nur einmal beim Übersetzen des Artikels und tritt beim Abrufen überhaupt nicht auf.

Das heißt, die Konstellation ist so

Zeitpunkt der Übersetzungsausführung

API-Kosten pro Anfrage

SSG + On-Demand-Übersetzung

Zur Build-Zeit

Zero (statische Dateiauslieferung)

SSR + Übersetzung bei jedem Request (Schlechtes Beispiel)

Pro Anfrage

Artikel × Zugriffszahl 😱

SSR + Vorausübersetzung in der Datenbank gespeichert (dieser Ansatz)

Bei der Inhaltserstellung (einmalig)

Null

Der Gedanke von SSG, "alles beim Build zu cachen", lässt sich in die Welt von SSR übertragen, indem man "übersetzte Inhalte dauerhaft in der Datenbank speichert". Das war die Erkenntnis dieses Mal. Selbst bei SSR werden Inhalte nicht ständig dynamisch generiert – wenn man nur die "schwere Verarbeitung" der Übersetzung vorher erledigt, ist die Auslieferung genauso leicht wie bei einer normalen CMS-Seite.

Die Geschichte, wie sich die Datenbank zwischen lokal und Produktion unterschied und wir mehrmals "Moment, das ist weg!?" erlebten

Die Implementierung dieses Mal hat mich am meisten bei diesem Punkt gezehrt. EmDash hat jeweils separate Datenbanken für die lokale Entwicklungsumgebung und die Produktionsumgebung. Das ist zwar logisch, aber genau das führte dazu, dass wir mehrfach erlebt haben: "Die Funktion, die gerade noch da war, ist weg!"

Die Symptome sah meistens so aus:

  • Die Navigation im Header ist plötzlich leer
  • Footer-Kategorieliste verschwindet
  • Seitenleisten-Widget auf der Artikelseite verschwindet
  • Lokal erstellte Menüpunkte und Widgets sind wieder auf ihren ursprünglichen Zustand zurückgesetzt

Außerdem treten diese Probleme immer genau dann auf, wenn "es gerade noch funktioniert hat", weshalb ich anfangs verdächtigt habe, selbst etwas kaputt gemacht zu haben, und danach lange nach der Ursache gesucht habe.

Ursache 1: Prozess zum Importieren der Produktions-DB lokal (pull.sh)

Während der Entwicklung habe ich mehrmals ein Skript ausgeführt, das die Produktions-DB vollständig lokal importiert, um "die neuesten Produktionsdaten auch lokal sehen zu können". Dies führt dazu, dass die lokale DB vollständig mit dem Produktionszustand überschrieben wird (d. h. englische Artikel sind noch im Entwurfsstadium, Menüstrukturen vor der Veröffentlichung usw.).

Mit anderen Worten: Wenn ich versuche, "auf der Produktionsseite noch nicht veröffentlichte Funktionen" lokal zu überprüfen, befinden sich diese unmittelbar nach dem Pull wieder im Produktionszustand, wodurch die Arbeiten, die ich lokal voraus gemacht hatte, vorübergehend nicht sichtbar sind.

Ursache 2: "Seed"-Prozess, der jedes Mal beim Anmelden an der lokalen Admin-Oberfläche ausgeführt wird

Die andere Ursache war, dass der Mechanismus zum erneuten Anmelden an der lokalen Admin-Oberfläche (Bypass-Login für die Entwicklung) gleichzeitig die seed.json (Initialdaten-Definitionsdatei) jedes Mal neu einlas.

Das war problematisch, weil:

  • Menüpunkte und Widgets → werden so überschrieben, dass sie «vollständig übereinstimmen» mit der Seed-Seite (= lokal manuell hinzugefügte Einträge werden gelöscht)
  • Taxonomien wie Kategorien und Tags → «bereits existierende werden übersprungen», daher verschwinden sie nicht

Das war eine Spezifikation mit unterschiedlichem Verhalten je nach Eintrag. Dadurch entstand das auf den ersten Blick unerklärliche Phänomen, dass «Kategorien erhalten bleiben, aber Menüs und Widgets bei jedem Durchlauf verschwinden».

Getroffene Maßnahmen

Diese doppelte Struktur (Überschreiben durch Pull / Überschreiben durch Seed) grundsätzlich zu beseitigen war schwierig. Daher wählten wir als pragmatischen Kompromiss:

  • seed.json von allen Demo-Dummy-Inhalten (Menüpunkte, Widgets, Kategorien usw.) leeren
  • Bei jedem Eintritt in die lokale Umgebung erforderliche Menüs und Widgets über die API neu erstellen

Mit dieser Betriebsweise kamen wir zurecht. Es ist zwar keine Grundlösung, sondern nur eine Symptombekämpfung, aber zumindest konnten wir den Unfall verhindern, dass «unbeabsichtigte Demo-Daten überschrieben werden».

Fazit

  • EmDash ist ursprünglich ein CMS mit Mehrsprachenfähigkeit, das Inhalte pro Gebietsschema speichern kann
  • Bei SSG reicht «Übersetzung beim Build → Caching», aber bei SSR wird «jedes Mal übersetzen» zum Billing-Albtraum
  • Die Lösung besteht darin, die Übersetzung nur einmal bei der Inhaltserstellung und nicht bei jeder Anfrage durchzuführen und das Ergebnis als normalen CMS-Inhalt in der Datenbank zu speichern.
  • Die verwendete Übersetzungs-Engine war Gemini API (gemini-3.6-flash)
  • Allerdings sollte man bei der Mehrsprachigkeit eines CMS nicht vergessen, dass die Tatsache selbst, dass die Datenbank zwischen lokaler und Produktionsumgebung getrennt ist, neue Fallstricke schafft.
  • Besonders bei einer Entwicklung, bei der man "einen Zustand, der noch nicht in der Produktion vorhanden ist, lokal voraus erstellt", wird man leicht von Nebenwirkungen der Umgebungssynchronisierungsmechanismen (Pull, Seeding, Caching usw.) beeinflusst.

Die Lehre aus diesem Fall ist: Wenn man nur diese beiden Timing-Designs vorab versteht – "wann die Übersetzung stattfindet" und "wann und was die Umgebungssynchronisierung überschreibt" –, dann kann man auch bei SSR mehrsprachige Unterstützung ohne Betriebskosten und Zwischenfälle umsetzen.

Wie wäre es mit Mehrsprachigkeit auch auf Ihrer Website?

Dieser Artikel wurde geschrieben von

Ich konzentriere mich auf Markup und entwickle Frontends mit JavaScript, React und Next.js. Es freut mich immer, wenn die Websites, an denen ich mitgearbeitet habe, erfolgreich veröffentlicht werden! Mein Hobby ist Gitarrespielen. Ich mag Katzen und gebackene Süßkartoffeln 🐱🍠

Hiraicchi

Frontend-Engineer / Eintritt 2022

Artikel dieses Mitarbeiters ansehen

Zuverlässige Teamstruktur und schnelle Reaktionsfähigkeit sind unsere Stärken

Bei Liberogic werden erfahrene Mitarbeiter aktiv bei der Projektförderung eingesetzt, daher erhalten wir hohe Bewertungen von unseren Kunden.
Wir weisen Projektmanager und Direktoren ordnungsgemäß zu und bemühen uns, Projekte reibungslos zu leiten. Wir vermeiden unnötige Kostensteigerungen durch vollständige Bindung und verteilen Ressourcen optimal. Wir sind auch bekannt für die Schnelligkeit bei der Erfassung von Geschäftsinhalten bis zur Erstellung und Einreichung von Angeboten.

※ Bitte beachten Sie, dass wir keine SES-ähnliche Vor-Ort-Arbeit aktiv durchführen.

Sie können nahezu alle wichtigen Projektmanagement-Tools und Chat-Tools verwenden, wie Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom, Webex und mehr.

Konsultieren Sie uns gerne bei Ihren Web-Fragen.

Fallstudien