diff --git a/NOTICE.md b/NOTICE.md index e46fe1b2..036409b7 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -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`)。 - 通过 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: > 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 等)所有。具体包括: - 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/` 中引用的 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 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. +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`. diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 1b503cf0..900b5faa 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -48,9 +48,22 @@ All names, characters, artwork, Spine models, UI graphics, music, sound effects [fexli/ArknightsResource](https://github.com/fexli/ArknightsResource), [isHarryh/Ark-Models](https://github.com/isHarryh/Ark-Models), [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 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 diff --git a/docs/DATA.md b/docs/DATA.md index 0a9e4a81..ffe9ebcc 100644 --- a/docs/DATA.md +++ b/docs/DATA.md @@ -1,8 +1,11 @@ # 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 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 downloads failed on this machine unless `--allow-shrink` (or `--prune`) is passed (docs/ASSETS.md, DESIGN §21.25). diff --git a/docs/I18N.md b/docs/I18N.md new file mode 100644 index 00000000..ad9bd5ce --- /dev/null +++ b/docs/I18N.md @@ -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 [--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 [--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 --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. diff --git a/docs/PLAYING.md b/docs/PLAYING.md index 70de41fa..64591cbb 100644 --- a/docs/PLAYING.md +++ b/docs/PLAYING.md @@ -4,6 +4,8 @@ 本文数值来自官方数据表(`data/config.json` 等)与 [research](research/00-INDEX.md);游戏内左下角 📖「玩法说明」有官方教程图(随素材一起下载;没下载到时显示文字要点)。操作方式见 [README · 操作](../README.md#操作)。 +界面语言:默认中文;标题画面右上角或「设置」里可切换 **中文 / English**(会记住选择,不用刷新)。英文的干员、技能、盟约、装备、敌人等游戏文本取自官方英文服数据;尚未翻译的界面文字暂时显示中文(docs/I18N.md)。 + ## 1. 一局的流程 ```