import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
I18N_DIR = ROOT / "i18n"
LOCALES_PATH = ROOT / "i18n-locales.json"
EN_PATH = I18N_DIR / "en.json"
PLACEHOLDER_RE = re.compile(r"\{[A-Za-z0-9_.-]+\}")
BACKTICK_RE = re.compile(r"`[^`]*`")
def load_json(path: Path):
with path.open("r", encoding="utf-8") as handle:
return json.load(handle)
def placeholders(text: str):
return PLACEHOLDER_RE.findall(text)
def backticks(text: str):
return BACKTICK_RE.findall(text)
def validate():
locales = load_json(LOCALES_PATH)
english = load_json(EN_PATH)
ok = True
for locale in locales:
path = I18N_DIR / f"{locale}.json"
if not path.exists():
print(f"missing locale file: {path}")
ok = False
continue
catalog = load_json(path)
missing = sorted(set(english) - set(catalog))
extra = sorted(set(catalog) - set(english))
if missing:
print(f"{locale}: missing keys: {', '.join(missing)}")
ok = False
if extra:
print(f"{locale}: stale keys: {', '.join(extra)}")
ok = False
for key, source in english.items():
target = catalog.get(key)
if not isinstance(target, str):
print(f"{locale}: key {key} must map to a string")
ok = False
continue
if placeholders(source) != placeholders(target):
print(f"{locale}: placeholder mismatch for {key}")
ok = False
if source.count("\n") != target.count("\n"):
print(f"{locale}: newline mismatch for {key}")
ok = False
if backticks(source) != backticks(target):
print(f"{locale}: backtick span mismatch for {key}")
ok = False
return 0 if ok else 1
def status():
locales = load_json(LOCALES_PATH)
english = load_json(EN_PATH)
dirty = False
for locale in locales:
path = I18N_DIR / f"{locale}.json"
if not path.exists():
print(f"{locale}: missing file")
dirty = True
continue
catalog = load_json(path)
missing = len(set(english) - set(catalog))
extra = len(set(catalog) - set(english))
if missing or extra:
dirty = True
print(
f"{locale}: missing={missing} stale={extra} keys={len(catalog)}"
)
return 1 if dirty else 0
def main():
if len(sys.argv) != 2 or sys.argv[1] not in {"validate", "status"}:
print("usage: ci/i18n_check.py [validate|status]", file=sys.stderr)
return 2
if sys.argv[1] == "validate":
return validate()
return status()
if __name__ == "__main__":
raise SystemExit(main())