Skip to content

Commit 4d01ab6

Browse files
committed
feat(i18n): replace hardcoded Chinese strings with i18n keys, data-driven presets
P0: Replace all hardcoded Chinese user-facing strings across 17 source files with this.t() calls and corresponding i18n keys in all 5 locale files (zh, en, zh-tw, ja, vi). Pure logic modules return key references for caller-side resolution. P1: Replace hardcoded preset provider buttons in panel-config-claude.html with data-driven loop over CLAUDE_PRESET_PROVIDERS constant. Update test mocks to supply t() via createI18nMethods() so assertions validate against real locale output. Regenerate precompiled render script.
1 parent 1c0e022 commit 4d01ab6

39 files changed

Lines changed: 1356 additions & 451 deletions

tests/unit/agents-modal-guards.test.mjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,10 @@ test('applyCodexConfigDirect keeps the successful apply result when only the ref
376376
assert.strictEqual(context.codexApplying, false);
377377
assert.deepStrictEqual(apiCalls, ['get-config-template', 'apply-config-template']);
378378
assert.deepStrictEqual(context.shownMessages, [{
379-
message: '配置已应用',
379+
message: 'Codex 配置已应用',
380380
type: 'success'
381381
}, {
382-
message: '配置已应用,但界面刷新失败,请手动刷新',
382+
message: '配置已应用但刷新模型失败',
383383
type: 'error'
384384
}]);
385385
});
@@ -421,6 +421,7 @@ test('openOpenclawWorkspaceEditor rejects invalid workspace filenames before loa
421421
});
422422
const context = {
423423
...methods,
424+
t(key) { return key; },
424425
openclawWorkspaceFileName: '../escape.md',
425426
shownMessages: [],
426427
showMessage(message, type) {
@@ -433,7 +434,7 @@ test('openOpenclawWorkspaceEditor rejects invalid workspace filenames before loa
433434
assert.strictEqual(apiCalls, 0);
434435
assert.strictEqual(context.agentsLoading, undefined);
435436
assert.deepStrictEqual(context.shownMessages, [{
436-
message: '仅支持 OpenClaw Workspace 内的 `.md` 文件',
437+
message: 'toast.agents.mdOnly',
437438
type: 'error'
438439
}]);
439440
});
@@ -550,6 +551,7 @@ test('applyAgentsContent rejects invalid workspace filenames before save api', a
550551
});
551552
const context = {
552553
...methods,
554+
t(key) { return key; },
553555
agentsContext: 'openclaw-workspace',
554556
agentsWorkspaceFileName: '../escape.md',
555557
agentsDiffVisible: true,
@@ -575,7 +577,7 @@ test('applyAgentsContent rejects invalid workspace filenames before save api', a
575577
assert.strictEqual(apiCalls, 0);
576578
assert.strictEqual(context.agentsSaving, undefined);
577579
assert.deepStrictEqual(context.shownMessages, [{
578-
message: '仅支持 OpenClaw Workspace 内的 `.md` 文件',
580+
message: 'toast.agents.mdOnly',
579581
type: 'error'
580582
}]);
581583
});

tests/unit/claude-settings-sync.test.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,8 @@ function instantiateFunction(funcSource, funcName, bindings = {}) {
230230
test('buildClaudeImportedConfigName derives host-based fallback name', () => {
231231
const source = extractMethodAsFunction(appSource, 'buildClaudeImportedConfigName');
232232
const buildClaudeImportedConfigName = instantiateFunction(source, 'buildClaudeImportedConfigName', { URL });
233-
const name = buildClaudeImportedConfigName('https://maxx-direct.cloverstd.com/project/ym/111');
234-
assert.strictEqual(name, '导入-maxx-direct.cloverstd.com');
233+
const name = buildClaudeImportedConfigName.call({ t(key) { return key; } }, 'https://maxx-direct.cloverstd.com/project/ym/111');
234+
assert.strictEqual(name, 'toast.claude.importedFrom');
235235
});
236236

237237

tests/unit/openclaw-editing.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const editingMethods = createOpenclawEditingMethods();
1717

1818
function createContext(overrides = {}) {
1919
return {
20+
t(key) { return key; },
2021
...coreMethods,
2122
...editingMethods,
2223
openclawEditing: {

tests/unit/openclaw-persist-regression.test.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ test('saveOpenclawConfig refuses local-only save for the default system config',
324324

325325
assert.strictEqual(context.openclawSaving, false);
326326
assert.deepStrictEqual(context.shownMessages, [{
327-
message: '默认配置代表当前系统配置,请使用“保存并应用”',
327+
message: '默认配置不可删除',
328328
type: 'info'
329329
}]);
330330
});
@@ -434,7 +434,7 @@ test('saveAndApplyOpenclawConfig closes modal after a successful apply while app
434434
assert.strictEqual(context.resetOpenclawStructuredCalls, 1);
435435
assert.strictEqual(context.resetOpenclawQuickCalls, 1);
436436
assert.deepStrictEqual(context.shownMessages, [{
437-
message: '已保存并应用 OpenClaw 配置(/tmp/openclaw.json)',
437+
message: '配置已保存并应用',
438438
type: 'success'
439439
}]);
440440
});
@@ -599,7 +599,7 @@ test('OpenClaw default config predicate uses invariant marker and canonical key'
599599

600600
assert.ok(context.openclawConfigs.displayDefault, 'marked default config should not be deleted');
601601
assert.deepStrictEqual(context.shownMessages, [{
602-
message: '默认配置始终映射当前系统配置,不可删除',
602+
message: '默认配置不可删除',
603603
type: 'info'
604604
}]);
605605

tests/unit/session-actions-standalone.test.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ test('copySessionLink shows an error when url cannot be built', async () => {
3838
const methods = createSessionActionMethods({ apiBase: '' });
3939
const context = {
4040
...methods,
41+
t(key) { return key; },
4142
shownMessages: [],
4243
showMessage(message, type) {
4344
this.shownMessages.push({ message, type });
@@ -55,7 +56,7 @@ test('copySessionLink shows an error when url cannot be built', async () => {
5556
}));
5657

5758
assert.deepStrictEqual(context.shownMessages, [{
58-
message: '无法生成链接',
59+
message: 'toast.session.generateLinkFail',
5960
type: 'error'
6061
}]);
6162
});
@@ -65,6 +66,7 @@ test('copySessionPath copies the session file path', async () => {
6566
const copied = [];
6667
const context = {
6768
...methods,
69+
t(key) { return key; },
6870
shownMessages: [],
6971
showMessage(message, type) {
7072
this.shownMessages.push({ message, type });
@@ -83,7 +85,7 @@ test('copySessionPath copies the session file path', async () => {
8385

8486
assert.deepStrictEqual(copied, ['/tmp/codexmate/session-1.jsonl']);
8587
assert.deepStrictEqual(context.shownMessages, [{
86-
message: '已复制路径',
88+
message: 'toast.session.pathCopied',
8789
type: 'success'
8890
}]);
8991
});
@@ -93,6 +95,7 @@ test('copySessionPath reports an error when the session has no file path', async
9395
let copied = false;
9496
const context = {
9597
...methods,
98+
t(key) { return key; },
9699
shownMessages: [],
97100
showMessage(message, type) {
98101
this.shownMessages.push({ message, type });
@@ -110,7 +113,7 @@ test('copySessionPath reports an error when the session has no file path', async
110113

111114
assert.strictEqual(copied, false);
112115
assert.deepStrictEqual(context.shownMessages, [{
113-
message: '无本地文件路径',
116+
message: 'toast.session.noLocalPath',
114117
type: 'error'
115118
}]);
116119
});

tests/unit/session-trash-state.test.mjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,6 +1623,7 @@ test('deleteSession increments trash badge count when only total count has been
16231623
let removed = false;
16241624
const messages = [];
16251625
const context = {
1626+
t(key) { return key; },
16261627
sessionDeleting: {},
16271628
sessionTrashLoadedOnce: false,
16281629
sessionTrashCountLoadedOnce: true,
@@ -1680,7 +1681,7 @@ test('deleteSession increments trash badge count when only total count has been
16801681
filePath: '/tmp/session-1.jsonl'
16811682
}]);
16821683
assert.strictEqual(requestedAction, 'trash-session');
1683-
assert.deepStrictEqual(messages, [{ message: '已移入回收站', tone: 'success' }]);
1684+
assert.deepStrictEqual(messages, [{ message: 'toast.session.movedToTrash', tone: 'success' }]);
16841685
});
16851686

16861687
test('deleteSession prefers authoritative trash totalCount from the backend response', async () => {
@@ -1695,6 +1696,7 @@ test('deleteSession prefers authoritative trash totalCount from the backend resp
16951696
});
16961697

16971698
const context = {
1699+
t(key) { return key; },
16981700
sessionDeleting: {},
16991701
sessionTrashLoadedOnce: false,
17001702
sessionTrashCountLoadedOnce: true,
@@ -1759,6 +1761,7 @@ test('deleteSession keeps success message when list cleanup fails after trash su
17591761

17601762
const messages = [];
17611763
const context = {
1764+
t(key) { return key; },
17621765
sessionDeleting: {},
17631766
sessionTrashLoadedOnce: false,
17641767
sessionTrashCountLoadedOnce: true,
@@ -1814,7 +1817,7 @@ test('deleteSession keeps success message when list cleanup fails after trash su
18141817
sessionId: 'session-1',
18151818
filePath: '/tmp/session-1.jsonl'
18161819
}]);
1817-
assert.deepStrictEqual(messages, [{ message: '已移入回收站', tone: 'success' }]);
1820+
assert.deepStrictEqual(messages, [{ message: 'toast.session.movedToTrash', tone: 'success' }]);
18181821
});
18191822

18201823
test('deleteSession permanently deletes when session trash is disabled', async () => {
@@ -1834,6 +1837,7 @@ test('deleteSession permanently deletes when session trash is disabled', async (
18341837

18351838
const messages = [];
18361839
const context = {
1840+
t(key) { return key; },
18371841
sessionDeleting: {},
18381842
sessionTrashEnabled: false,
18391843
sessionTrashLoadedOnce: false,
@@ -1900,7 +1904,7 @@ test('deleteSession permanently deletes when session trash is disabled', async (
19001904
filePath: '/tmp/session-1.jsonl'
19011905
}]);
19021906
assert.deepStrictEqual(context.invalidateUsageCalls, [{ preserveList: true }]);
1903-
assert.deepStrictEqual(messages, [{ message: '已删除', tone: 'success' }]);
1907+
assert.deepStrictEqual(messages, [{ message: 'toast.session.deleted', tone: 'success' }]);
19041908
});
19051909

19061910
test('cloneSession keeps success message when refresh fails after clone succeeds', async () => {

tests/unit/session-usage.test.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ test('buildUsageChartGroups aggregates codex and claude sessions into day bucket
3232
assert.strictEqual(result.topPaths[0].path, '/a');
3333
assert.strictEqual(result.topPaths[0].count, 2);
3434
assert.strictEqual(result.topPaths[0].messageTotal, 12);
35-
assert.strictEqual(result.recentSessions[0].title, '未命名会话');
35+
assert.strictEqual(result.recentSessions[0].title, 'session.unnamed');
3636
assert.strictEqual(result.recentSessions[0].sourceLabel, 'Claude Code');
3737
assert.strictEqual(result.topSessionsByMessages[0].messageCount, 7);
3838
assert.strictEqual(result.hourActivity.find(item => item.key === '09').count, 2);

tests/unit/skills-market-runtime.test.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const { createSkillsComputed } = await import(pathToFileURL(path.join(__dirname,
1111
function buildVm(apiImpl, overrides = {}) {
1212
const methods = createSkillsMethods({ api: apiImpl });
1313
const vm = {
14+
t(key) { return key; },
1415
skillsTargetApp: 'codex',
1516
skillsRootPath: '',
1617
skillsList: [],
@@ -149,7 +150,7 @@ test('setSkillsTargetApp rejects invalid explicit target without mutating state'
149150
assert.deepStrictEqual(vm.skillsList, [{ name: 'alpha', hasSkillFile: true }]);
150151
assert.strictEqual(loadCalled, false);
151152
assert.deepStrictEqual(vm.messageLog, [{
152-
message: '不支持的 Skills 安装目标:Claude',
153+
message: 'toast.skills.unsupportedTarget',
153154
type: 'error'
154155
}]);
155156
});
@@ -171,7 +172,7 @@ test('openSkillsManager rejects invalid explicit target without reopening the mo
171172
assert.strictEqual(vm.showSkillsModal, false);
172173
assert.strictEqual(refreshCalls, 0);
173174
assert.deepStrictEqual(vm.messageLog, [{
174-
message: '不支持的 Skills 安装目标:Claude',
175+
message: 'toast.skills.unsupportedTarget',
175176
type: 'error'
176177
}]);
177178
});
@@ -350,7 +351,7 @@ test('deleteSelectedSkills reports busy state while import sources scan is runni
350351
assert.strictEqual(confirmCalls, 0);
351352
assert.strictEqual(apiCalls, 0);
352353
assert.deepStrictEqual(vm.messageLog, [{
353-
message: '正在扫描导入源,请稍后再试',
354+
message: 'toast.skills.scanningPleaseWait',
354355
type: 'error'
355356
}]);
356357
});

0 commit comments

Comments
 (0)