diff --git a/.changeset/25502-ai-reset-action-translation.md b/.changeset/25502-ai-reset-action-translation.md new file mode 100644 index 00000000000..5f503ca41c1 --- /dev/null +++ b/.changeset/25502-ai-reset-action-translation.md @@ -0,0 +1,12 @@ +--- +'@objectstack/service-settings': patch +--- + +fix: Setup → AI's "Reset to environment defaults" button is now translated in zh-CN, ja-JP and es-ES + +Clause-②: no +Metadata-protocol: none + +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). + +The settings translation coverage test now reads every manifest's `action_button` labels: each shipped non-English locale must carry `settings..actions..label` for every action button, and no bundle may carry action copy for a button no manifest declares. diff --git a/packages/services/service-settings/src/translations/en.ts b/packages/services/service-settings/src/translations/en.ts index 6d425eedf93..08a00b069da 100644 --- a/packages/services/service-settings/src/translations/en.ts +++ b/packages/services/service-settings/src/translations/en.ts @@ -361,6 +361,7 @@ export const en: PlatformTranslationData = { actions: { test: { label: 'Test connection' }, test_embedder: { label: 'Test embedder' }, + reset: { label: 'Reset to environment defaults' }, }, banners: { log_prompts_warning: { title: 'Prompts may contain PII', diff --git a/packages/services/service-settings/src/translations/es-ES.ts b/packages/services/service-settings/src/translations/es-ES.ts index d226a1f83e7..eb08e6e4e8c 100644 --- a/packages/services/service-settings/src/translations/es-ES.ts +++ b/packages/services/service-settings/src/translations/es-ES.ts @@ -510,6 +510,7 @@ export const esES: PlatformTranslationData = { actions: { test: { label: 'Probar conexión' }, test_embedder: { label: 'Probar embedder' }, + reset: { label: 'Restablecer a los valores predeterminados del entorno' }, }, banners: { log_prompts_warning: { title: 'Los prompts pueden contener PII', diff --git a/packages/services/service-settings/src/translations/ja-JP.ts b/packages/services/service-settings/src/translations/ja-JP.ts index cd1b50017d3..e5ff34c9a9c 100644 --- a/packages/services/service-settings/src/translations/ja-JP.ts +++ b/packages/services/service-settings/src/translations/ja-JP.ts @@ -508,6 +508,7 @@ export const jaJP: PlatformTranslationData = { actions: { test: { label: '接続テスト' }, test_embedder: { label: 'Embedder をテスト' }, + reset: { label: '環境変数のデフォルトにリセット' }, }, banners: { log_prompts_warning: { title: 'PII 漏えいの可能性', diff --git a/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts b/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts index e44a94b39be..6a14ccbcd31 100644 --- a/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts +++ b/packages/services/service-settings/src/translations/settings-translation-coverage.test.ts @@ -11,8 +11,10 @@ * * This test pins each shipped non-English locale to full coverage of every * manifest's structural strings — namespace title/description, each group - * title, each field label, and each `info_banner`'s title and text (keyed by - * the banner's `id` under `settings..banners`). Dropdown *option* labels are intentionally + * title, each field label, each `info_banner`'s title and text (keyed by + * the banner's `id` under `settings..banners`), and each + * `action_button`'s label (keyed by the button's `id` under + * `settings..actions`). Dropdown *option* labels are intentionally * excluded: several must not be "translated" (the `timezone` option "UTC" is a * code; each `locale` option names its language in that language), and * option-level parity is filled per-locale on a best-effort basis. @@ -56,6 +58,14 @@ function missingFor(data: { settings?: Record }, m: SettingsManifes } if (tr?.banners?.[s.id]?.title == null) missing.push(`banner:${s.id}.title`); if (tr?.banners?.[s.id]?.text == null) missing.push(`banner:${s.id}.text`); + } else if (s.type === 'action_button') { + // Same as a banner: an action button is addressed by its `id`, and one + // without an id has no place in the bundle for its label. + if (!s.id) { + missing.push('action without an id'); + continue; + } + if (tr?.actions?.[s.id]?.label == null) missing.push(`action:${s.id}`); } else if (s.key) { if (tr?.keys?.[s.key]?.label == null) missing.push(`key:${s.key}`); } @@ -64,7 +74,7 @@ function missingFor(data: { settings?: Record }, m: SettingsManifes } /** - * The other direction: copy for a group, key or banner no manifest declares. + * The other direction: copy for a group, key, banner or action no manifest declares. * Such an entry renders nowhere, so it survives every review as dead text. The four * Localization format keys retired by #21958 are the case that named it. */ @@ -80,11 +90,14 @@ function undeclaredIn(data: { settings?: Record }): string[] { const groups = new Set(); const keys = new Set(); const banners = new Set(); + const actions = new Set(); for (const s of m.specifiers) { if (s.type === 'group') { if (s.id) groups.add(s.id); } else if (s.type === 'info_banner') { if (s.id) banners.add(s.id); + } else if (s.type === 'action_button') { + if (s.id) actions.add(s.id); } else if (s.key) { keys.add(s.key); } @@ -92,6 +105,7 @@ function undeclaredIn(data: { settings?: Record }): string[] { for (const g of Object.keys(tr?.groups ?? {})) if (!groups.has(g)) extra.push(`${ns}.group:${g}`); for (const k of Object.keys(tr?.keys ?? {})) if (!keys.has(k)) extra.push(`${ns}.key:${k}`); for (const b of Object.keys(tr?.banners ?? {})) if (!banners.has(b)) extra.push(`${ns}.banner:${b}`); + for (const a of Object.keys(tr?.actions ?? {})) if (!actions.has(a)) extra.push(`${ns}.action:${a}`); } return extra; } @@ -103,7 +117,7 @@ describe('settings translation coverage', () => { for (const [locale, data] of LOCALES) { for (const m of manifests) { - it(`${locale} covers settings.${m.namespace} (title / groups / keys / banners)`, () => { + it(`${locale} covers settings.${m.namespace} (title / groups / keys / banners / actions)`, () => { const missing = missingFor(data, m); expect(missing, `${locale} missing for ${m.namespace}: ${missing.join(', ')}`).toEqual([]); }); @@ -111,7 +125,7 @@ describe('settings translation coverage', () => { } for (const [locale, data] of [['en', en], ...LOCALES] as typeof LOCALES) { - it(`${locale} carries no copy for a group, key or banner no manifest declares`, () => { + it(`${locale} carries no copy for a group, key, banner or action no manifest declares`, () => { const extra = undeclaredIn(data); expect(extra, `${locale} has undeclared settings copy: ${extra.join(', ')}`).toEqual([]); }); diff --git a/packages/services/service-settings/src/translations/zh-CN.ts b/packages/services/service-settings/src/translations/zh-CN.ts index 7bd43be08bb..47f36fc7868 100644 --- a/packages/services/service-settings/src/translations/zh-CN.ts +++ b/packages/services/service-settings/src/translations/zh-CN.ts @@ -547,6 +547,7 @@ export const zhCN: PlatformTranslationData = { actions: { test: { label: '测试连接' }, test_embedder: { label: '测试 Embedder' }, + reset: { label: '重置为环境变量默认值' }, }, banners: { log_prompts_warning: { title: 'Prompt 可能含有 PII', text: '可能泄露 PII,合规场景请关闭。' },