greentic-bundle 1.1.0

Greentic bundle authoring CLI scaffold with embedded i18n and answer-document contracts.
Documentation
#!/usr/bin/env python3
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())