From 46e2d8663bcef0c3fc16091a6bee6abdf2088adf Mon Sep 17 00:00:00 2001 From: mistangl Date: Tue, 10 Feb 2026 15:51:09 +0100 Subject: [PATCH] Es wird in Zukunft immer ein multilang Block geschrieben. Nur ohne --translate wird die flache Hierarchie nach wie vor bedient --- lib/translate.py | 286 ++++++++++++++++++++++------------------------- 1 file changed, 135 insertions(+), 151 deletions(-) diff --git a/lib/translate.py b/lib/translate.py index a9a1aac..f00ff0d 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -1289,10 +1289,13 @@ def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: diese ERHALTEN und nur die neu angegebenen Sprachen aktualisiert. So können CS- und EN-Übersetzungen schrittweise in dieselbe Datei geschrieben werden. - Nur ohne --translate wird die flache Struktur verwendet: - - "translations": [...] + Nur ohne --translate (kein translation_languages) wird die flache Legacy-Struktur + erzeugt – sie dient als reiner Rohextrakt ohne Sprachzuordnung: + - "translations": [] (immer leer, da keine Sprache bekannt) - "untranslated": [...] - "ignored": [...] + Diese Dateien können anschliessend mit --retranslate-json + --translate LANG + in das Multilang-Format konvertiert und übersetzt werden. Args: texts: Liste der akzeptierten Texte @@ -1486,17 +1489,94 @@ def write_texts_to_text(texts: list[str], ignored_texts: list[str], output_path: sys.exit(1) +def _retranslate_language_block( + translations_list: list, + untranslated_list: list, + multi: dict[str, str], + trigramme: dict[str, str], + bigramme: dict[str, str], + single: dict[str, str], + label: str) -> tuple[int, int, int]: + """ + Verarbeitet einen einzelnen Sprach-Block einer JSON-Übersetzungsdatei in-place. + + Schritt 1 – Re-übersetze bestehende Einträge in translations_list: + Jeder Eintrag wird mit der aktuellen Config neu übersetzt. + Wird keine Übersetzung mehr gefunden, wandert der Eintrag nach untranslated_list. + + Schritt 2 – Auto-übersetze Einträge aus untranslated_list: + Für jeden Eintrag mit leerem target wird auto-übersetzt; + erfolgreiche Treffer wandern nach translations_list. + Einträge mit bereits gesetztem target (manuell befüllt) werden + ebenfalls direkt nach translations_list übernommen. + + Args: + translations_list: Liste der bereits übersetzten Einträge (in-place verändert) + untranslated_list: Liste der noch nicht übersetzten Einträge (in-place verändert) + multi: Multi-Phrasen-Dict + trigramme: Trigramme-Dict + bigramme: Bigramme-Dict + single: Einzelwörter-Dict + label: Bezeichnung für Warnmeldungen, z.B. "[CS]" + + Returns: + Tuple (aktualisiert, unveraendert, neu_aus_untranslated) + """ + updated = 0 + unchanged = 0 + newly = 0 + + # Schritt 1: Re-übersetze bestehende translations-Einträge + for entry in translations_list[:]: # Kopie für sicheres Entfernen + if not isinstance(entry, dict) or 'source' not in entry: + continue + source = entry['source'] + old_target = entry.get('target', '') + new_target = translate_text(source, multi, trigramme, bigramme, single) + if new_target: + if new_target != old_target: + entry['target'] = new_target + updated += 1 + else: + unchanged += 1 + else: + # Übersetzung nicht mehr verfügbar → nach untranslated verschieben + untranslated_list.append({"source": source, "target": ""}) + translations_list.remove(entry) + print(f" WARNUNG: {label} Übersetzung für '{source}' nicht mehr verfügbar, " + f"verschoben zu 'untranslated'") + + # Schritt 2: Auto-übersetze Einträge aus untranslated_list + for entry in untranslated_list[:]: # Kopie für sicheres Entfernen + if not isinstance(entry, dict) or 'source' not in entry: + continue + if entry.get('target', ''): + # Manuell befüllter Eintrag → direkt nach translations verschieben + translations_list.append(entry) + untranslated_list.remove(entry) + newly += 1 + continue + source = entry['source'] + new_target = translate_text(source, multi, trigramme, bigramme, single) + if new_target: + translations_list.append({"source": source, "target": new_target}) + untranslated_list.remove(entry) + newly += 1 + + return updated, unchanged, newly + + def retranslate_json_file(json_path: Path, translation_dicts_all: dict[str, tuple], translation_languages: list[str]): """ Re-übersetzt eine JSON-Datei mit neuen Übersetzungs-Configs. - Liest alle "source" Texte aus dem "translations" Block und übersetzt sie neu. Überschreibt die ursprüngliche JSON-Datei mit den aktualisierten Übersetzungen. - Unterstützt beide Strukturen: - - Alte Struktur: "translations": [{"source": "...", "target": "..."}, ...] - - Hierarchische Struktur: "[LANG]": {"translations": [...], ...} + Erkennt die Dateistruktur automatisch und delegiert die eigentliche + Übersetzungsarbeit an _retranslate_language_block(): + - Alte Struktur: "translations"/ "untranslated" auf oberster Ebene + - Hierarchische Struktur: "[CS]": {"translations": ..., "untranslated": ...} Args: json_path: Pfad zur JSON-Datei @@ -1504,203 +1584,107 @@ def retranslate_json_file(json_path: Path, translation_languages: Liste der Sprachen in der Reihenfolge der Verarbeitung """ try: - # Lade bestehende JSON-Datei with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) if not isinstance(data, dict): - 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, " + f"bekam {type(data).__name__})") sys.exit(1) - # Erkenne Struktur structure_type, existing_languages = detect_json_structure(data) - updated_count = 0 - unchanged_count = 0 - new_count = 0 + total_updated = 0 + total_unchanged = 0 + total_newly = 0 + new_block_count = 0 if structure_type == "legacy": - # Alte Struktur: ein Sprach-Block - if 'translations' not in data: - print(f"FEHLER: JSON-Datei enthält keinen 'translations' Block") + if 'translations' not in data or not isinstance(data['translations'], list): + print(f"FEHLER: JSON-Datei enthält keinen gültigen '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) - - # Verwende erste Sprache für Re-Übersetzung if not translation_languages or translation_languages[0] not in translation_dicts_all: print(f"FEHLER: Keine Übersetzungs-Config für Re-Übersetzung verfügbar") sys.exit(1) - multi, trigramme, bigramme, single = translation_dicts_all[translation_languages[0]] + if 'untranslated' not in data: + data['untranslated'] = [] - # Re-übersetze alle Einträge in translations - for entry in translations_list[:]: # Kopie für sicheres Entfernen - if not isinstance(entry, dict) or 'source' not in entry: - continue + lang = translation_languages[0] + multi, trigramme, bigramme, single = translation_dicts_all[lang] - source = entry['source'] - old_target = entry.get('target', '') + updated, unchanged, newly = _retranslate_language_block( + data['translations'], data['untranslated'], + multi, trigramme, bigramme, single, + f"[{lang}]" + ) + total_updated, total_unchanged, total_newly = updated, unchanged, newly - # Neue Übersetzung berechnen - new_target = translate_text(source, multi, trigramme, bigramme, single) + print(f"\nRe-Übersetzung abgeschlossen (alte Struktur → wird zu multilang konvertiert, Sprache: {lang}):") + print(f" Aktualisiert (translations): {updated}") + print(f" Unverändert: {unchanged}") + if newly: + print(f" Neu aus 'untranslated' übersetzt/verschoben: {newly}") - if new_target: - if new_target != old_target: - entry['target'] = new_target - updated_count += 1 - else: - unchanged_count += 1 - else: - # Keine Übersetzung mehr verfügbar -> in untranslated verschieben - if 'untranslated' not in data: - data['untranslated'] = [] - data['untranslated'].append({ - "source": source, - "target": "" - }) - translations_list.remove(entry) - print(f" WARNUNG: Übersetzung für '{source}' nicht mehr verfügbar, verschoben zu 'untranslated'") - - # Versuche auch Einträge aus untranslated zu auto-übersetzen und nach translations verschieben - untranslated_list = data.get('untranslated', []) - newly_translated = 0 - for entry in untranslated_list[:]: # Kopie für sicheres Entfernen - if not isinstance(entry, dict) or 'source' not in entry: - continue - if entry.get('target', ''): - # Manuell befüllter Eintrag – bereits übersetzt, in translations verschieben - translations_list.append(entry) - untranslated_list.remove(entry) - newly_translated += 1 - updated_count += 1 - continue - source = entry['source'] - new_target = translate_text(source, multi, trigramme, bigramme, single) - if new_target: - translations_list.append({"source": source, "target": new_target}) - untranslated_list.remove(entry) - newly_translated += 1 - updated_count += 1 - - print(f"\nRe-Übersetzung abgeschlossen (alte Struktur, Sprache: {translation_languages[0]}):") - print(f" Aktualisiert (translations): {updated_count - newly_translated}") - print(f" Unverändert: {unchanged_count}") - if newly_translated: - print(f" Neu aus 'untranslated' übersetzt/verschoben: {newly_translated}") + # Legacy → Multilang konvertieren: flache Struktur in [LANG]-Block einpacken + data = { + f"[{lang}]": { + "translations": data['translations'], + "untranslated": data['untranslated'], + }, + "ignored": data.get('ignored', []), + } elif structure_type == "multilang": - # Hierarchische mehrsprachige Struktur print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(existing_languages)}") - # Re-übersetze für alle angegebenen Sprachen for lang in translation_languages: lang_key = f"[{lang}]" - - # Wenn Sprache nicht in JSON existiert, erstelle neuen Block if lang_key not in data: print(f" Erstelle neuen Sprach-Block: [{lang}]") - data[lang_key] = { - "translations": [], - "untranslated": [] - } - new_count += 1 + data[lang_key] = {"translations": [], "untranslated": []} + new_block_count += 1 lang_data = data[lang_key] if not isinstance(lang_data, dict): print(f"WARNUNG: [{lang}] Block ist kein Object, überspringe") continue - - # Stelle sicher dass translations Block existiert if 'translations' not in lang_data: lang_data['translations'] = [] if 'untranslated' not in lang_data: lang_data['untranslated'] = [] - - translations_list = lang_data['translations'] - if not isinstance(translations_list, list): + if not isinstance(lang_data['translations'], list): print(f"WARNUNG: [{lang}][translations] ist kein Array, überspringe") continue - - # Hole Übersetzungs-Config für diese Sprache if lang not in translation_dicts_all: print(f"WARNUNG: Keine Übersetzungs-Config für [{lang}] verfügbar, überspringe") continue multi, trigramme, bigramme, single = translation_dicts_all[lang] + updated, unchanged, newly = _retranslate_language_block( + lang_data['translations'], lang_data['untranslated'], + multi, trigramme, bigramme, single, + f"[{lang}]" + ) + total_updated += updated + newly + total_unchanged += unchanged + total_newly += newly - lang_updated = 0 - lang_unchanged = 0 - - # Re-übersetze alle Einträge in translations - for entry in translations_list[:]: # Kopie der Liste für sicheres Entfernen - if not isinstance(entry, dict) or 'source' not in entry: - continue - - source = entry['source'] - old_target = entry.get('target', '') - - # Neue Übersetzung berechnen - new_target = translate_text(source, multi, trigramme, bigramme, single) - - if new_target: - if new_target != old_target: - entry['target'] = new_target - lang_updated += 1 - else: - lang_unchanged += 1 - else: - # Keine Übersetzung mehr verfügbar -> in untranslated verschieben - lang_data['untranslated'].append({ - "source": source, - "target": "" - }) - translations_list.remove(entry) - print(f" WARNUNG: [{lang}] Übersetzung für '{source}' nicht mehr verfügbar, verschoben zu 'untranslated'") - - # Versuche auch Einträge aus untranslated zu auto-übersetzen und nach translations verschieben - untranslated_list = lang_data['untranslated'] - lang_newly = 0 - for entry in untranslated_list[:]: # Kopie für sicheres Entfernen - if not isinstance(entry, dict) or 'source' not in entry: - continue - if entry.get('target', ''): - # Manuell befüllter Eintrag – bereits übersetzt, in translations verschieben - translations_list.append(entry) - untranslated_list.remove(entry) - lang_newly += 1 - lang_updated += 1 - continue - source = entry['source'] - new_target = translate_text(source, multi, trigramme, bigramme, single) - if new_target: - translations_list.append({"source": source, "target": new_target}) - untranslated_list.remove(entry) - lang_newly += 1 - lang_updated += 1 - - updated_count += lang_updated - unchanged_count += lang_unchanged - - print(f" [{lang}] Aktualisiert: {lang_updated - lang_newly}, Unverändert: {lang_unchanged}", end="") - if lang_newly: - print(f", Neu aus 'untranslated': {lang_newly}", end="") + print(f" [{lang}] Aktualisiert: {updated}, Unverändert: {unchanged}", end="") + if newly: + print(f", Neu aus 'untranslated': {newly}", end="") print() print(f"\nRe-Übersetzung abgeschlossen (mehrsprachige Struktur):") - print(f" Gesamt aktualisiert: {updated_count}") - print(f" Gesamt unverändert: {unchanged_count}") - if new_count > 0: - print(f" Neue Sprach-Blöcke erstellt: {new_count}") + print(f" Gesamt aktualisiert: {total_updated}") + print(f" Gesamt unverändert: {total_unchanged}") + if new_block_count: + print(f" Neue Sprach-Blöcke erstellt: {new_block_count}") else: print(f"FEHLER: JSON-Datei hat unbekannte Struktur") sys.exit(1) - # Schreibe aktualisierte JSON-Datei zurück with open(json_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2)