feedback5: merge W5S — i18n phase 2: every player-facing Chinese literal in the client, shared and server goes through t() / tParts() / N_() (1 128 → 0; en.json 1 015 entries), server-sent data texts show the English record, a language switch refreshes cached names; test/i18n.test.js enforces extract 0 / check --strict over the whole tree; Chinese output unchanged (per-file render proof); golden unchanged
This commit is contained in:
commit
3ac88dea43
73 files changed
+1280
-903
No files matched your search
+51
-18
@@ -24,25 +24,32 @@ t('还剩 {n} 秒', { n }); // params: {name}; arrays fill {0},
|
||||
t('同盟密钥为 {ROOM_CODE_LEN} 位字母或数字', { ROOM_CODE_LEN });
|
||||
tc('toggle', '关闭'); // context: the key 'toggle::关闭' ("Off"), else '关闭' ("Close")
|
||||
const TABLE = { a: N_('独立模拟') }; // module level: N_ only marks it; call t(TABLE.a) where it is shown
|
||||
html`<span>${tParts('第 {r} 回合 · 最终攻势', { r: html`<b class="num">${r}</b>` })}</span>`; // markup inside a sentence
|
||||
```
|
||||
|
||||
- **Placeholders** are the same in the msgid and the translation: `{name}`, positional `{0}`, and one plural form for
|
||||
English, `{n|one|other}` (`'{n} {n|round|rounds}'`). A value that is an array renders as a list (`、` / `, `). A
|
||||
missing value leaves the placeholder as it is (official templates such as `{0}博士…` pass through untouched).
|
||||
- **Markup inside a sentence** (a number in `<b>`, a `<kbd>`): `tParts(msgid, params)` returns the translation split at
|
||||
its placeholders, with a param that is markup (a vnode, or an array of vnodes) kept as it is — the translator gets the
|
||||
whole sentence and its word order, the Chinese pieces are the text around the markup exactly as before. Do not split
|
||||
such a sentence into fragments (`第` … `回合`).
|
||||
- **Game-data names** inside a message are marked `dn(name)` (`{ dn: '…' }`) and translated through the localized data
|
||||
(`tName(name)` does the same for a bare name). **Player names are never translated** — pass them as plain strings.
|
||||
- **Data texts are not msgids**: `getChess(id).name`, `getMode(id).desc`, `data.get('config').tips` … already come in the
|
||||
current language (§2). Do not wrap them in `t()`.
|
||||
- **Re-rendering**: `main.js` App subscribes with `useLang()` (`public/js/ui/lang.js`), so the whole tree re-renders on a
|
||||
switch. A component that caches text outside rendering (a `useMemo` returning text, a module-level string) must
|
||||
depend on the language (`useLang()`) or move the `t()` call into rendering.
|
||||
depend on the language (`useLang()`) or move the `t()` call into rendering; a memo over data records (the roster, the
|
||||
strategy list, `useGameData()`) lists `data.locale()` among its dependencies, so the game texts follow the switch.
|
||||
- Font samples, data keys and other Chinese that is not UI text can carry `// i18n-ignore` on the line.
|
||||
|
||||
### Translating
|
||||
|
||||
Edit `public/i18n/en.json` (`"msgid": "English"`, two-space JSON; `_about` is metadata). Keep every `{placeholder}`;
|
||||
`test/i18n.test.js` checks that, that no Chinese is left in a value, and that every msgid of the converted screens is
|
||||
present. Use the official EN terms of the game (the EN client's own wording, e.g. its tips and mode texts):
|
||||
`test/i18n.test.js` checks that, that no Chinese is left in a value, and that every msgid the code uses is present. A
|
||||
translation may use more params than its msgid (`'{n} {count|unit|units}'` with `count` passed beside a vnode `n`). Use
|
||||
the official EN terms of the game (the EN client's own wording, e.g. its tips and mode texts):
|
||||
|
||||
| 中文 | English | 中文 | English |
|
||||
|---|---|---|---|
|
||||
@@ -59,8 +66,9 @@ present. Use the official EN terms of the game (the EN client's own wording, e.g
|
||||
|
||||
### Tools: `node tools/i18n.mjs`
|
||||
|
||||
- `extract [paths] [--list]` — per file: the msgids in `t()` / `tc()` / `N_()` / `msg()`, and the Chinese literals still
|
||||
shown untranslated; the total says how many of those already have a translation (ready for the codemod).
|
||||
- `extract [paths] [--list]` — per file: the msgids in `t()` / `tc()` / `tParts()` / `N_()` / `msg()`, and the Chinese
|
||||
literals still shown untranslated; the total says how many of those already have a translation (ready for the
|
||||
codemod).
|
||||
- `check [paths] [--strict] [--stale]` — msgids the code uses that `public/i18n/en.json` lacks (`--strict` exits 1;
|
||||
`--stale` lists entries no code uses yet).
|
||||
- `codemod <files> [--write]` — wraps a file's Chinese literals: html`` text and attribute values → `${t('…')}`, strings →
|
||||
@@ -74,7 +82,15 @@ present. Use the official EN terms of the game (the EN client's own wording, e.g
|
||||
code uses (`--all`: also the literals not wrapped yet, under the msgid the codemod will give them).
|
||||
|
||||
Server code is scanned too: the first argument of `msg()` and a Chinese literal handed to `m.toast` / `ctx.toast` /
|
||||
`tickerText` are msgids (`server/sim` only for those — its own `t()` helpers are not translation calls).
|
||||
`tickerText` are msgids (`server/sim` only for those — its own `t()` helpers are not translation calls). Not player text,
|
||||
so neither wrapped nor counted: `console.*` / logger calls (`log.warn(…)`, `this.m.log?.info?.(…)`), `Error` messages,
|
||||
the `detail` of an error result (`{ error: 'BAD_TARGET', detail: '…' }`, `ev.detail = '…'` — players see `ERR_TEXT`), a
|
||||
line marked `// i18n-ignore` and a file marked `i18n-ignore-file` in a comment (the developer invariant reports
|
||||
`server/match/audit.js` / `invariants.js`, the bilingual HTTP error pages `server/http/*`). An aliased import
|
||||
(`import { t as tr }`, where `t` is a local variable) is read as the function it names.
|
||||
|
||||
`test/i18n.test.js` scans what `extract` / `check` scan (public/js, shared, server) and fails on a Chinese literal left
|
||||
unwrapped or a msgid missing from `public/i18n/en.json` — `extract` 0 and `check --strict` are part of the tests.
|
||||
|
||||
## 2. Game texts: `data/i18n/en.json`
|
||||
|
||||
@@ -132,21 +148,38 @@ protocol is backward compatible both ways (new fields are optional).
|
||||
this.m.toast(this, 'warn', msg('{names}只能部署在召唤者攻击范围内,已退回整备区', { names: back.map(dn) }));
|
||||
```
|
||||
|
||||
## 4. Phase 2 (after the 0.2.0 feature screens land)
|
||||
Game texts the server copies into `m.public` / `m.private` in Chinese — a 机变 card's name and description, the draft's
|
||||
name, an effect entry of the effects column, a reward offer's label, a result title — are shown from the localized data
|
||||
on the client: names through `tName()` (every record name is in the overlay's `names`), texts through
|
||||
`ui/lang.js sentText(sent, raw, local)`, which takes the localized record's text when the sent text is that record's own
|
||||
Chinese text (`data.lookupRaw` / `data.getRaw`), else the sent text (a line the server reworded — the two-battle text of a
|
||||
multi-round 悬赏, `server/match/choices.js bountyText` — stays Chinese). In Chinese both return what the server sent.
|
||||
|
||||
## 4. Coverage of the UI strings (phase 2, 0.2.0)
|
||||
|
||||
Phase 1 converted the title screen, the lobby, the room, the settings panel (with the language switch), the app shell
|
||||
(main.js: toasts, close reasons, the crash screen), `ui/toasts.js`, `DifficultyTag` and the server messages. Phase 2:
|
||||
(main.js: toasts, close reasons, the crash screen), `ui/toasts.js`, `DifficultyTag` and the server messages. Phase 2
|
||||
(2026-10-06) converted the rest: of the 1 128 Chinese literals `extract` listed, 1 056 go through `t()` / `tParts()` /
|
||||
`N_()` — the briefing, the band draft, the game screen and its overlays, 干员调配 / 干员持有, the result (275), the HUD,
|
||||
shop bar, detail card, bond strip, 机变 overlay, enemy drawer, team panel, guide, emotes, components and the client
|
||||
models (579), the phase banners, refusal reasons and formats of `ui/gameLogic` (106), the canvas / fallback-field / facing
|
||||
labels (16) and the msgid tables of `shared/constants.js` (80) — and 72 are marked as no player text (above). Now
|
||||
`node tools/i18n.mjs extract` reports 0, `check` 1 002 msgids, all in `public/i18n/en.json` (1 015 entries). The Chinese
|
||||
output did not change: a render of every converted file in Chinese (each `t()` call put back as the string it renders,
|
||||
html`` parsed with htm, text runs compared) equals the code before the sweep; the few rewrites it cannot follow (a
|
||||
variable folded into whole sentences, a list param in place of `.join('、')`, `fmtNum`'s English branch) were checked by
|
||||
hand. Numbers: `fmtNum` writes 万 / 亿 in Chinese and K / M / B in English.
|
||||
|
||||
1. `node tools/i18n.mjs extract` lists the rest (2026-10-05: 1 033 literals in 56 files — briefing, band draft, game
|
||||
screen and its HUD / panels / overlays, loadout, result, guide, connection banner, components …; 964 of them already
|
||||
have a translation seeded from PR #70).
|
||||
2. Per file (one stream per file group to avoid conflicts): `node tools/i18n.mjs codemod <file> --write`, review the
|
||||
diff (module-level tables: `t()` at the use site; join split sentences; `tc()` where one Chinese text needs two
|
||||
English ones), then `node tools/i18n.mjs check` and translate what is missing, in the official terms above.
|
||||
3. Texts the server puts in `m.public` / `m.private` (机变 card names and descriptions, effect names in views) should be
|
||||
looked up by id on the client (`getEffect` / `data.lookup`) instead of shown as sent.
|
||||
4. `public/js/ui/guide.js` chapter / page titles, the `GuideButton` / `LoadoutButton` default labels and tooltips.
|
||||
5. When the sweep is complete: `node tools/i18n.mjs check --strict` in CI, and `extract` reporting 0.
|
||||
What stays Chinese in English, and why:
|
||||
|
||||
- player names, including the AI seats' (`AI·华法琳` …, `server/lobby.js BOT_NAMES`) — a name is never translated;
|
||||
- a server text the client cannot match to a data record (the reworded multi-round 悬赏 text above);
|
||||
- game texts the official EN client does not have yet (§2: some 自选 operators, newer modules);
|
||||
- the 玩法说明 pages, which are the official Chinese pictures (their titles are translated).
|
||||
|
||||
Not player text, never translated: developer details and logs (`detail`, `log.*`, the invariant reports), the data keys
|
||||
(`shared/highGround.js` reads the trait text 「可以放置于远程位」), the development STUB match, and the HTTP error pages,
|
||||
which are bilingual on purpose.
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
Reference in new issue
Block a user