feedback5 W5S: i18n phase 2 coverage — test/i18n.test.js scans the whole client, shared and the server messages (no Chinese literal unwrapped, every msgid translated: extract 0 and check --strict as a test; floors 1 000 catalog entries, 1 350 msgid uses), public/i18n/en.json drops the 64 fragments / renamed msgids the sweep replaced (none is a data text), docs/I18N.md: phase-2 coverage, what stays Chinese and why, the server's game texts, the audit's skip rules

This commit is contained in:
sganggs committed 2026-10-06 18:31:28 +08:00
1 parent 0025ceac16
commit c2e77c7fe9
3 files changed
+67 -90

No files matched your search

+26 -11
View File
@@ -4,7 +4,7 @@
import { test, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
@@ -116,7 +116,7 @@ test('server wire format: text keeps the Chinese rendering for older clients; pa
test('public/i18n/en.json: valid, English values, placeholders kept, the pilot screens and server messages translated', () => {
const entries = Object.entries(EN).filter(([k]) => !k.startsWith('_'));
assert.ok(entries.length > 800, `seeded from PR #70: ${entries.length}`);
assert.ok(entries.length >= 1000, `PR #70's seed + phase 2: ${entries.length}`);
for (const [k, v] of entries) {
assert.equal(typeof v, 'string', k);
assert.ok(v.trim(), `empty translation for ${k}`);
@@ -126,25 +126,40 @@ test('public/i18n/en.json: valid, English values, placeholders kept, the pilot s
for (const p of want) assert.ok(have.has(p), `${k}: {${p}} missing in "${v}"`);
}
for (const k of ['开始', '博士代号', '设置', '语言', '同盟模拟', '创建同盟', '加入同盟', '开始模拟', 'toggle::关闭', '整备区已满',
'第 {r} 回合 · 最终攻势', '本局禁用{names}盟约,此策略效果可能无法发挥', 'bond-threshold::名', 'list::、', '干员持有', '先锋', '开心',
'{names}只能部署在召唤者攻击范围内,已退回整备区', '{name}博士的目标生命值已耗尽', '联防阶段:{names} 迎战突破防线的敌人', '【维多利亚】获得{n}件维式重锤']) {
assert.ok(EN[k], `en.json lacks ${k}`);
}
});
test('every msgid of the pilot screens, the shell and the server messages is in public/i18n/en.json', async () => {
const files = ['public/js/screens/title.js', 'public/js/screens/lobby.js', 'public/js/screens/room.js', 'public/js/ui/settings.js',
'public/js/screens/diy.js',
'public/js/main.js', 'public/js/ui/toasts.js', 'server/match/match/messaging.js', 'server/match/player/placement.js',
'server/match/player/basics.js', 'server/match/player/acquire.js', 'server/match/effectsMeta.js', 'server/match/match/settle.js',
'server/match/match/platform.js', 'server/match/match/unitePhase.js', 'server/match/match/bossRounds.js',
'server/sim/content/bonds/addon/meta.js', 'server/sim/content/bonds/core.js'];
test('the whole client, shared and the server messages: no Chinese literal left unwrapped, every msgid in public/i18n/en.json (extract 0, check --strict)', async () => {
// what tools/i18n.mjs scans: public/js (not dev / vendor / assets / fonts), shared, server — server/sim only for the
// texts it sends (msg() / ctx.toast), its own t() / N_() are other helpers
const SKIP = new Set(['node_modules', 'vendor', 'assets', 'fonts', 'dev']);
const files = [];
const visit = (rel) => {
for (const name of readdirSync(path.join(ROOT, rel)).sort()) {
if (SKIP.has(name) || name.startsWith('.')) continue;
const r = `${rel}/${name}`;
if (statSync(path.join(ROOT, r)).isDirectory()) visit(r);
else if (/\.m?js$/.test(name)) files.push(r);
}
};
for (const root of ['public/js', 'shared', 'server']) visit(root);
assert.ok(files.length > 300, `${files.length} files`);
const missing = [];
let used = 0;
for (const f of files) {
const { msgids, literals } = await scanSource(readFileSync(path.join(ROOT, f), 'utf8'), f);
const server = f.startsWith('server/');
for (const m of msgids) if ((!server || m.via === 'msg' || m.via === 'server') && !EN[m.msgid]) missing.push(`${f}:${m.line} ${m.msgid}`);
if (!server) for (const l of literals) if (!l.reason) missing.push(`${f}:${l.line} not wrapped: ${l.msgid}`);
for (const m of msgids) {
if (server && m.via !== 'msg' && m.via !== 'server') continue;
used++;
if (!EN[m.msgid]) missing.push(`${f}:${m.line} ${m.msgid}`);
}
if (!f.startsWith('server/sim/')) for (const l of literals) if (!l.reason) missing.push(`${f}:${l.line} not wrapped: ${l.msgid}`);
}
assert.ok(used >= 1350, `msgid uses: ${used} (1 404 at phase 2)`);
assert.deepEqual(missing, []);
});