Table des matières
La gestion des traductions semble simple jusqu'à ce que vous tentiez de maintenir trois systèmes synchronisés : un dépôt Git où les développeurs écrivent du code, une base de données où travaillent les traducteurs, et un CDN où votre application récupère les traductions à l'exécution. Modifiez une clé dans un système, et les deux autres doivent en être informés — de manière fiable, rapidement, et sans perte de données.
C'est le problème que nous avons construit le sync engine pour résoudre. Dans cet article, nous allons parcourir l'architecture, les types de messages, le système de détection des conflits et les garanties de fiabilité qui font fonctionner l'ensemble.
Le problème des workflows de traduction synchrones
Au début du développement de Better i18n, les synchronisations de traductions étaient synchrones. Un développeur poussait du code, notre gestionnaire de webhook traitait les changements en ligne, mettait à jour la base de données, régénérait les fichiers CDN et retournait une réponse. Ça fonctionnait — jusqu'à ce que ce ne soit plus le cas.
Les modes d'échec étaient prévisibles :
- Délais d'expiration. Comparer un dépôt contenant 5 000 clés prend du temps. Les webhooks GitHub ont un délai d'expiration de 10 secondes. Les synchronisations échouaient silencieusement sur les grands projets.
- Mises à jour partielles. Si l'upload CDN échouait après la mise à jour de la base de données, les traductions se retrouvaient désynchronisées. Les utilisateurs voyaient du contenu périmé jusqu'à ce que quelqu'un déclenche manuellement une nouvelle synchronisation.
- Aucune visibilité. Quand une synchronisation échouait, il n'y avait aucune trace de ce qui s'était passé. Le débogage nécessitait de lire les journaux du serveur et de corréler les horodatages.
Nous avions besoin d'une architecture qui découple le déclencheur du travail, offre des relances automatiques et donne une visibilité totale sur chaque opération.
Entrez Cloudflare Queues
Nous avons choisi Cloudflare Queues comme colonne vertébrale du sync engine. Queues offre une livraison de messages durable et ordonnée avec une sémantique « au moins une fois » — exactement ce dont nous avions besoin.
L'architecture est simple :
GitHub Webhook → API Handler → Queue (enqueue message) → Worker (process message)
↓
Activity Log + Database + CDN
L'API handler fait le minimum : valider le webhook, mettre en file d'attente un message REPO_PUSH_SYNC, et retourner un 200. Le traitement réel se passe de façon asynchrone dans le consommateur de file d'attente — un Cloudflare Worker qui récupère les messages et les exécute.
Cette séparation apporte trois avantages immédiats :
- Les réponses webhook sont rapides. Plus de délais d'expiration, même pour les dépôts massifs.
- Les échecs sont relancés automatiquement. Si le worker plante ou qu'un appel API échoue, le message est redistribué avec un backoff exponentiel.
- Les opérations sont observables. Chaque message produit un journal d'activité structuré.
10 types de messages, un seul consommateur
Le sync engine traite 10 types de messages distincts, chacun avec son propre gestionnaire :
Opérations de synchronisation :
SYNC_START— Synchronisation GitHub complète ou incrémentale. Récupère les fichiers, compare les clés, met à jour la base de données et génère optionnellement une pull request avec les nouvelles traductions.REPO_PUSH_SYNC— Chemin optimisé pour les événements webhook de push. Traite uniquement les fichiers modifiés dans le push, rendant les synchronisations incrémentielles quasi-instantanées.
Opérations CDN :
CDN_SETUP— Crée le manifeste initial et les fichiers de langue vides lorsqu'un projet connecte son CDN.CDN_UPLOAD— Écrit un seul fichier de traduction JSON dans le stockage R2.CDN_MERGE— Fusionne de nouvelles traductions dans un fichier CDN existant. C'est essentiel pour les publications partielles — vous souhaitez ajouter de nouvelles traductions sans supprimer celles qui n'ont pas changé.CDN_CLEANUP— Supprime tous les fichiers R2 d'un projet. Utilisé lors de la suppression d'un projet ou quand un utilisateur veut repartir de zéro.
Opérations IA :
AI_CONTEXT_ANALYSIS— Utilise Firecrawl pour scraper le site web du projet, puis alimente le contenu à Gemini pour construire un modèle de contexte de traduction. Ce contexte aide la traduction automatique à comprendre la terminologie propre au secteur.REPO_ANALYSIS— Scanne le dépôt GitHub pour détecter le framework (React, Next.js, Flutter, etc.), extraire les traductions existantes et construire un glossaire terminologique.
Publication :
PUBLISH_BATCH— L'étape finale du workflow de traduction. Prend les traductions approuvées et les pousse vers le CDN (pour une disponibilité immédiate) et vers GitHub (pour le contrôle de version). Il s'agit d'une opération atomique — si l'une ou l'autre des écritures échoue, toute la publication est relancée.
Glossaire :
GLOSSARY_SYNC— Synchronise les glossaires terminologiques avec DeepL. Lorsque vous définissez que « workspace » doit toujours se traduire par « espace de travail » en français, ce message s'assure que le glossaire de DeepL est mis à jour afin que toutes les futures traductions automatiques soient cohérentes.
Chaque type de message est isolé. Un échec dans CDN_UPLOAD ne bloque pas SYNC_START. Un AI_CONTEXT_ANALYSIS lent ne retarde pas PUBLISH_BATCH. Cet isolement est la clé de la fiabilité du moteur.
Le système de jobs
Les messages sont de bas niveau. Les jobs sont les workflows de haut niveau avec lesquels les utilisateurs et le système interagissent. Le sync engine supporte 12 types de jobs :
| Type de job | Déclencheur | Messages produits |
|---|---|---|
| initial_import | Configuration du projet | SYNC_START, CDN_SETUP |
| incremental_sync | Webhook push | REPO_PUSH_SYNC, CDN_MERGE |
| full_sync | Déclenchement manuel | SYNC_START, CDN_UPLOAD (par langue) |
| source_sync | Changement langue source | SYNC_START |
| bulk_translate | Demande de traduction en lot | Plusieurs CDN_UPLOAD |
| publish | Publication langue unique | PUBLISH_BATCH, CDN_UPLOAD |
| batch_publish | Publication multi-langue | Plusieurs PUBLISH_BATCH |
| cdn_upload | Écriture CDN directe | CDN_UPLOAD |
| cdn_merge | Mise à jour CDN partielle | CDN_MERGE |
| cdn_setup | Initialisation CDN | CDN_SETUP |
| cdn_cleanup | Nettoyage du projet | CDN_CLEANUP |
| glossary_sync | Mise à jour du glossaire | GLOSSARY_SYNC |
Un seul job peut produire plusieurs messages. Par exemple, un job full_sync sur un projet avec 8 langues produit 1 message SYNC_START suivi de 8 messages CDN_UPLOAD — un pour chaque fichier de langue. Le job suit le statut agrégé de tous ses messages.
Plus de 45 actions d'activité : une observabilité structurée
Chaque gestionnaire de message enregistre des actions d'activité structurées au fur et à mesure de sa progression. Ce ne sont pas des lignes de log en texte libre — ce sont des événements typés et structurés qui alimentent à la fois l'expérience de débogage et l'interface utilisateur en temps réel.
Un flux SYNC_START typique produit cette trace d'activité :
SYNC_STARTED → FETCH_FILES (fetching translation files from GitHub) → FILES_FETCHED (12 files found) → COMPARE_KEYS (diffing against database) → KEYS_ADDED (47 new keys) → KEYS_REMOVED (3 deprecated keys) → KEYS_UPDATED (12 modified values) → UPDATE_DATABASE (persisting changes) → PR_GENERATION_STARTED (creating translation PR) → PR_CREATED (PR #142 opened) → SYNC_COMPLETED (duration: 3.2s)
Avec plus de 45 types d'actions distincts, vous obtenez une visibilité granulaire sur chaque opération. Quand quelque chose échoue, la dernière action enregistrée vous indique exactement où le pipeline s'est arrêté et quelles données avaient déjà été traitées.
Ces actions d'activité alimentent également l'interface de l'historique de synchronisation. Votre équipe peut voir chaque synchronisation qui a eu lieu, ce qu'elle a fait, combien de temps elle a pris et si elle a réussi — sans toucher aux journaux du serveur.
Détection et résolution des conflits
Les conflits sont le problème le plus difficile dans tout système de synchronisation. Deux personnes modifient la même clé de traduction — l'une dans la base de code, l'autre dans l'interface de traduction. Qui l'emporte ?
Notre réponse : personne ne gagne automatiquement. Le sync engine détecte les conflits et les remonte pour une résolution humaine.
Détection
Pendant COMPARE_KEYS, le moteur vérifie chaque clé entrante par rapport à la base de données. Si une clé a été modifiée à la fois dans le dépôt et dans la base de données depuis la dernière synchronisation réussie, elle est marquée comme conflit. Le moteur stocke les deux valeurs ainsi que leurs horodatages de modification.
Résolution
Les conflits apparaissent dans le tableau de bord avec le contexte complet :
- La valeur source (du dépôt)
- La valeur en base de données (de l'interface de traduction)
- La dernière valeur synchronisée (l'ancêtre commun)
- Les horodatages de chaque modification
Les utilisateurs peuvent résoudre les conflits un par un ou en masse, en choisissant de conserver la valeur source, la valeur en base de données, ou d'écrire une fusion manuelle. Chaque résolution est enregistrée comme une action d'activité.
Cette approche prévient le scénario de perte de données le plus courant dans les workflows de traduction : un push de code d'un développeur qui écrase silencieusement le travail soigneusement relu d'un traducteur.
Garanties de fiabilité
Le sync engine est conçu autour de quatre principes de fiabilité :
Livraison au moins une fois. Cloudflare Queues garantit que chaque message est livré au moins une fois. Les messages survivent aux redémarrages de workers, aux déploiements et aux pannes d'infrastructure.
Gestionnaires idempotents. Les messages pouvant être livrés plus d'une fois, chaque gestionnaire est idempotent. Retraiter un CDN_UPLOAD avec le même contenu produit le même résultat. Retraiter un SYNC_START compare par rapport à l'état actuel de la base de données, donc les synchronisations dupliquées sont effectivement des no-ops.
Traitement ordonné. Les messages pour un même projet sont traités dans l'ordre. Un CDN_MERGE s'exécute toujours après le SYNC_START qui l'a produit. Cela évite les conditions de course où un fichier CDN serait mis à jour avant que la base de données ne reflète les nouvelles clés.
Relances automatiques avec backoff. Les messages échoués sont relancés avec un backoff exponentiel. Les erreurs transitoires — limites de débit d'API, coupures réseau, indisponibilité temporaire de R2 — se résolvent d'elles-mêmes sans intervention humaine. Les erreurs permanentes (données invalides, permissions manquantes) sont journalisées et remontées dans le tableau de bord.
Ce que cela signifie pour votre équipe
Le sync engine fonctionne en arrière-plan. Vous connectez votre dépôt GitHub, et les synchronisations se font toutes seules. Poussez du code, et vos traductions sont mises à jour en quelques secondes. Approuvez des traductions, et elles sont publiées sur votre CDN et commitées dans votre dépôt de façon atomique.
Quand quelque chose se passe mal — et dans les systèmes distribués, quelque chose se passe toujours mal — le moteur réessaie, journalise et remonte le problème. Pas d'échecs silencieux. Pas d'état incohérent. Pas de traductions perdues.
C'est la promesse du traitement asynchrone bien fait : votre équipe se concentre sur les traductions, et l'infrastructure s'occupe du reste.