Skip to content

Commit 57faf5d

Browse files
fix(service-settings): translate Data Lifecycle settings; AI & Knowledge help in business words (#28139)
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Fixes #28064 Fixes #28069 ## What changes ### #28064: the Data Lifecycle settings page is translated - `settings.lifecycle` is added to the zh-CN, ja-JP and es-ES files of `settingsBuiltinTranslations` (`packages/services/service-settings/src/translations/`). It covers the title, the description, and each key's label and help (`enabled`, `retention_overrides`, `quotas`, `quota_defaults`, `growth_alert_rows`). - The titles match the Setup sidebar entry (`数据生命周期` / `データライフサイクル` / `Ciclo de vida de los datos`). - The field terms follow the object lifecycle forms already shipped (保留期 / 保持期間 / retención, 生命周期类别 / ライフサイクルクラス / clase de ciclo de vida). - JSON examples inside the help stay as written. - `en` keeps falling back to the manifest literals, as it does for `sms`. - **The coverage test declares the lifecycle namespace instead of refusing it.** `settings-translation-coverage.test.ts` now checks every manifest whose copy this bundle carries: the bundled manifests plus a named list of *hosted* platform manifests. - A hosted manifest is one that another package registers into the same hub and that ships no bundle of its own. Today the list holds objectql's `lifecycleSettingsManifest`. - A hosted manifest gets the same full coverage check as a bundled one, and its namespace counts as declared for the no-undeclared-copy check. - A new test pins that the list contains `lifecycle` and that no namespace is declared twice. - **The parity pin loses its exception.** `setup-nav-hub-parity.test.ts` drops `HUB_UNTRANSLATED`. Its per-locale test is now "every hub page carries a translated title", so the sidebar-vs-hub label comparison also runs for Data Lifecycle. - The `translations/index.ts` docblock names the hosted namespace. ### #28069: AI & Embedder and Knowledge help is in business words This applies the treatment #28063 gave Authentication / Feature Flags / Mail. The manifest literals (the English fallback) and the en / zh-CN / ja-JP / es-ES packs change together: - **API keys and gateway model.** The OpenAI / Anthropic / Google API keys and the gateway model read "used instead of the key (model) set in the server configuration". Before, they read "Forwarded as `OPENAI_API_KEY`" (and `ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `AI_GATEWAY_MODEL`). The settings manifest has no per-field docs-link slot, so, as in #28063, the env var names leave the help entirely. They stay in the code comment beside the Reset action and in the deployment docs. - **Record traces** says it keeps a record of each AI request and its response, so a conversation can be inspected and replayed. It no longer names `sys_ai_trace`. - **Batch size** is "chunks sent to the embedding provider in one request". It no longer says `embed()`. - **Knowledge help:** - Indexing defaults: "a value set on a knowledge source itself wins over these defaults" (no `KnowledgeSource.adapterConfig`). - Over-fetch multiplier is described by its effect (no `topK × overFetch`, no "JS-side"). - The RAGFlow default dataset says "knowledge source". - The Turso group no longer says "tenant's primary libSQL connection". - Enforce RLS on search no longer names `IDataEngine` (manifest and zh-CN). - Code spans around model examples (`provider/model`, `openai/gpt-4o`, `default`) become plain text or quotes. The examples stay, because they are what the administrator types. - `settings-help-business-words.pin.test.ts` now sweeps `ai` and `knowledge` as well as `auth`, `feature_flags` and `mail`. It adds teeth cases for the strings #28069 was filed for, plus a negative case showing that model examples are not flagged. No key, default, option or validation changes in either part. `packages/objectql` is untouched. There are two `patch` changesets for `@objectstack/service-settings`. ## Why **#28064:** Setup → All Settings → Data Lifecycle read English under zh-CN, ja-JP and es-ES: the card title, its description and every field. Every other settings page and the sidebar entry for this page were translated. - The page comes from objectql's `lifecycleSettingsManifest`, and no bundle carried `settings.lifecycle.*`. - The built-in settings bundle is where every other settings page's copy lives, but its coverage test refused any namespace that no bundled manifest declares. - This PR widens that rule by naming the manifest, not by adding a bare exception. **#28069:** the help under AI & Embedder and Knowledge read like code comments in all four languages. It named env vars, the trace object, a code call and a config path that an administrator can neither see nor change from this page. Triage ruled that the help says what the setting does in business words. It is the remainder of #27521 row 2, whose other pages #28063 fixed. ## Verification All readings are against the worktree at `origin/main` `8cb8c7f4` plus the two commits (`273efdc1`, `b4c0a258`). | Check | Before | After | |:--|:--|:--| | `setup-nav-hub-parity.test.ts` with the exception list emptied | **3 failed** / 6 passed (9): zh-CN, ja-JP, es-ES each `expected [ 'lifecycle' ] to deeply equal []` | 9 / 9 passed | | `settings-translation-coverage.test.ts`, new test with the lifecycle entries removed | n/a | **3 failed** / 41 passed (44): `zh-CN/ja-JP/es-ES covers settings.lifecycle` | | Old coverage test with the new lifecycle entries | **3 failed** / 37 passed (40): undeclared `namespace:lifecycle` | n/a (rule replaced) | | `settings-help-business-words.pin.test.ts` sweeping `ai` and `knowledge`, against the old help | **10 failed** / 20 passed (30): the manifest and all four packs, for both namespaces, e.g. `ai.keys.openai_api_key.help → env var or error code: OPENAI_API_KEY`, `ai.trace_enabled.description → … sys_ai_trace`, `knowledge.indexing.description → camelCase code identifier: adapterConfig` | 30 / 30 passed | | `@objectstack/service-settings` `pnpm test` | n/a | 65 files, 1076 / 1076 passed | | `@objectstack/service-settings` `pnpm typecheck` | n/a | exit 0 | | eslint on the changed `.ts` files | n/a | exit 0 | | `check:adr-0087-registration`, `check:changeset-fixed`, `check:empty-changeset`, `check:doc-authoring`, `check:cross-package-test-inputs` | n/a | all exit 0 | | `check-commit-card-trailers --range origin/main..HEAD` | n/a | exit 0 (2 commits) | No test outside `service-settings` pinned the old AI or Knowledge help: `git grep` for each replaced phrase finds only released `CHANGELOG.md` entries. ## 维护者速读 - **改了什么**: - #28064:在内置设置翻译包的 zh-CN / ja-JP / es-ES 中补上「数据生命周期」页(`settings.lifecycle`)的标题、描述和 5 个字段的 label 与 help。覆盖测试把 objectql 的 `lifecycleSettingsManifest` 列为“托管清单”,纳入同一覆盖规则。对齐测试删除 `HUB_UNTRANSLATED`。 - #28069:「AI 与嵌入」和「知识库」的帮助文字改成业务用语(清单与四个语言包同步),不再出现环境变量名、`sys_ai_trace`、`embed()`、`KnowledgeSource.adapterConfig`、`topK × overFetch`。业务用语钉子测试扩展到这两个命名空间。 - **为什么改**: - 数据生命周期页在三种语言下全是英文,侧栏条目却已翻译,两边不一致。 - AI 与知识库的帮助像代码注释,管理员看不懂,也无法在这里改动。分诊裁定按 #28063 的做法改成业务用语。 - **风险与回滚**:低。只改翻译数据、帮助文字和测试,没有改键名、默认值或校验,也没动 objectql。每张卡对应一个提交,可以分别 revert。 - **先看**: - `settings-translation-coverage.test.ts` 的 `hostedManifests`(规则是否合理)。 - `manifests/ai.manifest.ts` 中 API Key 的新说法「用于代替服务器配置中设定的密钥」。 - `settings-help-business-words.pin.test.ts` 新增的命名空间与 teeth 用例。 - **亲手验证**:`pnpm --filter @objectstack/service-settings test`。或者把控制台切到中文、日文或西班牙文,打开 设置 → 数据生命周期 / AI / 知识库,看标题与帮助。 - **需要你做的**: - 分诊原话是把环境变量名移到“面向开发者”的一行,但 #28063 的钉子测试禁止帮助中出现任何环境变量名,清单也没有逐字段的文档链接位,所以这里按 #28063 的做法整句去掉。请确认这样可以接受。 - 确认把托管清单的译文放在 service-settings 包里可以接受。 Clause-②: no Metadata-protocol: none 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 277275a commit 57faf5d

12 files changed

Lines changed: 232 additions & 75 deletions
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(i18n): the Data Lifecycle settings page reads in the admin's language
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Setup → All Settings → Data Lifecycle read English under Chinese (zh-CN), Japanese (ja-JP) and Spanish (es-ES): the hub card's title and description and every field on the page, while every other settings page and the Setup sidebar entry for this page were translated. The page now reads in those languages too: its title, description, and each field's label and help (Enforce lifecycle policies, Retention overrides, Row quotas, Per-class quota defaults, Growth alert threshold). The hub card and the sidebar entry now carry the same name in every shipped locale.
11+
12+
Nothing to do: the translations ship in the built-in settings bundle and load with the settings service, as the other pages' do. The JSON examples inside the help text stay as written, since they are the values you type.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
fix(service-settings): AI & Embedder and Knowledge help is in business words
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
On Setup → Settings → AI & Embedder and Knowledge, the help under several fields read like code comments, in every language: "Forwarded as OPENAI_API_KEY", traces saved "to sys_ai_trace", "Chunks per embed() call", "Per-source values on KnowledgeSource.adapterConfig take precedence", "topK × overFetch". The help now says what each setting does:
11+
12+
- A provider API key or the gateway model is "used instead of the one set in the server configuration".
13+
- Record traces keeps a record of each AI request and its response, so a conversation can be inspected and replayed.
14+
- Batch size is the number of chunks sent to the embedding provider in one request.
15+
- Indexing defaults: a value set on a knowledge source itself wins over these defaults.
16+
- Over-fetch multiplier: search fetches this many times the requested number of results, so enough are left after filtering.
17+
18+
The English, Chinese, Japanese and Spanish text changed together. No setting, default or accepted value changed, and model examples such as openai/gpt-4o stay as written because they are what you type.

‎packages/services/service-settings/src/manifests/ai.manifest.ts‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ const manifest = {
5757
// ── Vercel AI Gateway ─────────────────────────────────────────
5858
{ type: 'group', id: 'gateway', label: 'Vercel AI Gateway', required: false,
5959
visible: "${data.provider === 'gateway'}",
60-
description: 'Multi-provider router. The model spec follows `provider/model`, e.g. `openai/gpt-4o`.' },
60+
description: 'Multi-provider router. The model is written provider/model, e.g. openai/gpt-4o.' },
6161
{ type: 'text', key: 'gateway_model', label: 'Gateway model', required: true,
62-
description: 'Forwarded as AI_GATEWAY_MODEL. Format: provider/model, e.g. openai/gpt-4o or anthropic/claude-sonnet-4.6.',
62+
description: 'Used instead of the model set in the server configuration. Format: provider/model, e.g. openai/gpt-4o or anthropic/claude-sonnet-4.6.',
6363
pattern: '^[A-Za-z0-9_-]+\\/[A-Za-z0-9._:-]+$',
6464
visible: "${data.provider === 'gateway'}" },
6565
{ type: 'password', key: 'gateway_api_key', label: 'Gateway API key',
@@ -72,7 +72,7 @@ const manifest = {
7272
visible: "${data.provider === 'openai'}" },
7373
{ type: 'password', key: 'openai_api_key', label: 'OpenAI API key',
7474
required: true, encrypted: true,
75-
description: 'Forwarded as OPENAI_API_KEY. Stored encrypted at rest.',
75+
description: 'Used instead of the key set in the server configuration. Stored encrypted at rest.',
7676
visible: "${data.provider === 'openai'}" },
7777
{ type: 'text', key: 'openai_model', label: 'Model', required: false, default: 'gpt-4o',
7878
description: 'Default model id. Per-agent overrides take precedence.',
@@ -86,7 +86,7 @@ const manifest = {
8686
visible: "${data.provider === 'anthropic'}" },
8787
{ type: 'password', key: 'anthropic_api_key', label: 'Anthropic API key',
8888
required: true, encrypted: true,
89-
description: 'Forwarded as ANTHROPIC_API_KEY. Stored encrypted at rest.',
89+
description: 'Used instead of the key set in the server configuration. Stored encrypted at rest.',
9090
visible: "${data.provider === 'anthropic'}" },
9191
{ type: 'text', key: 'anthropic_model', label: 'Model', required: false,
9292
default: 'claude-sonnet-4-20250514',
@@ -97,7 +97,7 @@ const manifest = {
9797
visible: "${data.provider === 'google'}" },
9898
{ type: 'password', key: 'google_api_key', label: 'Google API key',
9999
required: true, encrypted: true,
100-
description: 'Forwarded as GOOGLE_GENERATIVE_AI_API_KEY. Stored encrypted at rest.',
100+
description: 'Used instead of the key set in the server configuration. Stored encrypted at rest.',
101101
visible: "${data.provider === 'google'}" },
102102
{ type: 'text', key: 'google_model', label: 'Model', required: false,
103103
default: 'gemini-2.0-flash',
@@ -138,14 +138,14 @@ const manifest = {
138138
{ type: 'group', id: 'cloudflare', label: 'Cloudflare AI Gateway', required: false,
139139
visible: "${data.provider === 'cloudflare'}",
140140
description:
141-
'Uses the /compat endpoint so the model id is `provider/model` (e.g. ' +
142-
'`openai/gpt-4o-mini`, `anthropic/claude-3-5-sonnet`, `deepseek/deepseek-chat`). ' +
141+
'Uses the /compat endpoint, so the model is written provider/model (e.g. ' +
142+
'openai/gpt-4o-mini, anthropic/claude-3-5-sonnet, deepseek/deepseek-chat). ' +
143143
'Note: alibaba/qwen* is NOT supported by Cloudflare /compat — use the DashScope provider for Qwen.' },
144144
{ type: 'text', key: 'cloudflare_account_id', label: 'Cloudflare account id', required: true,
145145
description: 'The 32-char hex id from your Cloudflare dashboard URL.',
146146
visible: "${data.provider === 'cloudflare'}" },
147147
{ type: 'text', key: 'cloudflare_gateway_id', label: 'Gateway id', required: false, default: 'default',
148-
description: 'Gateway name configured in Cloudflare → AI Gateway. Defaults to `default`.',
148+
description: 'Gateway name configured in Cloudflare → AI Gateway. Defaults to "default".',
149149
visible: "${data.provider === 'cloudflare'}" },
150150
{ type: 'password', key: 'cloudflare_api_key', label: 'Cloudflare AI Gateway token',
151151
required: true, encrypted: true,
@@ -226,7 +226,7 @@ const manifest = {
226226
{ type: 'group', id: 'observability', label: 'Observability', required: false },
227227
{ type: 'toggle', key: 'trace_enabled', label: 'Record traces',
228228
required: false, default: true,
229-
description: 'Persist prompt/response traces to sys_ai_trace for debugging and replay.' },
229+
description: 'Keep a record of each AI request and its response, so a conversation can be inspected and replayed when something goes wrong.' },
230230
{ type: 'toggle', key: 'log_prompts', label: 'Log full prompts',
231231
required: false, default: false,
232232
description: 'Include rendered prompts (not just metadata) in trace rows.' },
@@ -303,7 +303,7 @@ const manifest = {
303303
visible: "${data.embedder_provider && data.embedder_provider !== 'none'}" },
304304
{ type: 'number', key: 'embedder_batch_size', label: 'Batch size',
305305
required: false, default: 64, min: 1, max: 2048,
306-
description: 'Chunks per embed() call. Reduce if hitting provider rate / size limits.',
306+
description: 'Chunks sent to the embedding provider in one request. Reduce if hitting provider rate / size limits.',
307307
visible: "${data.embedder_provider && data.embedder_provider !== 'none'}" },
308308
{ type: 'action_button', id: 'test_embedder', label: 'Test embedder',
309309
required: false, icon: 'Plug',

‎packages/services/service-settings/src/manifests/knowledge.manifest.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ const manifest = {
7373
visible: "${data.adapter === 'turso'}",
7474
description:
7575
'Works against managed Turso (libsql://…), local file (file:./knowledge.db), ' +
76-
'or in-memory (:memory:). For per-tenant cloud deployments, leave blank to ' +
77-
'reuse the tenant\'s primary libSQL connection.' },
76+
'or in-memory (:memory:). In a cloud deployment with a database per organization, leave blank to ' +
77+
'reuse that organization\'s own database connection.' },
7878
{ type: 'text', key: 'turso_url', label: 'Connection URL', required: false,
7979
description: 'Examples: libsql://your-tenant.turso.io · file:./.objectstack/knowledge.db · :memory:',
8080
visible: "${data.adapter === 'turso'}" },
@@ -95,12 +95,12 @@ const manifest = {
9595
visible: "${data.adapter === 'ragflow'}" },
9696
{ type: 'text', key: 'ragflow_default_dataset', label: 'Default dataset id',
9797
required: false,
98-
description: 'Used when a KnowledgeSource does not specify its own RAGFlow dataset.',
98+
description: 'Used when a knowledge source does not name its own RAGFlow dataset.',
9999
visible: "${data.adapter === 'ragflow'}" },
100100

101101
// ── Indexing defaults ─────────────────────────────────────────
102102
{ type: 'group', id: 'indexing', label: 'Indexing defaults', required: false,
103-
description: 'Per-source values on KnowledgeSource.adapterConfig take precedence.',
103+
description: 'A value set on a knowledge source itself wins over these defaults.',
104104
visible: "${data.adapter !== 'memory'}" },
105105
{ type: 'number', key: 'chunk_target', label: 'Target chunk size (chars)',
106106
required: false, default: 800, min: 64, max: 8192,
@@ -112,14 +112,14 @@ const manifest = {
112112
visible: "${data.adapter !== 'memory'}" },
113113
{ type: 'number', key: 'over_fetch', label: 'Over-fetch multiplier',
114114
required: false, default: 4, min: 1, max: 20,
115-
description: 'Internal `topK * overFetch` candidates fetched so JS-side metadata filtering still has rows to return.',
115+
description: 'Search fetches this many times the requested number of results, so enough are left to return after they are filtered by document details.',
116116
visible: "${data.adapter === 'turso'}" },
117117

118118
// ── Permissions ───────────────────────────────────────────────
119119
{ type: 'group', id: 'permissions', label: 'Permissions', required: false },
120120
{ type: 'toggle', key: 'enforce_rls', label: 'Enforce RLS on search',
121121
required: false, default: true,
122-
description: 'Re-check every hit against the caller\'s record-level permissions via IDataEngine.' },
122+
description: 'Re-check every hit against the caller\'s record-level permissions.' },
123123
{ type: 'info_banner', id: 'enforce_rls_warning', label: 'Leave RLS on in production',
124124
required: false, bannerSeverity: 'warning',
125125
bannerText:

‎packages/services/service-settings/src/settings-help-business-words.pin.test.ts‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
* The technical detail lives in the deployment docs; the help names the
1313
* effect in business words.
1414
*
15-
* The sweep reads every string an administrator is shown for those three
15+
* AI & Embedder and Knowledge had the same problem (#28069): the help named the
16+
* env vars a saved key or model stands in for (`OPENAI_API_KEY`,
17+
* `AI_GATEWAY_MODEL`), the trace object, the embedding call and the knowledge
18+
* source's config path. They now say what the setting does, and a saved key
19+
* is described as used instead of the one in the server configuration.
20+
*
21+
* The sweep reads every string an administrator is shown for those five
1622
* namespaces — the manifest literals (the English fallback) and each shipped
1723
* locale's copy — and refuses the four shapes that only a developer can read.
1824
* It pins SHAPES, never wording: a rewrite that stays in business words keeps
@@ -24,12 +30,17 @@ import type { SettingsManifest } from '@objectstack/spec/system';
2430
import { authSettingsManifest } from './manifests/auth.manifest.js';
2531
import { featureFlagsSettingsManifest } from './manifests/feature-flags.manifest.js';
2632
import { mailSettingsManifest } from './manifests/mail.manifest.js';
33+
import { aiSettingsManifest } from './manifests/ai.manifest.js';
34+
import { knowledgeSettingsManifest } from './manifests/knowledge.manifest.js';
2735
import { en, zhCN, jaJP, esES } from './translations/index.js';
2836

2937
const MANIFESTS: SettingsManifest[] = [
3038
authSettingsManifest,
3139
featureFlagsSettingsManifest,
3240
mailSettingsManifest,
41+
// #28069
42+
aiSettingsManifest,
43+
knowledgeSettingsManifest,
3344
];
3445

3546
const LOCALES: Array<[string, { settings?: Record<string, any>; settingsCommon?: Record<string, any> }]> = [
@@ -89,7 +100,7 @@ function packStrings(tr: Record<string, any> | undefined, ns: string): Array<[st
89100
return out;
90101
}
91102

92-
describe('Settings help is in business words (#27853)', () => {
103+
describe('Settings help is in business words (#27853, #28069)', () => {
93104
it('the sweep has teeth: each shape catches the string it was written for', () => {
94105
// Every shape is exercised by a string the old help carried, so a regex
95106
// that stops matching anything goes red here instead of passing quietly.
@@ -98,6 +109,12 @@ describe('Settings help is in business words (#27853)', () => {
98109
expect(shapesIn('emailAndPassword.requireEmailVerification: false')).not.toEqual([]);
99110
expect(shapesIn('declared via defineWebhook() is read')).not.toEqual([]);
100111
expect(shapesIn('Pin via env vars (e.g. `X`)')).not.toEqual([]);
112+
// The strings #28069 was filed for.
113+
expect(shapesIn('Forwarded as GOOGLE_GENERATIVE_AI_API_KEY.')).not.toEqual([]);
114+
expect(shapesIn('Persist traces to sys_ai_trace for replay.')).not.toEqual([]);
115+
expect(shapesIn('Per-source values on KnowledgeSource.adapterConfig take precedence.')).not.toEqual([]);
116+
expect(shapesIn('Chunks per embed() call.')).not.toEqual([]);
117+
expect(shapesIn('Format: provider/model, e.g. openai/gpt-4o. Examples: deepseek-chat (V3).')).toEqual([]);
101118
expect(shapesIn('Invitation only closes self-registration (SCIM, e.g. smtp.example.com).')).toEqual([]);
102119
});
103120

‎packages/services/service-settings/src/setup-nav-hub-parity.test.ts‎

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727
* the bundled manifests (as `os serve` does) and registers
2828
* `lifecycleSettingsManifest` the way `ObjectQLPlugin` does at `kernel:ready`.
2929
*
30-
* Data Lifecycle is `@objectstack/objectql`'s manifest, and no bundle carries a
31-
* translated title for it, so its hub card reads "Data Lifecycle" in every
32-
* locale. That gap is pinned by name below ({@link HUB_UNTRANSLATED}), so a
33-
* translation added later turns the label comparison on for it rather than
34-
* leaving it unchecked.
30+
* Every page the hub lists carries a translated title in each shipped locale,
31+
* Data Lifecycle included: it is `@objectstack/objectql`'s manifest, and its
32+
* copy lives in {@link settingsBuiltinTranslations} beside the bundled ones
33+
* (the coverage test there holds it to the same rule), so the label comparison
34+
* runs for every page and no page is exempt from it.
3535
*/
3636

3737
import { describe, expect, it } from 'vitest';
@@ -48,9 +48,6 @@ const SETTINGS_PAGE = /^\/apps\/setup\/system\/settings\/([a-z_]+)$/;
4848

4949
const TRANSLATED_LOCALES = ['zh-CN', 'ja-JP', 'es-ES'] as const;
5050

51-
/** Namespaces the hub lists with no translated title in any shipped locale. */
52-
const HUB_UNTRANSLATED = ['lifecycle'];
53-
5451
interface NavItem { id?: string; type?: string; label?: string; url?: string; children?: NavItem[] }
5552

5653
async function bootHub(): Promise<{ manifests: SettingsManifest[]; registered: unknown[] }> {
@@ -145,8 +142,8 @@ describe('Setup sidebar and settings hub list the same pages under the same name
145142
expect(drift).toEqual([]);
146143
});
147144

148-
it(`${locale}: the hub pages with no translated title are exactly the named ones`, () => {
149-
expect(manifests.filter((m) => hubTitle(locale, m) == null).map((m) => m.namespace).sort()).toEqual(HUB_UNTRANSLATED);
145+
it(`${locale}: every hub page carries a translated title`, () => {
146+
expect(manifests.filter((m) => hubTitle(locale, m) == null).map((m) => m.namespace)).toEqual([]);
150147
});
151148
}
152149
});

0 commit comments

Comments
 (0)