test / translate_xlf.py
Blaise
Add initial implementation of XLIFF translation script and Gradio interface
8f90c2f
#!/usr/bin/env python3
"""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
# ---------------------------------------------------------------------------
# Choix du moteur de traduction
# ---------------------------------------------------------------------------
TRANSLATOR = None
if os.getenv("DEEPL_AUTH_KEY"):
try:
import deepl # type: ignore
_deepl_translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"])
def _translate(text: str, dest: str, src: Optional[str] = None) -> str: # noqa: D401
"""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 # type: ignore
_gt = Translator()
def _translate(text: str, dest: str, src: Optional[str] = None) -> str: # noqa: D401
"""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)
# ---------------------------------------------------------------------------
# Fonctions principales
# ---------------------------------------------------------------------------
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()
# Namespace éventuel
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 # nothing to translate
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:
# Ne pas réécrire si déjà rempli et overwrite désactivé
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()