Système de Traduction : Approche Hybride à Trois Technologies

Cet article explique notre système de traduction qui combine trois technologies—Flask-Babel pour les modèles, TranslationManager pour les tables de phrases, et DeepSeek AI pour la traduction de contenu—pour fournir un support multilingue complet.

Le Problème : Traduire un Site Web Complexe

Notre site web a plusieurs types de contenu qui nécessitent une traduction :

  • Modèles statiques : Navigation, boutons, étiquettes (500+ chaînes)

  • Contenu dynamique : Noms de produits, descriptions, caractéristiques (10 000+ chaînes)

  • Contenu généré par les utilisateurs : Avis, commentaires, tickets d'assistance

  • Termes techniques : Noms de marques, SKU, URL (ne doivent PAS être traduits)

Une approche de traduction unique ne fonctionne pas :

  • Traduction automatique : Rapide mais imprécise pour les termes techniques

  • Traduction manuelle : Précise mais lente et coûteuse

  • Modèles uniquement : Ne gère pas le contenu dynamique

Nous avons besoin d'une approche hybride qui combine le meilleur des trois.

Architecture de Traduction

graph TD
    Request[Requête Utilisateur
lang=hi] subgraph Templates Babel[Flask-Babel
fichiers .po] Static[Chaînes statiques
boutons, étiquettes] end subgraph Dynamic TM[TranslationManager
tables de phrases] Phrases[Noms de produits
caractéristiques, termes] end subgraph AI DS[API DeepSeek
traduction IA] Content[Descriptions
articles, texte long] end Request --> Babel Request --> TM Request --> DS Babel --> Static TM --> Phrases DS --> Content Static --> Response[Page Traduite] Phrases --> Response Content --> Response

Trois Technologies

1. Flask-Babel : Traductions de Modèles

Flask-Babel gère les chaînes statiques des modèles en utilisant les fichiers gettext .po.

  • Cas d'usage : Navigation, boutons, étiquettes, messages d'erreur

  • Exemple :

# Modèle
{{ _('Add to Cart') }}

# Fichier .po (Hindi)
msgid "Add to Cart"
msgstr "कार्ट में जोड़ें"

Avantages :

  • ✅ Approche i18n standard

  • ✅ Support des outils de traduction (Poedit, Weblate)

  • ✅ Validation à la compilation

  • ✅ Recherche rapide (fichiers .mo compilés)

Limitations :

  • ❌ Ne fonctionne que pour les chaînes statiques

  • ❌ Requiert des modifications du code pour ajouter de nouvelles chaînes

  • ❌ Pas de support pour le contenu dynamique

2. TranslationManager : Tables de Phrases

Notre classe personnalisée TranslationManager gère le contenu dynamique en utilisant des tables de phrases JSON.

  • Cas d'usage : Noms de produits, caractéristiques, spécifications, descriptions

  • Exemple :

{
  "Mini PC": "मिनी पीसी",
  "Intel N100": "Intel N100",
  "8GB RAM": "8GB RAM",
  "256GB SSD": "256GB SSD"
}

Avantages :

  • ✅ Support du contenu dynamique

  • ✅ Mises à jour à l'exécution (pas besoin de redémarrage)

  • ✅ Règles de préservation (noms de marques, SKU)

  • ✅ Formatage régional (séparateurs décimaux)

  • ✅ File d'attente de traduction (chaînes manquantes)

Limitations :

  • ❌ Gestion manuelle des phrases

  • ❌ Pas de contexte pour les traducteurs

  • ❌ Requiert une traduction initiale

3. DeepSeek AI : Traduction de Contenu

Le modèle IA DeepSeek gère la traduction de contenu long.

  • Cas d'usage : Articles de blog, descriptions de produits, textes marketing

  • Exemple :

prompt = f"Traduire en {lang} : {text}"
response = deepseek.generate(prompt)

Avantages :

  • ✅ Gère le contenu long (1000+ mots)

  • ✅ Traduction contextuelle

  • ✅ Sortie en langage naturel

  • ✅ Traitement par lots

Limitations :

  • ❌ Coût de l'API par traduction

  • ❌ Requiert une connexion internet

  • ❌ Peut traduire incorrectement les termes techniques

Architecture de TranslationManager

Structure des Tables de Phrases

Les tables de phrases sont stockées sous forme de fichiers JSON par langue :

app/shared/translation/phrase_tables/

Chaque fichier fait correspondre des phrases anglaises à des traductions :

{
  "Mini PC": "मिनी पीसी",
  "Thin Client": "थिन क्लाइंट",
  "Industrial PC": "औद्योगिक पीसी",
  "All-in-One": "ऑल-इन-वन"
}

Recherche de Traduction

La méthode get() effectue une recherche à plusieurs niveaux :

1. Vérification de Préservation :

if self._should_skip_translation(text, lang):
    return text  # Ne pas traduire les noms de marques, SKU, URL

2. Séparation par Virgules (pour les chaînes longues) :

if len(text) > 150 and "," in text:
    parts = text.split(",")
    return ", ".join(self.get(p, lang) for p in parts)

3. Recherche dans la Table de Phrases :

if text in self.phrase_tables[lang]:
    return self.phrase_tables[lang][text]

4. Promotion du Cache Hérité :

if text_hash in self.legacy_caches[lang]:
    translation = self.legacy_caches[lang][text_hash]
    self.set(text, lang, translation)  # Promouvoir vers la table de phrases
    return translation

5. Mise en File d'Attente si Manquant :

if queue_if_missing:
    self.add_to_queue(text, lang)
return None  # Aucune traduction trouvée

Règles de Préservation

Nous préservons des types de contenu spécifiques :

Noms de Marques :

BRAND_NAMES_PRESERVE = [
    "Thinvent", "Intel", "AMD", "Microsoft", "Ubuntu",
    "Windows", "Linux", "WiFi", "Bluetooth"
]

Termes Techniques :

TECHNICAL_TERMS_PRESERVE = [
    "HDMI", "DisplayPort", "VGA", "USB", "Ethernet",
    "DDR4", "SSD", "NVMe", "PCIe", "SATA"
]

SKU (basé sur des motifs) :

if not " " in text and text.count("-") >= 2:
    return True  # Préserver "Treo-N100-8-256-2H-W6-11P"

URL :

if text.startswith(("/", "http://", "https://")):
    return True

Formatage Régional

Pour les locales européennes (allemand, français, espagnol), nous appliquons un formatage régional :

Séparateur Décimal :

# Anglais : 34.00
# Allemand : 34,00
text = text.replace(".", ",")

Tension/Intensité :

# Anglais : 12.5V
# Allemand : 12,5V
text = re.sub(r"(\d+)\.(\d+)V", r"\1,\2V", text)

Cela garantit que les nombres s'affichent correctement pour chaque locale.

File d'Attente de Traduction

Les traductions manquantes sont mises en file d'attente pour un traitement par lots :

[
  {
    "text": "Compact Desktop",
    "lang": "hi",
    "hash": "a1b2c3d4e5f6"
  },
  {
    "text": "Fanless Design",
    "lang": "hi",
    "hash": "f6e5d4c3b2a1"
  }
]

Un script en arrière-plan traite la file d'attente en utilisant DeepSeek AI :

for item in queue:
    translation = deepseek.translate(item["text"], item["lang"])
    translation_manager.set(item["text"], item["lang"], translation)

Verrouillage de Fichiers

Nous utilisons le verrouillage de fichiers fcntl pour empêcher les écritures concurrentes :

with open(lock_path, "w") as lock_file:
    fcntl.flock(lock_file, fcntl.LOCK_EX)
    try:
        # Lire, modifier, écrire la table de phrases
        with open(table_path, "r+") as f:
            data = json.load(f)
            data.update(new_translations)
            f.seek(0)
            json.dump(data, f)
            f.truncate()
    finally:
        fcntl.flock(lock_file, fcntl.LOCK_UN)

Cela garantit que plusieurs processus ne corrompent pas les tables de phrases.

Détection de Langue

Nous détectons la langue de la requête en utilisant les plages Unicode et les mots vides :

Détection par Plage Unicode

Devanagari (Hindi, Marathi) :

if 0x0900 <= ord(char) <= 0x097F:
    return "hi"

Bengali :

if 0x0980 <= ord(char) <= 0x09FF:
    return "bn"

Arabe :

if 0x0600 <= ord(char) <= 0x06FF:
    return "ar"

Cyrillique (Russe) :

if 0x0400 <= ord(char) <= 0x04FF:
    return "ru"

CJK (Chinois, Japonais, Coréen) :

if 0x4E00 <= ord(char) <= 0x9FFF:  # Kanji/Hanzi
    has_cjk = True
if 0x3040 <= ord(char) <= 0x30FF:  # Hiragana/Katakana
    return "ja"
if 0xAC00 <= ord(char) <= 0xD7AF:  # Hangul
    return "ko"

Détection par Mots Vides

Pour les langues basées sur le latin, nous utilisons les mots vides :

Espagnol :

SPANISH_STOPWORDS = {
    "el", "la", "los", "las", "un", "una", "para", "con",
    "en", "por", "que", "es", "su", "y", "del", "al"
}

Français :

FRENCH_STOPWORDS = {
    "le", "la", "les", "des", "du", "un", "une", "pour",
    "avec", "en", "est", "sur", "et", "au", "dans"
}

Allemand :

GERMAN_STOPWORDS = {
    "der", "die", "das", "ein", "eine", "für", "mit",
    "ist", "und", "auf", "den", "dem", "bei", "von"
}

Si un mot vide correspond, nous retournons cette langue.

Détection Dynamique de Langue

Les utilisateurs peuvent spécifier la langue via :

1. Paramètre d'URL

/p/Treo-N100-8-256-2H-W6-11P?lang=hi

2. Cookie

response.set_cookie("lang", "hi", max_age=31536000)

3. En-tête Accept-Language du Navigateur

lang = request.accept_languages.best_match(["en", "hi", "es", "fr", "de"])

Priorité : Paramètre d'URL > Cookie > Accept-Language > Par défaut (en)

Traduction Récursive

Nous traduisons récursivement les structures de données imbriquées :

def translate_data(obj, lang):
    if isinstance(obj, dict):
        return {k: translate_data(v, lang) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [translate_data(item, lang) for item in obj]
    elif isinstance(obj, str):
        return self.get(obj, lang) or obj
    return obj

Exemple :

data = {
    "title": "Mini PC",
    "features": {
        "RAM": "8GB",
        "Storage": "256GB SSD"
    },
    "price": 25000
}

translated = translate_data(data, "hi")
# Résultat :
# {
#     "title": "मिनी पीसी",
#     "features": {
#         "RAM": "8GB",
#         "Storage": "256GB SSD"
#     },
#     "price": 25000
# }

Traduction de Caractéristiques

Les caractéristiques de produits nécessitent un traitement spécial :

def translate_features(features, lang):
    translated = {}
    for heading, feature_dict in features.items():
        # Traduire l'en-tête
        translated_heading = self.get(heading, lang) or heading

        # Traduire les noms et valeurs des caractéristiques
        translated_features = {}
        for name, value in feature_dict.items():
            translated_name = self.get(name, lang) or name
            translated_value = self.get(value, lang) or value
            translated_features[translated_name] = translated_value

        translated[translated_heading] = translated_features

    return translated

Exemple :

features = {
    "Processing": {
        "Processor": "Intel N100",
        "Cores": "4",
        "RAM": "8GB"
    }
}

translated = translate_features(features, "hi")
# Résultat :
# {
#     "प्रोसेसिंग": {
#         "प्रोसेसर": "Intel N100",
#         "कोर": "4",
#         "RAM": "8GB"
#     }
# }

Intégration avec le Pipeline SEO

Le système de traduction s'intègre avec le pipeline SEO :

Propagation de la Langue des Requêtes

Lorsqu'un utilisateur recherche en hindi, nous propageons la langue aux recherches associées :

if SEO_ENABLE_LANGUAGE_PROPAGATION:
    lang = detect_query_language(query)
    if lang != "en":
        url = f"{url}?lang={lang}"

Cela garantit que les utilisateurs restent dans leur langue préférée lorsqu'ils cliquent sur des recherches associées.

Pages de Requête Traduites

Les pages de requête sont générées en plusieurs langues :

/q/mini-pc          (Anglais)
/q/mini-pc?lang=hi  (Hindi)
/q/mini-pc?lang=es  (Espagnol)

Le contenu est traduit en utilisant TranslationManager.

Caractéristiques de Performance

Recherche dans la Table de Phrases :

  • Temps : O(1) recherche dans le dictionnaire (~1μs)

  • Mémoire : ~5 Mo par langue (10 000 phrases)

Détection de Langue :

  • Temps : O(n) où n = longueur de la chaîne (~10μs pour 100 caractères)

  • Mémoire : Négligeable

Traduction Récursive :

  • Temps : O(n) où n = nombre de chaînes (~1ms pour 100 chaînes)

  • Mémoire : Proportionnelle à la taille de la structure de données

Verrouillage de Fichiers :

  • Temps : ~1ms par acquisition de verrou

  • Contention : Rare (les écritures sont peu fréquentes)

Références

Concepts Techniques

Bibliothèques et Outils