Blaise commited on
Commit
8f90c2f
·
1 Parent(s): bfac87c

Add initial implementation of XLIFF translation script and Gradio interface

Browse files
Files changed (3) hide show
  1. app.py +14 -0
  2. requirements.txt +3 -0
  3. translate_xlf.py +140 -0
app.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr, pathlib, tempfile
2
+ from translate_xlf import translate_xlf
3
+
4
+ def translate(file, target):
5
+ tmp_in = tempfile.NamedTemporaryFile(delete=False, suffix=".xlf")
6
+ tmp_in.write(file.read()); tmp_in.flush()
7
+ out = pathlib.Path(tmp_in.name).with_stem(f"{tmp_in.name}_{target}")
8
+ translate_xlf(tmp_in.name, target, out)
9
+ return out
10
+
11
+ demo = gr.Interface(fn=translate,
12
+ inputs=[gr.File(label="Fichier XLF"), gr.Textbox(label="Langue cible", value="fr")],
13
+ outputs=gr.File())
14
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ googletrans==4.0.0-rc1
3
+ # deepl # décommentez si vous utilisez une clé DeepL
translate_xlf.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """translate_xlf.py – Script de traduction XLIFF
3
+
4
+ Ce script lit un fichier XLF (XLIFF 1.2), traduit chaque entrée <source> dans la
5
+ langue cible désirée et écrit le résultat dans un nouveau fichier.
6
+
7
+ Dépendances :
8
+ pip install googletrans==4.0.0-rc1
9
+
10
+ Optionnel : si vous disposez d’une clef API DeepL dans la variable d’environnement
11
+ "DEEPL_AUTH_KEY", le script utilisera automatiquement DeepL pour une meilleure
12
+ qualité de traduction :
13
+ pip install deepl
14
+
15
+ Usage :
16
+ python translate_xlf.py chemin/vers/fichier.xlf fr -o chemin/sortie_fr.xlf
17
+
18
+ Auteur : ChatGPT – 23/05/2025
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import os
24
+ import sys
25
+ import xml.etree.ElementTree as ET
26
+ from pathlib import Path
27
+ from typing import Optional
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Choix du moteur de traduction
31
+ # ---------------------------------------------------------------------------
32
+ TRANSLATOR = None
33
+
34
+ if os.getenv("DEEPL_AUTH_KEY"):
35
+ try:
36
+ import deepl # type: ignore
37
+
38
+ _deepl_translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"])
39
+
40
+ def _translate(text: str, dest: str, src: Optional[str] = None) -> str: # noqa: D401
41
+ """Utilise l’API DeepL."""
42
+
43
+ result = _deepl_translator.translate_text(text, target_lang=dest.upper())
44
+ return result.text
45
+
46
+ TRANSLATOR = _translate
47
+ except ModuleNotFoundError:
48
+ print("Le module 'deepl' n’est pas installé. Installation recommandée : 'pip install deepl'", file=sys.stderr)
49
+
50
+ if TRANSLATOR is None:
51
+ try:
52
+ from googletrans import Translator # type: ignore
53
+
54
+ _gt = Translator()
55
+
56
+ def _translate(text: str, dest: str, src: Optional[str] = None) -> str: # noqa: D401
57
+ """Fallback sur Google Translate."""
58
+
59
+ return _gt.translate(text, dest=dest, src=src).text
60
+
61
+ TRANSLATOR = _translate
62
+ except ModuleNotFoundError:
63
+ print("Aucun moteur de traduction disponible. Installez 'googletrans==4.0.0-rc1' ou configurez DEEPL_AUTH_KEY.", file=sys.stderr)
64
+ sys.exit(1)
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Fonctions principales
68
+ # ---------------------------------------------------------------------------
69
+
70
+ def translate_xlf(input_path: Path, target_lang: str, output_path: Path, source_lang: Optional[str] = None, overwrite: bool = True) -> None:
71
+ """Traduit toutes les <source> d’un fichier XLF et écrit <target>."""
72
+
73
+ tree = ET.parse(input_path)
74
+ root = tree.getroot()
75
+
76
+ # Namespace éventuel
77
+ nsmap = {}
78
+ if root.tag.startswith("{"):
79
+ uri, _, _ = root.tag[1:].partition("}")
80
+ nsmap = {"ns": uri}
81
+ trans_unit_xpath = ".//ns:trans-unit"
82
+ else:
83
+ trans_unit_xpath = ".//trans-unit"
84
+
85
+ units = root.findall(trans_unit_xpath, namespaces=nsmap)
86
+ if not units:
87
+ print("Aucune balise <trans-unit> trouvée. Vérifiez le format du fichier.", file=sys.stderr)
88
+ sys.exit(1)
89
+
90
+ for tu in units:
91
+ source_elem = tu.find("ns:source" if nsmap else "source", namespaces=nsmap)
92
+ if source_elem is None or not (src_text := source_elem.text):
93
+ continue # nothing to translate
94
+
95
+ target_elem = tu.find("ns:target" if nsmap else "target", namespaces=nsmap)
96
+ if target_elem is None:
97
+ target_elem = ET.SubElement(tu, f"{{{nsmap.get('ns','')}}}target" if nsmap else "target")
98
+
99
+ if target_elem.text and not overwrite:
100
+ # Ne pas réécrire si déjà rempli et overwrite désactivé
101
+ continue
102
+
103
+ translated_text = TRANSLATOR(src_text, dest=target_lang, src=source_lang)
104
+ target_elem.text = translated_text
105
+
106
+ tree.write(output_path, encoding="utf-8", xml_declaration=True)
107
+ print(f"Fichier traduit écrit : {output_path}")
108
+
109
+
110
+ def build_argparser() -> argparse.ArgumentParser:
111
+ parser = argparse.ArgumentParser(description="Traduire un fichier XLF (XLIFF) dans la langue cible.")
112
+ parser.add_argument("input", type=Path, help="Fichier .xlf d’entrée")
113
+ parser.add_argument("target_lang", help="Langue cible (code ISO, p. ex. fr, de, es)")
114
+ parser.add_argument("-s", "--source-lang", dest="source_lang", help="Langue source (code ISO). Laisser vide pour détection auto.")
115
+ parser.add_argument("-o", "--output", type=Path, help="Chemin du fichier de sortie. Défaut : <input>_<lang>.xlf")
116
+ parser.add_argument("--no-overwrite", action="store_true", help="Ne pas écraser les balises <target> déjà existantes")
117
+ return parser
118
+
119
+
120
+ def main() -> None:
121
+ parser = build_argparser()
122
+ args = parser.parse_args()
123
+
124
+ input_path: Path = args.input
125
+ if not input_path.is_file():
126
+ parser.error("Le fichier d’entrée n’existe pas : %s" % input_path)
127
+
128
+ output_path: Path = args.output or input_path.with_stem(f"{input_path.stem}_{args.target_lang}")
129
+
130
+ translate_xlf(
131
+ input_path=input_path,
132
+ target_lang=args.target_lang,
133
+ output_path=output_path,
134
+ source_lang=args.source_lang,
135
+ overwrite=not args.no_overwrite,
136
+ )
137
+
138
+
139
+ if __name__ == "__main__":
140
+ main()