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:
|
Dieses Programm:
|
||||||
- liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte
|
- liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte
|
||||||
ODER liest Texte aus einer TXT Datei (ein Text pro Zeile)
|
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
|
- filtert die Texte basierend auf Mustern in translator.cfg
|
||||||
- übersetzt optional Texte mit Trigrammen, Bigrammen und Einzelwörtern aus translation/<LANG>.cfg
|
- übersetzt optional Texte mit Trigrammen, Bigrammen und Einzelwörtern aus translation/<LANG>.cfg
|
||||||
- schreibt die Ergebnisse in eine Excel- oder JSON-Datei
|
- 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).
|
Liest Texte aus einer UTF-8 Textdatei (ein Text pro Zeile).
|
||||||
Entfernt Duplikate und leere Zeilen.
|
Entfernt Duplikate und leere Zeilen.
|
||||||
|
Ignoriert Abschnitts-Marker (Zeilen die mit "---" beginnen).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename: Pfad zur Textdatei
|
filename: Pfad zur Textdatei
|
||||||
@@ -366,6 +368,9 @@ def read_texts_from_file(filename: Path) -> list[str]:
|
|||||||
for line in f:
|
for line in f:
|
||||||
text = line.strip()
|
text = line.strip()
|
||||||
if text: # Ignoriere leere Zeilen
|
if text: # Ignoriere leere Zeilen
|
||||||
|
# Ignoriere Abschnitts-Marker (z.B. "--- translations:", "--- ignored:")
|
||||||
|
if text.startswith('---'):
|
||||||
|
continue
|
||||||
# Konvertiere escaped Zeilenumbrüche zurück
|
# Konvertiere escaped Zeilenumbrüche zurück
|
||||||
text = text.replace('\\n', '\n').replace('\\r', '\r')
|
text = text.replace('\\n', '\n').replace('\\r', '\r')
|
||||||
texts_set.add(text)
|
texts_set.add(text)
|
||||||
@@ -379,6 +384,62 @@ def read_texts_from_file(filename: Path) -> list[str]:
|
|||||||
sys.exit(1)
|
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,
|
def write_texts_to_excel(texts: list[str], output_path: Path,
|
||||||
translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str]] = None):
|
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.
|
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:
|
Args:
|
||||||
texts: Liste der akzeptierten Texte
|
texts: Liste der akzeptierten Texte
|
||||||
ignored_texts: Liste der ignorierten 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 = {}
|
output_data = {}
|
||||||
|
|
||||||
# Block 1: Übersetzte Texte (als Liste von Dicts mit source/target)
|
# 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:
|
if translation_dicts:
|
||||||
trigramme, bigramme, single = translation_dicts
|
trigramme, bigramme, single = translation_dicts
|
||||||
translations = []
|
translations = []
|
||||||
untranslated_texts = []
|
untranslated = []
|
||||||
|
|
||||||
for text in texts:
|
for text in texts:
|
||||||
translated = translate_text(text, trigramme, bigramme, single)
|
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
|
"target": translated
|
||||||
})
|
})
|
||||||
else: # Keine Übersetzung vorhanden
|
else: # Keine Übersetzung vorhanden
|
||||||
untranslated_texts.append(text)
|
untranslated.append({
|
||||||
|
"source": text,
|
||||||
|
"target": ""
|
||||||
|
})
|
||||||
|
|
||||||
output_data["translations"] = translations
|
output_data["translations"] = translations
|
||||||
output_data["texts"] = untranslated_texts
|
output_data["untranslated"] = untranslated
|
||||||
else:
|
else:
|
||||||
# Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt
|
# Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt
|
||||||
output_data["translations"] = []
|
output_data["translations"] = []
|
||||||
output_data["texts"] = texts
|
output_data["untranslated"] = [{"source": text, "target": ""} for text in texts]
|
||||||
|
|
||||||
# Block 3: Ignorierte Texte (als Liste)
|
# Block 3: Ignorierte Texte (als Liste)
|
||||||
output_data["ignored"] = ignored_texts
|
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}")
|
print(f"JSON-Datei erfolgreich erstellt: {output_path}")
|
||||||
if translation_dicts:
|
if translation_dicts:
|
||||||
print(f" Übersetzungen: {len(output_data['translations'])}")
|
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:
|
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)}")
|
print(f" Ignorierte Texte: {len(ignored_texts)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Fehler beim Schreiben der JSON-Datei: {e}")
|
print(f"Fehler beim Schreiben der JSON-Datei: {e}")
|
||||||
sys.exit(1)
|
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__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(
|
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'
|
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 = parser.add_mutually_exclusive_group(required=True)
|
||||||
input_group.add_argument(
|
input_group.add_argument(
|
||||||
'-e', '--extract',
|
'-e', '--extract',
|
||||||
@@ -501,6 +649,12 @@ if __name__ == '__main__':
|
|||||||
metavar='FILE.txt',
|
metavar='FILE.txt',
|
||||||
help='Filtert Texte aus einer TXT Datei (ein Text pro Zeile)'
|
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(
|
parser.add_argument(
|
||||||
'-f', '--filename',
|
'-f', '--filename',
|
||||||
@@ -522,9 +676,9 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-t', '--export-type',
|
'-t', '--export-type',
|
||||||
action='store',
|
action='store',
|
||||||
default='excel',
|
default='json',
|
||||||
choices=['excel', 'json'],
|
choices=['excel', 'json', 'text'],
|
||||||
help='Exportformat: excel (Standard) oder json (JSON-Datei)'
|
help='Exportformat: json (Standard), excel (Excel-Datei) oder text (Textdatei mit Abschnitten)'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--translate',
|
'--translate',
|
||||||
@@ -543,6 +697,10 @@ if __name__ == '__main__':
|
|||||||
if args.filename and not args.extract:
|
if args.filename and not args.extract:
|
||||||
parser.error("--filename kann nur mit --extract verwendet werden")
|
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
|
# Prüfe Umgebungsvariablen
|
||||||
work_dir = check_environment_var('PROJECT_WORK')
|
work_dir = check_environment_var('PROJECT_WORK')
|
||||||
cfg_dir = check_environment_var('PROJECT_CFG')
|
cfg_dir = check_environment_var('PROJECT_CFG')
|
||||||
@@ -583,7 +741,7 @@ if __name__ == '__main__':
|
|||||||
print(f"Lese DXF Datei: {filename}")
|
print(f"Lese DXF Datei: {filename}")
|
||||||
all_texts = extract_text_from_dxf(filename)
|
all_texts = extract_text_from_dxf(filename)
|
||||||
|
|
||||||
else: # args.fromtxt
|
elif args.fromtxt:
|
||||||
# TXT-Modus
|
# TXT-Modus
|
||||||
input_filename = args.fromtxt
|
input_filename = args.fromtxt
|
||||||
|
|
||||||
@@ -605,6 +763,28 @@ if __name__ == '__main__':
|
|||||||
print(f"Lese Textdatei: {filename}")
|
print(f"Lese Textdatei: {filename}")
|
||||||
all_texts = read_texts_from_file(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
|
# Schritt 2: Texte filtern
|
||||||
texts, ignored_texts = filter_texts(all_texts, wildcard_patterns, regex_patterns, args.debug)
|
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 args.export_type == 'excel':
|
||||||
if not output_filename.endswith('.xlsx'):
|
if not output_filename.endswith('.xlsx'):
|
||||||
output_filename += '.xlsx'
|
output_filename += '.xlsx'
|
||||||
else: # json
|
elif args.export_type == 'json':
|
||||||
if not output_filename.endswith('.json'):
|
if not output_filename.endswith('.json'):
|
||||||
output_filename += '.json'
|
output_filename += '.json'
|
||||||
|
else: # text
|
||||||
|
if not output_filename.endswith('.txt'):
|
||||||
|
output_filename += '.txt'
|
||||||
else:
|
else:
|
||||||
# Standardname basierend auf Eingabedatei
|
# Standardname basierend auf Eingabedatei
|
||||||
if args.export_type == 'excel':
|
if args.export_type == 'excel':
|
||||||
output_filename = filename.stem + "_texts.xlsx"
|
output_filename = filename.stem + "_texts.xlsx"
|
||||||
else: # json
|
elif args.export_type == 'json':
|
||||||
output_filename = filename.stem + "_texts.json"
|
output_filename = filename.stem + "_texts.json"
|
||||||
|
else: # text
|
||||||
|
output_filename = filename.stem + "_texts.txt"
|
||||||
|
|
||||||
output_path = work_dir / output_filename
|
output_path = work_dir / output_filename
|
||||||
|
|
||||||
# Schreibe Texte in gewähltem Format
|
# Schreibe Texte in gewähltem Format
|
||||||
if args.export_type == 'excel':
|
if args.export_type == 'excel':
|
||||||
write_texts_to_excel(texts, output_path, translation_dicts)
|
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)
|
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.")
|
print("Verarbeitung erfolgreich abgeschlossen.")
|
||||||
|
|||||||
Reference in New Issue
Block a user