Skip to content

Commit 74496c6

Browse files
fix(service-settings): translate the AI "Reset to environment defaults" button and cover action_button labels (#25527)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuACdPWFJTLDcTrByFCHLzqb)_ Fixes #25502 Clause-②: no Metadata-protocol: none ## What changed Setup → AI's **Reset to environment defaults** button rendered in English in zh-CN, ja-JP and es-ES sessions, next to its translated siblings ("测试连接", "测试 Embedder"). - **The key:** `packages/services/service-settings/src/manifests/ai.manifest.ts` declares `{ type: 'action_button', id: 'reset', label: 'Reset to environment defaults' }`. The console resolves an action label as `settings.ai.actions.reset.label`. On base `dc3e5b9d7b`, none of the four bundles had that key: `ai.actions` held only `test` and `test_embedder`. - **Translations added** in `translations/{zh-CN,ja-JP,es-ES}.ts`. The wording follows each bundle's own word for the env source (`sourceLabels.env`): - zh-CN: `重置为环境变量默认值` - ja-JP: `環境変数のデフォルトにリセット` - es-ES: `Restablecer a los valores predeterminados del entorno` - `en.ts` also gets the English entry beside `test` / `test_embedder`, so the en fallback carries every AI action. - **The coverage gap is closed for the whole class.** Before this change, `settings-translation-coverage.test.ts` read titles, groups, field keys and `info_banner` copy, but never `action_button` labels: a button has an `id` and no `key`, so it matched none of the branches. The test now covers action buttons in both directions: - **Missing copy:** every shipped non-English locale must carry `settings.NAMESPACE.actions.ID.label` for each `action_button` in every manifest. A button with no `id` is reported, the same as a banner with no `id`. - **Dead copy:** no bundle, en included, may carry `actions` copy for a button no manifest declares. Landing: the claim's file surface (service-settings translation bundles and the coverage test). The manifest is unchanged, and `packages/spec` is not touched. ## Evidence - **Ablation:** I removed only the zh-CN `reset` entry through `scripts/ablation-replace.mjs`. The anchor hit once, x1 to x0, blob `47f36fc78683` to `7bd43be08bbc`. The coverage test went red with `zh-CN missing for ai: action:reset` (Tests 1 failed / 39 passed). The script restored the file (blob equals HEAD `47f36fc78683`, `git diff HEAD` empty). The test reads `src` through relative imports, so no dist rebuild was involved. - **Suite:** `pnpm --filter @objectstack/service-settings test` gave Test Files 60 passed (60), Tests 1000 passed (1000). `pnpm --filter @objectstack/service-settings typecheck` exited 0, and its tsconfig includes all of `src`, test files included. Both ran on head `591481a4d1`. - **Gates:** `node scripts/pm/dispatch-gates.mjs --commands` derived 67 commands. 66 ran and exited 0. `pnpm check:dual-build-cjs-loads` exited 3 with PREREQUISITE NOT MET: it needs a full `pnpm build`, so it was not measured locally and is left to CI. Reconciling with `--ran` accounted for all 67 (66 run, 1 not measured). ## Acceptance notes - With action buttons covered, no other action key is missing in a non-English locale: mail, sms, storage, knowledge and ai are all green. - `en.ts` has no `sms` namespace at all. en is not a gated locale, and the English label falls back to the manifest literal. This is noted, not changed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b8c9590 commit 74496c6

6 files changed

Lines changed: 35 additions & 5 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
fix: Setup → AI's "Reset to environment defaults" button is now translated in zh-CN, ja-JP and es-ES
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The `ai` settings manifest declares an `action_button` with id `reset`, but no locale bundle carried `settings.ai.actions.reset.label`, so the button rendered in English beside its translated siblings. The bundles now carry it (en, zh-CN, ja-JP, es-ES).
11+
12+
The settings translation coverage test now reads every manifest's `action_button` labels: each shipped non-English locale must carry `settings.<namespace>.actions.<id>.label` for every action button, and no bundle may carry action copy for a button no manifest declares.

‎packages/services/service-settings/src/translations/en.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ export const en: PlatformTranslationData = {
361361
actions: {
362362
test: { label: 'Test connection' },
363363
test_embedder: { label: 'Test embedder' },
364+
reset: { label: 'Reset to environment defaults' },
364365
},
365366
banners: {
366367
log_prompts_warning: { title: 'Prompts may contain PII',

‎packages/services/service-settings/src/translations/es-ES.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ export const esES: PlatformTranslationData = {
510510
actions: {
511511
test: { label: 'Probar conexión' },
512512
test_embedder: { label: 'Probar embedder' },
513+
reset: { label: 'Restablecer a los valores predeterminados del entorno' },
513514
},
514515
banners: {
515516
log_prompts_warning: { title: 'Los prompts pueden contener PII',

‎packages/services/service-settings/src/translations/ja-JP.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ export const jaJP: PlatformTranslationData = {
508508
actions: {
509509
test: { label: '接続テスト' },
510510
test_embedder: { label: 'Embedder をテスト' },
511+
reset: { label: '環境変数のデフォルトにリセット' },
511512
},
512513
banners: {
513514
log_prompts_warning: { title: 'PII 漏えいの可能性',

‎packages/services/service-settings/src/translations/settings-translation-coverage.test.ts‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
*
1212
* This test pins each shipped non-English locale to full coverage of every
1313
* manifest's structural strings — namespace title/description, each group
14-
* title, each field label, and each `info_banner`'s title and text (keyed by
15-
* the banner's `id` under `settings.<namespace>.banners`). Dropdown *option* labels are intentionally
14+
* title, each field label, each `info_banner`'s title and text (keyed by
15+
* the banner's `id` under `settings.<namespace>.banners`), and each
16+
* `action_button`'s label (keyed by the button's `id` under
17+
* `settings.<namespace>.actions`). Dropdown *option* labels are intentionally
1618
* excluded: several must not be "translated" (the `timezone` option "UTC" is a
1719
* code; each `locale` option names its language in that language), and
1820
* option-level parity is filled per-locale on a best-effort basis.
@@ -56,6 +58,14 @@ function missingFor(data: { settings?: Record<string, any> }, m: SettingsManifes
5658
}
5759
if (tr?.banners?.[s.id]?.title == null) missing.push(`banner:${s.id}.title`);
5860
if (tr?.banners?.[s.id]?.text == null) missing.push(`banner:${s.id}.text`);
61+
} else if (s.type === 'action_button') {
62+
// Same as a banner: an action button is addressed by its `id`, and one
63+
// without an id has no place in the bundle for its label.
64+
if (!s.id) {
65+
missing.push('action without an id');
66+
continue;
67+
}
68+
if (tr?.actions?.[s.id]?.label == null) missing.push(`action:${s.id}`);
5969
} else if (s.key) {
6070
if (tr?.keys?.[s.key]?.label == null) missing.push(`key:${s.key}`);
6171
}
@@ -64,7 +74,7 @@ function missingFor(data: { settings?: Record<string, any> }, m: SettingsManifes
6474
}
6575

6676
/**
67-
* The other direction: copy for a group, key or banner no manifest declares.
77+
* The other direction: copy for a group, key, banner or action no manifest declares.
6878
* Such an entry renders nowhere, so it survives every review as dead text. The four
6979
* Localization format keys retired by #21958 are the case that named it.
7080
*/
@@ -80,18 +90,22 @@ function undeclaredIn(data: { settings?: Record<string, any> }): string[] {
8090
const groups = new Set<string>();
8191
const keys = new Set<string>();
8292
const banners = new Set<string>();
93+
const actions = new Set<string>();
8394
for (const s of m.specifiers) {
8495
if (s.type === 'group') {
8596
if (s.id) groups.add(s.id);
8697
} else if (s.type === 'info_banner') {
8798
if (s.id) banners.add(s.id);
99+
} else if (s.type === 'action_button') {
100+
if (s.id) actions.add(s.id);
88101
} else if (s.key) {
89102
keys.add(s.key);
90103
}
91104
}
92105
for (const g of Object.keys(tr?.groups ?? {})) if (!groups.has(g)) extra.push(`${ns}.group:${g}`);
93106
for (const k of Object.keys(tr?.keys ?? {})) if (!keys.has(k)) extra.push(`${ns}.key:${k}`);
94107
for (const b of Object.keys(tr?.banners ?? {})) if (!banners.has(b)) extra.push(`${ns}.banner:${b}`);
108+
for (const a of Object.keys(tr?.actions ?? {})) if (!actions.has(a)) extra.push(`${ns}.action:${a}`);
95109
}
96110
return extra;
97111
}
@@ -103,15 +117,15 @@ describe('settings translation coverage', () => {
103117

104118
for (const [locale, data] of LOCALES) {
105119
for (const m of manifests) {
106-
it(`${locale} covers settings.${m.namespace} (title / groups / keys / banners)`, () => {
120+
it(`${locale} covers settings.${m.namespace} (title / groups / keys / banners / actions)`, () => {
107121
const missing = missingFor(data, m);
108122
expect(missing, `${locale} missing for ${m.namespace}: ${missing.join(', ')}`).toEqual([]);
109123
});
110124
}
111125
}
112126

113127
for (const [locale, data] of [['en', en], ...LOCALES] as typeof LOCALES) {
114-
it(`${locale} carries no copy for a group, key or banner no manifest declares`, () => {
128+
it(`${locale} carries no copy for a group, key, banner or action no manifest declares`, () => {
115129
const extra = undeclaredIn(data);
116130
expect(extra, `${locale} has undeclared settings copy: ${extra.join(', ')}`).toEqual([]);
117131
});

‎packages/services/service-settings/src/translations/zh-CN.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ export const zhCN: PlatformTranslationData = {
547547
actions: {
548548
test: { label: '测试连接' },
549549
test_embedder: { label: '测试 Embedder' },
550+
reset: { label: '重置为环境变量默认值' },
550551
},
551552
banners: {
552553
log_prompts_warning: { title: 'Prompt 可能含有 PII', text: '可能泄露 PII,合规场景请关闭。' },

0 commit comments

Comments
 (0)