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 --> ResponseTrois 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
-
Internationalisation (i18n) - Wikipedia
-
Bloc Unicode - Wikipedia
-
Gettext - Documentation GNU
-
fcntl - Documentation Python
Bibliothèques et Outils
-
Flask-Babel - Documentation officielle
-
[DeepSeek