v0.247.0: i18n spike — the dashboard can speak English, Hungarian byte-identical
gates / gates (push) Successful in 19s

Message bundles (internal/i18n) expanded into templates before parsing, one
template set per language. Launcher, /backups, /apps/<slug> and the layout
converted; household language setting, POST /settings/language, ?lang= override,
report field. Parity test against fixtures captured from unconverted templates;
copy gates read templates expanded; new i18n_missing_gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 14:50:57 +02:00
parent 89dd3e94b1
commit 612c417024
49 changed files with 11734 additions and 248 deletions
+33
View File
@@ -44,6 +44,7 @@ COVERS = {
"debug-routes": "a live dispatcher case commented out - the button survives, the handler dies",
"golden-notice": "R-410 in the other direction: an empty dir must not count as a bake",
"minagent-header": "the word MinAgent in prose / a code span, not the header line (test_minagent_header_gate.py)",
"i18n": "an undefined marker key, a pleading English value, a shrinking/growing gap (v0.247.0)",
}
fails = []
@@ -133,6 +134,38 @@ def _comment_out_the_partial(src):
swapped("app-row-dedup/comment", "app_row_dedup_gate.py",
os.path.join(TPL, "dashboard.html"), _comment_out_the_partial)
# --- i18n (v0.247.0): copy moved OUT of the templates into the bundles. Two families of decoy: ---
# --- the new gate itself, and every copy gate that must still see copy that now lives there. ---
LOC = os.path.join(CTRL, "internal", "i18n", "locales")
EN_JSON, HU_JSON = os.path.join(LOC, "en.json"), os.path.join(LOC, "hu.json")
LAUNCHER = os.path.join(TPL, "launcher.html")
def _one(old, new):
def t(src):
assert src.count(old) == 1, "decoy anchor %r not found exactly once — the decoy cannot be built" % old
return src.replace(old, new)
return t
swapped("i18n/undefined-key", "i18n_missing_gate.py", LAUNCHER,
_one('{{T "launcher.link_masolasa"}}', '{{T "launcher.no_such_key"}}'))
swapped("i18n/pleading", "i18n_missing_gate.py", EN_JSON,
_one('"launcher.link_masolasa": "Copy link"', '"launcher.link_masolasa": "Please copy the link"'))
swapped("i18n/gap-grew", "i18n_missing_gate.py", EN_JSON,
_one(' "launcher.link_masolasa": "Copy link",\n', ''))
swapped("i18n/new-formal-form", "i18n_missing_gate.py", HU_JSON,
_one('"launcher.link_masolasa": "Link másolása"', '"launcher.link_masolasa": "Kattintson a link másolásához"'))
# Scope: the copy gates read the page AS RENDERED, so copy that lives in a bundle is still judged.
swapped("emoji/bundle", "emoji_gate.py", EN_JSON,
_one('"launcher.link_masolasa": "Copy link"', '"launcher.link_masolasa": "Copy link \U0001F600"'))
swapped("native-confirm/bundle", "native_confirm_gate.py", EN_JSON,
_one('"launcher.masolva": "Copied"', '"launcher.masolva": "Copied\u0027+confirm(\u0027x\u0027)+\u0027"'))
swapped("retrieval-promise/bundle", "retrieval_promise_gate.py", HU_JSON,
_one('"launcher.link_masolasa": "Link másolása"', '"launcher.link_masolasa": "A mentésed bármikor visszaállítható."'))
swapped("secret-markup/bundle", "secret_in_markup_gate.py", EN_JSON,
_one('"launcher.link_masolasa": "Copy link"', '"launcher.link_masolasa": "Copy {{.RetrievalPassword}}"'))
# --- golden-notice: R-410's decoy, in the other direction. It is ADVISORY, so rc is never the ---
# --- question — what it COUNTED is. ---
ran += 1