Conversation
- ui/js/i18n.js (new): zero-dep I18N zh/en dictionaries (563 keys x2),
t(key, vars) with zh fallback, fmtNum/fmtRelTime localization,
data-i18n static batch replacement, backend error mapping (mapErr),
localStorage('atp_lang') persistence, <html lang> sync, atp:langchange
- index.html: data-i18n/data-i18n-ph/data-i18n-title on static text
(login/nav headers/marketplace/sharing/wallet/transactions/settings/
admin/ops/help/chat/tour); #prefs-lang dropdown (zh/en values)
- app.js: all user-facing strings -> t(); lang-sensitive constants
(NAV/VIEW_TITLE/TOUR_STEPS/HELP_KEYS/status maps/TX_COLUMNS) hold keys
resolved at render time so switching re-renders instantly; settings
language dropdown bound; atp:langchange -> renderNav+renderView+title
- api.js: errors passed through I18n.mapErr (en shows English for known
backend errors, e.g. 该模型暂无可用 key / 点数余额不足)
- ui/README.md: i18n conventions section (v1.21)
Rant: 2026-08-18T20:49:22.514499 (前端国际化 i18n — 中英文语言包 + 切换机制)
argszero
added a commit
that referenced
this pull request
Sep 11, 2026
The English pack rendered four stat-card sub-labels as raw code identifiers where the Chinese pack has proper prose: ops.stats.keys.sub.on status=on -> enabled ops.stats.calls.sub usage_records -> usage records admin.emp.stats.admins.sub role=admin -> admin role admin.emp.stats.deps.sub organization -> organization management These were introduced with the language packs (#86) and kept on the English side by the later i18n clean-up (#87), which stripped the English annotation suffixes from the zh pack but left these values as they were. `organization` also collided visually with the value of the adjacent Departments card ("0 departments" + "organization"). Only English values change; the zh pack is byte-identical, so no key, placeholder or parity change is involved (775 == 775). Co-authored-by: argszero <argszero@argszerodeMac-mini.local>
argszero
added a commit
that referenced
this pull request
Sep 15, 2026
The sidebar prints a keycap digit and a "Shortcut N" tooltip on every nav item, and one global number-key handler resolves the digit. Both sides must read the same array: const NAV_ORDER = NAV.flatMap((g) => g.items); const short = NAV_ORDER.indexOf(item) + 1; // the keycap const item = NAV_ORDER[Number(e.key) - 1]; // the handler renderNav()'s guest branch hand-built an equivalent-looking literal that is not a member of NAV_ORDER, so indexOf(item) was -1 and the guest sidebar advertised keycap 0 with the tooltip "Shortcut 0 . Marketplace" -- while 0 resolves to NAV_ORDER[-1], i.e. nothing at all. The digit that does open the marketplace is 2, which the guest is never told. The shape dates from #44 (v1.17 D keyboard accessibility), which added the keycap machinery without noticing that the guest item is not a registry member; it survived #86 (i18n) and the recent nav work. ui/README.md documents the opposite (the badge is NAV_ORDER's index + 1, and the digits are 1..N of that same array), so this is drift, not a trade-off. Fix: the guest group draws from the registry it shares with the handler -- NAV_ORDER.filter((it) => GUEST_VIEWS.includes(it.id)). Gate: state_gate::the_sidebar_advertises_only_digits_that_work -- four rules with independent teeth: (1) renderNav renders no hand-built nav-item literal; (2) the keycap is derived once and never branches on the session; (3) exactly one line indexes the registry and it selects with the pressed digit; (4) the registry is derived from NAV, not a second hand-written list. A companion test pins the scanners on synthetic input, comments included. Also recalibrates the i18n positive controls (T_LITERAL_COUNT 543 -> 542, T_LITERAL_DISTINCT 434 -> 433): removing that one T("nav.marketplace") call site is the only change to the T() literal population.
6 tasks
argszero
added a commit
that referenced
this pull request
Sep 15, 2026
A sharing key has three reachable statuses (`on` / `paused` / `off`): PATCH /api/sharings/:id accepts all three, and GET /api/sharings applies no status filter, so a soft-deleted row stays in the list. The row button labels the transition from the CURRENT status (Pause / Resume / Re-list), while the outcome toast picked it from the NEXT status with a two-valued ternary (`s.status === "on" ? "paused" : "on"`) -- so `off -> on` (re-list) was reported as a resume: button "Re-list", toast "Resumed sharing of ...". The key naming that branch, `share.toggle.relisted`, is present in both language packs and unreachable -- an unreachable key is the fingerprint of a lost branch. Provenance is drift, not a trade-off: 68f9f70 (#86) introduced the three-way button, 89963f3 (#94) replaced the mock's three-branch toggle with the two-branch ternary. - add `SHARE_TOGGLE` (status -> { label, next, outcome }): the button's action, its next status and its outcome text now come from the SAME entry, read by both consumers through `shareToggle(s.status)`. - new static gate `state_gate::the_sharing_toggle_outcome_comes_from_the_same_entry_as_its_action`: share.toggle.* literals may only live inside the table; the table's key set must equal `SHARE_STATUS`'s (both derived from source); the label/outcome columns must be unique per entry; and the handler (derived as the endpoint that sends a NON-literal status to /api/sharings/) must not name a share.toggle.* key nor branch on a status literal, and must share exactly one accessor with the button markup. Extractor self-proofs use synthetic inputs. - i18n positive controls recalibrated from what the gate reported: T() literals 542 -> 537, distinct 433 -> 428 (five literal call sites became table entries). - ui/README.md: rule + measured scope; index.html cache-bust for app.js.
argszero
added a commit
that referenced
this pull request
Sep 15, 2026
…265) A sharing key has three reachable statuses (`on` / `paused` / `off`): PATCH /api/sharings/:id accepts all three, and GET /api/sharings applies no status filter, so a soft-deleted row stays in the list. The row button labels the transition from the CURRENT status (Pause / Resume / Re-list), while the outcome toast picked it from the NEXT status with a two-valued ternary (`s.status === "on" ? "paused" : "on"`) -- so `off -> on` (re-list) was reported as a resume: button "Re-list", toast "Resumed sharing of ...". The key naming that branch, `share.toggle.relisted`, is present in both language packs and unreachable -- an unreachable key is the fingerprint of a lost branch. Provenance is drift, not a trade-off: 68f9f70 (#86) introduced the three-way button, 89963f3 (#94) replaced the mock's three-branch toggle with the two-branch ternary. - add `SHARE_TOGGLE` (status -> { label, next, outcome }): the button's action, its next status and its outcome text now come from the SAME entry, read by both consumers through `shareToggle(s.status)`. - new static gate `state_gate::the_sharing_toggle_outcome_comes_from_the_same_entry_as_its_action`: share.toggle.* literals may only live inside the table; the table's key set must equal `SHARE_STATUS`'s (both derived from source); the label/outcome columns must be unique per entry; and the handler (derived as the endpoint that sends a NON-literal status to /api/sharings/) must not name a share.toggle.* key nor branch on a status literal, and must share exactly one accessor with the button markup. Extractor self-proofs use synthetic inputs. - i18n positive controls recalibrated from what the gate reported: T() literals 542 -> 537, distinct 433 -> 428 (five literal call sites became table entries). - ui/README.md: rule + measured scope; index.html cache-bust for app.js.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Frontend i18n for the whole UI, per host rant (timestamp
2026-08-18T20:49:22.514499).Changes
ui/js/i18n.js(new): zero-dependency language packsI18N = { zh, en }(563 keys ×2) covering nav / login / view titles / buttons / form labels+placeholders / table headers / toasts / empty states / stat cards / role+status badges / settings / shortcuts help / onboarding tour / errors;t(key, vars)with zh fallback;fmtNum(zh-CN/en-USlocale) + relative-time strings;data-i18nstatic batch replacement (applyStatic); backend error mappingmapErr(15 known Chinese errors → English in en mode);localStorage('atp_lang')persistence;<html lang>sync;atp:langchangeevent.ui/index.html:data-i18n/data-i18n-ph/data-i18n-titleon static text (login page, sidebar, all view headers, marketplace toolbar/table head, sharing form, wallet cards, transactions, settings incl. endpoints/API keys/notify/prefs, admin/ops views, help panel, chat modal, tour pop); settings language dropdown now<select id="prefs-lang">withzh/envalues.ui/js/app.js: all user-facing strings →t('key'); lang-sensitive constants (NAV / VIEW_TITLE / TOUR_STEPS / HELP_KEYS / SHARE_STATUS / RAISE_STATUS / TX_COLUMNS / DAY_LABELS) hold keys resolved at render time so switching re-renders instantly;#prefs-langbound toI18n.setLang;atp:langchange→renderNav() + renderView(activeView) + document.title(+ tour re-render); provider labels zh/en aware.ui/js/api.js: errors passed throughI18n.mapErr()(en shows English for known backend errors like 「该模型暂无可用 key」「点数余额不足」「需要管理员权限」; unknown passthrough; zh passthrough).ui/README.md: i18n conventions section (v1.21).Acceptance (rant)
feat/i18n, PR self-merged, UI version v1.21Tests
cargo build/cargo test(102/102) /cargo fmt --check/cargo clippy --all-targets -- -D warningsall greennode --checkon i18n.js / app.js / api.js