#!/usr/bin/env python3 """Rebuild data/app_ui_strings.json from coach-visible Android code only.""" from __future__ import annotations import json import re from pathlib import Path ROOT = Path(__file__).resolve().parents[2] APP_JAVA = ROOT / "Questionnaire-App" / "app" / "src" / "main" LM_FILE = APP_JAVA / "java" / "com" / "dano" / "test1" / "utils" / "LanguageManager.kt" OUT = Path(__file__).resolve().parents[1] / "data" / "app_ui_strings.json" DEV_KT = { "DevSettingsActivity.kt", "DatabaseButtonHandler.kt", "SaveButtonHandler.kt", "HeaderOrderRepository.kt", "ExcelExportService.kt", } DEV_ONLY_KEYS = { "database", "database_clients_title", "download_header", "export_success_downloads", "export_failed", "headers", "view_missing", "no_header_template_found", "saved_pdf_csv", "no_pdf_viewer", "save_error", "no_clients_available", "questionnaire_id", "questionnaires", "status", "data", "data_final_warning", "open_client_via_load", "ask_before_upload", "You have completed questionnaires that have not been uploaded yet.", "You have completed questionnaires that have not been uploaded yet. Tap to open the app and upload.", } EXTRA_COACH_KEYS = { "save", "please_client_code", "choose_more_elements", "previous", "client", "question", "answer", "no_questionnaires", "no_questions_available", "error", "client_code", "cancel", "ok", "not_done", "none", } def parse_german_defaults(text: str) -> dict[str, str]: out: dict[str, str] = {} in_german = False for line in text.splitlines(): if '"GERMAN" to mapOf(' in line: in_german = True continue if not in_german: continue m = re.match(r'\s*"([^"]+)"\s+to\s+"(.*)",?\s*$', line) if m: key = m.group(1) val = m.group(2).replace("\\n", "\n").replace('\\"', '"') out[key] = val elif line.strip().startswith("),") or ('"ENGLISH"' in line and "mapOf" in line): break return out def extract_keys(text: str) -> set[str]: keys: set[str] = set() patterns = [ r'LanguageManager\.getText(?:Formatted)?\(\s*[^,]+,\s*"([^"]+)"', r'LanguageManager\.getText(?:Formatted)?\(\s*"([^"]+)"\s*,\s*"([^"]+)"', r'\bt\(\s*"([^"]+)"\s*\)', r'\bt\(\s*lang\s*,\s*"([^"]+)"', ] for pat in patterns: for m in re.finditer(pat, text): keys.add(m.group(m.lastindex)) return keys def main() -> None: de_defaults = parse_german_defaults(LM_FILE.read_text(encoding="utf-8")) old = json.loads(OUT.read_text(encoding="utf-8")) if OUT.exists() else {} old_def = old.get("germanDefaults", {}) coach: set[str] = set(EXTRA_COACH_KEYS) dev: set[str] = set() for kt in (APP_JAVA / "java").rglob("*.kt"): keys = extract_keys(kt.read_text(encoding="utf-8", errors="ignore")) if kt.name in DEV_KT: dev |= keys else: coach |= keys for xml in (APP_JAVA / "res" / "layout").rglob("*.xml"): for m in re.finditer(r'android:tag="([^"]+)"', xml.read_text(encoding="utf-8", errors="ignore")): coach.add(m.group(1)) for month in ( "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december", ): coach.add(month) keys = sorted(coach - dev - DEV_ONLY_KEYS) german_defaults = { k: de_defaults.get(k) or old_def.get(k) or k for k in keys } payload = { "version": 2, "description": "Coach-visible app UI strings only (excludes dev settings / database tools).", "keys": keys, "germanDefaults": german_defaults, } OUT.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(f"Wrote {len(keys)} keys to {OUT}") if __name__ == "__main__": main()