From 2fcf7180fef38234c819ca85ae07e19fcff0905d Mon Sep 17 00:00:00 2001 From: argszero Date: Tue, 15 Sep 2026 15:13:38 +0800 Subject: [PATCH] fix(ui): own static i18n content at the innermost element that carries it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `applyStatic()` in `ui/js/i18n.js` walks every `[data-i18n]` element and does `els[i].innerHTML = t(key)`. A element that already carries a *text* `data-i18n` therefore cannot also rely on language hooks on its children: the ancestor's step replaces the whole inner HTML, detaching the child element (and its own `data-i18n*` attribute) from the document; the loop then assigns into that detached node -- no exception, no effect. jsdom measures `isConnected === false`. Two sites did this, and both existing gates are structurally blind to it: `every_static_i18n_attribute_resolves` only asks whether the key exists in both packs (it does), and a "find the key as text" dead-key scan sees the key literal sitting right there in `index.html` (so it counts as used). An attribute that can never take effect is neither. - `index.html:617` -- the raise-request card: `

加额申请

`. Both pack values are plain text, so the child span was permanently destroyed: the hint "(member applies -> admin approves / rejects)" did not render in *either* language, and it never recovers -- `app.js` only writes `#raise-requests` and never redraws that heading. Fixed by making the two strings sibling carriers (the repository already uses this shape in `.wallet-hint`), so the heading keeps its own localization. - `index.html:79` -- the login footer: `

`. `login.foot`'s value *embeds* the same markup (including `id="reg-link"`), so the rendered text and the click (a document-delegated listener keyed on `t.id`) were never broken -- but the static hook was dead, which made `login.register` an orphan key fed only by it. Removed the dead attribute and the now-unreferenced key from both packs (the value already carries the link). Guard rails: - New scanner `scan_i18n_nesting` + test `no_data_i18n_attribute_nests_inside_a_data_i18n_element` in `src/i18n_pack.rs`: a tag-stack walk over `index.html` that fails when any `data-i18n*` attribute sits inside an ancestor with a text `data-i18n`. HTML comments are stripped first (their markup is not structure), void/self-closing elements never enter the stack, and the tag-end scan skips quoted `>` so an attribute value cannot desynchronise the parser. It also reports three positive controls (start tags, text carriers, unclosed stack at EOF) so "0 violations" cannot be misread as "the scanner saw nothing". - `nested_i18n_detector_detects_injected_defects`: a negative control built from the real pre-fix markup, plus positive controls that the legal shapes are not reported -- an ancestor carrying only an *attribute-kind* hook (`data-i18n-title` / `-label` write a single attribute via `setAttribute` and leave child markup alone, which is why `select#tx-range`'s five `