Multi Language translation impl. Eine .json Datei enthält die ÜBersetztungen von allen Sprachen die übersetzt werden sollen. Dazu müssen diese Sektione allerdings auch befüllt sein
This commit is contained in:
+101
-7
@@ -29,8 +29,11 @@ class TestRunner:
|
|||||||
self.testdata_dir = Path(os.environ.get('PROJECT_TEST', self.project_root / "testdata"))
|
self.testdata_dir = Path(os.environ.get('PROJECT_TEST', self.project_root / "testdata"))
|
||||||
self.work_dir = Path(os.environ.get('PROJECT_WORK', self.project_root / "work"))
|
self.work_dir = Path(os.environ.get('PROJECT_WORK', self.project_root / "work"))
|
||||||
self.bin_dir = Path(os.environ.get('PROJECT_BIN', self.project_root / "bin"))
|
self.bin_dir = Path(os.environ.get('PROJECT_BIN', self.project_root / "bin"))
|
||||||
|
self.lib_dir = Path(os.environ.get('PROJECT_LIB', self.project_root / "lib"))
|
||||||
|
self.cfg_dir = Path(os.environ.get('PROJECT_CFG', self.project_root / "cfg"))
|
||||||
self.getexdraw_bat = self.bin_dir / "getexdraw.bat"
|
self.getexdraw_bat = self.bin_dir / "getexdraw.bat"
|
||||||
self.ioconverter_bat = self.bin_dir / "ioconverter.bat"
|
self.ioconverter_bat = self.bin_dir / "ioconverter.bat"
|
||||||
|
self.translate_py = self.lib_dir / "translate.py"
|
||||||
|
|
||||||
self.test_results = []
|
self.test_results = []
|
||||||
self.failed_tests = []
|
self.failed_tests = []
|
||||||
@@ -128,6 +131,80 @@ class TestRunner:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f"Exception during execution: {str(e)}"
|
return False, f"Exception during execution: {str(e)}"
|
||||||
|
|
||||||
|
def run_translate(self, test_filename):
|
||||||
|
"""Execute translate.py for given test file with three variants: raw, cs, multilang"""
|
||||||
|
dxf_file = self.testdata_dir / f"{test_filename}.dxf"
|
||||||
|
if not dxf_file.exists():
|
||||||
|
return False, f"Test file {dxf_file} not found"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Set up environment for translate.py
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['PROJECT_TRANSLATION'] = str(self.cfg_dir / "translation")
|
||||||
|
|
||||||
|
# Variant 1: Raw extraction (no translation)
|
||||||
|
cmd_raw = [
|
||||||
|
sys.executable,
|
||||||
|
str(self.translate_py),
|
||||||
|
'--extract',
|
||||||
|
'--filename', str(dxf_file),
|
||||||
|
'--export-type', 'json',
|
||||||
|
'--outname', f'{test_filename}_raw'
|
||||||
|
]
|
||||||
|
result_raw = subprocess.run(cmd_raw, capture_output=True, text=True, env=env)
|
||||||
|
|
||||||
|
# Variant 2: CS translation (single language)
|
||||||
|
cmd_cs = [
|
||||||
|
sys.executable,
|
||||||
|
str(self.translate_py),
|
||||||
|
'--extract',
|
||||||
|
'--filename', str(dxf_file),
|
||||||
|
'--export-type', 'json',
|
||||||
|
'--outname', f'{test_filename}_cs',
|
||||||
|
'--translate', 'CS'
|
||||||
|
]
|
||||||
|
result_cs = subprocess.run(cmd_cs, capture_output=True, text=True, env=env)
|
||||||
|
|
||||||
|
# Variant 3: Multilang translation (CS,EN)
|
||||||
|
cmd_multilang = [
|
||||||
|
sys.executable,
|
||||||
|
str(self.translate_py),
|
||||||
|
'--extract',
|
||||||
|
'--filename', str(dxf_file),
|
||||||
|
'--export-type', 'json',
|
||||||
|
'--outname', f'{test_filename}_multilang',
|
||||||
|
'--translate', 'CS,EN'
|
||||||
|
]
|
||||||
|
result_multilang = subprocess.run(cmd_multilang, capture_output=True, text=True, env=env)
|
||||||
|
|
||||||
|
# Check if all expected output files were created
|
||||||
|
expected_raw = self.work_dir / f"{test_filename}_raw.json"
|
||||||
|
expected_cs = self.work_dir / f"{test_filename}_cs.json"
|
||||||
|
expected_multilang = self.work_dir / f"{test_filename}_multilang.json"
|
||||||
|
|
||||||
|
missing_files = []
|
||||||
|
if not expected_raw.exists():
|
||||||
|
missing_files.append(f"{test_filename}_raw.json")
|
||||||
|
if not expected_cs.exists():
|
||||||
|
missing_files.append(f"{test_filename}_cs.json")
|
||||||
|
if not expected_multilang.exists():
|
||||||
|
missing_files.append(f"{test_filename}_multilang.json")
|
||||||
|
|
||||||
|
if missing_files:
|
||||||
|
error_msg = f"translate.py failed - expected output files not found: {', '.join(missing_files)}"
|
||||||
|
if result_raw.returncode != 0:
|
||||||
|
error_msg += f"\n\nRaw variant STDOUT:\n{result_raw.stdout}\nSTDERR:\n{result_raw.stderr}"
|
||||||
|
if result_cs.returncode != 0:
|
||||||
|
error_msg += f"\n\nCS variant STDOUT:\n{result_cs.stdout}\nSTDERR:\n{result_cs.stderr}"
|
||||||
|
if result_multilang.returncode != 0:
|
||||||
|
error_msg += f"\n\nMultilang variant STDOUT:\n{result_multilang.stdout}\nSTDERR:\n{result_multilang.stderr}"
|
||||||
|
return False, error_msg
|
||||||
|
else:
|
||||||
|
return True, "Success"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"Exception during execution: {str(e)}"
|
||||||
|
|
||||||
def cleanup_test_files(self, test_filename):
|
def cleanup_test_files(self, test_filename):
|
||||||
"""Remove the work folder for the given test"""
|
"""Remove the work folder for the given test"""
|
||||||
if not self.clean:
|
if not self.clean:
|
||||||
@@ -149,7 +226,11 @@ class TestRunner:
|
|||||||
# Check what files actually exist and determine comparison strategy based on mode
|
# Check what files actually exist and determine comparison strategy based on mode
|
||||||
errors_file = self.work_dir / test_filename / f"{test_filename}_errors.json"
|
errors_file = self.work_dir / test_filename / f"{test_filename}_errors.json"
|
||||||
|
|
||||||
if self.mode == 'ioconverter':
|
if self.mode == 'translation':
|
||||||
|
# For translation mode: compare _raw, _cs, and _multilang variants
|
||||||
|
file_suffixes = ['_raw', '_cs', '_multilang']
|
||||||
|
# Translation files are directly in work_dir, not in subdirectory
|
||||||
|
elif self.mode == 'ioconverter':
|
||||||
# For ioconverter mode: check for _positionsconv.json
|
# For ioconverter mode: check for _positionsconv.json
|
||||||
positions_file = self.work_dir / test_filename / f"{test_filename}_positionsconv.json"
|
positions_file = self.work_dir / test_filename / f"{test_filename}_positionsconv.json"
|
||||||
|
|
||||||
@@ -178,8 +259,12 @@ class TestRunner:
|
|||||||
return False, f"No generated files found for {test_filename}", []
|
return False, f"No generated files found for {test_filename}", []
|
||||||
|
|
||||||
for suffix in file_suffixes:
|
for suffix in file_suffixes:
|
||||||
# Generated file should be in work/<testfilename>/<testfilename><suffix>.json
|
# For translation mode, files are directly in work_dir
|
||||||
generated_file = self.work_dir / test_filename / f"{test_filename}{suffix}.json"
|
if self.mode == 'translation':
|
||||||
|
generated_file = self.work_dir / f"{test_filename}{suffix}.json"
|
||||||
|
else:
|
||||||
|
# For other modes, files are in work/<testfilename>/ subdirectory
|
||||||
|
generated_file = self.work_dir / test_filename / f"{test_filename}{suffix}.json"
|
||||||
|
|
||||||
# Reference file should be in testdata/<testfilename><suffix>.json
|
# Reference file should be in testdata/<testfilename><suffix>.json
|
||||||
reference_file = self.testdata_dir / f"{test_filename}{suffix}.json"
|
reference_file = self.testdata_dir / f"{test_filename}{suffix}.json"
|
||||||
@@ -237,8 +322,10 @@ class TestRunner:
|
|||||||
|
|
||||||
result = TestResult(test_filename)
|
result = TestResult(test_filename)
|
||||||
|
|
||||||
# Step 1: Run the appropriate batch script based on mode
|
# Step 1: Run the appropriate script based on mode
|
||||||
if self.mode == 'ioconverter':
|
if self.mode == 'translation':
|
||||||
|
success, msg = self.run_translate(test_filename)
|
||||||
|
elif self.mode == 'ioconverter':
|
||||||
success, msg = self.run_ioconverter(test_filename)
|
success, msg = self.run_ioconverter(test_filename)
|
||||||
else:
|
else:
|
||||||
success, msg = self.run_getexdraw(test_filename)
|
success, msg = self.run_getexdraw(test_filename)
|
||||||
@@ -370,11 +457,18 @@ def main():
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Run ioconverter.bat tests'
|
help='Run ioconverter.bat tests'
|
||||||
)
|
)
|
||||||
|
mode_group.add_argument(
|
||||||
|
'--translation',
|
||||||
|
action='store_true',
|
||||||
|
help='Run translate.py tests (extract text with raw, CS, and multilang variants)'
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Determine mode - default to getexdraw if neither is specified
|
# Determine mode - default to getexdraw if none is specified
|
||||||
if args.ioconverter:
|
if args.translation:
|
||||||
|
mode = 'translation'
|
||||||
|
elif args.ioconverter:
|
||||||
mode = 'ioconverter'
|
mode = 'ioconverter'
|
||||||
else:
|
else:
|
||||||
mode = 'getexdraw'
|
mode = 'getexdraw'
|
||||||
|
|||||||
+307
-101
@@ -525,16 +525,49 @@ def read_texts_from_file(filename: Path) -> list[str]:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_json_structure(data: dict) -> tuple[str, list[str]]:
|
||||||
|
"""
|
||||||
|
Erkennt die Struktur einer JSON-Datei (alte oder hierarchische mehrsprachige Struktur).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Geladene JSON-Daten als Dictionary
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple mit (Struktur-Typ, Liste der Sprachen)
|
||||||
|
- Struktur-Typ: "legacy" (alte Struktur) oder "multilang" (mehrsprachige hierarchische Struktur)
|
||||||
|
- Liste der Sprachen: leer bei legacy, z.B. ["EN", "FR", "CS"] bei multilang
|
||||||
|
"""
|
||||||
|
# Prüfe ob alte Struktur (hat 'untranslated' oder 'translations' Keys)
|
||||||
|
if 'untranslated' in data or 'translations' in data:
|
||||||
|
return "legacy", []
|
||||||
|
|
||||||
|
# Prüfe ob neue hierarchische Struktur (hat [LANG] Keys)
|
||||||
|
languages = []
|
||||||
|
for key in data.keys():
|
||||||
|
if key.startswith('[') and key.endswith(']') and len(key) > 2:
|
||||||
|
# Extrahiere Sprachcode aus [LANG]
|
||||||
|
lang = key[1:-1]
|
||||||
|
languages.append(lang)
|
||||||
|
|
||||||
|
if languages:
|
||||||
|
return "multilang", languages
|
||||||
|
|
||||||
|
# Unbekannte Struktur
|
||||||
|
return "unknown", []
|
||||||
|
|
||||||
|
|
||||||
def read_texts_from_json(filename: Path) -> list[str]:
|
def read_texts_from_json(filename: Path) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Liest Texte aus dem 'untranslated' Block einer JSON-Datei.
|
Liest Texte aus einer JSON-Datei.
|
||||||
Erwartet eine Struktur mit "untranslated": [{"source": "...", "target": ""}, ...]
|
Unterstützt beide Strukturen:
|
||||||
|
- Alte Struktur: "untranslated": [{"source": "...", "target": ""}, ...]
|
||||||
|
- Hierarchische Struktur: "[LANG]": {"untranslated": [...], ...}
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename: Pfad zur JSON-Datei
|
filename: Pfad zur JSON-Datei
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Alphabetisch sortierte Liste aller "source" Texte aus dem untranslated Block
|
Alphabetisch sortierte Liste aller "source" Texte aus den untranslated Blöcken
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with open(filename, 'r', encoding='utf-8') as f:
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
@@ -544,33 +577,77 @@ def read_texts_from_json(filename: Path) -> list[str]:
|
|||||||
print(f"FEHLER: JSON-Datei hat ungültige Struktur (erwartet Object, bekam {type(data).__name__})")
|
print(f"FEHLER: JSON-Datei hat ungültige Struktur (erwartet Object, bekam {type(data).__name__})")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if 'untranslated' not in data:
|
# Erkenne Struktur
|
||||||
print(f"FEHLER: JSON-Datei enthält keinen 'untranslated' Block")
|
structure_type, languages = detect_json_structure(data)
|
||||||
|
|
||||||
|
texts_set = set() # Verwende Set um Duplikate zu vermeiden
|
||||||
|
|
||||||
|
if structure_type == "legacy":
|
||||||
|
# Alte Struktur
|
||||||
|
if 'untranslated' not in data:
|
||||||
|
print(f"FEHLER: JSON-Datei enthält keinen 'untranslated' Block")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
untranslated = data['untranslated']
|
||||||
|
if not isinstance(untranslated, list):
|
||||||
|
print(f"FEHLER: 'untranslated' Block hat ungültige Struktur (erwartet Array)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
for idx, entry in enumerate(untranslated):
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
print(f"WARNUNG: Eintrag {idx} in 'untranslated' ist kein Object, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'source' not in entry:
|
||||||
|
print(f"WARNUNG: Eintrag {idx} in 'untranslated' hat kein 'source' Feld, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_text = entry['source']
|
||||||
|
if source_text: # Ignoriere leere Texte
|
||||||
|
texts_set.add(source_text)
|
||||||
|
|
||||||
|
print(f"Texte aus JSON-Datei gelesen (alte Struktur): {len(texts_set)}")
|
||||||
|
|
||||||
|
elif structure_type == "multilang":
|
||||||
|
# Hierarchische mehrsprachige Struktur
|
||||||
|
print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(languages)}")
|
||||||
|
|
||||||
|
for lang in languages:
|
||||||
|
lang_key = f"[{lang}]"
|
||||||
|
if lang_key not in data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
lang_data = data[lang_key]
|
||||||
|
if not isinstance(lang_data, dict):
|
||||||
|
print(f"WARNUNG: [{lang}] Block ist kein Object, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Lese aus untranslated Block
|
||||||
|
if 'untranslated' in lang_data:
|
||||||
|
untranslated = lang_data['untranslated']
|
||||||
|
if not isinstance(untranslated, list):
|
||||||
|
print(f"WARNUNG: [{lang}][untranslated] ist kein Array, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for idx, entry in enumerate(untranslated):
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'source' not in entry:
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_text = entry['source']
|
||||||
|
if source_text:
|
||||||
|
texts_set.add(source_text)
|
||||||
|
|
||||||
|
print(f"Texte aus JSON-Datei gelesen (mehrsprachige Struktur): {len(texts_set)}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"FEHLER: JSON-Datei hat unbekannte Struktur")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
untranslated = data['untranslated']
|
# Konvertiere Set zu sortierter Liste
|
||||||
if not isinstance(untranslated, list):
|
texts = sorted(texts_set)
|
||||||
print(f"FEHLER: 'untranslated' Block hat ungültige Struktur (erwartet Array)")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
texts = []
|
|
||||||
for idx, entry in enumerate(untranslated):
|
|
||||||
if not isinstance(entry, dict):
|
|
||||||
print(f"WARNUNG: Eintrag {idx} in 'untranslated' ist kein Object, überspringe")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if 'source' not in entry:
|
|
||||||
print(f"WARNUNG: Eintrag {idx} in 'untranslated' hat kein 'source' Feld, überspringe")
|
|
||||||
continue
|
|
||||||
|
|
||||||
source_text = entry['source']
|
|
||||||
if source_text: # Ignoriere leere Texte
|
|
||||||
texts.append(source_text)
|
|
||||||
|
|
||||||
# Sortiere alphabetisch
|
|
||||||
texts.sort()
|
|
||||||
|
|
||||||
print(f"Texte aus JSON-Datei gelesen: {len(texts)}")
|
|
||||||
return texts
|
return texts
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
@@ -581,13 +658,19 @@ def read_texts_from_json(filename: Path) -> list[str]:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def load_translations_from_json(filename: Path) -> dict[str, str]:
|
def load_translations_from_json(filename: Path, target_lang: str = None) -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Liest Übersetzungen aus dem 'translations' Block einer JSON-Datei.
|
Liest Übersetzungen aus einer JSON-Datei.
|
||||||
Erwartet eine Struktur mit "translations": [{"source": "...", "target": "..."}, ...]
|
Unterstützt beide Strukturen:
|
||||||
|
- Alte Struktur: "translations": [{"source": "...", "target": "..."}, ...]
|
||||||
|
- Hierarchische Struktur: "[LANG]": {"translated": [...], ...}
|
||||||
|
|
||||||
|
Bei hierarchischer Struktur werden Übersetzungen aus allen Sprachen oder nur der angegebenen Sprache gelesen.
|
||||||
|
Wenn mehrere Übersetzungen für denselben source existieren, wird die letzte verwendet (Sprache mit höherer Priorität).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename: Pfad zur JSON-Datei
|
filename: Pfad zur JSON-Datei
|
||||||
|
target_lang: Optional: Bei hierarchischer Struktur nur Übersetzungen dieser Sprache laden
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary mit source -> target Mappings
|
Dictionary mit source -> target Mappings
|
||||||
@@ -600,31 +683,83 @@ def load_translations_from_json(filename: Path) -> dict[str, str]:
|
|||||||
print(f"FEHLER: JSON-Datei hat ungültige Struktur (erwartet Object, bekam {type(data).__name__})")
|
print(f"FEHLER: JSON-Datei hat ungültige Struktur (erwartet Object, bekam {type(data).__name__})")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if 'translations' not in data:
|
# Erkenne Struktur
|
||||||
print(f"FEHLER: JSON-Datei enthält keinen 'translations' Block")
|
structure_type, languages = detect_json_structure(data)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
translations_list = data['translations']
|
|
||||||
if not isinstance(translations_list, list):
|
|
||||||
print(f"FEHLER: 'translations' Block hat ungültige Struktur (erwartet Array)")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
translations = {}
|
translations = {}
|
||||||
for idx, entry in enumerate(translations_list):
|
|
||||||
if not isinstance(entry, dict):
|
|
||||||
print(f"WARNUNG: Eintrag {idx} in 'translations' ist kein Object, überspringe")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if 'source' not in entry or 'target' not in entry:
|
if structure_type == "legacy":
|
||||||
print(f"WARNUNG: Eintrag {idx} in 'translations' fehlt 'source' oder 'target', überspringe")
|
# Alte Struktur
|
||||||
continue
|
if 'translations' not in data:
|
||||||
|
print(f"FEHLER: JSON-Datei enthält keinen 'translations' Block")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
source = entry['source']
|
translations_list = data['translations']
|
||||||
target = entry['target']
|
if not isinstance(translations_list, list):
|
||||||
if source and target: # Ignoriere leere Einträge
|
print(f"FEHLER: 'translations' Block hat ungültige Struktur (erwartet Array)")
|
||||||
translations[source] = target
|
sys.exit(1)
|
||||||
|
|
||||||
|
for idx, entry in enumerate(translations_list):
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
print(f"WARNUNG: Eintrag {idx} in 'translations' ist kein Object, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'source' not in entry or 'target' not in entry:
|
||||||
|
print(f"WARNUNG: Eintrag {idx} in 'translations' fehlt 'source' oder 'target', überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
source = entry['source']
|
||||||
|
target = entry['target']
|
||||||
|
if source and target: # Ignoriere leere Einträge
|
||||||
|
translations[source] = target
|
||||||
|
|
||||||
|
print(f"Übersetzungen aus JSON-Datei geladen (alte Struktur): {len(translations)}")
|
||||||
|
|
||||||
|
elif structure_type == "multilang":
|
||||||
|
# Hierarchische mehrsprachige Struktur
|
||||||
|
print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(languages)}")
|
||||||
|
|
||||||
|
# Filtere Sprachen falls target_lang angegeben
|
||||||
|
langs_to_load = [target_lang] if target_lang and target_lang in languages else languages
|
||||||
|
|
||||||
|
for lang in langs_to_load:
|
||||||
|
lang_key = f"[{lang}]"
|
||||||
|
if lang_key not in data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
lang_data = data[lang_key]
|
||||||
|
if not isinstance(lang_data, dict):
|
||||||
|
print(f"WARNUNG: [{lang}] Block ist kein Object, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Lese aus translated Block
|
||||||
|
if 'translated' in lang_data:
|
||||||
|
translated = lang_data['translated']
|
||||||
|
if not isinstance(translated, list):
|
||||||
|
print(f"WARNUNG: [{lang}][translated] ist kein Array, überspringe")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for idx, entry in enumerate(translated):
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'source' not in entry or 'target' not in entry:
|
||||||
|
continue
|
||||||
|
|
||||||
|
source = entry['source']
|
||||||
|
target = entry['target']
|
||||||
|
if source and target:
|
||||||
|
translations[source] = target
|
||||||
|
|
||||||
|
if target_lang:
|
||||||
|
print(f"Übersetzungen für Sprache [{target_lang}] aus JSON-Datei geladen: {len(translations)}")
|
||||||
|
else:
|
||||||
|
print(f"Übersetzungen aus JSON-Datei geladen (mehrsprachige Struktur, alle Sprachen): {len(translations)}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"FEHLER: JSON-Datei hat unbekannte Struktur")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"Übersetzungen aus JSON-Datei geladen: {len(translations)}")
|
|
||||||
return translations
|
return translations
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
@@ -959,63 +1094,117 @@ def write_texts_to_excel(texts: list[str], output_path: Path,
|
|||||||
|
|
||||||
|
|
||||||
def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: Path,
|
def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: Path,
|
||||||
translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]] = None):
|
translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]] = None,
|
||||||
|
translation_dicts_all: dict[str, tuple] = None,
|
||||||
|
translation_languages: list[str] = None):
|
||||||
"""
|
"""
|
||||||
Schreibt die Texte in eine JSON-Datei mit drei Blöcken.
|
Schreibt die Texte in eine JSON-Datei.
|
||||||
|
|
||||||
Die JSON-Struktur enthält:
|
Bei einer Sprache (alte Struktur):
|
||||||
- "translations": Liste von {source, target} für übersetzte Texte
|
- "translations": Liste von {source, target} für übersetzte Texte
|
||||||
- "untranslated": Liste von {source, target: ""} für nicht übersetzte Texte
|
- "untranslated": Liste von {source, target: ""} für nicht übersetzte Texte
|
||||||
- "ignored": Liste von Strings für gefilterte/ignorierte Texte
|
- "ignored": Liste von Strings für gefilterte/ignorierte Texte
|
||||||
|
|
||||||
|
Bei mehreren Sprachen (hierarchische Struktur):
|
||||||
|
- "[EN]": {"translated": [...], "untranslated": [...]}
|
||||||
|
- "[FR]": {"translated": [...], "untranslated": [...]}
|
||||||
|
- "ignored": Liste von Strings für gefilterte/ignorierte Texte
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
texts: Liste der akzeptierten Texte
|
texts: Liste der akzeptierten Texte
|
||||||
ignored_texts: Liste der ignorierten Texte
|
ignored_texts: Liste der ignorierten Texte
|
||||||
output_path: Pfad zur Ausgabe-JSON-Datei
|
output_path: Pfad zur Ausgabe-JSON-Datei
|
||||||
translation_dicts: Optional: Tuple mit (Multi, Trigramme, Bigramme, Single) für Übersetzungen
|
translation_dicts: Optional: Tuple mit (Multi, Trigramme, Bigramme, Single) für eine Sprache (backwards compatibility)
|
||||||
|
translation_dicts_all: Optional: Dict mit {LANG: (multi, trigramme, bigramme, single)} für mehrere Sprachen
|
||||||
|
translation_languages: Optional: Liste der Sprachen in der Reihenfolge der Verarbeitung
|
||||||
"""
|
"""
|
||||||
output_data = {}
|
output_data = {}
|
||||||
|
|
||||||
# Block 1: Übersetzte Texte (als Liste von Dicts mit source/target)
|
# Mehrsprachige hierarchische Struktur
|
||||||
# Block 2: Nicht übersetzte Texte (als Liste von Dicts mit source und leerem target)
|
if translation_dicts_all and translation_languages and len(translation_languages) > 1:
|
||||||
if translation_dicts:
|
for lang in translation_languages:
|
||||||
multi, trigramme, bigramme, single = translation_dicts
|
if lang not in translation_dicts_all:
|
||||||
translations = []
|
continue
|
||||||
untranslated = []
|
|
||||||
|
|
||||||
for text in texts:
|
multi, trigramme, bigramme, single = translation_dicts_all[lang]
|
||||||
translated = translate_text(text, multi, trigramme, bigramme, single)
|
translations = []
|
||||||
if translated: # Übersetzung vorhanden
|
untranslated = []
|
||||||
translations.append({
|
|
||||||
"source": text,
|
|
||||||
"target": translated
|
|
||||||
})
|
|
||||||
else: # Keine Übersetzung vorhanden
|
|
||||||
untranslated.append({
|
|
||||||
"source": text,
|
|
||||||
"target": ""
|
|
||||||
})
|
|
||||||
|
|
||||||
output_data["translations"] = translations
|
for text in texts:
|
||||||
output_data["untranslated"] = untranslated
|
translated = translate_text(text, multi, trigramme, bigramme, single)
|
||||||
|
if translated: # Übersetzung vorhanden
|
||||||
|
translations.append({
|
||||||
|
"source": text,
|
||||||
|
"target": translated
|
||||||
|
})
|
||||||
|
else: # Keine Übersetzung vorhanden
|
||||||
|
untranslated.append({
|
||||||
|
"source": text,
|
||||||
|
"target": ""
|
||||||
|
})
|
||||||
|
|
||||||
|
# Hierarchische Struktur: [LANG] als Schlüssel
|
||||||
|
output_data[f"[{lang}]"] = {
|
||||||
|
"translated": translations,
|
||||||
|
"untranslated": untranslated
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ignorierte Texte am Ende (ohne Sprachzuordnung)
|
||||||
|
output_data["ignored"] = ignored_texts
|
||||||
|
|
||||||
|
# Alte Struktur (eine Sprache oder keine Übersetzung)
|
||||||
else:
|
else:
|
||||||
# Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt
|
# Block 1: Übersetzte Texte (als Liste von Dicts mit source/target)
|
||||||
output_data["translations"] = []
|
# Block 2: Nicht übersetzte Texte (als Liste von Dicts mit source und leerem target)
|
||||||
output_data["untranslated"] = [{"source": text, "target": ""} for text in texts]
|
if translation_dicts:
|
||||||
|
multi, trigramme, bigramme, single = translation_dicts
|
||||||
|
translations = []
|
||||||
|
untranslated = []
|
||||||
|
|
||||||
# Block 3: Ignorierte Texte (als Liste)
|
for text in texts:
|
||||||
output_data["ignored"] = ignored_texts
|
translated = translate_text(text, multi, trigramme, bigramme, single)
|
||||||
|
if translated: # Übersetzung vorhanden
|
||||||
|
translations.append({
|
||||||
|
"source": text,
|
||||||
|
"target": translated
|
||||||
|
})
|
||||||
|
else: # Keine Übersetzung vorhanden
|
||||||
|
untranslated.append({
|
||||||
|
"source": text,
|
||||||
|
"target": ""
|
||||||
|
})
|
||||||
|
|
||||||
|
output_data["translations"] = translations
|
||||||
|
output_data["untranslated"] = untranslated
|
||||||
|
else:
|
||||||
|
# Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt
|
||||||
|
output_data["translations"] = []
|
||||||
|
output_data["untranslated"] = [{"source": text, "target": ""} for text in texts]
|
||||||
|
|
||||||
|
# Block 3: Ignorierte Texte (als Liste)
|
||||||
|
output_data["ignored"] = ignored_texts
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(output_path, 'w', encoding='utf-8') as f:
|
with open(output_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(output_data, f, ensure_ascii=False, indent=2)
|
json.dump(output_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
print(f"JSON-Datei erfolgreich erstellt: {output_path}")
|
print(f"JSON-Datei erfolgreich erstellt: {output_path}")
|
||||||
if translation_dicts:
|
|
||||||
print(f" Übersetzungen: {len(output_data['translations'])}")
|
# Ausgabe-Statistiken
|
||||||
print(f" Nicht übersetzte Texte: {len(output_data['untranslated'])}")
|
if translation_dicts_all and translation_languages and len(translation_languages) > 1:
|
||||||
|
# Mehrsprachige Struktur
|
||||||
|
for lang in translation_languages:
|
||||||
|
if f"[{lang}]" in output_data:
|
||||||
|
lang_data = output_data[f"[{lang}]"]
|
||||||
|
print(f" [{lang}] Übersetzungen: {len(lang_data['translated'])}, Nicht übersetzt: {len(lang_data['untranslated'])}")
|
||||||
else:
|
else:
|
||||||
print(f" Nicht übersetzte Texte: {len(output_data['untranslated'])}")
|
# Alte Struktur
|
||||||
|
if translation_dicts:
|
||||||
|
print(f" Übersetzungen: {len(output_data['translations'])}")
|
||||||
|
print(f" Nicht übersetzte Texte: {len(output_data['untranslated'])}")
|
||||||
|
else:
|
||||||
|
print(f" Nicht übersetzte Texte: {len(output_data['untranslated'])}")
|
||||||
|
|
||||||
print(f" Ignorierte Texte: {len(ignored_texts)}")
|
print(f" Ignorierte Texte: {len(ignored_texts)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Fehler beim Schreiben der JSON-Datei: {e}")
|
print(f"Fehler beim Schreiben der JSON-Datei: {e}")
|
||||||
@@ -1160,7 +1349,7 @@ if __name__ == '__main__':
|
|||||||
'--translate',
|
'--translate',
|
||||||
action='store',
|
action='store',
|
||||||
metavar='LANG',
|
metavar='LANG',
|
||||||
help='Übersetzt Texte mit Config aus translation/<LANG>.cfg (z.B. CS, EN, FR)'
|
help='Übersetzt Texte mit Config aus translation/<LANG>.cfg (z.B. CS, EN, FR). Mehrere Sprachen kommasepariert möglich (z.B. EN,FR,CS)'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--lang_source',
|
'--lang_source',
|
||||||
@@ -1222,25 +1411,41 @@ if __name__ == '__main__':
|
|||||||
symbol_attr_mapping = load_symbol_attribute_mapping(cfg_dir)
|
symbol_attr_mapping = load_symbol_attribute_mapping(cfg_dir)
|
||||||
symbol_layer_mapping = load_symbol_layer_mapping(cfg_dir)
|
symbol_layer_mapping = load_symbol_layer_mapping(cfg_dir)
|
||||||
|
|
||||||
# Bestimme Quell- und Ziel-Attributnamen basierend auf Sprachcodes
|
# Parse Sprachen aus --translate Parameter (kommaseparierte Liste)
|
||||||
source_attr_name = symbol_attr_mapping.get(args.lang_source, 'TEXT-D')
|
translation_languages = []
|
||||||
target_attr_name = symbol_attr_mapping.get(args.translate, 'TEXT-CS') if args.translate else 'TEXT-CS'
|
if args.translate:
|
||||||
|
translation_languages = [lang.strip().upper() for lang in args.translate.split(',')]
|
||||||
|
# Entferne Duplikate und behalte Reihenfolge
|
||||||
|
seen = set()
|
||||||
|
translation_languages = [lang for lang in translation_languages if lang and not (lang in seen or seen.add(lang))]
|
||||||
|
|
||||||
# Bestimme Ziel-Layer für Blockattribute (nur relevant bei --todxf)
|
# Bestimme Quell-Attributnamen basierend auf Sprachcode
|
||||||
target_layer_name = symbol_layer_mapping.get(args.translate) if args.translate else None
|
source_attr_name = symbol_attr_mapping.get(args.lang_source, 'TEXT-D')
|
||||||
|
|
||||||
|
# Für --todxf: verwende nur die erste Sprache als Ziel (backwards compatibility)
|
||||||
|
target_attr_name = symbol_attr_mapping.get(translation_languages[0], 'TEXT-CS') if translation_languages else 'TEXT-CS'
|
||||||
|
target_layer_name = symbol_layer_mapping.get(translation_languages[0]) if translation_languages else None
|
||||||
|
|
||||||
print(f"\nVerwendete Attribut-Namen:")
|
print(f"\nVerwendete Attribut-Namen:")
|
||||||
print(f" Quell-Attribut (--lang_source {args.lang_source}): {source_attr_name}")
|
print(f" Quell-Attribut (--lang_source {args.lang_source}): {source_attr_name}")
|
||||||
if args.translate:
|
if translation_languages:
|
||||||
print(f" Ziel-Attribut (--translate {args.translate}): {target_attr_name}")
|
if len(translation_languages) == 1:
|
||||||
if target_layer_name:
|
print(f" Ziel-Attribut (--translate {translation_languages[0]}): {target_attr_name}")
|
||||||
print(f" Ziel-Layer (--translate {args.translate}): {target_layer_name}")
|
if target_layer_name:
|
||||||
|
print(f" Ziel-Layer (--translate {translation_languages[0]}): {target_layer_name}")
|
||||||
|
else:
|
||||||
|
print(f" Ziel-Sprachen (--translate): {', '.join(translation_languages)}")
|
||||||
|
|
||||||
# Optional: Lade Übersetzungs-Config
|
# Optional: Lade Übersetzungs-Configs für alle Sprachen
|
||||||
translation_dicts = None
|
# translation_dicts_all ist ein Dict: {LANG: (multi, trigramme, bigramme, single), ...}
|
||||||
if args.translate:
|
translation_dicts_all = {}
|
||||||
|
if translation_languages:
|
||||||
translation_dir = check_environment_var('PROJECT_TRANSLATION')
|
translation_dir = check_environment_var('PROJECT_TRANSLATION')
|
||||||
translation_dicts = load_translation_config(args.translate, translation_dir)
|
for lang in translation_languages:
|
||||||
|
translation_dicts_all[lang] = load_translation_config(lang, translation_dir)
|
||||||
|
|
||||||
|
# Für backwards compatibility: wenn nur eine Sprache, setze translation_dicts wie früher
|
||||||
|
translation_dicts = translation_dicts_all.get(translation_languages[0]) if len(translation_languages) == 1 else None
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print("DEBUG-Modus aktiviert")
|
print("DEBUG-Modus aktiviert")
|
||||||
@@ -1286,8 +1491,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
print(f"Übersetzungs-JSON: {translation_json}")
|
print(f"Übersetzungs-JSON: {translation_json}")
|
||||||
|
|
||||||
# Lade Übersetzungen
|
# Lade Übersetzungen (verwende erste Sprache als Ziel-Sprache)
|
||||||
translations = load_translations_from_json(translation_json)
|
target_lang = translation_languages[0] if translation_languages else None
|
||||||
|
translations = load_translations_from_json(translation_json, target_lang)
|
||||||
|
|
||||||
# Bestimme Ausgabe-DXF
|
# Bestimme Ausgabe-DXF
|
||||||
output_dxf_name = args.todxf
|
output_dxf_name = args.todxf
|
||||||
@@ -1415,7 +1621,7 @@ if __name__ == '__main__':
|
|||||||
if export_type == 'excel':
|
if export_type == 'excel':
|
||||||
write_texts_to_excel(texts, output_path, translation_dicts)
|
write_texts_to_excel(texts, output_path, translation_dicts)
|
||||||
elif export_type == 'json':
|
elif export_type == 'json':
|
||||||
write_texts_to_json(texts, ignored_texts, output_path, translation_dicts)
|
write_texts_to_json(texts, ignored_texts, output_path, translation_dicts, translation_dicts_all, translation_languages)
|
||||||
else: # text
|
else: # text
|
||||||
write_texts_to_text(texts, ignored_texts, output_path, translation_dicts)
|
write_texts_to_text(texts, ignored_texts, output_path, translation_dicts)
|
||||||
|
|
||||||
|
|||||||
Vendored
+154358
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user