diff --git a/bin/setenv.bat b/bin/setenv.bat index bb7f15b..5507701 100644 --- a/bin/setenv.bat +++ b/bin/setenv.bat @@ -10,6 +10,7 @@ set PROJECT_LIB=%PROJECT%\lib set PROJECT_DATA=%PROJECT%\data set PROJECT_WORK=%PROJECT%\work set PROJECT_TEST=%PROJECT%\testdata +set PROJECT_LOG=%PROJECT%\logs set PROJECT_TRANSLATION=%PROJECT%\translation set SIVAS_TEILESTAMM=\\195.243.223.3\sivas\jit\programme\KSbExcelExportSivasTeilestamm.exe @@ -18,6 +19,7 @@ set SIVAS_EXCEL_EXPORT_DIR=%PROJECT_DATA% if not exist %PROJECT%\work mkdir %PROJECT%\work if not exist %PROJECT%\data mkdir %PROJECT%\data if not exist %PROJECT%\translation mkdir %PROJECT%\translation +if not exist %PROJECT%\logs mkdir %PROJECT%\logs set PROJECT_IO_RESULTS=Z:\Automatisierung\Konvertierungstools\4_io-Konvertierer set PROJECT_BOM_RESULTS=Z:\Automatisierung\Konvertierungstools\3_Kabellaengen-Konvertierer diff --git a/lib/translate.py b/lib/translate.py index eb6075b..f06cbdc 100644 --- a/lib/translate.py +++ b/lib/translate.py @@ -1,4 +1,5 @@ import argparse +import json import os import re import sys @@ -15,7 +16,8 @@ Dieses Programm: - liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte ODER liest Texte aus einer TXT Datei (ein Text pro Zeile) - filtert die Texte basierend auf Mustern in translator.cfg -- schreibt die Ergebnisse in eine Excel- oder Textdatei +- übersetzt optional Texte mit Trigrammen, Bigrammen und Einzelwörtern aus translation/.cfg +- schreibt die Ergebnisse in eine Excel- oder JSON-Datei - speichert die Ausgabedatei im PROJECT_WORK Verzeichnis """ @@ -30,6 +32,69 @@ def check_environment_var(env_str: str) -> Path: sys.exit(1) +def load_translation_config(translation_lang: str, translation_dir: Path) -> tuple[dict[str, str], dict[str, str], dict[str, str]]: + """ + Lädt eine Übersetzungs-Config-Datei (z.B. CSK.cfg, EN.cfg, FR.cfg). + + Args: + translation_lang: Sprach-Code (z.B. "CSK", "EN", "FR") + translation_dir: Verzeichnis mit Übersetzungs-Configs (aus PROJECT_TRANSLATION) + + Returns: + Tuple mit (Trigramme-Dict, Bigramme-Dict, Single-Dict) + """ + config_file = translation_dir / f"{translation_lang}.cfg" + + if not config_file.exists(): + print(f"FEHLER: Übersetzungs-Konfiguration nicht gefunden: {config_file}") + sys.exit(1) + + # RawConfigParser mit case-sensitive Keys + config = RawConfigParser() + # Überschreibe optionxform um Groß-/Kleinschreibung zu erhalten + config.optionxform = str + try: + config.read(config_file, encoding='utf-8') + except Exception as e: + print(f"Fehler beim Lesen der Übersetzungs-Konfiguration: {e}") + sys.exit(1) + + trigramme = {} + bigramme = {} + single = {} + + # Lade Trigramme + if config.has_section('trigramme'): + for key in config['trigramme']: + original = key + translation = config['trigramme'][key] + if translation and not translation.startswith('#'): + trigramme[original] = translation + + # Lade Bigramme + if config.has_section('bigramme'): + for key in config['bigramme']: + original = key + translation = config['bigramme'][key] + if translation and not translation.startswith('#'): + bigramme[original] = translation + + # Lade Single + if config.has_section('single'): + for key in config['single']: + original = key + translation = config['single'][key] + if translation and not translation.startswith('#'): + single[original] = translation + + print(f"\nÜbersetzungs-Config geladen ({translation_lang}):") + print(f" Trigramme: {len(trigramme)}") + print(f" Bigramme: {len(bigramme)}") + print(f" Einzelwörter: {len(single)}") + + return trigramme, bigramme, single + + def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: """ Liest die ignore_pattern aus der translator.cfg Datei. @@ -50,6 +115,8 @@ def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: # RawConfigParser verhindert dass Backslashes in Regex-Mustern escaped werden config = RawConfigParser() + # Überschreibe optionxform um Groß-/Kleinschreibung zu erhalten + config.optionxform = str try: config.read(config_file, encoding='utf-8') except Exception as e: @@ -84,6 +151,50 @@ def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: return wildcard_patterns, regex_patterns +def translate_text(text: str, trigramme: dict[str, str], bigramme: dict[str, str], single: dict[str, str]) -> str: + """ + Übersetzt einen Text durch Ersetzen von Trigrammen, Bigrammen und Einzelwörtern. + Ersetzungen erfolgen in dieser Reihenfolge: Trigramme → Bigramme → Einzelwörter. + + Args: + text: Zu übersetzender Text + trigramme: Dictionary mit Drei-Wort-Phrasen + bigramme: Dictionary mit Zwei-Wort-Phrasen + single: Dictionary mit Einzelwörtern + + Returns: + Übersetzter Text (leer wenn keine Übersetzung gefunden wurde) + """ + translated_text = text + has_translation = False + + # 1. Trigramme ersetzen (längste Phrasen zuerst) + for original, translation in trigramme.items(): + if original in translated_text: + translated_text = translated_text.replace(original, translation) + has_translation = True + + # 2. Bigramme ersetzen + for original, translation in bigramme.items(): + if original in translated_text: + translated_text = translated_text.replace(original, translation) + has_translation = True + + # 3. Einzelwörter ersetzen + words = translated_text.split() + translated_words = [] + for word in words: + if word in single: + translated_words.append(single[word]) + has_translation = True + else: + translated_words.append(word) + translated_text = ' '.join(translated_words) + + # Gib nur übersetzten Text zurück wenn tatsächlich etwas übersetzt wurde + return translated_text if has_translation else "" + + def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> bool: """ Prüft ob ein Text ignoriert werden soll basierend auf den Mustern. @@ -245,13 +356,16 @@ def read_texts_from_file(filename: Path) -> list[str]: sys.exit(1) -def write_texts_to_excel(texts: list[str], output_path: Path): +def write_texts_to_excel(texts: list[str], output_path: Path, + translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str]] = None): """ Schreibt die Texte in die erste Spalte einer Excel-Datei. + Optional: Schreibt Übersetzungen in die zweite Spalte. Args: texts: Liste der zu schreibenden Texte output_path: Pfad zur Ausgabe-Excel-Datei + translation_dicts: Optional: Tuple mit (Trigramme, Bigramme, Single) für Übersetzungen """ wb = Workbook() ws = wb.active @@ -259,61 +373,95 @@ def write_texts_to_excel(texts: list[str], output_path: Path): # Kopfzeile ws['A1'] = "Text" + if translation_dicts: + ws['B1'] = "Übersetzung" - # Texte in die erste Spalte schreiben + # Texte schreiben for idx, text in enumerate(texts, start=2): ws[f'A{idx}'] = text - # Spaltenbreite anpassen + # Optional: Übersetzung in zweite Spalte + if translation_dicts: + trigramme, bigramme, single = translation_dicts + translated = translate_text(text, trigramme, bigramme, single) + if translated: # Nur wenn Übersetzung gefunden wurde + ws[f'B{idx}'] = translated + # Sonst bleibt die Zelle leer + + # Spaltenbreiten anpassen ws.column_dimensions['A'].width = 50 + if translation_dicts: + ws.column_dimensions['B'].width = 50 try: wb.save(output_path) print(f"Excel-Datei erfolgreich erstellt: {output_path}") + if translation_dicts: + print(f" Mit Übersetzungen in Spalte B") except Exception as e: print(f"Fehler beim Schreiben der Excel-Datei: {e}") sys.exit(1) -def write_texts_to_textfile(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]] = None): """ - Schreibt die Texte in eine UTF-8 Textdatei. - Jeder Text wird in einer eigenen Zeile geschrieben (auch Multiline-Texte). - Die Datei enthält zwei Teile: akzeptierte und ignorierte Texte. + Schreibt die Texte in eine JSON-Datei mit drei Blöcken. Args: texts: Liste der akzeptierten Texte ignored_texts: Liste der ignorierten Texte - output_path: Pfad zur Ausgabe-Textdatei + output_path: Pfad zur Ausgabe-JSON-Datei + translation_dicts: Optional: Tuple mit (Trigramme, Bigramme, Single) für Übersetzungen """ + output_data = {} + + # Block 1: Übersetzte Texte (als Liste von Dicts mit source/target) + # Block 2: Nicht übersetzte Texte (nur akzeptierte Texte ohne Übersetzung) + if translation_dicts: + trigramme, bigramme, single = translation_dicts + translations = [] + untranslated_texts = [] + + for text in texts: + translated = translate_text(text, trigramme, bigramme, single) + if translated: # Übersetzung vorhanden + translations.append({ + "source": text, + "target": translated + }) + else: # Keine Übersetzung vorhanden + untranslated_texts.append(text) + + output_data["translations"] = translations + output_data["texts"] = untranslated_texts + else: + # Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt + output_data["translations"] = [] + output_data["texts"] = texts + + # Block 3: Ignorierte Texte (als Liste) + output_data["ignored"] = ignored_texts + try: with open(output_path, 'w', encoding='utf-8') as f: - # Schreibe akzeptierte Texte - for text in texts: - # Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen) - text_single_line = text.replace('\n', '\\n').replace('\r', '\\r') - f.write(text_single_line + '\n') + json.dump(output_data, f, ensure_ascii=False, indent=2) - # Trennzeile - f.write('\n-- durch translator.cfg ausgeschlossen --\n\n') - - # Schreibe ignorierte Texte - for text in ignored_texts: - # Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen) - text_single_line = text.replace('\n', '\\n').replace('\r', '\\r') - f.write(text_single_line + '\n') - - print(f"Textdatei erfolgreich erstellt: {output_path}") - print(f" Akzeptierte Texte: {len(texts)}") + 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'])}") + else: + print(f" Akzeptierte Texte: {len(output_data['texts'])}") print(f" Ignorierte Texte: {len(ignored_texts)}") except Exception as e: - print(f"Fehler beim Schreiben der Textdatei: {e}") + print(f"Fehler beim Schreiben der JSON-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', + description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei oder filtert Texte aus einer TXT Datei. Optional mit Übersetzungsfunktion.', prog='translate' ) @@ -352,8 +500,14 @@ if __name__ == '__main__': '-t', '--export-type', action='store', default='excel', - choices=['excel', 'text'], - help='Exportformat: excel (Standard) oder text (UTF-8 Textdatei)' + choices=['excel', 'json'], + help='Exportformat: excel (Standard) oder json (JSON-Datei)' + ) + parser.add_argument( + '--translate', + action='store', + metavar='LANG', + help='Übersetzt Texte mit Config aus translation/.cfg (z.B. CSK, EN, FR)' ) args = parser.parse_args() @@ -373,6 +527,12 @@ if __name__ == '__main__': # Lade Ignore-Muster aus Konfigurationsdatei wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir) + # Optional: Lade Übersetzungs-Config + translation_dicts = None + if args.translate: + translation_dir = check_environment_var('PROJECT_TRANSLATION') + translation_dicts = load_translation_config(args.translate, translation_dir) + if args.debug: print("DEBUG-Modus aktiviert") @@ -433,22 +593,22 @@ if __name__ == '__main__': if args.export_type == 'excel': if not output_filename.endswith('.xlsx'): output_filename += '.xlsx' - else: # text - if not output_filename.endswith('.txt'): - output_filename += '.txt' + else: # json + if not output_filename.endswith('.json'): + output_filename += '.json' else: # Standardname basierend auf Eingabedatei if args.export_type == 'excel': output_filename = filename.stem + "_texts.xlsx" - else: # text - output_filename = filename.stem + "_texts.txt" + else: # json + output_filename = filename.stem + "_texts.json" output_path = work_dir / output_filename # Schreibe Texte in gewähltem Format if args.export_type == 'excel': - write_texts_to_excel(texts, output_path) - else: # text - write_texts_to_textfile(texts, ignored_texts, output_path) + write_texts_to_excel(texts, output_path, translation_dicts) + else: # json + write_texts_to_json(texts, ignored_texts, output_path, translation_dicts) print("Verarbeitung erfolgreich abgeschlossen.")