====== apply_review.py ====== Übersetzungsprüfung: wendet die Prüfungsmodule ''r_*.py'' auf ''translations/.json'' an (Ersetzungen bzw. Neufassungen, danach Glossar-Regeln auf jede geprüfte Meldung, Dubletten automatisch) und schreibt die Änderungsliste ([[ki:galnet:uebersetzungspruefung_3301]], [[ki:galnet:uebersetzungspruefung_3302]], [[ki:galnet:uebersetzungspruefung_3303]], [[ki:galnet:uebersetzungspruefung_3304]], [[ki:galnet:uebersetzungspruefung_3305]], [[ki:galnet:uebersetzungspruefung_3309]], [[ki:galnet:uebersetzungspruefung_3310]]). Dazu ''dump.py'' (Original und deutsche Fassung nebeneinander; ohne englischen Text wird ES/FR/PT-BR/RU gezeigt, Dubletten nach Originaltext), ''cov.py'' (Vollständigkeit, Jahr über Umgebungsvariable ''Y''), ''filt.py'' (Vorfilterung), ''wkchk.py'' (Fraktionslisten der Wochenberichte gegen das Original) und ''mkpkg.py'' (Korrekturpaket). Aufruf: ''python3 translations/review/apply_review.py galnet_raw '' (Eingabe ist der Stand vor der Prüfung). Herunterladen: auf den Dateinamen über dem Code klicken. ===== apply_review.py ===== Ablage beim Arbeiten: ''translations/review/apply_review.py''. """apply_review.py RAW JAHR -> wendet die Durchsicht (translations/review/r_*.py) auf translations/.json an und schreibt die Aenderungsliste translations/review/aenderungen_.txt (Wiki-Syntax). R[fid] = (Titel, Text, [Aenderungen]) ersetzt die Fassung; None = geprueft, keine inhaltliche Aenderung.""" import json, csv, sys, re, glob, importlib, collections from pathlib import Path sys.path.insert(0, 'translations/review') R_ = Path(sys.argv[1]); y = int(sys.argv[2]) idx = [r for r in csv.DictReader(open(R_ / 'index.csv', encoding='utf-8'), delimiter=';') if int(r['date'][:4]) == y] byday = collections.defaultdict(list) for r in idx: byday[r['date'][:10]].append(r['frontier_id']) PID = {} for d, fids in byday.items(): for n, f in enumerate(sorted(fids), 1): PID[f] = f"news:galnet:{d[:4]}:{d[5:7]}:{d[8:10]}_{n:02d}" DATE = {r['frontier_id']: r['date'][:10] for r in idx} P = f'translations/{y}.json' T = json.load(open(P, encoding='utf-8')) R = {} for f in sorted(glob.glob(f'translations/review/r{y}_*.py')): R.update(importlib.import_module(Path(f).stem).R) orig = {k: v['body'] for k, v in T.items()} MISS = [] # Glossar (ki:galnet:glossar): einheitliche Begriffe nach Frontier-DE ab 3306, auf jede geprüfte Meldung angewandt ART = {'der': 'der Föderalen Flotte', 'die': 'die Föderale Flotte', 'Der': 'Der Föderalen Flotte', 'Die': 'Die Föderale Flotte'} def _cmdr(m): # Singular vor Namen oder nach der/ein/jeder, sonst Plural (deutsch „die Commander“) if m[1] or m[2]: return (m[1] or '') + 'Kommandant' return 'Kommandanten' GLOSS = [ # Behörden wie Frontier-DE: IISS = Imperialer Interner Sicherheitsdienst, FIA = Föderaler Nachrichtendienst, FSS = Föderaler Sicherheitsdienst (r'\b(der|Der) Imperial Internal Security Service\b', r'\1 Imperiale Interne Sicherheitsdienst'), (r'\bdes Imperial Internal Security Service\b', 'des Imperialen Internen Sicherheitsdienstes'), (r'\b(dem|Dem|vom|Vom|zum|beim|den|Den) Imperial Internal Security Service\b', r'\1 Imperialen Internen Sicherheitsdienst'), (r'\bImperial Internal Security Service\b', 'Imperialer Interner Sicherheitsdienst'), (r'\b(?:des|der) Internen Imperialen Sicherheitsdienst(?:es|s)?\b', 'des Imperialen Internen Sicherheitsdienstes'), (r'\bImperiale(\w*) Innere(\w*) Sicherheitsdienst', r'Imperiale\1 Interne\2 Sicherheitsdienst'), (r'\b(der|Der) Geheimdienst der Föderation\b', r'\1 Föderale Nachrichtendienst'), (r'\b(dem|Dem|vom|Vom|zum|beim|den|Den) Geheimdienst der Föderation\b', r'\1 Föderalen Nachrichtendienst'), (r'\bdes [Ff]öderalen Geheimdienst(?:es|s)?\b', 'des Föderalen Nachrichtendienstes'), (r'\b[Ff]öderale(n|r)? Geheimdienst(es|s)?\b', lambda m: 'Föderale' + (m[1] or '') + ' Nachrichtendienst' + (m[2] or '')), (r'\bProfessor (Katrien )?Rook\b', lambda m: 'Professorin ' + (m[1] or '') + 'Rook'), (r'\bFabrikatoren\b', 'Autofabrikatoren'), (r'\bCobalt\b', 'Kobalt'), (r'\b(der|Der) Federal Security Service\b', r'\1 Föderale Sicherheitsdienst'), (r'\b(dem|Dem|vom|Vom|zum|beim|den|Den) Federal Security Service\b', r'\1 Föderalen Sicherheitsdienst'), (r"\b(?:Imperial Dawn|Imperatorendämmerung|(?:Imperialen?|Imperatorischen?) Morgen(?:röte|dämmerung))\b", "Emperor's Dawn"), (r"\b(gegen|über|für|an) die Emperor's Dawn\b", r"\1 Emperor's Dawn"), (r'^(?:Galaktische Nachrichten|Galactic News):', 'Galaktische News:'), (r'^(?:Freelancer Report|Freelance Report|Freiberuflicher Bericht|Freelancer-Bericht):', 'Freelancer-Report:'), (r'^(?:Community Goal|Gemeinschaftsziel):', 'Community-Ziel:'), (r'^(?:Powerplay|Machtspiele): (?:Wöchentlicher Bericht|Wochenbericht)$', 'Machtspiele: Wochenbericht'), (r'^Powerplay:', 'Machtspiele:'), (r'\bKumo-Besatzung\b', 'Kumo Crew'), (r'\bThargoid Raumpocken-', 'Thargoiden-Barnacle-'), (r'\bRaumpocken-', 'Barnacle-'), (r'\bRaumpocken\b', 'Barnacles'), (r'\bRaumpocke\b', 'Barnacle'), (r'\bWitch Head Nebel', 'Witch-Head-Nebel'), (r'\bInterstellaren? Landwirtschafts-Gesellschaft\b', 'Interstellar Association for Agriculture'), (r'\bILG\b', 'IAA'), (r'\bIISD\b', 'IISS'), (r'\bProfessor (Alba )?Tesreau\b', lambda m: 'Professorin ' + (m[1] or '') + 'Tesreau'), (r'\bOcellus-Sphäre-Sternenhafen\b', 'Ocellus-Sternenhäfen'), (r'\bBarnaklen-', 'Barnacle-'), (r'\b(?:Barnaklen|Baranklen)\b', 'Barnacles'), (r'\bSeepocken\b', 'Barnacles'), (r'\bSeepocke\b', 'Barnacle'), (r'\bPiraten-Lords?\b', lambda m: 'Piratenfürst' + ('en' if m[0].endswith('s') else '')), (r'\bImperatorin(?= Arissa)', 'Imperator'), (r'\bPiratenlords\b', 'Piratenfürsten'), (r'\bPiratenlord\b', 'Piratenfürst'), (r"\b(?:Entdecker|Forscher)vereinigung\b", "Explorers' Association"), (r"\bExplorer's Association\b", "Explorers' Association"), (r'\bSterne?nflughafen(s)?\b', r'Sternenhafen\1'), (r'\bSterne?nflughäfen\b', 'Sternenhäfen'), (r'\bKumo-Crew\b', 'Kumo Crew'), (r'\bSchild(?:es)? der Gerechtigkeit\b', 'Shield of Justice'), (r'\bKorrigendum\b', 'Corrigendum'), (r'\bLibertas-Kooperative\b', 'Libertas Cooperative'), (r'\bDefence Force of Amitrite\b', 'Defence Force of Amitrite'), (r'\bCerberus-(?:Seuche|Pest)\b', 'Cerberus-Plage'), (r'\bZerberus-?[Pp]est\b', 'Cerberus-Plage'), (r'\bRaumschiff Eins\b', 'Starship One'), (r'\bRaumhafen(s)?\b', r'Sternenhafen\1'), (r'\bRaumhäfen\b', 'Sternenhäfen'), (r'\bAdmiral(s)? der Flotte\b', lambda m: 'Flottenadmiral' + (m[1] or '')), (r'\bSternhäfen\b', 'Sternenhäfen'), (r'\b(der|die|Der|Die) (?:Föderationsmarine|Bundesmarine|[Ff]öderalen? Marine|Federal Navy|Föderalen? Navy|Föderal Navy)\b', lambda m: f"{m[1]} {'Föderalen' if m[1].lower() == 'der' else 'Föderale'} Flotte"), (r'\b(?:Föderationsmarine|Bundesmarine|[Ff]öderale Marine|Föderal Navy)\b', 'Föderale Flotte'), (r'\b(der|die|Der|Die) (?:[Ii]mperialen? Marine)\b', lambda m: f"{m[1]} {'Imperialen' if m[1].lower() == 'der' else 'Imperiale'} Flotte"), (r'\b[Ii]mperiale Marine\b', 'Imperiale Flotte'), (r'\bKommandeur(?:s)?\b', 'Kommandant'), (r'\bKommandeure(?:n)?\b', 'Kommandanten'), (r'(? ===== dump.py ===== Ablage beim Arbeiten: ''translations/review/dump.py''. """dump.py RAW JAHR MONAT -> Englisch und aktuelle deutsche Fassung nebeneinander (zur Durchsicht)""" import json, csv, sys from pathlib import Path R = Path(sys.argv[1]); y = int(sys.argv[2]); mon = sys.argv[3] idx = [r for r in csv.DictReader(open(R / 'index.csv', encoding='utf-8'), delimiter=';') if r['date'][:7] == f'{y}-{mon}'] EN = {} for f in (R / 'EN').glob(f'{y - 1286 + 1286 - 1286 + 3301 - 2015 + y - y}*.json' if False else f'{y + 1286}*.json'): for a in json.load(open(f, encoding='utf-8'))['articles']: EN[a['id']] = a T = json.load(open(f'translations/{y}.json', encoding='utf-8')) OTHER = {} # Fallback, wenn Frontier keinen englischen Text hat (z. B. nur ES) for L in ('ES', 'FR', 'PT-BR', 'RU', 'EN-US'): for f in (R / L).glob(f'{y + 1286}*.json'): for a in json.load(open(f, encoding='utf-8'))['articles']: OTHER.setdefault(a['id'], (L, a)) seen = {} for r in idx: k = r['frontier_id'] if k not in T: continue e = EN.get(k); lab = 'EN' if not e and k in OTHER: lab, e = OTHER[k] key = (e['body'] if e else T[k]['body'])[:400] # Dublette nach Originaltext, nicht nach Übersetzung if key in seen: print(f"=== {k} {r['date'][:10]} DUBLETTE von {seen[key]}\n"); continue seen[key] = k print(f"=== {k} {r['date'][:10]} [{T[k]['source']}]") print(f'{lab}:', e['title'] if e else '-'); print(e['body'].strip() if e else '') print('DE:', T[k]['title']); print(T[k]['body']); print() ===== cov.py ===== Ablage beim Arbeiten: ''/tmp/claude-0/-home-claude/412360f8-9c0b-55d1-a24e-f4646e8683bb/scratchpad/cov.py''. import sys, os, importlib; sys.path[:0]=['translations/review','translations/src']; Y=os.environ.get('Y','2015') m=sys.argv[1]; S=sys.argv[2]; R={} for p in sys.argv[3:]+[f"r{Y}_weekly"]: R.update(importlib.import_module(p).R) ids=[l.split()[1] for l in open(f'{S}/rv_{Y}_{m}.txt') if l.startswith('=== ') and 'DUBLETTE' not in l] print('fehlt:',[i for i in ids if i not in R]); W=importlib.import_module(f'r{Y}_weekly'); print("extra:",[i for i in R if i not in ids and i not in W.R]) ===== filt.py ===== Ablage beim Arbeiten: ''/tmp/claude-0/-home-claude/412360f8-9c0b-55d1-a24e-f4646e8683bb/scratchpad/filt.py''. import sys, os, re; sys.path[:0]=['translations/review','translations/src']; Y=os.environ.get('Y','2015') import importlib, weekly; w=importlib.import_module(f'r{Y}_weekly') m=sys.argv[1]; S=sys.argv[2] txt=open(f'{S}/rv_{Y}_{m}.txt').read() blocks=re.split(r'(?m)^(?==== )', txt) out=[] for b in blocks: if not b.strip(): continue fid=b.split()[1] if 'DUBLETTE' in b.split('\n')[0] or fid in w.R: continue t=b.split('\n')[1][4:].strip() if '[KI]' in b.split('\n')[0] and t in weekly.TITLES: out.append(f"=== {fid} KI-WEEKLY\n\n"); continue out.append(b) open(f'{S}/rf_{Y}_{m}.txt','w').write(''.join(out)) print(m, len(out), sum(len(x) for x in out)) ===== wkchk.py ===== Ablage beim Arbeiten: ''/tmp/claude-0/-home-claude/412360f8-9c0b-55d1-a24e-f4646e8683bb/scratchpad/wkchk.py''. import re, sys S='/tmp/claude-0/-home-claude/412360f8-9c0b-55d1-a24e-f4646e8683bb/scratchpad' Y, ms = sys.argv[1], sys.argv[2:] for m in ms: out=[] for b in re.split(r'(?m)^(?==== )', open(f'{S}/rf_{Y}_{m}.txt').read()): l=b.rstrip('\n').split('\n') if len(l)<3 or 'KI-WEEKLY' in l[0]: out.append(b); continue i=[n for n,x in enumerate(l) if x.startswith('DE: ')][0] de=l[i:] if ('Wöchentlicher' in de[0] and 'report' in de[0].lower()) or 'Status-Update' in de[0]: bul=[x[2:].strip() for x in de if x.startswith('• ')] src=[x.replace('
','').strip() for x in l[2:i] if x.replace('
','').strip() and not re.search(r'[.:!?]\s*$', x.replace('
','').strip()) and len(x.split())<=7] src=[re.sub(r'\t',', ',x).strip() for x in src] norm=lambda x: re.sub(r'[\s,]+',' ',x).strip() ok = [norm(x) for x in bul]==[norm(x) for x in src] print(m, l[0].split()[1], 'WEEKLY', 'OK' if ok else f'DIFF {set(src)^set(bul)}') continue out.append(b) open(f'{S}/rg_{Y}_{m}.txt','w').write(''.join(out))
===== mkpkg.py ===== Ablage beim Arbeiten: ''/tmp/claude-0/-home-claude/412360f8-9c0b-55d1-a24e-f4646e8683bb/scratchpad/mkpkg.py''. """mkpkg.py OUT YEAR:OLD:NEW [...] -- Korrekturpaket: geänderte Meldungsseiten, Korrektur-CSV, Prüfungs-/Werkzeugseiten""" import os, sys, shutil, json, glob, filecmp, csv S='/tmp/claude-0/-home-claude/412360f8-9c0b-55d1-a24e-f4646e8683bb/scratchpad' OUT=sys.argv[1]; K=f'{S}/{OUT}'; W=f'{K}/pages/ki/galnet/werkzeuge' shutil.rmtree(K, ignore_errors=True); os.makedirs(W) years=[] for a in sys.argv[2:]: y,old,new=a.split(':'); years.append(y); n=0 for root,_,fs in os.walk(f'{S}/{new}/pages/news/galnet/{y}'): for f in fs: nf=os.path.join(root,f); of=nf.replace(f'/{new}/',f'/{old}/') if not os.path.exists(of) or not filecmp.cmp(of,nf,shallow=False): d=nf.replace(f'{S}/{new}/',f'{K}/'); os.makedirs(os.path.dirname(d),exist_ok=True); shutil.copy(nf,d); n+=1 oc=f'{S}/{old}/galnet_{y}.csv' if os.path.exists(oc): old_r={r['pid']:r for r in csv.DictReader(open(oc))} rows=list(csv.DictReader(open(f'{S}/{new}/galnet_{y}.csv'))); ch=[r for r in rows if old_r.get(r['pid'])!=r] if ch: with open(f'{K}/galnet_{y}_korrektur.csv','w',newline='') as f: w=csv.DictWriter(f,list(rows[0].keys())); w.writeheader(); w.writerows(ch) else: ch=[] print(y,'Seiten',n,'CSV',len(ch)) shutil.copy('translations/review/glossar.md', f'{K}/pages/ki/galnet/glossar.txt') def page(name, title, intro, files): out=[f'====== {title} ======','',intro,'',"Herunterladen: auf den Dateinamen über dem Code klicken.",''] for path, lang in files: fn=os.path.basename(path) out += [f"===== {fn} =====", f"Ablage beim Arbeiten: ''{path}''.", '', f'', open(path, encoding='utf-8').read().rstrip('\n'), '', ''] open(f'{W}/{name}.txt','w',encoding='utf-8').write('\n'.join(out)) done=sorted(int(f[-8:-4]) for f in glob.glob('translations/review/aenderungen_*.txt')) links=', '.join(f'[[ki:galnet:uebersetzungspruefung_{y+1286}]]' for y in done) page('apply_review','apply_review.py',f"Übersetzungsprüfung: wendet die Prüfungsmodule ''r_*.py'' auf ''translations/.json'' an (Ersetzungen bzw. Neufassungen, danach Glossar-Regeln auf jede geprüfte Meldung, Dubletten automatisch) und schreibt die Änderungsliste ({links}). Dazu ''dump.py'' (Original und deutsche Fassung nebeneinander; ohne englischen Text wird ES/FR/PT-BR/RU gezeigt, Dubletten nach Originaltext), ''cov.py'' (Vollständigkeit, Jahr über Umgebungsvariable ''Y''), ''filt.py'' (Vorfilterung), ''wkchk.py'' (Fraktionslisten der Wochenberichte gegen das Original) und ''mkpkg.py'' (Korrekturpaket).\n\nAufruf: ''python3 translations/review/apply_review.py galnet_raw '' (Eingabe ist der Stand vor der Prüfung).", [('translations/review/apply_review.py','python'),('translations/review/dump.py','python'),(f'{S}/cov.py','python'),(f'{S}/filt.py','python'),(f'{S}/wkchk.py','python'),(f'{S}/mkpkg.py','python')]) for y in years: j=int(y)+1286 shutil.copy(f'translations/review/aenderungen_{y}.txt', f'{K}/pages/ki/galnet/uebersetzungspruefung_{j}.txt') mods=sorted(glob.glob(f'translations/review/r{y}_*.py')) page(f'review_{y}',f'Prüfungsmodule {j}',f"Ergebnis der vollständigen Durchsicht {j}: ''R[frontier_id] = None'' (geprüft, in Ordnung), ''('sub', [(alt, neu), …], Notizen)'' (gezielte Ersetzungen) oder ''(Titel, Text, Notizen)'' (Neufassung). Notizen: **S** Sinnfehler, **F** Fakt/Name/Zahl, **B** Begriff, **Sp** Sprache. ''r{y}_weekly.py'' setzt die alten Wochenberichte mit ''weekly.py'' neu und wird zuletzt geladen.", [(m,'python') for m in mods]) T=json.load(open(f'translations/{y}.json')); src={} for v in T.values(): src[v['source']]=src.get(v['source'],0)+1 open(f'{W}/translations_{y}.txt','w',encoding='utf-8').write('\n'.join([f'====== {y}.json ======','', f"Übersetzungen {j} (keine Frontier-DE): {src.get('eigene',0)} × Oliver, {src.get('KI',0)} × Claude. **Stand nach der vollständigen Übersetzungsprüfung** ([[ki:galnet:uebersetzungspruefung_{j}]]): alle Meldungen gegen das Original gelesen, nach [[ki:galnet:glossar]] vereinheitlicht.", '',f"Ablage beim Arbeiten: ''translations/{y}.json''. Herunterladen: auf den Dateinamen über dem Code klicken.",'',f'', open(f'translations/{y}.json',encoding='utf-8').read().rstrip('\n'), '','']))