feedback5: Japanese, Korean and Traditional Chinese ship — the official game texts of the JP / KR / TW clients (data/i18n/ja.json 96.5 %, ko.json 97.4 %, zh-TW.json 91.1 % of 22 114 texts, built with build-i18n --lang from ArknightsAssets/ArknightsGamedata) and minimal UI packs public/i18n/ja.json, ko.json, zh-TW.json (a _meta manifest, no UI strings yet): ja / ko fall back to the English UI, zh-TW to the Simplified Chinese UI (no fallback; numbers in 萬 / 億); the owner's decision of 2026-10-07; UI translations welcome from the community; golden: unchanged (client text only)
- The packs: _meta = type lang, the code, its own and English name (日本語 / Japanese, 한국어 / Korean, 繁體中文 / Traditional Chinese), version 0.2.0, app >=0.2.0, authors, credits (the official client data, © Hypergryph (/ Yostar for JP and KR), via ArknightsAssets/ArknightsGamedata), fallback ["en"] for ja / ko and [] for zh-TW; formatted as `node tools/i18n.mjs template` writes them, so a translator runs it on the shipped file and keeps the manifest. - Built offline from the tables W5AD downloaded and checked today (jp / kr 51.10.0, tw 51.2.0; copied into .cache/gamedata-<code>): coverage as W5AD measured; `build-i18n --lang <code> --check` up to date for en, ja, ko, zh-TW. `node tools/i18n.mjs check --all --strict`: 0 errors (en complete; ja / ko / zh-TW 0 of 1022 msgids, fallback). `node tools/packs.mjs list` lists the four packs with their game texts; `tools/package.mjs --dry-run --list` ships data/i18n and public/i18n of all four and the generated packs/index.json. - With five languages the menu is a list (ui/lang.js SEGMENTED_MAX 4, W5AD's rule): test/ui/i18n.e2e.test.js reads and picks either form, expects the shipped languages, and checks each new pack in the browser (its official game text, the fallback UI, <html lang>) — SP_E2E=1: 3 / 3; the title page of the five devices (devices.e2e "title page") still fits: 5 / 5. - Docs: docs/I18N.md (intro, §2, §5 "Shipped packs" with the call for UI translations), docs/PLAYING.md (语言), DESIGN §25.20 and the §25 intro's decisions of 2026-10-07. Gate: non-browser suites 5024 / 5024 (test/i18n*, test/package, client-static 358 / 358), npm run golden ok, test/golden.test.js 9 / 9, lint 0 errors, check:imports ok, typecheck ok.
This commit is contained in:
1 parent
9738bb829a
commit
2e83a6b977
10 files changed
+125
-17
No files matched your search
@@ -7,7 +7,10 @@
|
||||
// game-data overlay (data/i18n/en.json) is applied; `?lang=zh` switches back and leaves the address bar. Language packs
|
||||
// (docs/I18N.md "Adding a language", docs/PACKS.md): a pack file dropped into public/i18n/ and a pack folder dropped
|
||||
// into packs/ while the server runs show in the menu and switch, with English filling what they lack; a removed pack
|
||||
// sends a stored choice back to Chinese. No console / page / request errors. docs/I18N.md.
|
||||
// sends a stored choice back to Chinese. The shipped Japanese, Korean and Traditional Chinese packs (the owner's decision of
|
||||
// 2026-10-07) switch to their official game texts with the UI of their fallback (English; Simplified Chinese for zh-TW).
|
||||
// With more than four languages the menu is a list (ui/lang.js SEGMENTED_MAX): the helpers read and pick either form.
|
||||
// No console / page / request errors. docs/I18N.md.
|
||||
|
||||
import { test, describe, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -38,6 +41,17 @@ describe('language switch on the title screen', { skip: !ENABLED && 'set SP_E2E=
|
||||
after(async () => { removePacks(); await browser?.close(); await srv?.close(); });
|
||||
|
||||
const text = (page, sel) => page.$eval(sel, (el) => el.textContent.replace(/\s+/g, ' ').trim());
|
||||
/** The menu's languages, [code, label] — its buttons, or the options of its list. */
|
||||
const menu = (page) => page.$$eval('[data-testid="lang-toggle"] button, [data-testid="lang-toggle"] option',
|
||||
(els) => els.map((e) => [e.dataset.lang ?? e.value, e.textContent.trim()]));
|
||||
/** Pick a language: its button, or its option in the list. */
|
||||
const pick = async (page, code) => {
|
||||
if (await page.$('[data-testid="lang-toggle"] select')) await page.select('[data-testid="lang-toggle"] select', code);
|
||||
else await page.click(`[data-testid="lang-toggle"] button[data-lang="${code}"]`);
|
||||
};
|
||||
const SHIPPED = [['zh', '中文'], ['en', 'English'], ['ja', '日本語'], ['ko', '한국어'], ['zh-TW', '繁體中文']];
|
||||
/** A game text of a shipped pack's overlay (data/i18n/<code>.json; a leaf is a string or { _s }). */
|
||||
const gameText = (code) => { const v = JSON.parse(fs.readFileSync(path.join(ROOT, `data/i18n/${code}.json`), 'utf8')).files.config.modes.mode_multi_abyss.name; return typeof v === 'string' ? v : v._s; };
|
||||
|
||||
test('中文 by default; English in place, kept across a reload; ?lang=zh back to Chinese', async () => {
|
||||
const page = await browser.newPage();
|
||||
@@ -52,7 +66,8 @@ describe('language switch on the title screen', { skip: !ENABLED && 'set SP_E2E=
|
||||
assert.equal(await text(page, '.title-login .btn--primary'), '开始');
|
||||
assert.equal(await page.evaluate(() => document.documentElement.lang), 'zh-CN');
|
||||
|
||||
await page.click('[data-testid="lang-toggle"] button[data-lang="en"]');
|
||||
assert.deepEqual(await menu(page), SHIPPED, 'the shipped languages');
|
||||
await pick(page, 'en');
|
||||
await page.waitForFunction(() => document.querySelector('.title-cn')?.textContent.includes('Stronghold Protocol'), { timeout: 8000 });
|
||||
assert.equal(await text(page, '.title-cn'), 'Stronghold Protocol: Alliance');
|
||||
assert.equal(await text(page, '.title-login .btn--primary'), 'Start');
|
||||
@@ -93,11 +108,10 @@ describe('language switch on the title screen', { skip: !ENABLED && 'set SP_E2E=
|
||||
fs.writeFileSync(path.join(PACK_DIR, 'ui.json'), JSON.stringify({ 开始: 'Qab-Start' }));
|
||||
await sleep(1100); // the server's registry looks at the folders again after a second
|
||||
await page.goto(`${base}/?lang=zh`, { waitUntil: 'networkidle0' });
|
||||
await page.waitForSelector('.title-screen [data-testid="lang-toggle"] button[data-lang="qaa"]');
|
||||
assert.deepEqual(await page.$$eval('[data-testid="lang-toggle"] button', (bs) => bs.map((b) => [b.dataset.lang, b.textContent.trim()])),
|
||||
[['zh', '中文'], ['en', 'English'], ['qaa', 'Testisch'], ['qab', 'Qabisch']]);
|
||||
await page.waitForFunction(() => [...document.querySelectorAll('.title-screen [data-testid="lang-toggle"] :is(button, option)')].some((e) => (e.dataset.lang ?? e.value) === 'qaa'), { timeout: 8000 });
|
||||
assert.deepEqual(await menu(page), [...SHIPPED.slice(0, 4), ['qaa', 'Testisch'], ['qab', 'Qabisch'], SHIPPED[4]]);
|
||||
|
||||
await page.click('[data-testid="lang-toggle"] button[data-lang="qaa"]');
|
||||
await pick(page, 'qaa');
|
||||
await page.waitForFunction(() => document.querySelector('.title-login .btn--primary')?.textContent.includes('Los'), { timeout: 8000 });
|
||||
assert.equal(await text(page, '.title-cn'), 'Testprotokoll: Bund', 'an alphabetic title: the display face, an ASCII colon');
|
||||
assert.match(await text(page, '.title-tag'), /^Allocate Funds and Operators/, 'untranslated: English, the pack\'s fallback');
|
||||
@@ -107,22 +121,41 @@ describe('language switch on the title screen', { skip: !ENABLED && 'set SP_E2E=
|
||||
await page.waitForFunction(() => globalThis.__SP__?.data?.locale() === 'qaa', { timeout: 8000 });
|
||||
assert.equal(await page.evaluate(() => globalThis.__SP__.data.get('config').modes.mode_multi_abyss.name), 'Ultimate Simulation');
|
||||
|
||||
await page.click('[data-testid="lang-toggle"] button[data-lang="qab"]');
|
||||
await pick(page, 'qab');
|
||||
await page.waitForFunction(() => document.querySelector('.title-login .btn--primary')?.textContent.includes('Qab-Start'), { timeout: 8000 });
|
||||
assert.equal(await text(page, '.title-cn'), '卫戍协议:盟约', 'no fallback declared: the Chinese msgid');
|
||||
|
||||
await page.click('[data-testid="lang-toggle"] button[data-lang="qaa"]');
|
||||
await pick(page, 'qaa');
|
||||
await page.waitForFunction(() => document.querySelector('.title-login .btn--primary')?.textContent.includes('Los'), { timeout: 8000 });
|
||||
removePacks();
|
||||
await sleep(1100);
|
||||
await page.reload({ waitUntil: 'networkidle0' });
|
||||
await page.waitForSelector('.title-screen .title-cn');
|
||||
assert.equal(await text(page, '.title-cn'), '卫戍协议:盟约', 'the stored pack is gone: Chinese');
|
||||
assert.deepEqual(await page.$$eval('[data-testid="lang-toggle"] button', (bs) => bs.map((b) => b.dataset.lang)), ['zh', 'en']);
|
||||
assert.deepEqual(await menu(page), SHIPPED);
|
||||
assert.deepEqual(problems, []);
|
||||
} finally {
|
||||
removePacks();
|
||||
await page.close();
|
||||
}
|
||||
});
|
||||
test('the shipped 日本語 / 한국어 / 繁體中文 packs: the official game texts, the UI of their fallback (English; Simplified Chinese for zh-TW)', async () => {
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1600, height: 900 });
|
||||
const problems = [];
|
||||
page.on('console', (m) => { if (m.type() === 'error') problems.push(`console: ${m.text()}`); });
|
||||
page.on('pageerror', (e) => problems.push(`pageerror: ${e.message}`));
|
||||
page.on('response', (r) => { if (r.status() >= 400 && !/fonts\.(googleapis|gstatic)/.test(r.url())) problems.push(`http ${r.status()}: ${r.url()}`); });
|
||||
await page.goto(`${base}/?lang=zh`, { waitUntil: 'networkidle0' });
|
||||
await page.waitForSelector('.title-screen [data-testid="lang-toggle"]');
|
||||
for (const [code, ui, htmlLang] of [['ja', 'Start', 'ja'], ['ko', 'Start', 'ko'], ['zh-TW', '开始', 'zh-TW']]) {
|
||||
await pick(page, code);
|
||||
await page.waitForFunction((c) => globalThis.__SP__?.data?.locale() === c, { timeout: 8000 }, code);
|
||||
assert.equal(await page.evaluate(() => globalThis.__SP__.data.get('config').modes.mode_multi_abyss.name), gameText(code), `${code}: the official game text`);
|
||||
assert.equal(await text(page, '.title-login .btn--primary'), ui, `${code}: the fallback UI`);
|
||||
assert.equal(await page.evaluate(() => document.documentElement.lang), htmlLang);
|
||||
}
|
||||
assert.deepEqual(problems, []);
|
||||
await page.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user