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:
+203
-16
@@ -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.")
|
||||
|
||||
Reference in New Issue
Block a user