frontend_nyla/scripts/apply_i18n_from_index.py
2026-04-08 20:28:44 +02:00

112 lines
4 KiB
Python

#!/usr/bin/env python3
# Copyright (c) 2025 Patrick Motsch
"""
i18n-Index für DB-gestützte UI-Sprachen (de = Key, siehe wiki).
Dieses Skript **regeneriert** `i18n_ui_strings_index.json` aus den im Projekt verwendeten
`t('')`- und `t("")`-Aufrufen in den angegebenen Quellfiles. Es wendet **keine**
Code-Änderungen an — Migration neuer Texte erfolgt im TSX mit `t()`, anschließend Index
mit `--write` aktualisieren und fehlende Keys in der Admin-UI (UI-Sprachen) nachziehen.
Nutzung (im Ordner frontend_nyla):
python scripts/apply_i18n_from_index.py --write
python scripts/apply_i18n_from_index.py # nur prüfen: gleiche Keys wie in JSON?
Konvention: deutscher Klartext ist der Key (siehe `2026-04-ui-i18n-dynamic-language-sets.md`).
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
INDEX_PATH = Path(__file__).resolve().parent / "i18n_ui_strings_index.json"
# Dateien mit nutzerrelevanten t()-Aufrufen (erweitern bei weiteren Migrationen)
SCAN_PATHS = [
ROOT / "src/pages/views/trustee/TrusteeAccountingSettingsView.tsx",
ROOT / "src/pages/AutomationsDashboardPage.tsx",
ROOT / "src/pages/billing/BillingDataView.tsx",
ROOT / "src/pages/billing/BillingAdmin.tsx",
ROOT / "src/components/Navigation/MandateNavigation.tsx",
]
def _extractTKeys(source: str) -> set[str]:
"""Extrahiert erste String-Literale aus t('') / t("") (ohne verschachtelte Klammern)."""
keys: set[str] = set()
for m in re.finditer(r"\bt\(\s*((?:'([^'\\]|\\.)*'|\"([^\"\\]|\\.)*\"))\s*(?:,|\))", source):
raw = m.group(1)
if raw.startswith("'"):
inner = raw[1:-1].replace("\\'", "'").replace("\\\\", "\\")
else:
inner = raw[1:-1].replace('\\"', '"').replace("\\\\", "\\")
keys.add(inner)
return keys
def _scanAllKeys() -> dict[str, list[str]]:
fileToKeys: dict[str, list[str]] = {}
for p in SCAN_PATHS:
if not p.exists():
print(f"WARN: fehlt {p}", file=sys.stderr)
continue
text = p.read_text(encoding="utf-8")
keys = sorted(_extractTKeys(text))
rel = str(p.relative_to(ROOT)).replace("\\", "/")
fileToKeys[rel] = keys
return fileToKeys
def _mergeUnique(fileToKeys: dict[str, list[str]]) -> list[str]:
allKeys: set[str] = set()
for ks in fileToKeys.values():
allKeys.update(ks)
return sorted(allKeys)
def main() -> None:
parser = argparse.ArgumentParser(description="i18n-Index aus t()-Aufrufen pflegen.")
parser.add_argument("--write", action="store_true", help="i18n_ui_strings_index.json schreiben")
args = parser.parse_args()
byFile = _scanAllKeys()
merged = _mergeUnique(byFile)
payload = {
"version": 2,
"description": "Extrahierte UI-Keys (Deutsch = Key) für DB/Admin UI-Sprachen; erzeugt durch apply_i18n_from_index.py",
"sourceRoot": "frontend_nyla",
"keysByFile": byFile,
"keysAll": merged,
}
if args.write:
INDEX_PATH.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"Written {INDEX_PATH} ({len(merged)} unique keys)")
return
if INDEX_PATH.exists():
existing = json.loads(INDEX_PATH.read_text(encoding="utf-8"))
prev = set(existing.get("keysAll", []))
now = set(merged)
if prev != now:
onlyNew = sorted(now - prev)
onlyOld = sorted(prev - now)
print("Index differs from scan. Run with --write to refresh.")
if onlyNew:
print(f" + new ({len(onlyNew)}):", onlyNew[:20], "..." if len(onlyNew) > 20 else "")
if onlyOld:
print(f" - removed ({len(onlyOld)}):", onlyOld[:20], "..." if len(onlyOld) > 20 else "")
sys.exit(1)
print(f"OK: index matches scan ({len(merged)} keys)")
else:
print("No index file; run with --write to create.", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()