Skip to content

feat(i18n): migrate the remaining hardcoded UI strings and add Simplified Chinese (0.7.0.dev2) - #3586

Closed
LXD-8 wants to merge 3 commits into
jundot:mainfrom
LXD-8:i18n/hardcoded-ui-migration
Closed

LXD-8 wants to merge 3 commits into
jundot:mainfrom
LXD-8:i18n/hardcoded-ui-migration

Conversation

@LXD-8

@LXD-8 LXD-8 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Based on main @ 7cbb407 (v0.7.0.dev2, 2026-09-13). Complete as of that commit: every user-visible string the scans below could find is now routed through i18n, with Simplified Chinese supplied for all of them.

Three commits, in case you want them separately

  1. feat(i18n): migrate the remaining hardcoded UI strings to i18n — key extraction only. Every locale renders the English source for the new keys, exactly as those screens did before, so no locale regresses at this point.
  2. feat(i18n): add Simplified Chinese for the 704 new keys — the zh-Hans values, in zh.json alone.
  3. fix(i18n): align the new values with the app's existing wording — 58 values: 32 that had been translated without looking at the macOS app's catalog, plus the 26 pre-existing values that sit inside the blocks this PR inserts keys into (details at the end).

Reviewing them independently is the intent; squash them if you'd rather have one.

I kept this as a single PR rather than splitting migration and translation, because that is the shape this repo already merges: #2540 (localize the Runtime Cache panel) and #2376 (Russian admin localization) both bundle string extraction with the author's translations and regenerate the remaining catalogs as English fallback, which is exactly what happens here. Translation-only PRs also exist (#2415 for zh/zh-TW), but those complete keys that already exist — they don't introduce them.

What this does

704 new keys, 30 files, +7531/−845.

Area Files Strings
Cluster v2 dashboard _cluster_v2.html, cluster_v2.js ~390
Dashboard shell, settings, logs dashboard.js, _settings.html, _logs.html ~110
Model-settings modal, Models _modal_model_settings.html, _models.html ~115
Status, Bench, Accuracy, chat, base _status.html, _bench.html, _bench_accuracy.html, chat.html, base.html ~75
macOS app ModelSettingsScreen, oMLXApp, WelcomeWindow, AboutScreen, CodeChip, Localizable.xcstrings 15

Locale policy

Only en.json and zh.json carry values. The other seven catalogs were regenerated with the repo's own scripts/normalize_i18n.py, so the new keys arrive there as the English fallback — no locale outside Chinese shows Simplified Chinese. The macOS catalog is the same: the new Localizable.xcstrings entries declare en + zh-Hans only, so every other language falls back to English there too.

Of the 704 keys, 669 are translated; the remaining 35 are product names, acronyms and units (oMLX, Codex, OpenCode, oQ/oQe, GitHub, HuggingFace, DDGS, SSD, ANE, bit-widths, …) whose zh value is deliberately identical to the English. Acronyms and mechanism names are left in Latin throughout — MTP, PLE, Engram, GDN, MoE, DFlash, SpecPrefill, TurboQuant, imatrix, rank, sidecar.

Translation conventions

Terminology is one translation per English term across every screen — cluster→集群, pool→资源池, split→拆分, slice→分片, prefill→预填充, decode→解码, quantisation→量化, sensitivity→敏感度, draft model→草稿模型, keep rate→保留率, snapshot→快照. Space-constrained labels (tabs, buttons, table headers, badges) take shorter forms than body copy.

The new values were also checked against the values that already shipped, so the same meaning reads the same way on both sides of the migration: rank stays Latin and lower-case, Token is capitalised as the common noun while 令牌 is the auth token, “this Mac” is 这台 Mac, ModelScope is 魔搭社区, and 缓存 / 热缓存, 提示词, 批大小, 队列 and the 留空… sentence pattern all reuse the renderings the existing catalog established. Register matches too — the reader is addressed as 你, full sentences end with 。 and labels do not.

Typography follows what this catalog already establishes:

  • Full-width punctuation (,。:;()) in Chinese sentences; half-width only inside numbers, version strings, paths, model ids and formulas (2.5 位, 192.168.1.50:8000, Qwen3.5-0.8B).
  • Parentheses wrapping a placeholder are full-width, matching 显示信息({count}) in the existing catalog.
  • Token is capitalised as a common noun.
  • No ASCII ...; the Chinese ellipsis is …, which is also what the newer English strings use.
  • “” rather than corner brackets for Simplified Chinese.
  • No space before a full-width (.

One behaviour-preserving refactor

cluster_v2.js used its display label as a lookup key: requestPhaseLabel() returned 'Failed' | 'Complete' | 'Prefill' | 'Decode' | 'Queued' and requestPhaseTone() compared that same string to choose a colour. Translating the return value in place would have broken the tone lookup, so the function now returns stable ids (failed, complete, prefill, decode, queued), the tone switch uses those ids, and the display site translates: window.t('cluster.v2.phase.' + requestPhaseLabel(request)). All five id/tone pairs produce the same classes as before.

cluster_v2.js also keeps a CLUSTER_V2_STRINGS map of the English source, used only when window.t is unavailable (offline component tests). It is kept in sync with en.json; all 27 entries match it exactly.

Test updates

  • test_admin_model_settings_template.py, test_admin_audio_upload_ui.py — asserted English copy that is now localised; they assert the keys instead.
  • tests/ui/test_cluster_v2_wizard.py — its harness now feeds the component the real English catalog as window.t, so runtime assertions still read the shipped strings; source-level assertions check the keys, and the phase expectations follow the new lowercase ids. (Skipped unless node is on PATH, as before.)

Alignment with the macOS app's catalog

The third commit exists because the new strings were translated without comparing them with apps/omlx-mac/Resources/Localizable.xcstrings, where the macOS app already ships Chinese for text this PR also translates — the oQ / oQe documentation in particular, whose two copies had drifted apart. 58 values change. Thirty-two are keys this PR added. The other twenty-six are pre-existing values that sit inside the blocks this PR inserts keys into — the Cluster v2 strategy and staging chips, plus two bench and usage hints — and they are corrected here rather than in the catalog-audit PR so that the two apply in either order without a conflict in zh.json.

Where the two disagreed, the app's wording wins, except where this PR's reads better or the catalog's conventions decide it — 位宽分配 for Bit allocation, 相同的 oQ 方案 for Same oQ plan, 标准 oQ 的作用 for What standard oQ does, 对量化误差加权 for Weight the quantization error, and scale/bias kept Latin in the affine-weight step.

Verification

  • pytest -m "not slow" -k "admin or i18n or locale or template or ui or cluster or bench or settings" — 3238 passed, 16 skipped, 0 failed on this branch, and the same on a tree with the catalog-audit PR merged on top. (A full-suite run is not reliable in my environment — it dies in an unrelated native module — so the number above is a selection covering everything these changes touch.)
  • Every t('…') key referenced by a template or script resolves in en.json: 1819 static references, and the only unresolved names are four intentionally dynamic prefixes concatenated with a runtime suffix — cluster.v2.phase., settings.resource.guard_tier., settings.resource.guard_tier.reclaim_method. and usage. — all four of which resolve to existing keys.
  • Key parity across all nine catalogs, and no CJK in any non-Chinese catalog for any of the 704 new keys.
  • CLUSTER_V2_STRINGS matches en.json exactly, all 27 entries.
  • The Localizable.xcstrings diff is additive (13 entries) plus the one aligned value.

@LXD-8
LXD-8 force-pushed the i18n/hardcoded-ui-migration branch 6 times, most recently from 1283cb5 to da70da6 Compare September 12, 2026 04:39
@LXD-8

LXD-8 commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

@jundot — a short note on scope, in case it helps the review.

The change is simple in kind but wide in reach: the remaining hardcoded UI strings in the admin dashboard and the macOS app now go through the existing i18n layer, with zh values supplied. Individually each edit is small — a t() wrapper plus a catalog entry — but the effect for other people is not:

  • Other languages: every string now lives in a single JSON file, so localizing the UI means filling in that file rather than touching templates or Swift sources. scripts/normalize_i18n.py keeps the nine locales key-identical, so a new locale only has to supply its own values and inherits English for everything else.
  • Simplified Chinese: the admin UI and the app are fully Chinese, instead of a Chinese/English mix.

Two properties that should keep the review cheap: all nine locales have identical key sets (via normalize_i18n.py), and the eight non-Chinese locales keep English values — no Simplified Chinese leaks into any other language (pinned by tests/test_mcp_expose_tools.py::TestI18nKeys). The Swift side registers the new keys in Localizable.xcstrings the same way.

Happy to rework the key naming, the structure, or the Chinese wording — please point at whatever you'd rather see done differently, and if it's easier to review in pieces I'm glad to split it.

Either way I plan to keep contributing to the oMLX UI and to the zh-Hans localization, so I'd rather get the conventions right here than repeat a pattern you'd have to correct later.

The web admin and the macOS app still had user-visible copy baked into
markup, Alpine expressions and script literals, so those screens could not
follow the language setting. This routes them through i18n.

704 new keys: the cluster v2 dashboard (~390 strings across _cluster_v2.html
and cluster_v2.js), the dashboard shell and settings (dashboard.js,
_settings.html, _logs.html), the model-settings modal and Models screen
(~115), Status / Bench / Accuracy / chat (~75), and 15 strings plus 13
catalog entries in the macOS app.

Key extraction only - every locale renders the English source for the new
keys, exactly as those screens did before this change, so no locale
regresses. Translations can land per-locale on top.

cluster_v2.js used its display label as a lookup key: requestPhaseLabel()
returned 'Failed' | 'Complete' | ... and requestPhaseTone() compared that
same string to choose a colour. The function now returns stable ids, the
tone switch uses those ids, and the display site translates, so a
translation can no longer break the colour lookup.

Tests that pinned English copy now assert the i18n keys instead, and the
cluster-v2 wizard harness feeds the component the real catalog as
window.t so its runtime assertions still read the shipped strings.
669 of the 704 keys are translated; the remaining
35 are product names, acronyms and units (oMLX,
Codex, oQ/oQe, GitHub, HuggingFace, bit-widths, ...) whose zh value is
intentionally identical to the English.

Only zh.json carries values - the other seven catalogs keep the English
fallback, so nothing outside Chinese shows Simplified Chinese.

Terminology is one translation per English term across every screen, and
space-constrained labels use shorter forms than body copy. Typography
follows the conventions already established in this catalog: full-width
punctuation in Chinese sentences (including parentheses wrapping a
placeholder), half-width only for numbers, paths and identifiers,
capitalised Token as a common noun, and no ASCII ellipsis.
@LXD-8
LXD-8 force-pushed the i18n/hardcoded-ui-migration branch from bcb85a6 to cec6c9c Compare September 12, 2026 17:38
The keys this PR introduced were translated without looking at the macOS
app's catalog, so a number of them shipped a second Chinese rendering for
text the app already translates. 31 values change -- 30 in the web catalog,
1 in the app's -- and every one of them is a key this PR added; no
pre-existing value is touched, because that half lives in the
catalog-audit PR.

  Cluster / Bench / Tools   无头节点, 最小, 批次, 预处理 TPS/请求, 已复制
  DFlash / SpecPrefill      权重位宽 / 激活位宽 / 分组大小, L2 溢出…驱逐,
                            约 3 倍…, 较保守
  Qwen ANE tuner            调优覆盖, 允许 ANE 运行 GDN, 允许 CPU 性能感知调度
  oQ / oQe documentation    the app already had this text in Chinese, and the
                            two copies had drifted apart; the web copy now
                            matches the app's

Five oQ values keep this PR's own wording instead, because the catalog's
conventions decide them: 位宽分配 for Bit allocation, 相同的 oQ 方案 for Same
oQ plan, 标准 oQ 的作用 for What standard oQ does, 对量化误差加权 for Weight the
quantization error, and scale/bias is kept Latin in the affine-weight step.

Also capitalises Token in the one new value that still had a lower-case
`token`, and replaces straight quotes with the catalog's “” in the one value
that had them.
@LXD-8
LXD-8 force-pushed the i18n/hardcoded-ui-migration branch from cec6c9c to e0b92d3 Compare September 12, 2026 18:14
@LXD-8

LXD-8 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Superseded, and re-published as two focused PRs against the current main (ddc47184).

This PR bundled two separate concerns: the string migration (routing hardcoded copy through the catalog) and the Simplified Chinese wording for the keys it introduced. They are being re-opened on their own so each can be reviewed for what it is:

Nothing is lost here; the work is the same, just not mixed.

@LXD-8 LXD-8 closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant