diff --git a/cfg/translator.cfg b/cfg/translator.cfg index 7087cbe..fed30a0 100644 --- a/cfg/translator.cfg +++ b/cfg/translator.cfg @@ -64,6 +64,8 @@ KR_code = KR-M* #h1875ff = ^\\H1.875x;.*$ #millimeter = ^\d+\s*mm$ #m_numbers = ^M\d{3}$ +ET200_UC =\+UC\d{4}\\P.*3 x ET200 SP DI16 \d{3}\.\d - \d{3}\.\d +MTEXT_UZ =\\H1\.875x.*\+UZ\d{4} # Koordinaten mit Pfeilen coord_arrows = ^\+\d{1,3}(?:[.,]\d{3})\s*(?:<<<|>>>)\s*\+\d{1,3}(?:[.,]\d{3})$ diff --git a/lib/translate.py b/lib/translate.py index 04c3547..ccc3ea1 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -32,9 +32,10 @@ SCHRITT 1 – Texte aus DXF extrahieren und Roh-JSON erzeugen Optional: zusätzliche Ausgabe als Excel oder Text (-t excel,json,text). ─────────────────────────────────────────────────────────────────────────────── -SCHRITT 2a – JSON auto-übersetzen (Drop auf tr2dxf_CS.bat / tr2dxf_EN.bat) +SCHRITT 2a – JSON/TXT auto-übersetzen (Drop auf tr2dxf_CS.bat / tr2dxf_EN.bat) ─────────────────────────────────────────────────────────────────────────────── --retranslate-json myfile_texts.json --translate CS[,EN,...] + --retranslate-txt myfile_texts.txt --translate CS Liest eine bestehende JSON-Datei (Legacy-Flat oder Multilang), übersetzt alle Texte aus 'untranslated' automatisch und verschiebt Treffer nach @@ -1937,6 +1938,201 @@ def resolve_input_file(name: str, work_dir: Path, label: str = "Datei") -> Path: return path +def _parse_txt_sections(filename: Path) -> tuple[list[tuple[str, str]], list[str], list[str]]: + """ + Parst eine Textdatei im Format von write_texts_to_text. + + Erkannte Abschnitte: + - "--- translations:" → Zeilen im Format "source -> target" + - "--- untranslated:" → einfache Texte (eine pro Zeile) + - "--- ignored:" → einfache Texte (eine pro Zeile) + + Args: + filename: Pfad zur Textdatei + + Returns: + Tuple (translations, untranslated, ignored): + - translations: Liste von (source, target) Tupeln + - untranslated: Liste von source-Texten ohne Übersetzung + - ignored: Liste von ignorierten Texten + """ + translations: list[tuple[str, str]] = [] + untranslated: list[str] = [] + ignored: list[str] = [] + current_section: str | None = None + + try: + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + stripped = line.rstrip('\n\r').strip() + + if stripped.startswith('---'): + low = stripped.lower() + if 'translations' in low: + current_section = 'translations' + elif 'untranslated' in low: + current_section = 'untranslated' + elif 'ignored' in low: + current_section = 'ignored' + else: + current_section = None + continue + + if not stripped or current_section is None: + continue + + if current_section == 'translations': + if ' -> ' in stripped: + source, target = stripped.split(' -> ', 1) + translations.append((source, target)) + else: + # Eintrag ohne Ziel → als untranslated behandeln + untranslated.append(stripped) + elif current_section == 'untranslated': + untranslated.append(stripped) + elif current_section == 'ignored': + ignored.append(stripped) + + except Exception as e: + print(f"Fehler beim Lesen der Textdatei: {e}") + sys.exit(1) + + return translations, untranslated, ignored + + +def retranslate_txt_file(txt_path: Path, + translation_dicts_all: dict[str, tuple], + translation_languages: list[str], + wildcard_patterns: list[str] | None = None, + regex_patterns: list[str] | None = None) -> None: + """ + Re-übersetzt eine Textdatei im Format von write_texts_to_text. + Überschreibt die ursprüngliche Datei mit aktualisierten Inhalten. + + Verarbeitungsreihenfolge: + 1. Bestehende translations werden mit aktueller Config neu übersetzt. + Texte für die keine Übersetzung mehr gefunden wird, wandern nach untranslated. + 2. Einträge aus untranslated werden gegen die Ignore-Pattern geprüft: + - Treffer → ignored (neu erkannte Pattern seit dem letzten Extrakt) + - Kein Treffer → auto-übersetzen; Erfolg → translations, sonst bleibt in untranslated. + + Arbeitet immer mit der ersten Sprache aus translation_languages. + + Args: + txt_path: Pfad zur Textdatei (wird in-place überschrieben) + translation_dicts_all: Dict mit {LANG: (multi, trigramme, bigramme, single), ...} + translation_languages: Liste der Sprachen (nur die erste wird verwendet) + wildcard_patterns: Wildcard-Muster zum Filtern (aus translator.cfg) + regex_patterns: Regex-Muster zum Filtern (aus translator.cfg) + """ + 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) + + wildcard_patterns = wildcard_patterns or [] + regex_patterns = regex_patterns or [] + + lang = translation_languages[0] + multi, trigramme, bigramme, single = translation_dicts_all[lang] + + existing_translations, untranslated, ignored = _parse_txt_sections(txt_path) + + print(f" Vorhandene Übersetzungen: {len(existing_translations)}") + print(f" Nicht übersetzt: {len(untranslated)}") + print(f" Ignoriert: {len(ignored)}") + + new_translations: list[tuple[str, str]] = [] + updated = 0 + unchanged = 0 + moved_to_untranslated: list[str] = [] + + # Schritt 1: Bestehende Übersetzungen neu übersetzen + for source, old_target in existing_translations: + new_target = translate_text(source, multi, trigramme, bigramme, single) + if new_target: + new_translations.append((source, new_target)) + if new_target != old_target: + updated += 1 + else: + unchanged += 1 + else: + moved_to_untranslated.append(source) + print(f" WARNUNG: Übersetzung nicht mehr verfügbar für '{source}', " + f"verschoben zu 'untranslated'") + + # Schritt 2: Untranslated Texte prüfen und auto-übersetzen + still_untranslated: list[str] = list(moved_to_untranslated) + newly = 0 + newly_ignored = 0 + for text in untranslated: + # Neu hinzugefügte Ignore-Pattern prüfen + if should_ignore_text(text, wildcard_patterns, regex_patterns): + ignored.append(text) + newly_ignored += 1 + continue + new_target = translate_text(text, multi, trigramme, bigramme, single) + if new_target: + new_translations.append((text, new_target)) + newly += 1 + else: + still_untranslated.append(text) + + # Ergebnis zurückschreiben + try: + with open(txt_path, 'w', encoding='utf-8') as f: + f.write("--- translations:\n\n") + for source, target in new_translations: + f.write(f"{source} -> {target}\n") + f.write("\n--- untranslated:\n\n") + for text in still_untranslated: + f.write(f"{text}\n") + f.write("\n--- ignored:\n\n") + for text in ignored: + f.write(f"{text}\n") + except Exception as e: + print(f"Fehler beim Schreiben der Textdatei: {e}") + sys.exit(1) + + print(f"\nRe-Übersetzung abgeschlossen (Sprache: {lang}):") + print(f" Aktualisiert: {updated}") + print(f" Unverändert: {unchanged}") + if newly: + print(f" Neu übersetzt: {newly}") + if newly_ignored: + print(f" Neu ignoriert (Pattern): {newly_ignored}") + if moved_to_untranslated: + print(f" Zurück zu untranslated: {len(moved_to_untranslated)}") + print(f" Noch nicht übersetzt: {len(still_untranslated)}") + print(f"\nDatei aktualisiert: {txt_path}") + + +def _run_retranslate_txt( + args, + work_dir: Path, + translation_dicts_all: dict, + translation_languages: list, + wildcard_patterns: list, + regex_patterns: list) -> None: + """Workflow: Re-Übersetzung einer bestehenden TXT-Datei (--retranslate-txt).""" + print("=== Re-Übersetzung TXT-Datei ===\n") + + input_filename = args.retranslate_txt + if not input_filename.lower().endswith('.txt'): + print("Mit --retranslate-txt nur .txt Dateien erlaubt") + sys.exit(1) + + txt_file = resolve_input_file(input_filename, work_dir, "TXT-Datei") + + print(f"TXT-Datei: {txt_file}") + print(f"Sprache: {translation_languages[0]}") + + retranslate_txt_file(txt_file, translation_dicts_all, translation_languages, + wildcard_patterns, regex_patterns) + + print("\nVerarbeitung erfolgreich abgeschlossen.") + sys.exit(0) + + def _build_arg_parser() -> argparse.ArgumentParser: """Erstellt und konfiguriert den Argument-Parser für translate.py.""" parser = argparse.ArgumentParser( @@ -1969,6 +2165,12 @@ def _build_arg_parser() -> argparse.ArgumentParser: metavar='FILE.json', help='Re-übersetzt translations Block in JSON-Datei mit aktueller Sprachkonfig (benötigt --translate)' ) + input_group.add_argument( + '--retranslate-txt', + action='store', + metavar='FILE.txt', + help='Re-übersetzt translations/untranslated Blöcke in TXT-Datei mit aktueller Sprachkonfig (benötigt --translate)' + ) input_group.add_argument( '--todxf', action='store', @@ -2208,6 +2410,8 @@ def main() -> None: parser.error("--fromjson benötigt --translate (Übersetzung ist erforderlich)") if args.retranslate_json and not args.translate: parser.error("--retranslate-json benötigt --translate (Übersetzung ist erforderlich)") + if args.retranslate_txt and not args.translate: + parser.error("--retranslate-txt benötigt --translate (Übersetzung ist erforderlich)") if args.todxf and not args.filename: parser.error("--todxf benötigt --filename (Quell-DXF muss angegeben werden)") if args.translation_json and not args.todxf: @@ -2262,6 +2466,10 @@ def main() -> None: if args.retranslate_json: _run_retranslate(args, work_dir, translation_dicts_all, translation_languages) + if args.retranslate_txt: + _run_retranslate_txt(args, work_dir, translation_dicts_all, translation_languages, + wildcard_patterns, regex_patterns) + if args.todxf: _run_todxf(args, work_dir, translation_languages, wildcard_patterns, regex_patterns, source_attr_name, target_attr_name, target_layer_name)