From 418882e78f4c8362ae2151f7e8642608e19262ea Mon Sep 17 00:00:00 2001 From: mistangl Date: Thu, 4 Dec 2025 10:29:09 +0100 Subject: [PATCH] =?UTF-8?q?Schalter=20--fromtext=20modifiziert,=20dass=20B?= =?UTF-8?q?lockangaben=20wie=20ignored,=20etc.=20=C3=9CBerlesen=20werden.?= =?UTF-8?q?=20Schalter=20-t=20mit=20json=20als=20Ziel=20modifiziert,=20das?= =?UTF-8?q?s=20die=20originaltexte=20als=20leere=20Eintr=C3=A4ge=20vorhand?= =?UTF-8?q?en=20sind,=20die=20mit=20Deepl=20leicht=20=C3=BCbersetzt=20werd?= =?UTF-8?q?en=20k=C3=B6nnen.=20-t=20text=20hinzugef=C3=BCgt,=20dass=20aus?= =?UTF-8?q?=20einer=20dxf=20Datei=20erst=20einmal=20einfach=20eine=20Textd?= =?UTF-8?q?atei=20entsteht,=20die=20man=20im=20Editor=20aufmachen=20kann.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/translate.py | 219 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 203 insertions(+), 16 deletions(-) diff --git a/lib/translate.py b/lib/translate.py index 51cc6fb..4fd21a8 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -15,6 +15,7 @@ from openpyxl import Workbook Dieses Programm: - liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte ODER liest Texte aus einer TXT Datei (ein Text pro Zeile) + ODER liest den 'untranslated' Block aus einer JSON Datei - filtert die Texte basierend auf Mustern in translator.cfg - übersetzt optional Texte mit Trigrammen, Bigrammen und Einzelwörtern aus translation/.cfg - schreibt die Ergebnisse in eine Excel- oder JSON-Datei @@ -352,6 +353,7 @@ def read_texts_from_file(filename: Path) -> list[str]: """ Liest Texte aus einer UTF-8 Textdatei (ein Text pro Zeile). Entfernt Duplikate und leere Zeilen. + Ignoriert Abschnitts-Marker (Zeilen die mit "---" beginnen). Args: filename: Pfad zur Textdatei @@ -366,6 +368,9 @@ def read_texts_from_file(filename: Path) -> list[str]: for line in f: text = line.strip() if text: # Ignoriere leere Zeilen + # Ignoriere Abschnitts-Marker (z.B. "--- translations:", "--- ignored:") + if text.startswith('---'): + continue # Konvertiere escaped Zeilenumbrüche zurück text = text.replace('\\n', '\n').replace('\\r', '\r') texts_set.add(text) @@ -379,6 +384,62 @@ def read_texts_from_file(filename: Path) -> list[str]: sys.exit(1) +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": ""}, ...] + + Args: + filename: Pfad zur JSON-Datei + + Returns: + Alphabetisch sortierte Liste aller "source" Texte aus dem untranslated Block + """ + try: + with open(filename, '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__})") + sys.exit(1) + + 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) + + 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 + + except json.JSONDecodeError as e: + print(f"Fehler beim Parsen der JSON-Datei: {e}") + sys.exit(1) + except Exception as e: + print(f"Fehler beim Lesen der JSON-Datei: {e}") + sys.exit(1) + + def write_texts_to_excel(texts: list[str], output_path: Path, translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str]] = None): """ @@ -431,6 +492,11 @@ def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: """ Schreibt die Texte in eine JSON-Datei mit drei Blöcken. + Die JSON-Struktur enthält: + - "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 + Args: texts: Liste der akzeptierten Texte ignored_texts: Liste der ignorierten Texte @@ -440,11 +506,11 @@ def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: output_data = {} # Block 1: Übersetzte Texte (als Liste von Dicts mit source/target) - # Block 2: Nicht übersetzte Texte (nur akzeptierte Texte ohne Übersetzung) + # Block 2: Nicht übersetzte Texte (als Liste von Dicts mit source und leerem target) if translation_dicts: trigramme, bigramme, single = translation_dicts translations = [] - untranslated_texts = [] + untranslated = [] for text in texts: translated = translate_text(text, trigramme, bigramme, single) @@ -454,14 +520,17 @@ def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: "target": translated }) else: # Keine Übersetzung vorhanden - untranslated_texts.append(text) + untranslated.append({ + "source": text, + "target": "" + }) output_data["translations"] = translations - output_data["texts"] = untranslated_texts + output_data["untranslated"] = untranslated else: # Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt output_data["translations"] = [] - output_data["texts"] = texts + output_data["untranslated"] = [{"source": text, "target": ""} for text in texts] # Block 3: Ignorierte Texte (als Liste) output_data["ignored"] = ignored_texts @@ -473,22 +542,101 @@ def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: 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['texts'])}") + print(f" Nicht übersetzte Texte: {len(output_data['untranslated'])}") else: - print(f" Akzeptierte Texte: {len(output_data['texts'])}") + 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}") sys.exit(1) +def write_texts_to_text(texts: list[str], ignored_texts: list[str], output_path: Path, + translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str]] = None): + """ + Schreibt die Texte in eine Textdatei mit drei Abschnitten. + + Format: + --- translations: + + source1 -> target1 + source2 -> target2 + + --- untranslated: + + text1 + text2 + + --- ignored: + + ignored1 + ignored2 + + Args: + texts: Liste der akzeptierten Texte + ignored_texts: Liste der ignorierten Texte + output_path: Pfad zur Ausgabe-Textdatei + translation_dicts: Optional: Tuple mit (Trigramme, Bigramme, Single) für Übersetzungen + """ + try: + with open(output_path, 'w', encoding='utf-8') as f: + # Block 1: Übersetzte Texte + if translation_dicts: + trigramme, bigramme, single = translation_dicts + translations = [] + untranslated = [] + + for text in texts: + translated = translate_text(text, trigramme, bigramme, single) + if translated: + translations.append((text, translated)) + else: + untranslated.append(text) + + # Schreibe translations Block + f.write("--- translations:\n\n") + for source, target in translations: + f.write(f"{source} -> {target}\n") + + # Schreibe untranslated Block + f.write("\n--- untranslated:\n\n") + for text in untranslated: + f.write(f"{text}\n") + + else: + # Ohne Übersetzungs-Config: alle Texte sind nicht übersetzt + f.write("--- translations:\n\n") + f.write("\n--- untranslated:\n\n") + for text in texts: + f.write(f"{text}\n") + + # Block 3: Ignorierte Texte + f.write("\n--- ignored:\n\n") + for text in ignored_texts: + f.write(f"{text}\n") + + print(f"Text-Datei erfolgreich erstellt: {output_path}") + if translation_dicts: + translations_count = sum(1 for text in texts if translate_text(text, trigramme, bigramme, single)) + untranslated_count = len(texts) - translations_count + print(f" Übersetzungen: {translations_count}") + print(f" Nicht übersetzte Texte: {untranslated_count}") + else: + print(f" Nicht übersetzte Texte: {len(texts)}") + print(f" Ignorierte Texte: {len(ignored_texts)}") + + except Exception as e: + print(f"Fehler beim Schreiben der Text-Datei: {e}") + sys.exit(1) + + if __name__ == '__main__': parser = argparse.ArgumentParser( - description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei oder filtert Texte aus einer TXT Datei. Optional mit Übersetzungsfunktion.', + description='Extrahiert TEXT/MTEXT aus DXF, filtert Texte aus TXT, oder verarbeitet untranslated Block aus JSON. Optional mit Übersetzungsfunktion.', prog='translate' ) - # Mutually exclusive group: entweder DXF-Extraktion oder TXT-Filterung + # Mutually exclusive group: entweder DXF-Extraktion, TXT-Filterung oder JSON-Reprocessing input_group = parser.add_mutually_exclusive_group(required=True) input_group.add_argument( '-e', '--extract', @@ -501,6 +649,12 @@ if __name__ == '__main__': metavar='FILE.txt', help='Filtert Texte aus einer TXT Datei (ein Text pro Zeile)' ) + input_group.add_argument( + '--fromjson', + action='store', + metavar='FILE.json', + help='Liest untranslated Block aus JSON-Datei und übersetzt erneut (benötigt --translate)' + ) parser.add_argument( '-f', '--filename', @@ -522,9 +676,9 @@ if __name__ == '__main__': parser.add_argument( '-t', '--export-type', action='store', - default='excel', - choices=['excel', 'json'], - help='Exportformat: excel (Standard) oder json (JSON-Datei)' + default='json', + choices=['excel', 'json', 'text'], + help='Exportformat: json (Standard), excel (Excel-Datei) oder text (Textdatei mit Abschnitten)' ) parser.add_argument( '--translate', @@ -543,6 +697,10 @@ if __name__ == '__main__': if args.filename and not args.extract: parser.error("--filename kann nur mit --extract verwendet werden") + # Validierung: --fromjson benötigt --translate + if args.fromjson and not args.translate: + parser.error("--fromjson benötigt --translate (Übersetzung ist erforderlich)") + # Prüfe Umgebungsvariablen work_dir = check_environment_var('PROJECT_WORK') cfg_dir = check_environment_var('PROJECT_CFG') @@ -583,7 +741,7 @@ if __name__ == '__main__': print(f"Lese DXF Datei: {filename}") all_texts = extract_text_from_dxf(filename) - else: # args.fromtxt + elif args.fromtxt: # TXT-Modus input_filename = args.fromtxt @@ -605,6 +763,28 @@ if __name__ == '__main__': print(f"Lese Textdatei: {filename}") all_texts = read_texts_from_file(filename) + else: # args.fromjson + # JSON-Modus + input_filename = args.fromjson + + if not input_filename.lower().endswith('.json'): + print("Mit --fromjson nur .json Dateien erlaubt") + sys.exit(1) + + # Versuche zuerst Datei im Work-Ordner zu finden + filename = work_dir / input_filename + if not filename.exists(): + # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist + filename = Path(input_filename) + if not filename.exists(): + print(f"Datei nicht gefunden: {input_filename}") + print(f" Geprüft in Work-Ordner: {work_dir / input_filename}") + print(f" Geprüft als vollständiger Pfad: {filename}") + sys.exit(1) + + print(f"Lese JSON-Datei: {filename}") + all_texts = read_texts_from_json(filename) + # Schritt 2: Texte filtern texts, ignored_texts = filter_texts(all_texts, wildcard_patterns, regex_patterns, args.debug) @@ -616,22 +796,29 @@ if __name__ == '__main__': if args.export_type == 'excel': if not output_filename.endswith('.xlsx'): output_filename += '.xlsx' - else: # json + elif args.export_type == 'json': if not output_filename.endswith('.json'): output_filename += '.json' + else: # text + if not output_filename.endswith('.txt'): + output_filename += '.txt' else: # Standardname basierend auf Eingabedatei if args.export_type == 'excel': output_filename = filename.stem + "_texts.xlsx" - else: # json + elif args.export_type == 'json': output_filename = filename.stem + "_texts.json" + else: # text + output_filename = filename.stem + "_texts.txt" output_path = work_dir / output_filename # Schreibe Texte in gewähltem Format if args.export_type == 'excel': write_texts_to_excel(texts, output_path, translation_dicts) - else: # json + elif args.export_type == 'json': write_texts_to_json(texts, ignored_texts, output_path, translation_dicts) + else: # text + write_texts_to_text(texts, ignored_texts, output_path, translation_dicts) print("Verarbeitung erfolgreich abgeschlossen.")