From 2f6d2f0e3ba179ed65ae34fc2d3124bf0b5f6503 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 10:13:10 +0000 Subject: [PATCH] Exempt a region identity only while REGION_KEY still names it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit studio's judgement on the nineteen REGION_OF findings: the English values are identities, not reader text. Node ids derive from them, and every surface that shows one translates it at render — upg-100-list.vue:440 and poster-wall.vue:129 both read REGION_KEY[english] and call t(`poster.${…}`). The exemption is conditional, and the condition is the whole rule. It holds only for a name that IS a key of REGION_KEY. A region added to REGION_OF without a matching key falls through the `|| english` arm and renders English in all eleven locales, which is the defect upg100 already shipped once with Americas and Europe. So the gate keeps working as the table grows rather than blessing the file. Deliberately not "object values" and not "legend-tree.js", as studio asked: either would exempt the next table nobody has checked. The parser had no newline requirement in the end, and finding out why is the part worth keeping. The first version anchored on a closing brace at the start of a line, so it never matched a REGION_KEY written on ONE line — regionKeyNames returned null, the rule exited early, and BOTH two-sided cases passed while the exemption never ran once. Poisoning the membership test produced no failure, which is what exposed it. With the anchor gone the poison fails, so the conditional is now proven load-bearing. .js findings 41 -> 34. --- internal/check-hardcoded-english.mjs | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/internal/check-hardcoded-english.mjs b/internal/check-hardcoded-english.mjs index daf887e6e..0a1a6b7c5 100644 --- a/internal/check-hardcoded-english.mjs +++ b/internal/check-hardcoded-english.mjs @@ -351,6 +351,36 @@ function isDataComparison(node, parent) { // resource not found means NOT FOUND in the pools checked" is a sentence that happens to be used as // a key, and that is a real leak — gospel found twelve defects hiding in exactly that kind of // already-classified bucket. Anything with sentence punctuation stays loud whatever its value is. +// ★ REGION IDENTITIES, EXEMPT ONLY WHILE THEY ARE TRANSLATABLE (studio's judgement, R26). +// legend-tree's REGION_OF maps a zone slug to a region NAME, and that name is an identity: node ids +// derive from it, and every surface that shows one translates it at render through REGION_KEY — +// upg-100-list.vue:440 `REGION_KEY[english] ? t(\`poster.${…}\`) : english`, poster-wall.vue:129 the +// same. So the English never reaches a reader. +// +// ⚠ THE EXEMPTION IS CONDITIONAL, AND THAT IS THE WHOLE POINT. It holds only for a name that IS a +// key of REGION_KEY. A new region added to REGION_OF without a REGION_KEY entry falls through that +// `|| english` and renders English in all eleven locales — which is precisely the defect upg100 +// already shipped once with Americas and Europe. So the rule flags it, and the gate keeps working as +// the table grows instead of blessing the file. +// +// Deliberately NOT "object values" and NOT "legend-tree.js": either would exempt the next table +// nobody has checked. The membership test is what makes this safe to have at all. +function regionKeyNames(code) { + // ⚠ NO NEWLINE REQUIREMENT. The first version anchored on `\n}` and so never matched a REGION_KEY + // written on ONE LINE — regionKeyNames returned null, the rule exited early, and BOTH two-sided + // cases passed without the exemption ever running. Found by poisoning the membership test and + // seeing nothing fail. + const m = /\bREGION_KEY\s*=\s*\{([\s\S]*?)\}/.exec(code) + if (!m) return null + return new Set([...m[1].matchAll(/'([^']+)'\s*:/g)].map((x) => x[1])) +} +function isTranslatedRegionIdentity(text, code) { + const names = regionKeyNames(code) + if (!names || !names.has(text)) return false + // it must actually be used as a region identity in this file, not merely share a spelling + return /const REGION_OF\s*=/.test(code) || /regionFor\s*=/.test(code) +} + const DATA_MAP_MIN_ENTRIES = 6 function isPureDataMapKey(node, parent, grand) { if (!parent || parent.type !== 'ObjectProperty' || parent.key !== node) return false @@ -610,6 +640,8 @@ function scanScript(code, file, out, lineBase = 0) { if (!text.length || !isProse(text)) return // gospel: empty matches were being reported if (!/\s/.test(text)) return // ⚠ the single-word blind spot, documented above if (allowedBy(text)) return + // A region identity translated at render — exempt only while REGION_KEY names it. + if (isTranslatedRegionIdentity(text, code)) return out.push({ file, kind: 'script-string', text, line: lineOf(node, lineBase) }) return } @@ -977,6 +1009,17 @@ const RENDERED = { africa: 'A sentence the reader actually sees here', asia: 'An // names, values that are prose a reader sees: the keys must STILL count, because this is a // label table, not a join table. Without this case the allData guard can be removed and every // test still passes — measured by poisoning it. + // ★ REGION IDENTITIES (studio, R26): exempt ONLY while REGION_KEY names them. A region added + // without a key falls through `|| english` and renders English in eleven locales — the defect + // upg100 already shipped once with Americas and Europe. The conditional IS the rule. + { + const KEYED = "const REGION_KEY={'Africa':'region_africa','Middle East':'region_middle_east','India':'region_india','Other':'region_other','China + Mongolia':'region_china_mongolia','Central Asia':'region_central_asia'}" + const mk = (of) => `\n` + ok(!scanSfc(mk("{ 'west-africa': 'Africa', 'middle-east-core': 'Middle East' }"), 'rg.vue').some((f) => f.text === 'Africa'), + 'QUIET on a region identity that REGION_KEY names') + ok(scanSfc(mk("{ 'west-africa': 'Africa', 'new-place': 'Antarctica Region' }"), 'rb.vue').some((f) => f.text === 'Antarctica Region'), + 'COUNTS a region with NO REGION_KEY entry — it would render English through the fallback') + } ok(scanSfc(`