feedback5 W5S: i18n phase 2, shared and server texts — shared/constants.js msgid tables (difficulties, phases, error codes, emote sets and labels) marked N_(); what is not player text is marked and skipped by the audit: error details and logger lines (tools/i18n.mjs reads detail: values, log.* calls and i18n-ignore-file), the developer invariant reports, the bilingual HTTP error pages, the AI seats' player names, the dev STUB match, data keys / fallbacks; the 虫动 emote set follows the official EN name (Slug Style); node tools/i18n.mjs extract now reports 0
This commit is contained in:
1 parent
7e262dc809
commit
7697b4ff40
19 files changed
+102
-70
No files matched your search
@@ -1,4 +1,5 @@
|
||||
// server/http/common.js — what every HTTP answer shares:
|
||||
// (i18n-ignore-file: the error pages are bilingual by design, 中文 · English — docs/I18N.md)
|
||||
//
|
||||
// * the security headers, set on every response before any route runs (routes.js);
|
||||
// * splitUrl (raw path + query, also from absolute-form URLs), the bilingual error page (sendError) and JSON replies
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// server/http/media.js — the extension-less audio route /media/… → public/assets/audio (see serveMedia).
|
||||
// (i18n-ignore-file: the error pages are bilingual by design, 中文 · English — docs/I18N.md)
|
||||
|
||||
import fsp from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// server/http/routes.js — the node:http request listener. Every response gets the security headers (common.js), then:
|
||||
// (i18n-ignore-file: the error pages are bilingual by design, 中文 · English — docs/I18N.md)
|
||||
//
|
||||
// * a URL longer than 4096 characters → 414; one that does not parse → 400;
|
||||
// * any method but GET / HEAD → 405 with `Allow: GET, HEAD`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// server/http/static.js — the static file server (routes.js hands it every GET / HEAD that is not an API route):
|
||||
// (i18n-ignore-file: the error pages are bilingual by design, 中文 · English — docs/I18N.md)
|
||||
//
|
||||
// / → public/ (index.html for directories)
|
||||
// /data/ → data/ (generated game data)
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ export const LOBBY_DEFAULTS = Object.freeze({
|
||||
export const SOLO_RECONNECT_FALLBACK_SEC = 86_400;
|
||||
|
||||
/** Display names for AI teammates (the tutorial NPCs first, then a few familiar faces). */
|
||||
export const BOT_NAMES = Object.freeze(['AI·华法琳', 'AI·阿米娅', 'AI·惊蛰', 'AI·杜宾', 'AI·凯尔希', 'AI·可露希尔']);
|
||||
export const BOT_NAMES = Object.freeze(['AI·华法琳', 'AI·阿米娅', 'AI·惊蛰', 'AI·杜宾', 'AI·凯尔希', 'AI·可露希尔']); // i18n-ignore: player names (docs/I18N.md)
|
||||
|
||||
const OK = Object.freeze({ ok: true });
|
||||
const fail = (code, detail) => (detail ? { error: code, detail } : { error: code });
|
||||
|
||||
@@ -225,7 +225,7 @@ export class StubMatch {
|
||||
})),
|
||||
fields: [],
|
||||
stub: true,
|
||||
message: '对局核心尚未实现(平台占位 STUB):全员确认本局信息或倒计时结束后将直接结算。',
|
||||
message: '对局核心尚未实现(平台占位 STUB):全员确认本局信息或倒计时结束后将直接结算。', // i18n-ignore: development placeholder
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// server/match/audit.js — rule auditor for sweeps and tests (tools/matchrun.mjs --check, test/match/fullmatch.test.js).
|
||||
// (i18n-ignore-file: developer reports in English with the game's terms, never shown to players — docs/I18N.md)
|
||||
//
|
||||
// attachAudit(m) wraps a live Match's phase transitions and a few prep handlers (instance-level wrappers; the engine
|
||||
// is untouched) and records every rule violation it observes, next to the structural invariants of invariants.js:
|
||||
|
||||
@@ -91,7 +91,7 @@ const ITEM_HANDLERS = {
|
||||
const n = int(paramsOf(ctx, ev.item).count, 1);
|
||||
const rec = ctx.gd.item(ev.item.id);
|
||||
ctx.addEffect({
|
||||
id: `doll:${ev.item.uid}`, key: 'effect:builtin_round_coin', name: rec ? rec.name : '精打细算玩偶', desc: rec ? rec.desc : '',
|
||||
id: `doll:${ev.item.uid}`, key: 'effect:builtin_round_coin', name: rec ? rec.name : '精打细算玩偶', desc: rec ? rec.desc : '', // i18n-ignore: the item's data name
|
||||
iconKind: 'item', iconId: rec ? rec.iconId || rec.id : ev.item.id, battle: false, params: { count: n },
|
||||
});
|
||||
},
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
import { weightedPick } from './waves.js';
|
||||
|
||||
export const FAMILY_NAMES = { bounty: '悬赏决策', supply: '道具补给', shop: '机密商店', tactic: '战术决策' };
|
||||
export const FAMILY_NAMES = { bounty: '悬赏决策', supply: '道具补给', shop: '机密商店', tactic: '战术决策' }; // i18n-ignore: = choices.json families (the client shows the localized record)
|
||||
|
||||
/**
|
||||
* Battles a multi-round bounty card lasts (data `rounds` 99, official text "之后 / 后续的<@ba.vdown>每场</>作战":
|
||||
@@ -82,7 +82,7 @@ export function bountyBattles(c) {
|
||||
const r = Number(c && c.rounds);
|
||||
return Math.max(1, Math.min(99, Number.isInteger(r) ? r : 1));
|
||||
}
|
||||
const N_ZH = ['', '一', '两', '三', '四', '五'];
|
||||
const N_ZH = ['', '一', '两', '三', '四', '五']; // i18n-ignore: rewrites the official Chinese bounty text
|
||||
/**
|
||||
* A bounty text as the card lasts: a multi-round card's "之后的 / 后续每场作战" (rich `<@ba.vdown>每场</>` or plain) reads
|
||||
* "接下来<@ba.vup>两场作战</>" like the official two-battle cards while MULTI_ROUND_BOUNTY_BATTLES is set; any other
|
||||
@@ -91,7 +91,7 @@ const N_ZH = ['', '一', '两', '三', '四', '五'];
|
||||
export function bountyText(text, c) {
|
||||
if (typeof text !== 'string' || !text || !isMultiRoundBounty(c) || !Number.isInteger(MULTI_ROUND_BOUNTY_BATTLES)) return text;
|
||||
const n = MULTI_ROUND_BOUNTY_BATTLES;
|
||||
const battles = `${N_ZH[n] || n}场作战`;
|
||||
const battles = `${N_ZH[n] || n}场作战`; // i18n-ignore
|
||||
return text
|
||||
.replace(/(?:之后的|后续的?)<@ba\.vdown>每场<\/>作战/g, `接下来<@ba.vup>${battles}</>`)
|
||||
.replace(/(?:之后的|后续的?)每场作战/g, `接下来${battles}`);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// server/match/invariants.js — engine invariants (DESIGN §11) as a non-throwing check. Used by the test harness
|
||||
// (i18n-ignore-file: developer reports in English with the game's terms, never shown to players — docs/I18N.md)
|
||||
// (test/match/harness.js checkInvariants asserts the list is empty) and by tools/matchrun.mjs --check sweeps.
|
||||
//
|
||||
// collectViolations(m) → string[] (empty when every invariant holds):
|
||||
|
||||
@@ -166,7 +166,7 @@ export class MatchPhases {
|
||||
if (this.draft.picks[ps.playerId]) return fail(ERR.ALREADY);
|
||||
if (this.draftTurn() !== ps.playerId) return fail(ERR.NOT_YOUR_TURN);
|
||||
if (typeof bandId !== 'string' || !this.gd.bandAllowed(bandId)) return fail(ERR.BAD_TARGET);
|
||||
if (this.bandTaken(bandId, ps.playerId)) return fail(ERR.BAD_TARGET, '队友已选');
|
||||
if (this.bandTaken(bandId, ps.playerId)) return fail(ERR.BAD_TARGET, '队友已选'); // i18n-ignore: developer detail (players see ERR_TEXT)
|
||||
this._applyBand(ps, bandId);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export class MatchSpDraft {
|
||||
const rounds = bountyBattles(card);
|
||||
const b = {
|
||||
id: `bounty:${this.nextUid()}`,
|
||||
card: { effectId: card.effectId ?? card.id ?? null, name: card.name ?? '悬赏', desc: card.desc ?? '', tier: card.tier ?? 1, coin: Math.max(0, Math.trunc(Number(card.coin) || 0)), payout: card.payout === 'perfect' ? 'perfect' : 'kill', rounds, multiRound: isMultiRoundBounty(card), enemyKey: card.enemyKey, count: Math.max(1, Math.min(20, Number.isInteger(card.count) ? card.count : 1)) },
|
||||
card: { effectId: card.effectId ?? card.id ?? null, name: card.name ?? '悬赏', desc: card.desc ?? '', tier: card.tier ?? 1, coin: Math.max(0, Math.trunc(Number(card.coin) || 0)), payout: card.payout === 'perfect' ? 'perfect' : 'kill', rounds, multiRound: isMultiRoundBounty(card), enemyKey: card.enemyKey, count: Math.max(1, Math.min(20, Number.isInteger(card.count) ? card.count : 1)) }, // i18n-ignore: a data-less card's fallback name
|
||||
roundsLeft: rounds,
|
||||
};
|
||||
ps.bounties.push(b);
|
||||
|
||||
@@ -42,8 +42,8 @@ export class PlayerViews {
|
||||
const left = b.roundsLeft >= 90 ? null : b.roundsLeft;
|
||||
const eff = b.card.effectId ? this.gd.effect(b.card.effectId) : null;
|
||||
out.push({
|
||||
id: b.id, name: b.card.name || '悬赏', desc: bountyText((eff && eff.descRaw) || b.card.desc || '', b.card), iconKind: 'choice', iconId: b.card.effectId || 'bounty',
|
||||
counter: left, counterText: left == null ? '之后的每场作战' : `还剩 ${left} 场作战`,
|
||||
id: b.id, name: b.card.name || '悬赏', desc: bountyText((eff && eff.descRaw) || b.card.desc || '', b.card), iconKind: 'choice', iconId: b.card.effectId || 'bounty', // i18n-ignore: a data-less card's fallback name
|
||||
counter: left, counterText: left == null ? '之后的每场作战' : `还剩 ${left} 场作战`, // i18n-ignore: older clients (ui/effectsList.js builds it from counter)
|
||||
});
|
||||
}
|
||||
return out;
|
||||
|
||||
Reference in new issue
Block a user