diff --git a/src/i18n_pack.rs b/src/i18n_pack.rs index 983ebe5..f754683 100644 --- a/src/i18n_pack.rs +++ b/src/i18n_pack.rs @@ -99,12 +99,12 @@ const EN_END: &str = "\n };"; /// /// ⚠️ `T_LITERAL_COUNT` 是 `T("…")` **调用点**总数,不是键数,也不是去重后的键数 —— /// 三个集合各不相同(坑 99);说「这个数不该变」之前先确认它在数哪个集合。 -const ZH_KEY_COUNT: usize = 792; -const EN_KEY_COUNT: usize = 792; +const ZH_KEY_COUNT: usize = 791; +const EN_KEY_COUNT: usize = 791; const STATIC_ATTR_COUNT: usize = 335; const STATIC_ATTR_DISTINCT: usize = 309; -const T_LITERAL_COUNT: usize = 545; -const T_LITERAL_DISTINCT: usize = 434; +const T_LITERAL_COUNT: usize = 544; +const T_LITERAL_DISTINCT: usize = 433; /// 切出语言包区段(起点标记 → 终点标记,含起点)。 fn pack_region<'a>(src: &'a str, start_mark: &str, end_mark: &str) -> &'a str { diff --git a/src/state_gate.rs b/src/state_gate.rs index 07ea584..c90a7c4 100644 --- a/src/state_gate.rs +++ b/src/state_gate.rs @@ -14148,3 +14148,706 @@ fn the_r99_rules_have_teeth() { rd_gone.report() ); } + +// ══════════════ R169:带 `data-i18n` 的静态控件,标签归 markup 那个键所有 ══════════════ +// +// 一个静态控件的标签只有一个来源:`ui/index.html` 的 `data-i18n`,由 `applyStatic()` 填充 +// (`innerHTML = t(key)`;只在 `setLang()` 与 `DOMContentLoaded` 两处被调)。交互态可以**临时** +// 换文案(忙碌态),但**收尾必须回到该控件自己的来源**:要么先捕获它原来的值再写回(仓库成例是 +// `withLoading`:`const orig = btn.innerHTML` → `btn.innerHTML = orig`,另有四处同形),要么写回 +// markup 声明的那个键。 +// +// 破在最显眼的地方:`#login-form` 的提交按钮出厂是 `login.enter`(zh「进入平台」/ en「Enter」), +// 而它的提交处理器在 `finally` 里**恢复**成 `T("login.submit")`(zh「登 录」/ en「Sign in」)—— +// 即**别人的键**。于是输错一次密码之后按钮改口说另一句话,而登录页没有语言切换器 ⇒ +// **整会话不自愈**(只有切语言或刷新才会被 `applyStatic()` 纠正)。溯源是漂移不是取舍: +// `8bd1063`(P2-A 登录对接)在同一个提交里既挂了 markup 的「进入平台」又手写 +// `btn.textContent = "登 录"`,`68f9f70`(i18n 语言包)把两句各翻成一个键,从此一个控件两个键。 +// +// 三条规则全部**派生**(#469:门禁不许把这次编辑的字面量写死): +// R1(轴) —— 名册里**会写自己标签**的那些成员(今天恰一个:`#login-form`):母集不为空,且每个 +// 成员的处理器里**最后一次**写标签的表达式必须派生自该元素自己的来源 —— +// ①对该元素的**捕获读**(`const x = btn.textContent;` 之后再写回 `x`),或 +// ②`T(k)` 且 `k ==` `ui/index.html` 给这个控件声明的键(键**从 markup 读**)。 +// R2(类) —— `ui/index.html` 里**每个「提交按钮带 `data-i18n` 的表单」**都是成员(今日 5 个, +// 下界 2):每个成员都必须能在 `ui/js/app.js` 里**解出恰好一个**提交处理器 +// (零个或两个以上=响亮地红:解不出 ≠ 干净,#477),且不得以别人的键收尾。 +// R3(反向)—— 那些会写标签的成员,处理器里对同一按钮的标签写必须**至少两次**(先忙碌态、再 +// 收尾)——「把忙碌态删掉」不能算合格。运行时证据是探针 `r169_probe.js` 的 `C4` 腿 +// (它在**第一个 `await` 之前**采样,证明忙碌态真的发生过)。 +// +// 射程(如实,并写进 `ui/README.md`): +// · 门禁是**词法**的 —— 它证明「收尾的表达式与 markup 声明的键同源」;`ui/index.html` 与 +// `ui/js/app.js` 是它读的两个制品。它**不证**屏幕上那一刻渲染出的标签(那半归 jsdom 探针)。 +// · 句柄只认本仓的主导写法 `querySelector('button[type="submit"]')`(今天四处:login / register / +// verify / share)。`#forgot-form` 用的是 `querySelector("button[type=submit]")`(另一种拼法), +// 不在词法射程内 —— 它因此**不计入**「会写标签的成员」,如实记录,不假装覆盖。 +// · 刻意放行:把 markup 改标成 `login.submit`(两边自洽)也能过 R1–R3 —— 那枚按钮就不再是设计 +// 原型印的那句话,挡它的是探针的 `D2` 腿(设计基线),如同 R92 的「同义词」不对称。 + +/// 绑到「表单的提交按钮」的句柄写法(本门禁引用的唯一一个选择器字面量)。 +const R169_SUBMIT_SELECTOR: &str = "querySelector('button[type=\"submit\"]')"; +/// 提交处理器的绑定后缀(`$("#id")` 直绑与 `const formEl = $("#id")` 别名共用)。 +const R169_BIND_SUFFIX: &str = ".addEventListener(\"submit\""; +/// 表单的射程边界(名册从这里派生)。 +const R169_FORM_OPEN: &str = "