| |
| """translate_xlf.py – Script de traduction XLIFF |
| |
| Ce script lit un fichier XLF (XLIFF 1.2), traduit chaque entrée <source> dans la |
| langue cible désirée et écrit le résultat dans un nouveau fichier. |
| |
| Dépendances : |
| pip install googletrans==4.0.0-rc1 |
| |
| Optionnel : si vous disposez d’une clef API DeepL dans la variable d’environnement |
| "DEEPL_AUTH_KEY", le script utilisera automatiquement DeepL pour une meilleure |
| qualité de traduction : |
| pip install deepl |
| |
| Usage : |
| python translate_xlf.py chemin/vers/fichier.xlf fr -o chemin/sortie_fr.xlf |
| |
| Auteur : ChatGPT – 23/05/2025 |
| """ |
| from __future__ import annotations |
|
|
| import argparse |
| import os |
| import sys |
| import xml.etree.ElementTree as ET |
| from pathlib import Path |
| from typing import Optional |
|
|
| |
| |
| |
| TRANSLATOR = None |
|
|
| if os.getenv("DEEPL_AUTH_KEY"): |
| try: |
| import deepl |
|
|
| _deepl_translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"]) |
|
|
| def _translate(text: str, dest: str, src: Optional[str] = None) -> str: |
| """Utilise l’API DeepL.""" |
|
|
| result = _deepl_translator.translate_text(text, target_lang=dest.upper()) |
| return result.text |
|
|
| TRANSLATOR = _translate |
| except ModuleNotFoundError: |
| print("Le module 'deepl' n’est pas installé. Installation recommandée : 'pip install deepl'", file=sys.stderr) |
|
|
| if TRANSLATOR is None: |
| try: |
| from googletrans import Translator |
|
|
| _gt = Translator() |
|
|
| def _translate(text: str, dest: str, src: Optional[str] = None) -> str: |
| """Fallback sur Google Translate.""" |
|
|
| return _gt.translate(text, dest=dest, src=src).text |
|
|
| TRANSLATOR = _translate |
| except ModuleNotFoundError: |
| print("Aucun moteur de traduction disponible. Installez 'googletrans==4.0.0-rc1' ou configurez DEEPL_AUTH_KEY.", file=sys.stderr) |
| sys.exit(1) |
|
|
| |
| |
| |
|
|
| def translate_xlf(input_path: Path, target_lang: str, output_path: Path, source_lang: Optional[str] = None, overwrite: bool = True) -> None: |
| """Traduit toutes les <source> d’un fichier XLF et écrit <target>.""" |
|
|
| tree = ET.parse(input_path) |
| root = tree.getroot() |
|
|
| |
| nsmap = {} |
| if root.tag.startswith("{"): |
| uri, _, _ = root.tag[1:].partition("}") |
| nsmap = {"ns": uri} |
| trans_unit_xpath = ".//ns:trans-unit" |
| else: |
| trans_unit_xpath = ".//trans-unit" |
|
|
| units = root.findall(trans_unit_xpath, namespaces=nsmap) |
| if not units: |
| print("Aucune balise <trans-unit> trouvée. Vérifiez le format du fichier.", file=sys.stderr) |
| sys.exit(1) |
|
|
| for tu in units: |
| source_elem = tu.find("ns:source" if nsmap else "source", namespaces=nsmap) |
| if source_elem is None or not (src_text := source_elem.text): |
| continue |
|
|
| target_elem = tu.find("ns:target" if nsmap else "target", namespaces=nsmap) |
| if target_elem is None: |
| target_elem = ET.SubElement(tu, f"{{{nsmap.get('ns','')}}}target" if nsmap else "target") |
|
|
| if target_elem.text and not overwrite: |
| |
| continue |
|
|
| translated_text = TRANSLATOR(src_text, dest=target_lang, src=source_lang) |
| target_elem.text = translated_text |
|
|
| tree.write(output_path, encoding="utf-8", xml_declaration=True) |
| print(f"Fichier traduit écrit : {output_path}") |
|
|
|
|
| def build_argparser() -> argparse.ArgumentParser: |
| parser = argparse.ArgumentParser(description="Traduire un fichier XLF (XLIFF) dans la langue cible.") |
| parser.add_argument("input", type=Path, help="Fichier .xlf d’entrée") |
| parser.add_argument("target_lang", help="Langue cible (code ISO, p. ex. fr, de, es)") |
| parser.add_argument("-s", "--source-lang", dest="source_lang", help="Langue source (code ISO). Laisser vide pour détection auto.") |
| parser.add_argument("-o", "--output", type=Path, help="Chemin du fichier de sortie. Défaut : <input>_<lang>.xlf") |
| parser.add_argument("--no-overwrite", action="store_true", help="Ne pas écraser les balises <target> déjà existantes") |
| return parser |
|
|
|
|
| def main() -> None: |
| parser = build_argparser() |
| args = parser.parse_args() |
|
|
| input_path: Path = args.input |
| if not input_path.is_file(): |
| parser.error("Le fichier d’entrée n’existe pas : %s" % input_path) |
|
|
| output_path: Path = args.output or input_path.with_stem(f"{input_path.stem}_{args.target_lang}") |
|
|
| translate_xlf( |
| input_path=input_path, |
| target_lang=args.target_lang, |
| output_path=output_path, |
| source_lang=args.source_lang, |
| overwrite=not args.no_overwrite, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|