feedback5 WAD: docs for language packs and content packs — docs/PACKS.md (the manifest, the two layouts, discovery and the index, what is served, the types and how a later type plugs in), docs/I18N.md §5 "Adding a language" (step by step for a translator) with the fallback chain, placeholder validation and plural categories, CONTRIBUTING / README pointers, DEPLOY §7 (packs/ and packs/index.json in the zips), ARCHITECTURE, docs/history/0.2.0.md §25.16; build-i18n --dict (a community dictionary for a language without an official client); the browser check of a pack file and a pack folder dropped in while the server runs (test/ui/i18n.e2e.test.js); golden unchanged
This commit is contained in:
1 parent
25b2a87b26
commit
1ee39a79b5
11 files changed
+466
-64
No files matched your search
+107
-23
@@ -1,17 +1,22 @@
|
||||
# I18N.md — languages (中文 / English)
|
||||
# I18N.md — languages (中文, English, language packs)
|
||||
|
||||
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).
|
||||
Chinese is the default; every other language is a switch (the owner's decision of 2026-10-05): an English browser still
|
||||
opens in Chinese until the player picks a language on the title screen (top right) or in 设置 / Settings. Every
|
||||
language but Chinese is a **language pack** — a file in the language folder, nothing else (the owner's decision of
|
||||
2026-10-07; §5 "Adding a language"): the menu lists every pack the server finds, each under its own name (buttons up to
|
||||
four languages, a list beyond). English is a pack like any other; it ships with the game and is the complete one. The
|
||||
choice is kept in `localStorage` (`sp.pref.lang`); `?lang=<code>` in the address bar (`?lang=en`, `?lang=zh`) sets it
|
||||
once and is then removed; a stored language whose pack is gone opens in Chinese. A switch re-renders the page in place
|
||||
(no reload). Language packs are the first type of the content packs (docs/PACKS.md: the manifest, the registry, the
|
||||
index, the pack folders).
|
||||
|
||||
Three kinds of text, three mechanisms — no i18n framework:
|
||||
|
||||
| Text | Source of the English | Mechanism |
|
||||
| Text | Source of a language | 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 |
|
||||
| UI strings (buttons, labels, hints, toasts) | `public/i18n/<code>.json` — Chinese msgid → translation (English: `public/i18n/en.json`) | `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/<code>.json` — the official client texts by record id and field (English: the EN client) | `public/js/data.js` returns localized records |
|
||||
| Server messages (m.toast, m.ticker, error codes) | the same `public/i18n/<code>.json` | the server sends msgid + params, the client translates |
|
||||
|
||||
## 1. UI strings: `t()`
|
||||
|
||||
@@ -27,9 +32,25 @@ const TABLE = { a: N_('独立模拟') }; // module level: N_ only marks it;
|
||||
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).
|
||||
- **Placeholders** are the same in the msgid and the translation: `{name}`, positional `{0}`, and plural forms. Two
|
||||
plain forms `{n|one|other}` (`'{n} {n|round|rounds}'`) pick `one` when the language's plural category of the number
|
||||
is `one` (`Intl.PluralRules`; in English when n is 1 — as before packs, but for −1, now singular) and `other` otherwise. A language with
|
||||
more categories names them: `{n|one:секунда|few:секунды|many:секунд|other:секунды}` (categories `zero`, `one`, `two`,
|
||||
`few`, `many`, `other`; `other` is required; three or more plain forms are refused — the order of a language's
|
||||
categories differs between engines). A plural form holds no placeholder of its own: write the number beside it
|
||||
(`{n} {n|…}`). A value that is an array renders as a list joined with the language's separator (its translation of
|
||||
`list::、`; `、` in Chinese, `, ` without one). A missing value leaves the placeholder as it is (official templates such
|
||||
as `{0}博士…` pass through untouched).
|
||||
- **Validation.** A translation must keep every plain `{name}` of its msgid (a plural form does not count: it does not
|
||||
show the number), and may use another name only when the caller passes it (`'{n} {count|unit|units}'` with `count`
|
||||
passed beside a markup `n`). A translation that drops or invents a placeholder, or has a broken plural form, is
|
||||
skipped like a missing one — the next language of the chain shows — and reported once on the console of a
|
||||
development page (a dev build, or a page served from this machine: localhost); `node tools/i18n.mjs check` flags it
|
||||
against the call sites.
|
||||
- **Fallback chain**, per string: the chosen pack → its base (`_meta.base`, else the language of a regional code:
|
||||
`pt-BR` → `pt` when there is a `pt` pack; `zh-TW` has none — Chinese is the source) with the base's own chain → the
|
||||
packs its `_meta.fallback` names (`["en"]`: what it lacks shows in English) → the Chinese msgid. A partial pack is
|
||||
fine. `tc()` tries its context key and then the plain msgid in each language before the next one.
|
||||
- **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
|
||||
@@ -46,10 +67,13 @@ html`<span>${tParts('第 {r} 回合 · 最终攻势', { r: html`<b class="num">$
|
||||
|
||||
### 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 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):
|
||||
Edit the pack, e.g. `public/i18n/en.json` (`"msgid": "English"`, two-space JSON; `_meta` is its manifest, `_about` a
|
||||
note; keys starting with `_` are never strings). Keep every `{placeholder}` (above); `test/i18n.test.js` checks that,
|
||||
that no Chinese is left in an English value, and that every msgid the code uses is in English — English declares
|
||||
`"complete": true`, and `node tools/i18n.mjs check --strict` holds a complete pack to every msgid, a partial pack only to
|
||||
its placeholders. A translation may use more params than its msgid (`'{n} {count|unit|units}'` with `count` passed
|
||||
beside a vnode `n`). Use the official terms of the game (for English the EN client's own wording, e.g. its tips and mode
|
||||
texts):
|
||||
|
||||
| 中文 | English | 中文 | English |
|
||||
|---|---|---|---|
|
||||
@@ -69,8 +93,16 @@ the official EN terms of the game (the EN client's own wording, e.g. its tips an
|
||||
- `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).
|
||||
- `check [<code>… | --all] [paths] [--strict] [--stale] [--list]` — per language pack (default `en`): how many of the
|
||||
msgids the code uses it translates, and its errors — a translation that drops a placeholder, uses one no call site
|
||||
passes, or has a broken plural form; a value that is not a string; a manifest problem. Missing strings are listed for
|
||||
a complete pack (English) or with `--list`; `--stale` lists entries no code uses and no complete pack has. `--strict`
|
||||
exits 1 on an error, or when a complete pack misses a msgid. `--root <dir>` reads the packs of another checkout.
|
||||
- `template <code> [--fill <code>]` — writes `public/i18n/<code>.json`: a `_meta` manifest to fill in (the names from
|
||||
`Intl.DisplayNames`, `"fallback": ["en"]` — none for a Chinese variant, whose gaps show the Chinese msgid) and every
|
||||
msgid with an empty value in the order of English (`--fill en`: English values to overwrite). On an existing pack it
|
||||
keeps the translations and `_meta` and adds the msgids the pack lacks — run it again after an update.
|
||||
- `index` — the same as `node tools/packs.mjs index` (the pack index for a static host, docs/PACKS.md).
|
||||
- `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
|
||||
@@ -92,18 +124,22 @@ line marked `// i18n-ignore` and a file marked `i18n-ignore-file` in a comment (
|
||||
`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`
|
||||
## 2. Game texts: `data/i18n/<code>.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)
|
||||
node tools/build-i18n.mjs --lang ja # another official client: ja (JP), ko (KR), zh-TW (TW) → data/i18n/<code>.json
|
||||
node tools/build-i18n.mjs --lang fr --dict fr-texts.json # no official client: a community dictionary (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).
|
||||
player sees the current Chinese text, never an outdated number). The same holds for the game texts of every pack: a
|
||||
stale text of a pack falls back like a missing one (the next overlay of its chain, then Chinese) until the pack is
|
||||
rebuilt.
|
||||
|
||||
- **Sources.** The English comes from the official EN client tables. Default `--source assets`:
|
||||
[ArknightsAssets/ArknightsGamedata](https://github.com/ArknightsAssets/ArknightsGamedata) `en/gamedata` — current, and
|
||||
@@ -131,9 +167,27 @@ player sees the current Chinese text, never an outdated number).
|
||||
data. What stays Chinese is CN-only content the EN client does not have yet: some of the 71 owned 6★ of the 自选
|
||||
pool (names, skills, talents) and some newer modules (before the 自选 data: 14 931 texts, 99.9 %). 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.
|
||||
- **Other official clients** (`--lang`): ArknightsAssets/ArknightsGamedata also mirrors the JP, KR and TW clients
|
||||
(`jp/`, `kr/`, `tw/gamedata`), each with both 盟约 seasons (checked 2026-10-07): `--lang ja`, `ko` and `zh-TW` build the
|
||||
game texts of a pack of that language the same way (measured on that day: ja 96.5 %, ko 97.4 %, zh-TW 91.1 % of the
|
||||
22 114 texts — the TW data is older, 51.2, and a Traditional text equal to the Simplified one counts as untranslated
|
||||
although it shows the same). The remake's own strings (stage-name composites, the fallback files) are English only;
|
||||
for another language the pack's UI file (`public/i18n/<code>.json`) is the fallback dictionary, and what is left falls
|
||||
back per text at run time (the pack's chain, then Chinese). Only English is generated in the repository: a game-text
|
||||
overlay ships with the UI pack of its language (a pack is listed by its UI file).
|
||||
- **A language without an official client** (French, German, Russian …) needs no game texts at all: they follow the
|
||||
pack's chain (`"fallback": ["en"]` shows the English ones). To translate some, write a community dictionary — a flat
|
||||
JSON `{ "<Chinese data text>": "<translation>" }`; the `names` of `data/i18n/en.json` (every game-data name with its
|
||||
English) is a good first list — and run `node tools/build-i18n.mjs --lang <code> --dict <file.json>`: it writes
|
||||
`data/i18n/<code>.json` from the dictionary and the pack's UI file (exact matches only: a text whose numbers build-data
|
||||
resolved needs its own entry), and every text it does not cover falls back per text at run time. `--dict` also adds to
|
||||
an official client (tried after it).
|
||||
- **Client**: `data.setLocale(lang, chain)` downloads the overlays of the chain once each — the pack's own, its base's,
|
||||
a fallback's (`ui/lang.js dataChain`: the languages of the pack's chain that have game texts, with the URLs the pack
|
||||
index gives; English ~1.5 MB, ~190 KB gzipped) — and from then on `get`, `lookup` and `list` (and every getter built
|
||||
on them) return localized copies, **each text from the first overlay of the chain that has it**, else the Chinese one
|
||||
(`shared/i18nData.js applyFileOverlay(json, overlay, onto)`); `data.getRaw(name)` keeps the Chinese file;
|
||||
`data.localeName(zh)` translates a name through the chain.
|
||||
|
||||
## 3. Server messages
|
||||
|
||||
@@ -181,6 +235,36 @@ Not player text, never translated: developer details and logs (`detail`, `log.*`
|
||||
(`shared/highGround.js` reads the trait text 「可以放置于远程位」), the development STUB match, and the HTTP error pages,
|
||||
which are bilingual on purpose.
|
||||
|
||||
## 5. Adding a language
|
||||
|
||||
A language is a file in the language folder; the server lists it on the next page load (no restart, no build step, no
|
||||
code change). For a community translator:
|
||||
|
||||
1. **Get the code and the tools**: a clone of the repository and `npm install` (the checker reads the code's msgids).
|
||||
Pick the language's BCP 47 code in its usual case: `ja`, `ko`, `fr`, `pt-BR`, `zh-TW`.
|
||||
2. **Make the skeleton**: `node tools/i18n.mjs template <code>` writes `public/i18n/<code>.json` — the `_meta` manifest
|
||||
and every msgid (about 1 000) with an empty value, in the order of the English pack (`--fill en` fills in the English
|
||||
to overwrite). Fill in `_meta`: `name` (the language in itself, shown in the menu), `englishName`, `authors`,
|
||||
`version`; keep `"fallback": ["en"]` to show English where a string is still empty (drop it to show Chinese); set
|
||||
`numberUnits` if the language counts large numbers in 10⁴ steps like Chinese (`["万", "億"]`).
|
||||
3. **Translate** the values (empty = not yet: it falls back). Keep every `{placeholder}` of the msgid; plural forms as
|
||||
in §1; `public/i18n/en.json` is the reference for meaning and context (`ctx::msgid` keys are the same Chinese text in
|
||||
another context). Use the official terms of your language's client where it has one.
|
||||
4. **Check and try**: `node tools/i18n.mjs check <code>` — coverage, and errors to fix (a dropped or unknown
|
||||
placeholder, a broken plural form). Start the server (`npm start`), pick the language on the title screen; a
|
||||
translation the game skips is reported on the browser console. `node tools/packs.mjs list` says why a pack is not
|
||||
listed (a file name not in its usual case, broken JSON …).
|
||||
5. **Game texts** (optional): with an official client of the language, `node tools/build-i18n.mjs --lang <code>` writes
|
||||
`data/i18n/<code>.json` (§2); without one, the game texts follow the chain, or a dictionary translates some of them
|
||||
(`--dict`, §2). Share the pack as a pull request (the
|
||||
file, plus the game-text file if built), or as a folder pack that a server installs by copying it into `packs/`
|
||||
(`packs/<id>/pack.json` naming `ui.json` and `data.json`: docs/PACKS.md). After a game update, run `template <code>`
|
||||
again: it keeps the translations and adds the new msgids.
|
||||
|
||||
The tests: `test/i18n-packs.test.js` (discovery, the fallback chain, placeholders, plurals in English and Russian, the
|
||||
template and check commands, and that the Chinese and English output of `t()` is what it was before packs: every msgid
|
||||
of `public/i18n/en.json` rendered with sample params, compared with the renderer of 0.2.0 before packs).
|
||||
|
||||
## Credits
|
||||
|
||||
The English UI strings were seeded from GitHub PR #70 (branch `en-translation`) by **@YuriRestia**, reused with credit
|
||||
|
||||
Reference in new issue
Block a user