#!/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()