Engineering//8 Min. Lesezeit

Ein Blick in die Translation Sync Engine: Wie wir eine zuverlässige Async-Pipeline für Lokalisierung gebaut haben

Eray Gündoğmuş
Teilen

Übersetzungsmanagement klingt einfach – bis man versucht, drei Systeme synchron zu halten: ein Git-Repository, in dem Entwickler Code schreiben, eine Datenbank, in der Übersetzer arbeiten, und ein CDN, von dem die App zur Laufzeit Übersetzungen abruft. Ändert man einen Key in einem System, müssen die anderen beiden davon erfahren – zuverlässig, schnell und ohne Datenverlust.

Genau dieses Problem haben wir mit der Sync Engine gelöst. In diesem Beitrag erläutern wir die Architektur, die Message-Typen, das Konflikterkennungssystem und die Zuverlässigkeitsgarantien, die das alles zum Laufen bringen.


Das Problem mit synchronen Übersetzungs-Workflows

In der frühen Entwicklungsphase von Better i18n liefen Translation-Syncs synchron ab. Schob ein Entwickler Code, verarbeitete unser Webhook-Handler die Änderungen direkt, aktualisierte die Datenbank, regenerierte die CDN-Dateien und lieferte eine Antwort zurück. Das funktionierte – bis es nicht mehr funktionierte.

Die Fehlermuster waren vorhersehbar:

  • Timeouts. Ein Repository mit 5.000 Keys braucht Zeit zum Diffing. GitHub-Webhooks haben ein Timeout von 10 Sekunden. Bei großen Projekten schlugen Syncs lautlos fehl.
  • Teilaktualisierungen. Schlug der CDN-Upload nach einer Datenbankaktualisierung fehl, waren die Übersetzungen nicht mehr synchron. Nutzer sahen veraltete Inhalte, bis jemand manuell einen Re-Sync auslöste.
  • Keine Transparenz. Schlug ein Sync fehl, gab es keine Aufzeichnung darüber, was passiert war. Debugging erforderte das Lesen von Server-Logs und das Korrelieren von Zeitstempeln.

Wir brauchten eine Architektur, die den Trigger von der Arbeit entkoppelt, automatische Wiederholungsversuche ermöglicht und volle Transparenz über jede Operation bietet.


Cloudflare Queues als Lösung

Wir haben Cloudflare Queues als Backbone der Sync Engine gewählt. Queues bieten dauerhafte, geordnete Nachrichtenübermittlung mit At-least-once-Semantik – genau das, was wir brauchten.

Die Architektur ist unkompliziert:

GitHub Webhook → API Handler → Queue (Nachricht einreihen) → Worker (Nachricht verarbeiten)
                                                                   ↓
                                                      Activity Log + Database + CDN

Der API-Handler erledigt minimale Arbeit: Webhook validieren, eine REPO_PUSH_SYNC-Nachricht einreihen und 200 zurückgeben. Die eigentliche Verarbeitung erfolgt asynchron im Queue-Consumer – einem Cloudflare Worker, der Nachrichten abholt und ausführt.

Diese Trennung hat drei unmittelbare Vorteile:

  1. Webhook-Antworten sind schnell. Keine Timeouts mehr, selbst bei sehr großen Repositories.
  2. Fehler werden automatisch wiederholt. Stürzt der Worker ab oder schlägt ein API-Aufruf fehl, wird die Nachricht mit exponentiellem Backoff erneut zugestellt.
  3. Operationen sind beobachtbar. Jede Nachricht erzeugt ein strukturiertes Activity-Log.

10 Message-Typen, ein Consumer

Die Sync Engine verarbeitet 10 verschiedene Message-Typen, jeder mit eigenem Handler:

Sync-Operationen:

  • SYNC_START — Vollständiger oder inkrementeller GitHub-Sync. Ruft Dateien ab, vergleicht Keys, aktualisiert die Datenbank und erstellt optional einen Pull Request mit neuen Übersetzungen.
  • REPO_PUSH_SYNC — Optimierter Pfad für Push-Webhook-Events. Verarbeitet nur die im Push geänderten Dateien – inkrementelle Syncs werden dadurch nahezu sofortig.

CDN-Operationen:

  • CDN_SETUP — Erstellt das initiale Manifest und leere Sprachdateien, wenn ein Projekt sein CDN verbindet.
  • CDN_UPLOAD — Schreibt eine einzelne JSON-Übersetzungsdatei in den R2-Speicher.
  • CDN_MERGE — Fügt neue Übersetzungen in eine bestehende CDN-Datei ein. Das ist entscheidend für partielle Veröffentlichungen – neue Übersetzungen sollen hinzugefügt werden, ohne unveränderte zu entfernen.
  • CDN_CLEANUP — Löscht alle R2-Dateien eines Projekts. Wird bei der Projektlöschung oder wenn ein Nutzer neu beginnen möchte verwendet.

AI-Operationen:

  • AI_CONTEXT_ANALYSIS — Verwendet Firecrawl, um die Website des Projekts zu scrapen, und füttert den Inhalt dann an Gemini, um ein Übersetzungskontextmodell zu erstellen. Dieser Kontext hilft der maschinellen Übersetzung, branchenspezifische Terminologie zu verstehen.
  • REPO_ANALYSIS — Scannt das GitHub-Repository, um das Framework zu erkennen (React, Next.js, Flutter usw.), vorhandene Übersetzungen zu extrahieren und ein Terminologie-Glossar aufzubauen.

Veröffentlichung:

  • PUBLISH_BATCH — Der letzte Schritt im Übersetzungs-Workflow. Übernimmt genehmigte Übersetzungen und überträgt sie sowohl ans CDN (für sofortige Verfügbarkeit) als auch an GitHub (zur Versionskontrolle). Das ist eine atomare Operation – schlägt einer der beiden Schreibvorgänge fehl, wird die gesamte Veröffentlichung wiederholt.

Glossar:

  • GLOSSARY_SYNC — Synchronisiert Terminologie-Glossare mit DeepL. Wenn man festlegt, dass „workspace" im Französischen immer als „espace de travail" übersetzt werden soll, stellt diese Nachricht sicher, dass DeepLs Glossar aktualisiert wird, sodass alle zukünftigen maschinellen Übersetzungen konsistent sind.

Jeder Message-Typ ist isoliert. Ein Fehler in CDN_UPLOAD blockiert nicht SYNC_START. Ein langsames AI_CONTEXT_ANALYSIS verzögert nicht PUBLISH_BATCH. Diese Isolation ist der Schlüssel zur Zuverlässigkeit der Engine.


Das Job-System

Nachrichten sind Low-Level. Jobs sind die übergeordneten Workflows, mit denen Nutzer und das System interagieren. Die Sync Engine unterstützt 12 Job-Typen:

Job-TypAuslöserErzeugte Nachrichten
initial_importProjekt-SetupSYNC_START, CDN_SETUP
incremental_syncPush-WebhookREPO_PUSH_SYNC, CDN_MERGE
full_syncManueller AuslöserSYNC_START, CDN_UPLOAD (pro Sprache)
source_syncÄnderung der QuellspracheSYNC_START
bulk_translateStapelübersetzungsanfrageMehrere CDN_UPLOAD
publishEinzelsprachige VeröffentlichungPUBLISH_BATCH, CDN_UPLOAD
batch_publishMehrsprachige VeröffentlichungMehrere PUBLISH_BATCH
cdn_uploadDirekter CDN-SchreibvorgangCDN_UPLOAD
cdn_mergePartielle CDN-AktualisierungCDN_MERGE
cdn_setupCDN-InitialisierungCDN_SETUP
cdn_cleanupProjektbereinigungCDN_CLEANUP
glossary_syncGlossar-AktualisierungGLOSSARY_SYNC

Ein einzelner Job kann mehrere Nachrichten erzeugen. Zum Beispiel erzeugt ein full_sync-Job bei einem Projekt mit 8 Sprachen 1 SYNC_START-Nachricht gefolgt von 8 CDN_UPLOAD-Nachrichten – eine pro Sprachdatei. Der Job verfolgt den aggregierten Status über alle seine Nachrichten hinweg.


45+ Activity-Actions: Strukturierte Beobachtbarkeit

Jeder Message-Handler protokolliert strukturierte Activity-Actions, während er voranschreitet. Das sind keine Freitext-Logzeilen – es sind typisierte, strukturierte Events, die sowohl die Debugging-Erfahrung als auch die Echtzeit-Benutzeroberfläche antreiben.

Ein typischer SYNC_START-Ablauf erzeugt diese Activity-Spur:

SYNC_STARTED
  → FETCH_FILES (Übersetzungsdateien von GitHub abrufen)
  → FILES_FETCHED (12 Dateien gefunden)
  → COMPARE_KEYS (Abgleich mit Datenbank)
  → KEYS_ADDED (47 neue Keys)
  → KEYS_REMOVED (3 veraltete Keys)
  → KEYS_UPDATED (12 geänderte Werte)
  → UPDATE_DATABASE (Änderungen speichern)
  → PR_GENERATION_STARTED (Übersetzungs-PR erstellen)
  → PR_CREATED (PR #142 geöffnet)
  → SYNC_COMPLETED (Dauer: 3,2 s)

Mit über 45 verschiedenen Action-Typen erhält man granulare Transparenz über jede Operation. Wenn etwas fehlschlägt, zeigt die zuletzt aufgezeichnete Action genau, wo die Pipeline stoppte und welche Daten bereits verarbeitet wurden.

Diese Activity-Actions treiben auch die Sync-History-Benutzeroberfläche an. Das Team kann jeden jemals durchgeführten Sync sehen, was er getan hat, wie lange er gedauert hat und ob er erfolgreich war – ohne Server-Logs anzufassen.


Konflikterkennung und -auflösung

Konflikte sind das schwierigste Problem in jedem Sync-System. Zwei Personen bearbeiten denselben Übersetzungs-Key – eine im Codebase, eine in der Übersetzungs-Benutzeroberfläche. Wer gewinnt?

Unsere Antwort: Niemand gewinnt automatisch. Die Sync Engine erkennt Konflikte und bringt sie zur menschlichen Auflösung an die Oberfläche.

Erkennung

Während COMPARE_KEYS prüft die Engine jeden eingehenden Key gegen die Datenbank. Wurde ein Key sowohl im Repository als auch in der Datenbank seit dem letzten erfolgreichen Sync geändert, wird er als Konflikt markiert. Die Engine speichert beide Werte zusammen mit ihren Änderungszeitstempeln.

Auflösung

Konflikte erscheinen im Dashboard mit vollem Kontext:

  • Der Quellwert (aus dem Repository)
  • Der Datenbankwert (aus der Übersetzungs-Benutzeroberfläche)
  • Der zuletzt synchronisierte Wert (der gemeinsame Vorläufer)
  • Zeitstempel für jede Änderung

Nutzer können Konflikte einzeln oder in Massen auflösen und dabei wählen, ob sie den Quellwert behalten, den Datenbankwert behalten oder einen manuellen Merge schreiben möchten. Jede Auflösung wird als Activity-Action protokolliert.

Dieser Ansatz verhindert das häufigste Datenverlust-Szenario in Übersetzungs-Workflows: dass ein Code-Push eines Entwicklers die sorgfältig überprüfte Arbeit eines Übersetzers stillschweigend überschreibt.


Zuverlässigkeitsgarantien

Die Sync Engine ist um vier Zuverlässigkeitsprinzipien herum konzipiert:

At-least-once-Zustellung. Cloudflare Queues garantiert, dass jede Nachricht mindestens einmal zugestellt wird. Nachrichten überleben Worker-Neustarts, Deployments und Infrastrukturausfälle.

Idempotente Handler. Da Nachrichten mehr als einmal zugestellt werden können, ist jeder Handler idempotent. Ein erneutes Verarbeiten eines CDN_UPLOAD mit demselben Inhalt liefert dasselbe Ergebnis. Ein erneutes Verarbeiten eines SYNC_START vergleicht mit dem aktuellen Datenbankzustand, sodass doppelte Syncs effektiv No-ops sind.

Geordnete Verarbeitung. Nachrichten für dasselbe Projekt werden in der richtigen Reihenfolge verarbeitet. Ein CDN_MERGE läuft immer nach dem SYNC_START, der es erzeugt hat. Das verhindert Race Conditions, bei denen eine CDN-Datei aktualisiert wird, bevor die Datenbank die neuen Keys widerspiegelt.

Automatische Wiederholungsversuche mit Backoff. Fehlgeschlagene Nachrichten werden mit exponentiellem Backoff wiederholt. Transiente Fehler – API-Ratenlimits, Netzwerkaussetzer, vorübergehende R2-Nichtverfügbarkeit – lösen sich ohne menschliches Eingreifen von selbst. Permanente Fehler (ungültige Daten, fehlende Berechtigungen) werden protokolliert und im Dashboard angezeigt.


Was das für Ihr Team bedeutet

Die Sync Engine läuft im Hintergrund. Sie verbinden Ihr GitHub-Repository, und Syncs funktionieren einfach. Code pushen, und die Übersetzungen werden innerhalb von Sekunden aktualisiert. Übersetzungen genehmigen, und sie werden atomar in Ihr CDN veröffentlicht und in Ihr Repository committed.

Wenn etwas schiefläuft – und in verteilten Systemen geht immer etwas schief – wiederholt die Engine, protokolliert und bringt das Problem an die Oberfläche. Keine stillen Fehler. Kein inkonsistenter Zustand. Keine verlorenen Übersetzungen.

Das ist das Versprechen von richtig umgesetztem Async-Processing: Ihr Team konzentriert sich auf Übersetzungen, und die Infrastruktur erledigt den Rest.

Comments

Loading comments...