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:
2025-12-15 13:39:39 +01:00
parent ccbfcafb63
commit 6524a76c2a
3 changed files with 154766 additions and 108 deletions
+307 -101
View File
@@ -525,16 +525,49 @@ def read_texts_from_file(filename: Path) -> list[str]:
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]:
"""
Liest Texte aus dem 'untranslated' Block einer JSON-Datei.
Erwartet eine Struktur mit "untranslated": [{"source": "...", "target": ""}, ...]
Liest Texte aus einer JSON-Datei.
Unterstützt beide Strukturen:
- Alte Struktur: "untranslated": [{"source": "...", "target": ""}, ...]
- Hierarchische Struktur: "[LANG]": {"untranslated": [...], ...}
Args:
filename: Pfad zur JSON-Datei
Returns:
Alphabetisch sortierte Liste aller "source" Texte aus dem untranslated Block
Alphabetisch sortierte Liste aller "source" Texte aus den untranslated Blöcken
"""
try:
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__})")
sys.exit(1)
if 'untranslated' not in data:
print(f"FEHLER: JSON-Datei enthält keinen 'untranslated' Block")
# Erkenne Struktur
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)
untranslated = data['untranslated']
if not isinstance(untranslated, list):
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)}")
# Konvertiere Set zu sortierter Liste
texts = sorted(texts_set)
return texts
except json.JSONDecodeError as e:
@@ -581,13 +658,19 @@ def read_texts_from_json(filename: Path) -> list[str]:
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.
Erwartet eine Struktur mit "translations": [{"source": "...", "target": "..."}, ...]
Liest Übersetzungen aus einer JSON-Datei.
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:
filename: Pfad zur JSON-Datei
target_lang: Optional: Bei hierarchischer Struktur nur Übersetzungen dieser Sprache laden
Returns:
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__})")
sys.exit(1)
if 'translations' not in data:
print(f"FEHLER: JSON-Datei enthält keinen 'translations' Block")
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)
# Erkenne Struktur
structure_type, languages = detect_json_structure(data)
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:
print(f"WARNUNG: Eintrag {idx} in 'translations' fehlt 'source' oder 'target', überspringe")
continue
if structure_type == "legacy":
# Alte Struktur
if 'translations' not in data:
print(f"FEHLER: JSON-Datei enthält keinen 'translations' Block")
sys.exit(1)
source = entry['source']
target = entry['target']
if source and target: # Ignoriere leere Einträge
translations[source] = target
translations_list = data['translations']
if not isinstance(translations_list, list):
print(f"FEHLER: 'translations' Block hat ungültige Struktur (erwartet Array)")
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
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,
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
- "untranslated": Liste von {source, target: ""} für nicht übersetzte 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:
texts: Liste der akzeptierten Texte
ignored_texts: Liste der ignorierten Texte
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 = {}
# Block 1: Übersetzte Texte (als Liste von Dicts mit source/target)
# Block 2: Nicht übersetzte Texte (als Liste von Dicts mit source und leerem target)
if translation_dicts:
multi, trigramme, bigramme, single = translation_dicts
translations = []
untranslated = []
# Mehrsprachige hierarchische Struktur
if translation_dicts_all and translation_languages and len(translation_languages) > 1:
for lang in translation_languages:
if lang not in translation_dicts_all:
continue
for text in 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": ""
})
multi, trigramme, bigramme, single = translation_dicts_all[lang]
translations = []
untranslated = []
output_data["translations"] = translations
output_data["untranslated"] = untranslated
for text in 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": ""
})
# 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:
# Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt
output_data["translations"] = []
output_data["untranslated"] = [{"source": text, "target": ""} for text in texts]
# Block 1: Übersetzte Texte (als Liste von Dicts mit source/target)
# Block 2: Nicht übersetzte Texte (als Liste von Dicts mit source und leerem target)
if translation_dicts:
multi, trigramme, bigramme, single = translation_dicts
translations = []
untranslated = []
# Block 3: Ignorierte Texte (als Liste)
output_data["ignored"] = ignored_texts
for text in 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:
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(output_data, f, ensure_ascii=False, indent=2)
print(f"JSON-Datei erfolgreich erstellt: {output_path}")
if translation_dicts:
print(f" Übersetzungen: {len(output_data['translations'])}")
print(f" Nicht übersetzte Texte: {len(output_data['untranslated'])}")
# Ausgabe-Statistiken
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:
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)}")
except Exception as e:
print(f"Fehler beim Schreiben der JSON-Datei: {e}")
@@ -1160,7 +1349,7 @@ if __name__ == '__main__':
'--translate',
action='store',
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(
'--lang_source',
@@ -1222,25 +1411,41 @@ if __name__ == '__main__':
symbol_attr_mapping = load_symbol_attribute_mapping(cfg_dir)
symbol_layer_mapping = load_symbol_layer_mapping(cfg_dir)
# Bestimme Quell- und Ziel-Attributnamen basierend auf Sprachcodes
source_attr_name = symbol_attr_mapping.get(args.lang_source, 'TEXT-D')
target_attr_name = symbol_attr_mapping.get(args.translate, 'TEXT-CS') if args.translate else 'TEXT-CS'
# Parse Sprachen aus --translate Parameter (kommaseparierte Liste)
translation_languages = []
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)
target_layer_name = symbol_layer_mapping.get(args.translate) if args.translate else None
# Bestimme Quell-Attributnamen basierend auf Sprachcode
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" Quell-Attribut (--lang_source {args.lang_source}): {source_attr_name}")
if args.translate:
print(f" Ziel-Attribut (--translate {args.translate}): {target_attr_name}")
if target_layer_name:
print(f" Ziel-Layer (--translate {args.translate}): {target_layer_name}")
if translation_languages:
if len(translation_languages) == 1:
print(f" Ziel-Attribut (--translate {translation_languages[0]}): {target_attr_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
translation_dicts = None
if args.translate:
# Optional: Lade Übersetzungs-Configs für alle Sprachen
# translation_dicts_all ist ein Dict: {LANG: (multi, trigramme, bigramme, single), ...}
translation_dicts_all = {}
if translation_languages:
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:
print("DEBUG-Modus aktiviert")
@@ -1286,8 +1491,9 @@ if __name__ == '__main__':
print(f"Übersetzungs-JSON: {translation_json}")
# Lade Übersetzungen
translations = load_translations_from_json(translation_json)
# Lade Übersetzungen (verwende erste Sprache als Ziel-Sprache)
target_lang = translation_languages[0] if translation_languages else None
translations = load_translations_from_json(translation_json, target_lang)
# Bestimme Ausgabe-DXF
output_dxf_name = args.todxf
@@ -1415,7 +1621,7 @@ if __name__ == '__main__':
if export_type == 'excel':
write_texts_to_excel(texts, output_path, translation_dicts)
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
write_texts_to_text(texts, ignored_texts, output_path, translation_dicts)