112 lines
4 KiB
Python
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()
|