Skip to content

Commit bf01226

Browse files
test(i18n): give the studio zh-only key families one home (#27289)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8WHECehD1njgR1VYkaf17RP)_ Fixes #27250 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:studio 命名空间里「只有中文、没有英文」的三个键族前缀,原先在 i18n 包和 app-shell 包的两个测试里各抄一份;现在只在 i18n 的一个测试辅助文件里写一次,app-shell 的测试断言自己那份理由清单的前缀与它完全相等。 - 为什么改:两份清单各自都会拦住清单外的键,但互不对照,以后有人只改其中一份,两边就会对「哪些中文独有键是允许的」说法不一,却没有任何测试变红。 - 风险与回滚:只动测试,不改任何运行时代码、不发布任何东西;回滚就是 revert 这一个提交。 - 先看:`ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts` 新增的相等断言;`ui/packages/i18n/src/__tests__/studio-zh-only-families.ts`;`ui/packages/i18n/src/__tests__/studio-namespace.test.ts`。 - 两分钟验证:在仓根跑 `pnpm --dir ui exec vitest run packages/i18n/src/__tests__/studio-namespace.test.ts packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts`,应看到两个文件全绿;在任一清单里多加一个前缀,应只有「a reason is given for exactly the families the i18n pin allows」这一条变红。 - 需要你做的:无,席位落地。 ## What changes - New test-support module `ui/packages/i18n/src/__tests__/studio-zh-only-families.ts` exports `STUDIO_ZH_ONLY_FAMILIES`, the three zh-only prefixes (`engine.flowNode.`, `engine.enum.type.`, `engine.packages.form.help.`). It sits under `__tests__/`, which the package's `tsconfig.json` excludes, so it is not in `dist` and nothing is added to `@object-ui/i18n`'s exports map. - `studio-namespace.test.ts` drops its local `ZH_ONLY_FAMILIES` literal and reads the module. - `inspectorStrings.studioNamespace-25922.test.ts` imports the module by relative path (the dependency direction stays app-shell → i18n) and adds one case, "a reason is given for exactly the families the i18n pin allows", asserting the sorted `ZH_ONLY_REASONS` prefixes equal the sorted list. The reasons and citation anchors stay in app-shell, beside the files they cite. ## Why After the designer-table parity gate retired, the zh-only family list had two homes and nothing held them equal (triage's contract review of PR #27136). Route taken: one list on the i18n side plus an equality assertion, the dispatch's first option. Deriving `ZH_ONLY_REASONS` from the i18n list is not possible, because its reasons and anchors are app-shell facts. ## Tests - Both pins: `Test Files 2 passed (2)`, `Tests 201 passed (201)` (the new case is the 201st). - Ablation, two one-shot legs through `scripts/ablation-replace.mjs`, each with on-disk anchor counts and blob hashes, restored after: - fourth prefix added to `STUDIO_ZH_ONLY_FAMILIES` only: red, `Tests 1 failed | 200 passed (201)`, the one failure being the new equality case. - fourth entry added to `ZH_ONLY_REASONS` only (with a valid citation anchor): red, `Tests 1 failed | 201 passed (202)`, again only the new equality case. - After both legs `git diff HEAD` is empty and both files' blobs equal their `HEAD` blobs. ## Acceptance notes - `ZH_ONLY_FLOOR` in the app-shell pin keys its floors by a coarser prefix, `engine.enum.` rather than `engine.enum.type.`. It holds counts, not the allowed set, so it is outside this card; noted only. - No changeset: the diff touches test files only, and the new module sits in a directory the build excludes, so no released package publishes anything new (`skip-changeset`). ## Verification Local readings on head `37238cc1` (merge base `1e8e5296`). Exit codes captured before any pipe; CI owns every other family. <details><summary>24 derived gate families: 24 run, all exit 0; <code>dispatch-gates --ran</code> reconciles 24/24, 0 NOT-MEASURED</summary> ```text node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` </details> <details><summary>Owed beyond the derived list: both package type-checks (each compiles its tests through <code>tsconfig.test.json</code>, so the cross-package import is type-checked) and the line-citation check</summary> ```text pnpm --filter @object-ui/i18n run type-check :: exit 0 pnpm --filter @object-ui/app-shell run type-check :: exit 0 pnpm --dir ui run check:new-line-citations :: exit 0 ``` `check:new-line-citations` printed `0 new citation(s)`. </details> `dispatch-gates --tier` on this surface: no path-derived mandate (TIER_DEFAULT). Changed lines 34 (+26 / -8). ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (lint is CI's run) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset. (publishes nothing) - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 block is the dispatch-required Chinese summary). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxban7wCJnPiiSY4ULYz)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent b88519e commit bf01226

3 files changed

Lines changed: 26 additions & 8 deletions

File tree

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import path from 'node:path';
2929
import { fileURLToPath } from 'node:url';
3030
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
3131
import { t, tOptional } from '../i18n';
32+
// The i18n pin's own family list (a test-support module, not a package export).
33+
import { STUDIO_ZH_ONLY_FAMILIES } from '../../../../../i18n/src/__tests__/studio-zh-only-families';
3234

3335
const EN = await loadStudioCatalogue('en');
3436
// Resident before any case, so the zh lookups below read synchronously.
@@ -152,6 +154,8 @@ const ZH_ONLY_FLOOR: Readonly<Record<string, number>> = Object.freeze({
152154
* here from the retired `check:i18n-designer-parity` gate with slice S7. A
153155
* citation is `{ file, anchor }`, the anchor is TEXT and never a line number,
154156
* and it must occur exactly once in its file or it stops locating anything.
157+
* Its prefixes are held equal to `STUDIO_ZH_ONLY_FAMILIES`, the list the i18n
158+
* pin `studio-namespace.test.ts` allows, so neither can widen alone.
155159
*/
156160
const ZH_ONLY_REASONS = Object.freeze([
157161
{
@@ -248,6 +252,10 @@ describe('the namespace is the only home (objectstack#25922 slice S7)', () => {
248252
expect(wrong).toEqual([]);
249253
});
250254

255+
it('a reason is given for exactly the families the i18n pin allows', () => {
256+
expect(ZH_ONLY_REASONS.map((f) => f.prefix).sort()).toEqual([...STUDIO_ZH_ONLY_FAMILIES].sort());
257+
});
258+
251259
it.each(ZH_ONLY_REASONS.map((f) => [f.prefix, f] as const))('%s: its citation anchor occurs exactly once', (_prefix, family) => {
252260
const text = readFileSync(path.join(here, family.file), 'utf8');
253261
expect(text.split(family.anchor).length - 1).toBe(1);

‎ui/packages/i18n/src/__tests__/studio-namespace.test.ts‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,13 @@ import {
3030
resolveStudioLanguage,
3131
studioString,
3232
} from '../namespaces/studio/index.js';
33+
// The `zh`-only families, each one-sided on purpose; the list's one home.
34+
import { STUDIO_ZH_ONLY_FAMILIES } from './studio-zh-only-families.js';
3335

3436
/** `tFormat()`'s own placeholder pattern (app-shell `views/metadata-admin/i18n.ts`). */
3537
const PLACEHOLDER = /\{(\w+)\}/g;
3638
const placeholders = (value: string) => [...new Set(Array.from(value.matchAll(PLACEHOLDER), (m) => m[1]))].sort();
3739

38-
/**
39-
* The `zh`-only families, each one-sided on purpose. `ZH_ONLY_REASONS` in
40-
* app-shell's `inspectorStrings.studioNamespace-25922.test.ts` carries the
41-
* reason and the consumer for each, and checks that its citation still locates it.
42-
*/
43-
const ZH_ONLY_FAMILIES = ['engine.flowNode.', 'engine.enum.type.', 'engine.packages.form.help.'];
44-
4540
function flatKeys(node: unknown, prefix = ''): string[] {
4641
if (typeof node !== 'object' || node === null) return [prefix];
4742
return Object.entries(node).flatMap(([k, v]) => flatKeys(v, prefix ? `${prefix}.${k}` : k));
@@ -69,7 +64,7 @@ describe('studio namespace — scoped parity', () => {
6964

7065
it('a zh-only key belongs to a documented family', () => {
7166
const stray = zhKeys.filter(
72-
(key) => !(key in STUDIO_EN) && !ZH_ONLY_FAMILIES.some((family) => key.startsWith(family)),
67+
(key) => !(key in STUDIO_EN) && !STUDIO_ZH_ONLY_FAMILIES.some((family) => key.startsWith(family)),
7368
);
7469
expect(stray).toEqual([]);
7570
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* The `studio` namespace's `zh`-only key families, the one list.
3+
*
4+
* Two pins read it: `studio-namespace.test.ts` here, which reds on a `zh`-only
5+
* key outside these prefixes, and app-shell's
6+
* `inspectorStrings.studioNamespace-25922.test.ts`, whose `ZH_ONLY_REASONS`
7+
* carries the reason and consumer for each family and asserts its prefixes
8+
* equal this list. A test-support module, not an export of the package entry:
9+
* `__tests__/` is excluded from the build, so nothing here ships.
10+
*/
11+
export const STUDIO_ZH_ONLY_FAMILIES: readonly string[] = Object.freeze([
12+
'engine.flowNode.',
13+
'engine.enum.type.',
14+
'engine.packages.form.help.',
15+
]);

0 commit comments

Comments
 (0)