i18n: route the remaining 300 hardcoded strings through the table

Grid, offset-maker, liquidity-maker and maker-points wrote their trade log and
Telegram alerts as Chinese literals, so LANG=en changed the menu but not a
single runtime message. src/ now holds zero user-facing Chinese literals.

Reuses rather than duplicates: the four engines' subscription boilerplate maps
onto the existing log.subscribe.* / log.process.* keys, and the spot-maker
wording shared by offset-maker and liquidity-maker became one log.spotMaker.*
set instead of two.

Exchange gateways were treated differently on purpose: aster's exception and
console.error text is developer diagnostics, not UI, and its eight sibling
gateways already used English — so those were translated to English rather than
added to the table.

Adds tests/i18n-coverage.test.ts to hold the line: it fails on any new CJK
literal in src/, on a key missing either language, and on a duplicate key.
Several existing tests asserted the Chinese literals, which is what let the gap
persist; they now assert the resolved key and hold in either language.

214 new keys (300 -> 514). 275 pass; tsc and oxlint clean.
This commit is contained in:
discountry
2026-07-29 21:35:19 +08:00
parent b9331c516a
commit 22b9c5a39d
14 changed files with 964 additions and 273 deletions
+5 -4
View File
@@ -8,6 +8,7 @@ import {
evaluateDefense,
type DefenseInputs,
} from "./maker-points-defense";
import { t } from "../i18n";
const NOW = 1_700_000_000_000;
@@ -149,18 +150,18 @@ describe("describeDefenseReasons", () => {
restConsecutiveErrors: 4,
})
);
expect(summary).toContain("StandX深度(7s)");
expect(summary).toContain("StandX REST错误(4次)");
expect(summary).toContain(t("defense.reason.depth", { seconds: 7 }));
expect(summary).toContain(t("defense.reason.rest", { count: 4 }));
});
it("falls back to unknown when nothing is flagged", () => {
expect(describeDefenseReasons(defenseReasonsFor({}))).toBe("unknown");
expect(describeDefenseReasons(defenseReasonsFor({}))).toBe(t("defense.reason.unknown"));
});
it("omits the Binance book reason when there is none", () => {
const summary = describeDefenseReasons(
defenseReasonsFor({ binanceUnhealthy: true, binanceHealthReason: null })
);
expect(summary).toBe("unknown");
expect(summary).toBe(t("defense.reason.unknown"));
});
});