Schalter --fromtext modifiziert, dass Blockangaben wie ignored, etc. ÜBerlesen werden. Schalter -t mit json als Ziel modifiziert, dass die originaltexte als leere Einträge vorhanden sind, die mit Deepl leicht übersetzt werden können. -t text hinzugefügt, dass aus einer dxf Datei erst einmal einfach eine Textdatei entsteht, die man im Editor aufmachen kann.

This commit is contained in:
2025-12-04 10:29:09 +01:00
parent b9d45e6be0
commit 418882e78f
+203 -16
View File
@@ -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/<LANG>.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.")