""" translator.py — Traduction multi-moteurs avec fallback automatique Utilise deep-translator uniquement (pas de conflit avec gradio/httpx) Ordre de fallback : 1. deep-translator GoogleTranslator (Google Translate, gratuit) 2. deep-translator MyMemoryTranslator (5000 mots/jour gratuit) 3. deep-translator LibreTranslator (open source) 4. Texte français original conservé (jamais de crash) Codes de langue supportés : fr, en, sw, rn (Rundi/Kirundi) """ import time # ── Mapping codes internes → codes deep-translator ─────────────────────────── # deep-translator GoogleTranslator accepte les noms complets ou codes ISO DEEP_GOOGLE_CODES = { "fr": "fr", "en": "en", "sw": "sw", "rn": "rn", # Rundi — supporté par Google Translate } # MyMemoryTranslator utilise le format "lang-REGION" MYMEMORY_CODES = { "fr": "fr-FR", "en": "en-US", "sw": "sw-KE", "rn": "rn-BI", # Rundi — Burundi } # LibreTranslator codes ISO 639-1 LIBRE_CODES = { "fr": "fr", "en": "en", "sw": "sw", "rn": "rn", } # ══════════════════════════════════════════════════════════════════════════════ # MOTEUR 1 — deep-translator GoogleTranslator # Utilise Google Translate en arrière-plan, gratuit et sans API key # ══════════════════════════════════════════════════════════════════════════════ def _translate_deep_google(text: str, lang_code: str) -> str | None: dest = DEEP_GOOGLE_CODES.get(lang_code) if not dest: return None try: from deep_translator import GoogleTranslator result = GoogleTranslator(source="fr", target=dest).translate(text) if result and result.strip() and len(result.strip()) > 3: return result.strip() except Exception as e: print(f" deep-google ECHEC ({lang_code}) : {str(e)[:70]}") return None # ══════════════════════════════════════════════════════════════════════════════ # MOTEUR 2 — deep-translator MyMemoryTranslator # 5000 mots/jour gratuit sans compte # ══════════════════════════════════════════════════════════════════════════════ def _translate_mymemory(text: str, lang_code: str) -> str | None: dest = MYMEMORY_CODES.get(lang_code) if not dest: return None try: from deep_translator import MyMemoryTranslator result = MyMemoryTranslator(source="fr-FR", target=dest).translate(text) if result and result.strip() and len(result.strip()) > 3: # MyMemory retourne un avertissement si la limite est atteinte if any(w in result for w in ["MYMEMORY WARNING", "QUOTA FINISHED", "YOU USED"]): print(f" MyMemory : limite atteinte") return None return result.strip() except Exception as e: print(f" MyMemory ECHEC ({lang_code}) : {str(e)[:70]}") return None # ══════════════════════════════════════════════════════════════════════════════ # MOTEUR 3 — deep-translator LibreTranslator # Open source, serveur public disponible # ══════════════════════════════════════════════════════════════════════════════ def _translate_libre(text: str, lang_code: str) -> str | None: dest = LIBRE_CODES.get(lang_code) if not dest: return None try: from deep_translator import LibreTranslator result = LibreTranslator( source="fr", target=dest, url="https://libretranslate.com", api_key="" ).translate(text) if result and result.strip() and len(result.strip()) > 3: return result.strip() except Exception as e: print(f" LibreTranslate ECHEC ({lang_code}) : {str(e)[:70]}") return None # ══════════════════════════════════════════════════════════════════════════════ # FONCTION PRINCIPALE — fallback automatique # ══════════════════════════════════════════════════════════════════════════════ # Ordre des moteurs : du plus fiable au moins fiable _ENGINES = [ ("deep-google", _translate_deep_google), ("mymemory", _translate_mymemory), ("libretranslate",_translate_libre), ] def translate(text: str, lang_code: str, pause: float = 0.1) -> str: """ Traduit un texte français vers lang_code avec fallback automatique. Essaie chaque moteur dans l'ordre jusqu'au premier succès. Si tous échouent → retourne le texte original. lang_code : "fr" | "en" | "sw" | "rn" pause : délai entre tentatives (secondes) """ if lang_code == "fr" or not text or not text.strip(): return text for engine_name, engine_fn in _ENGINES: try: result = engine_fn(text, lang_code) if result: print(f"Traduction OK via {engine_name} ({lang_code})") time.sleep(pause) return result else: print(f" {engine_name} : résultat vide → moteur suivant") except Exception as e: print(f" {engine_name} erreur inattendue : {str(e)[:60]}") time.sleep(pause) # Tous les moteurs ont échoué print(f"TOUS les moteurs ont échoué ({lang_code}) — texte original conservé") return text def translate_fields(fields: dict, lang_code: str) -> dict: """ Traduit un dictionnaire de champs texte du français vers lang_code. Chaque champ est traduit indépendamment avec fallback. fields : {"disease_name": "...", "cause": "...", ...} Retourne les champs traduits (ou originaux si échec). """ if lang_code == "fr": return fields translated = {} for key, value in fields.items(): if not value or value in ("—", ""): translated[key] = value continue translated[key] = translate(value, lang_code) return translated # ══════════════════════════════════════════════════════════════════════════════ # TEST — lance ce fichier directement pour vérifier les moteurs # python translator.py # ══════════════════════════════════════════════════════════════════════════════ if __name__ == "__main__": tests = { "disease_name": "Mildiou de la tomate", "cause": "Champignon Phytophthora infestans. Favorisé par l'humidité élevée.", "treatment": "Appliquer un fongicide cuivrique. Retirer les feuilles infectées.", } for code, label in [("en", "English"), ("sw", "Swahili"), ("rn", "Rundi/Kirundi")]: print(f"\n{'='*50}") print(f"LANGUE : {label} ({code})") print(f"{'='*50}") results = translate_fields(tests, code) for key, val in results.items(): print(f" {key}: {val}")