9354d7be1a
git add apps/studio/config/locales/zh_CN.json `
apps/studio/config/locales/en_US.json `
apps/studio/tests/test_locales.py `
apps/studio/tools/ui_check.py `
apps/studio/tests/test_ui_smoke.py `
apps/studio/src/orisys_studio/app_window.py `
apps/studio/src/orisys_studio/ui/cal_panel.py `
apps/studio/src/orisys_studio/ui/playback_dialog.py `
apps/studio/src/orisys_studio/ui/force_plots.py `
apps/studio/docs/record-channel.md `
shared/src/orisys_app_shared/force/calibration_recorder.py `
shared/src/orisys_app_shared/record/__init__.py `
shared/src/orisys_app_shared/record/playback.py `
shared/src/orisys_app_shared/record/depth.py `
shared/tests/test_record_channel.py `
shared/tests/test_playback.py
120 lines
4.1 KiB
Python
120 lines
4.1 KiB
Python
"""Static checks: translation keys and preference defaults stay in sync."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
APP_ROOT = Path(__file__).resolve().parents[1]
|
|
SRC_DIR = APP_ROOT / "src"
|
|
LOCALES_DIR = APP_ROOT / "config" / "locales"
|
|
DEFAULTS_JSON = APP_ROOT / "config" / "preferences.default.json"
|
|
|
|
REPO_ROOT = APP_ROOT.parents[1]
|
|
STORE_PY = REPO_ROOT / "shared" / "src" / "orisys_app_shared" / "prefs" / "store.py"
|
|
|
|
_T_CALL = re.compile(r"""\bt\(\s*["']([A-Za-z0-9_.]+)["']""")
|
|
_DEFAULT_BLOCK = re.compile(r"_DEFAULTS[^=]*=\s*\{(?P<body>.*?)\n\}", re.DOTALL)
|
|
_KEY = re.compile(r"""["']([A-Za-z0-9_]+)["']\s*:""")
|
|
|
|
|
|
def _load_locale(name: str) -> dict:
|
|
return json.loads((LOCALES_DIR / f"{name}.json").read_text(encoding="utf-8"))
|
|
|
|
|
|
def _flatten(data: dict, prefix: str = "") -> set[str]:
|
|
keys: set[str] = set()
|
|
for key, value in data.items():
|
|
dotted = f"{prefix}{key}"
|
|
if isinstance(value, dict):
|
|
keys |= _flatten(value, f"{dotted}.")
|
|
else:
|
|
keys.add(dotted)
|
|
return keys
|
|
|
|
|
|
def _referenced_keys() -> set[str]:
|
|
found: set[str] = set()
|
|
for path in sorted(SRC_DIR.rglob("*.py")):
|
|
found |= set(_T_CALL.findall(path.read_text(encoding="utf-8")))
|
|
return found
|
|
|
|
|
|
def test_locales_parity_and_used_keys():
|
|
zh = _flatten(_load_locale("zh_CN"))
|
|
en = _flatten(_load_locale("en_US"))
|
|
|
|
assert zh == en, {
|
|
"zh_only": sorted(zh - en),
|
|
"en_only": sorted(en - zh),
|
|
}
|
|
|
|
referenced = _referenced_keys()
|
|
assert referenced, "no t() calls found — the source scan is broken"
|
|
missing = sorted(key for key in referenced if key not in zh)
|
|
assert not missing, f"t() keys missing from locale files: {missing}"
|
|
|
|
|
|
def test_preference_defaults_match_store_defaults():
|
|
json_keys = set(json.loads(DEFAULTS_JSON.read_text(encoding="utf-8")))
|
|
source = STORE_PY.read_text(encoding="utf-8")
|
|
match = _DEFAULT_BLOCK.search(source)
|
|
assert match, "could not locate _DEFAULTS in prefs/store.py"
|
|
store_keys = set(_KEY.findall(match.group("body")))
|
|
|
|
assert json_keys == store_keys, {
|
|
"json_only": sorted(json_keys - store_keys),
|
|
"store_only": sorted(store_keys - json_keys),
|
|
}
|
|
|
|
|
|
def test_locale_files_are_valid_utf8_json():
|
|
for name in ("zh_CN", "en_US"):
|
|
data = _load_locale(name)
|
|
assert isinstance(data, dict)
|
|
assert data.get("app", {}).get("title")
|
|
|
|
|
|
#: Sequences that only ever appear when UTF-8 text was decoded as a single-byte
|
|
#: codepage and then re-encoded. A ``Get-Content -Raw | Set-Content`` round trip
|
|
#: in Windows PowerShell does exactly that and silently mangles every non-ASCII
|
|
#: string, which is how the Chinese UI once ended up unreadable.
|
|
#:
|
|
#: Written as escapes, with no literal examples in the comments, so that this
|
|
#: guard cannot itself be corrupted by the same mistake.
|
|
_MOJIBAKE = (
|
|
"\u00c3",
|
|
"\u00c2",
|
|
"\u00e2\u20ac",
|
|
"\u00e3\u20ac",
|
|
"\u00ef\u00bf\u00bd",
|
|
)
|
|
|
|
|
|
def test_locale_files_are_utf8_without_bom_and_not_mojibake():
|
|
for name in ("zh_CN", "en_US"):
|
|
path = LOCALES_DIR / f"{name}.json"
|
|
raw = path.read_bytes()
|
|
assert not raw.startswith(b"\xef\xbb\xbf"), f"{name}.json starts with a BOM"
|
|
text = raw.decode("utf-8") # raises if it is not valid UTF-8
|
|
|
|
for sequence in _MOJIBAKE:
|
|
assert sequence not in text, (
|
|
f"{name}.json contains {sequence!r}: the file was decoded with the "
|
|
"wrong encoding. Rewrite it with an explicit UTF-8 writer instead "
|
|
"of a PowerShell Get-Content/Set-Content round trip."
|
|
)
|
|
|
|
|
|
def test_chinese_locale_actually_contains_chinese():
|
|
"""A corrupted zh_CN.json still parses as JSON, so check the content."""
|
|
data = _load_locale("zh_CN")
|
|
for key in ("menu/theme_dark", "app/about_title", "chart/mode_follow"):
|
|
node = data
|
|
for part in key.split("/"):
|
|
node = node[part]
|
|
assert any("\u4e00" <= char <= "\u9fff" for char in node), (
|
|
f"zh_CN.{key} has no Chinese characters: {node!r}"
|
|
)
|