feedback5 WI: docs/I18N.md (translating, adding strings, rebuilding the EN game texts, phase 2) and credits

NOTICE.md and THIRD-PARTY-NOTICES.md credit @YuriRestia (PR #70) for the English UI strings and fallback
translations, note that PR #70's ak-spa-database.pages.dev texts are not used and that arknights.wiki.gg is
CC BY-SA 4.0, and name the EN data mirrors (ArknightsAssets/ArknightsGamedata, Kengxxiao/ArknightsGameData_YoStar).
DATA.md: data/i18n/en.json is written by tools/build-i18n.mjs (rerun after a text-changing build).
PLAYING.md: the 中文 / English switch.
This commit is contained in:
sganggs committed 2026-10-05 20:20:09 +08:00
1 parent 5398a0cfc5
commit e50497b1ce
5 files changed
+179 -2

No files matched your search

+5 -1
View File
@@ -13,6 +13,8 @@ Copyright (C) 2026 Stronghold-Protocol contributors
- `tools/local-extract/aklz4.py` 来自 [isHarryh/Ark-Unpacker](https://github.com/isHarryh/Ark-Unpacker),保持 BSD-3-Clause 许可(见 `tools/local-extract/LICENSE-Ark-Unpacker.txt`)。 - `tools/local-extract/aklz4.py` 来自 [isHarryh/Ark-Unpacker](https://github.com/isHarryh/Ark-Unpacker),保持 BSD-3-Clause 许可(见 `tools/local-extract/LICENSE-Ark-Unpacker.txt`)。
- 通过 npm 安装的第三方库(PixiJS、pixi-spine、Preact、htm、three.js、ws 等)和字体各自保留原许可证,见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。 - 通过 npm 安装的第三方库(PixiJS、pixi-spine、Preact、htm、three.js、ws 等)和字体各自保留原许可证,见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。
**英文翻译致谢**:界面英文文本 `public/i18n/en.json` 与游戏文本的补充译文 `tools/i18n/fallback-pr70.json` 以 GitHub PR #70(分支 `en-translation`)中 **@YuriRestia** 的翻译为基础,经项目所有者决定(2026-10-05)署名沿用,随本项目一同以 GPL-3.0-or-later 发布;PR #70 中取自 ak-spa-database.pages.dev(未声明许可)的文本未被采用。英文游戏文本 `data/i18n/en.json` 本身是官方英文服数据(见第 2 节)。
**附加许可(GPL-3.0 第 7 条)** — Additional permission under GNU GPL version 3 section 7: **附加许可(GPL-3.0 第 7 条)** — Additional permission under GNU GPL version 3 section 7:
> If you modify this Program, or any covered work, by linking or combining it with the Spine Runtimes (as shipped in > If you modify this Program, or any covered work, by linking or combining it with the Spine Runtimes (as shipped in
@@ -28,7 +30,7 @@ Copyright (C) 2026 Stronghold-Protocol contributors
《明日方舟》及「卫戍协议」相关的全部**名称、角色、美术、Spine 模型、界面图、音乐音效、文本与游戏数据**,版权归上海鹰角网络科技有限公司及其授权方(Yostar 等)所有。具体包括: 《明日方舟》及「卫戍协议」相关的全部**名称、角色、美术、Spine 模型、界面图、音乐音效、文本与游戏数据**,版权归上海鹰角网络科技有限公司及其授权方(Yostar 等)所有。具体包括:
- Release 完整包中的 `public/assets/**`(含从官方客户端本地提取的 3D 棋盘模型与贴图 `public/assets/local/**`)和 `public/fonts/**`(字体归各自作者); - Release 完整包中的 `public/assets/**`(含从官方客户端本地提取的 3D 棋盘模型与贴图 `public/assets/local/**`)和 `public/fonts/**`(字体归各自作者);
- 由官方数据表生成的 `data/*.json`,以及含有或派生自游戏数据的 `docs/research/*.json`、`test/fixtures/official-waves.json`、`public/dev/recordings/*.json`; - 由官方数据表生成的 `data/*.json` 与 `data/i18n/*.json`(官方英文服文本),以及含有或派生自游戏数据的 `docs/research/*.json`、`test/fixtures/official-waves.json`、`public/dev/recordings/*.json`;
- `docs/img/` 中的游戏截图; - `docs/img/` 中的游戏截图;
- `docs/` 中引用的 PRTS、BWIKI、NGA、巴哈姆特等社区页面的文字(仍按其来源的许可,维基文本为 CC BY-NC-SA)。 - `docs/` 中引用的 PRTS、BWIKI、NGA、巴哈姆特等社区页面的文字(仍按其来源的许可,维基文本为 CC BY-NC-SA)。
@@ -64,3 +66,5 @@ art, models, audio and data — including everything under `public/assets/` in t
Yostar and their licensors, are **not** covered by the GPL, and may be used for study and personal non-commercial Yostar and their licensors, are **not** covered by the GPL, and may be used for study and personal non-commercial
purposes only: no selling, paid distribution, paid hosting, ads, donations or any other monetisation. Rights holders purposes only: no selling, paid distribution, paid hosting, ads, donations or any other monetisation. Rights holders
can request removal through a GitHub issue and the content will be taken down. No warranty of any kind. can request removal through a GitHub issue and the content will be taken down. No warranty of any kind.
The English UI strings were seeded from GitHub PR #70 by @YuriRestia (credited, released with the project under
GPL-3.0-or-later); the English game texts (`data/i18n/en.json`) are official EN client data like `data/*.json`.
+13
View File
@@ -48,9 +48,22 @@ All names, characters, artwork, Spine models, UI graphics, music, sound effects
[fexli/ArknightsResource](https://github.com/fexli/ArknightsResource), [fexli/ArknightsResource](https://github.com/fexli/ArknightsResource),
[isHarryh/Ark-Models](https://github.com/isHarryh/Ark-Models), [isHarryh/Ark-Models](https://github.com/isHarryh/Ark-Models),
[ArknightsAssets/ArknightsAssets2](https://github.com/ArknightsAssets/ArknightsAssets2) — thanks to their maintainers. [ArknightsAssets/ArknightsAssets2](https://github.com/ArknightsAssets/ArknightsAssets2) — thanks to their maintainers.
The English game texts (`data/i18n/en.json`, `tools/build-i18n.mjs`) come from the official EN client tables mirrored by
[ArknightsAssets/ArknightsGamedata](https://github.com/ArknightsAssets/ArknightsGamedata) (`en/gamedata`, the default
source) and [Kengxxiao/ArknightsGameData_YoStar](https://github.com/Kengxxiao/ArknightsGameData_YoStar) (`en_US`,
`--source yostar`); same terms as the rest of the game data.
Quotations of PRTS Wiki, BWIKI, NGA, 巴哈姆特 and other community pages in `docs/` stay under the terms of their Quotations of PRTS Wiki, BWIKI, NGA, 巴哈姆特 and other community pages in `docs/` stay under the terms of their
sources (the wikis' texts are CC BY-NC-SA). sources (the wikis' texts are CC BY-NC-SA).
## Translations
The English UI strings (`public/i18n/en.json`) and the fallback game-text translations (`tools/i18n/fallback-pr70.json`)
are based on GitHub PR #70 (branch `en-translation`) by **@YuriRestia**, reused with credit and released with the project
under GPL-3.0-or-later (docs/I18N.md). PR #70 named ak-spa-database.pages.dev and arknights.wiki.gg as references: texts
it took from ak-spa-database.pages.dev (no licence) are excluded; arknights.wiki.gg is CC BY-SA 4.0, which may be combined
into a GPL-3.0 work (the one-way compatibility Creative Commons declared) — thanks to its contributors. Official game
terms and names in those files remain © Hypergryph / Yostar.
--- ---
## Licence texts ## Licence texts
+4 -1
View File
@@ -1,8 +1,11 @@
# DATA.md — generated game data (`data/*.json`) # DATA.md — generated game data (`data/*.json`)
All files in `data/` except `data/assets.json` are produced by **`node tools/build-data.mjs`** (task F1) from the All files in `data/` except `data/assets.json` and `data/i18n/` are produced by **`node tools/build-data.mjs`** (task F1) from the
official zh_CN client data ([Kengxxiao/ArknightsGameData](https://github.com/Kengxxiao/ArknightsGameData)) joined official zh_CN client data ([Kengxxiao/ArknightsGameData](https://github.com/Kengxxiao/ArknightsGameData)) joined
with `docs/research/*.json`. Do not edit them by hand — change the build script and rebuild. with `docs/research/*.json`. Do not edit them by hand — change the build script and rebuild.
`data/i18n/en.json` (the official English texts of these files by record id and field) is written by
`node tools/build-i18n.mjs` — rerun it after a build that changes a text (docs/I18N.md §2; `test/i18n-data.test.js`
fails while it is stale).
`data/assets.json` is written by `tools/fetch-assets.mjs`, which keeps the current file rather than drop entries whose `data/assets.json` is written by `tools/fetch-assets.mjs`, which keeps the current file rather than drop entries whose
downloads failed on this machine unless `--allow-shrink` (or `--prune`) is passed (docs/ASSETS.md, DESIGN §21.25). downloads failed on this machine unless `--allow-shrink` (or `--prune`) is passed (docs/ASSETS.md, DESIGN §21.25).
+155
View File
@@ -0,0 +1,155 @@
# I18N.md — languages (中文 / English)
Chinese is the default; English is a switch (the owner's decision of 2026-10-05): an English browser still opens in
Chinese until the player picks English on the title screen (top right) or in 设置 / Settings. The choice is kept in
`localStorage` (`sp.pref.lang`); `?lang=en` / `?lang=zh` in the address bar sets it once and is then removed. A switch
re-renders the page in place (no reload).
Three kinds of text, three mechanisms — no i18n framework:
| Text | Source of the English | Mechanism |
|---|---|---|
| UI strings (buttons, labels, hints, toasts) | `public/i18n/en.json` — Chinese msgid → English | `t()` in the code (`shared/i18n.js`) |
| Game texts (operators, skills, talents, modules, traits, enemies, bonds, items, effects, strategies, 特质, 机变 cards, modes, stages, tokens) | `data/i18n/en.json` — the official EN client texts by record id and field | `public/js/data.js` returns localized records |
| Server messages (m.toast, m.ticker, error codes) | the same `public/i18n/en.json` | the server sends msgid + params, the client translates |
## 1. UI strings: `t()`
gettext style — the Chinese source string is the message id, so an untranslated string shows Chinese, never a key:
```js
import { t, tc, N_ } from '../../../shared/i18n.js';
t('整备区已满'); // 'Bench is full' in English
t('还剩 {n} 秒', { n }); // params: {name}; arrays fill {0}, {1} …
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
```
- **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).
- **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.
- 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):
| 中文 | English | 中文 | English |
|---|---|---|---|
| 盟约 | Alliance | 调度中心 | Dispatch Center |
| 整备区 / 临时整备区 | Bench / temporary Bench | 资金 | Funds |
| 休整期 | Rest Phase | 机变阶段 | Improv Phase |
| 联防 / 联防阶段 | Unite / Unite Phase | 最终攻势 | Final Assault |
| 隐秘核心 | Hidden Core | 目标生命值 | LP |
| 联合模拟 / 同盟模拟 | Team Simulation | 独立模拟 | Solo Simulation |
| 标准 / 险境 / 绝境 / 终极模拟 | Standard / Perilous / Dire / Ultimate Simulation | 入门协议 | Entry Protocol |
| 策略 | Strategy | 特质 | Attribute |
| 精锐(干员) | Elite (Operator) | 层数 | stacks |
| 同盟密钥 | Alliance Key | 创建者 | Host |
### 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).
- `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 →
`t('…')`, template literals → `t('…{name}…', { name })` (an expression's param name: identifier → itself, `a.b` → `b`,
`x.length` → `n`, else its position), nested strings in params included; adds the import. Module-level literals get
`N_()` and are listed for a manual `t()` where they are shown; compared literals, object keys, string-method
arguments, `console.*` and `Error` messages are left alone; a text run with a child that is not a plain value is split
into its static pieces. **Review the diff**: join fragments into whole sentences where it reads better (the settings
hotkey line is an example), check module-level tables.
- `seed --from <map.json> [--all] [--write]` — fills `en.json` from a flat `{ msgid: English }` map for the msgids the
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).
## 2. Game texts: `data/i18n/en.json`
```
node tools/build-i18n.mjs # downloads the EN tables once into .cache/gamedata-en/, writes data/i18n/en.json
node tools/build-i18n.mjs --offline # never download (the zh_CN tables come from .cache/gamedata, as for build-data)
node tools/build-i18n.mjs --check # exit 1 when data/i18n/en.json is out of date (nothing written)
node tools/build-i18n.mjs --source yostar # the other EN source (see below)
```
**Rebuild it after every `node tools/build-data.mjs`** that changes a text: `test/i18n-data.test.js` fails while a
translation's Chinese source no longer matches `data/*.json`, and at run time such a stale leaf is not applied (the
player sees the current Chinese text, never an outdated number).
- **Sources.** The English comes from the official EN client tables. Default `--source assets`:
[ArknightsAssets/ArknightsGamedata](https://github.com/ArknightsAssets/ArknightsGamedata) `en/gamedata` — current, and
its `activity_table` has both 盟约 seasons (`act1autochess`, `act2autochess`, "Stronghold Protocol: Alliance") with
the same ids as zh_CN. `--source yostar`:
[Kengxxiao/ArknightsGameData_YoStar](https://github.com/Kengxxiao/ArknightsGameData_YoStar) `en_US` — archived in
2025-11, it predates 盟约 (only the 2025 test event `act1vautochess`) and many operators, so most mode texts and new
operators stay Chinese with it.
- **Matching** (no id table to maintain): the zh_CN and EN tables are walked side by side; a data text equal to a
Chinese source takes its English; a text whose blackboard placeholders build-data resolved (`{atk:0%}` → `80%`) is
matched against the Chinese templates of the same shape, its numbers are read back and the English template is
filled with them (the remake's numbers, not the EN server's); stage names are assembled from their parts. Ties prefer
the source on the record's own ids, then the 下半 season (`act2autochess`). Rich-text tags and placeholders are the
same as in Chinese, so `ui/richText.js` renders the English unchanged (a few official EN texts leave a tag unclosed;
the parser runs it to the end of the text, as the game does).
- **Fallbacks**, in order: `tools/i18n/fallback-remake.json` (the remake's own strings: stage-name parts, item notes,
the 联防 map names, the season name), `tools/i18n/fallback-pr70.json` (PR #70's translations of texts the EN client
lacks: modules of CN-only operators, the 甄选 slots, 结算称号 criteria, emote labels), then `public/i18n/en.json`;
anything else stays Chinese. Research notes (`bonds.*.spec`, `implFormula`, `assumed` …) are never translated.
- **Format** (`shared/i18nData.js`): `{ version, lang, meta: { source, coverage, totals }, names, files }`. `files[file][id]`
mirrors the record — string leaves replace the Chinese at the same path, arrays are index-aligned (null = keep) —
with `_h`, a 2-character hash of each replaced Chinese string; `names` maps every record-level Chinese name to English
(server messages and ticker lines name operators / items / bonds in Chinese).
- **Coverage** (build of 2026-10-05, 14 931 texts): 99.9 % translated, 99.3 % from the official EN data; only nine
CN-only operators of the 甄选 pool keep Chinese names. Per kind in `.cache/build-i18n-report.json` (also `missing`:
every untranslated text) and in `meta.coverage`.
- **Client**: `data.setLocale(lang)` downloads the overlay once (~1.5 MB, ~190 KB gzipped) and from then on `get`,
`lookup` and `list` (and every getter built on them) return localized copies; `data.getRaw(name)` keeps the Chinese
file; `data.localeName(zh)` translates a name.
## 3. Server messages
The server sends a toast or a custom ticker line as a plain string (its own msgid) or as
`msg(msgid, params)` (`shared/i18n.js`). Frames keep `text` — the Chinese rendering, what older clients show — and add
`msgid` / `params`; data names are `dn()` params. A `config.broadcasts` line (`tickerFor`) also carries its `args`, and
the client fills the localized broadcast template (`ui/lang.js tickerText`; `{0}` is the player's name and is never
translated). Error frames already carry their ERR code: `ui/toasts.js describeError` translates `ERR_TEXT[code]`. The
protocol is backward compatible both ways (new fields are optional).
```js
this.m.toast(this, 'warn', msg('{names}只能部署在召唤者攻击范围内,已退回整备区', { names: back.map(dn) }));
```
## 4. Phase 2 (after the 0.2.0 feature screens land)
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:
1. `node tools/i18n.mjs extract` lists the rest (2026-10-05: 1 036 literals in 57 files — briefing, band draft, game
screen and its HUD / panels / overlays, loadout, result, guide, connection banner, components …; 966 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.
## Credits
The English UI strings were seeded from GitHub PR #70 (branch `en-translation`) by **@YuriRestia**, reused with credit
(the owner's decision of 2026-10-05); see NOTICE.md and THIRD-PARTY-NOTICES.md. PR #70's texts taken from
ak-spa-database.pages.dev (no licence) are not used. The English game texts are the official EN client data
(© Hypergryph / Yostar), via the community mirrors named above.
+2
View File
@@ -4,6 +4,8 @@
本文数值来自官方数据表(`data/config.json` 等)与 [research](research/00-INDEX.md);游戏内左下角 📖「玩法说明」有官方教程图(随素材一起下载;没下载到时显示文字要点)。操作方式见 [README · 操作](../README.md#操作)。 本文数值来自官方数据表(`data/config.json` 等)与 [research](research/00-INDEX.md);游戏内左下角 📖「玩法说明」有官方教程图(随素材一起下载;没下载到时显示文字要点)。操作方式见 [README · 操作](../README.md#操作)。
界面语言:默认中文;标题画面右上角或「设置」里可切换 **中文 / English**(会记住选择,不用刷新)。英文的干员、技能、盟约、装备、敌人等游戏文本取自官方英文服数据;尚未翻译的界面文字暂时显示中文(docs/I18N.md)。
## 1. 一局的流程 ## 1. 一局的流程
``` ```