Skip to content

Commit eec56c3

Browse files
Samclaude
andauthored
fix(platform-objects): keep the userFilters element tokens English in translated tooltips (#19520)
Fixes #19430 Clause-②: no The accept set does not move: no key joins a published payload, no member joins an enum, no symbol is exported, nothing is registered. The diff is three translated string leaves, one unit test file that exports nothing, and a changeset. `packages/spec` is untouched — the schema is not changing in this round. ## What was wrong `metadataForms.view.fields.userFilters.helpText` names the legal values of a **strict** enum, and it is the only place the `view` panel names them at all. All three translated catalogs rendered those tokens as ordinary words, so an author — or an AI — working in a translated locale was shown a value the schema refuses. This is the inverse of #19403's defect: there English leaked into translated UI; here translation leaked into a machine token that has to stay English to be writable. The prose around the values is translated correctly and stays translated. ## Per-locale before/after, beside the enum it must agree with The enum, unchanged by this PR, read on this branch at `packages/spec/src/ui/view.zod.ts:1506` (inside `UserFiltersSchema`, declared at `:1497`): ```ts element: z.enum(['dropdown', 'tabs', 'toggle']).default('dropdown') ``` | locale | before | after | |:--|:--|:--| | `en` (control, ⛔ not edited) | `Quick-filter bar: element style (dropdown / tabs / toggle) + exposed fields or tab presets` | unchanged | | `zh-CN` | `快速筛选栏:控件样式(下拉 / 标签页 / 开关)+ 暴露的字段或标签页预设` | `快速筛选栏:控件样式(dropdown / tabs / toggle)+ 暴露的字段或标签页预设` | | `ja-JP` | `クイックフィルターバー:要素スタイル(ドロップダウン / タブ / トグル)+ 公開フィールドまたはタブプリセット` | `クイックフィルターバー:要素スタイル(dropdown / tabs / toggle)+ 公開フィールドまたはタブプリセット` | | `es-ES` | `Barra de filtros rápidos: estilo de elemento (desplegable / pestañas / interruptor) + campos expuestos o preajustes de pestañas` | `Barra de filtros rápidos: estilo de elemento (dropdown / tabs / toggle) + campos expuestos o preajustes de pestañas` | (The table renders the CJK brackets as ASCII for legibility; the files keep each locale's own full-width punctuation, unchanged by this PR — read the diff for the bytes.) ⇒ after: every token the tooltip names is a value `UserFiltersSchema` accepts, in all four locales. The prose keeps its own language: 快速筛选栏 · クイックフィルターバー · Barra de filtros rápidos, and the trailing "tab presets" prose (标签页预设 · タブプリセット · preajustes de pestañas) stays translated — it is prose, not a token. Only leaf string values changed. No key was added or dropped, so the bundle structure is untouched and no `*.source-hashes*.ts` file moves: `metadataForms.view.fields.userFilters.helpText` carries no provenance entry in any of the three locales (it is LEGACY-TRUSTED — already translator-written before this round), and `pnpm check:i18n` reports all 9 packages in sync. ## The two `element` enums — which tooltip describes which, and the verdict on the second Both readings taken on this branch at `1b2abe57`, where `packages/spec/src/ui/view.zod.ts` is byte-identical to `origin/main` at `48c39e00`: | line | enum | owning schema | |:--|:--|:--| | `view.zod.ts:1506` | `z.enum(['dropdown', 'tabs', 'toggle'])` | `UserFiltersSchema` (`:1497`) | | `view.zod.ts:4205` | `z.enum(['dropdown', 'toggle'])` — ⛔ no `tabs` | `ObjectUserFiltersSchema` (`:4195`) | The card measured the second one at `:4193`; on this tree it is `:4205`. Same declaration, re-taken here. **The repaired tooltip describes the FIRST enum.** The chain, each link read on this tree: 1. The tooltip's source is `packages/spec/src/ui/view.form.ts:119`, inside `defineForm({ schemaId: 'view' })` (`view.form.ts:18-19`). 2. `schemaId: 'view'` resolves through `METADATA_FORM_REGISTRY` to `ViewMetadataSchema` (`view.zod.ts:5586`), a union over four wire members (`VIEW_METADATA_MEMBERS`, `:5181`). 3. The form's `userFilters` is a **top-level** field. The member that carries a top-level `userFilters` is `ListViewOverlayWireSchema` — `ListViewShapeSchema.extend(...)` — and `ListViewShapeSchema.userFilters` is `UserFiltersSchema.optional()` at `view.zod.ts:2410`. The `viewItem` member carries the same schema one level down, under `config`. 4. So the three values the tooltip names are exactly the three `UserFiltersSchema.element` accepts. The `en` copy was already correct; that is why it is the control. **Verdict on the second surface (`:4205`): no defect, because no tooltip reaches it.** `ObjectUserFiltersSchema` is referenced from exactly one place — `ObjectListViewSchema` (`view.zod.ts:4220-4222`) — which is what `ViewSchema`'s container keys `views.list` and `views.listViews` use (`:4306`, `:4308`). No metadata form declares a field for them: - `grep -n "field: '"` over `packages/spec/src/data/object.form.ts` returns no `views`, `listViews` or `userFilters` entry; - `grep -n "listViews" en.metadata-forms.generated.ts` returns nothing; - `grep -n "dropdown"` over the whole `en` catalog returns exactly **one** line — `view.fields.userFilters.helpText`, the leaf repaired here. ⇒ the second enum has **no** metadata-form tooltip, so it carries neither this defect nor the worse one (a tooltip naming a value its own enum refuses). Its own English `.describe()` at `view.zod.ts:4206` is correct and says so explicitly: `"tabs" is page-only — use listViews for named presets`. One neighbour worth stating because it looks adjacent and is not: `page.fields['interfaceConfig.userFilters']` describes the SAME `UserFiltersSchema` (`page.zod.ts:591`), and its tooltip already keeps `None / Tabs / Dropdown` verbatim English in all three locales — the shape PR #19428 chose. ⛔ It is **not** the shape this PR applies, and the corrected changeset now says so: those three are the `filter-mode` widget's UI mode names, capitalised, and `z.enum` is case-sensitive, so the enum refuses all three; `None` stands for the absence of the config rather than a value; `toggle` is deliberately not offered there. What this PR keeps verbatim is the enum's own tokens, which is the stricter requirement, because they are values an author types. <sub>— clause corrected in place by the `domain:spec` seat 3 at 2026-09-21T06:20Z, after the at-tier review found it false; the original read 「and the shape this PR applies to the `view` panel」. Disclosed here and in the review record rather than silently rewritten.</sub> ## The pin, and the four controls that keep it honest `packages/platform-objects/src/apps/translations/user-filters-element-tokens.test.ts` — 11 cases, one leaf, three locales. Not a census: it reads one key. 1. **The set is derived, not listed.** `SCHEMA_ELEMENT_VALUES` is read off `UserFiltersSchema.shape.element` (unwrapping `.default()`) and asserted SET-EQUAL to the pinned literal; every tooltip assertion then runs over the derived set. So enum **growth** reddens, not only shrinkage — a hand-listed set catches a dropped member and lets a newly legal value go unnamed in all four catalogs with the pin green, which is this card's own defect class reintroduced. 2. **Schema leg.** Every derived token is `safeParse`d through `UserFiltersSchema` and must be ACCEPTED; the words the translations used to carry (下拉/开关 · ドロップダウン/トグル · desplegable/interruptor) and the neighbouring panel's capitalised `None / Tabs / Dropdown` are `safeParse`d and must be REFUSED. 3. **Exact-set leg.** The tooltip's parenthetical must name the derived values and nothing else, in order. Checking only "no fewer" lets `(dropdown / tabs / toggle / chips)` through. 4. **Still-translated leg.** Each translated helpText must differ from the `en` source and must still carry its own prose at BOTH ends of the sentence. Anchoring the leading fragment alone lets the trailing half revert to English — #19403's defect surviving on the larger half of the sentence. `tabs` is deliberately absent from the "former words" row of every locale: its former word is also the locale's ordinary word for the tab-presets **prose** at the end of the same sentence, which is translated on purpose and must stay. Asserting its absence would demand the prose be de-translated; it is covered by controls 1, 3 and the presence leg instead. The reason is written in the test. **Reverse verification** — five legs, each driven from the committed state with `scripts/ablation-replace.mjs` (anchor hit asserted, blob hash before/after recorded, restore proved by `blob == HEAD` and an empty `git diff HEAD`): | mutation | expected | observed | |:--|:--|:--| | `zh-CN` helpText reverted to the pre-fix translated words | red | **1 failed / 10 passed** — `zh-CN helpText does not name 'dropdown'` | | `zh-CN` helpText replaced by the `en` source sentence | red, on the still-translated leg | **1 failed / 10 passed** — `zh-CN helpText is a byte copy of the en source` | | `zh-CN` trailing clause replaced by its English source | red — the N1 escape, now closed | **1 failed / 10 passed** — `zh-CN helpText lost its trailing prose` | | `zh-CN` parenthetical grown to `(dropdown / tabs / toggle / chips)` | red — the N2 escape, now closed | **1 failed / 10 passed** — `names a different set from the enum` | | `UserFiltersSchema.element` grown by a fourth member in `packages/spec/src/ui/view.zod.ts`, spec rebuilt | red — the B2 class | **5 failed / 6 passed** — the derived-set control plus all four catalogs' presence legs | Every leg restored: `blob == HEAD`, `git diff HEAD` empty. The fourth-member leg additionally left a mutated `packages/spec/dist`, so spec was rebuilt and the restore proved by grep with a lit control: the mutated spelling is absent from `packages/spec/dist` (grep exit 1, 0 hits) while the canonical one is present in 22 files (grep exit 0). ⛔ `view.zod.ts` is untouched by the delivered diff — that mutation was an ablation, restored, never committed. <sub>⚠️ This section was rewritten in place by the `domain:spec` seat 3 at 2026-09-21T06:40Z. It previously described the file at the earlier head `1b2abe57` — 「9 cases」, 「two controls」, and a two-row ablation table — which the at-tier review found stale for this head. An interim seat note at 06:36Z said only that the table had not been re-taken here; the round has since re-taken it, so the measured five-leg table above replaces that note. ⛔ The numbers above are the round's measurements, not the seat's; the seat independently re-took only the case count (11) and the CI roster.</sub> ## Verification All readings taken in `/home/user/objectstack-issue-19430` at `805493a4`, merge base `48c39e00`, every exit code captured **before** any pipe. - `node scripts/pm/dispatch-gates.mjs --changed --repo objectstack-ai/objectstack --commands` → **60** families, byte-identical to the derivation on the previous head. All 60 run, **all 60 at exit 0 on the first run** here (the three that needed a build on the previous head had their prerequisites already cleared). `--ran` reconciliation: **exit 0 — 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN**. - `pnpm --filter @objectstack/platform-objects test` → **48 files / 679 tests passed**, including this pin's 11 cases. - `pnpm --filter @objectstack/platform-objects typecheck` → exit 0 (`tsc --noEmit`, `tsconfig.scripts.json`, and `check:test-typecheck`, which compiles the new test file; the debt ledger did not grow). - `pnpm build` → 73/73 tasks; the `check:i18n` build closure → 57/57. - `pnpm lint` (`eslint . --no-inline-config`, the repo-wide scan, not a narrowing) → exit 0. - `pnpm check:i18n` → all 9 packages in sync, 11 bundles for `platform-objects`. `pnpm check:nul-bytes` → 0, plus a direct control-character scan over all five changed files: each `grep -naP` exits 1 (clean). - **CI on this head**, read at 2026-09-21T06:33Z from `GET /repos/objectstack-ai/objectstack/commits/805493a4.../check-runs`: 41 check runs — **36 success, 5 skipped, 0 failure, 0 in progress, 0 queued**. All six `Test Core` shards are `success`, so CI has now confirmed `user-filters-element-tokens.test.ts`; `Lint & Repo Gates`, `TypeScript Type Check`, `Build Core`, `Dogfood Regression Gate`, `Temporal Conformance (live PG + MySQL)` and `Governed Surface Queue Guard` are all `success` — the full required set. ## Acceptance notes - **Fenced and left alone, as the card records it.** `page.fields['interfaceConfig.appearance'].helpText` writes its renderer tokens capitalised (`Grid / Kanban / Calendar / …`) while `page.fields.type.helpText` on the same panel writes them lower-case (`grid / kanban / calendar / …`, matching `VisualizationTypeSchema`). Both already keep the tokens English in all three locales, so this is a capitalisation question, not a writability one. ⛔ Not swept in, ⛔ not filed — it is this class's recorded edge. - **The instrument the card proposes was reported, not built.** A corpus-wide census over "every schema key whose tooltip names enum values" is a new gate/row, and ruling batch #208 R6 requires the maintainer's sentence on its own card for one. None exists. The test added here is an ordinary unit pin over the single repaired leaf. - I wrote no label myself: the dispatch named none, and the PR carries a changeset so `skip-changeset` does not apply. The labels now on the PR — `documentation`, `size/m`, `tests`, `tooling` — were applied by the Auto Label check-run, not by me. --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a60c913 commit eec56c3

5 files changed

Lines changed: 230 additions & 3 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
Keep the `userFilters` element tokens English in the translated metadata-form tooltips
6+
7+
`metadataForms.view.fields.userFilters.helpText` names the legal values of `UserFiltersSchema.element`, a strict `z.enum(['dropdown', 'tabs', 'toggle'])`, and it is the only place the `view` panel names them at all. All three translated catalogs rendered those tokens as ordinary words — 下拉 / 标签页 / 开关, ドロップダウン / タブ / トグル, desplegable / pestañas / interruptor — so an author working in a translated locale was shown a value the schema refuses.
8+
9+
The enum values are now verbatim English inside the translated sentence and the prose around them stays translated. The `page` Interface panel is a neighbour here, not a precedent: its tooltip keeps `None / Tabs / Dropdown` English too, but those are the `filter-mode` widget's UI mode names — capitalised, and `z.enum` is case-sensitive, so the enum refuses all three; `None` stands for the absence of the config rather than a value; and `toggle` is deliberately not offered there. What this change keeps verbatim is the enum's own tokens, which is the stricter requirement, because they are values an author types.
10+
11+
`user-filters-element-tokens.test.ts` pins the repaired leaf in the three locales. It derives the accepted set from `UserFiltersSchema` and asserts set equality against it, so a value added to the enum reddens instead of going unnamed; it requires each tooltip to name that set and nothing else; and it holds each translated sentence's prose at both ends, so the assertion cannot be satisfied by copying the English sentence back in.

‎packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
737737
},
738738
userFilters: {
739739
label: "Filtros de usuario",
740-
helpText: "Barra de filtros rápidos: estilo de elemento (desplegable / pestañas / interruptor) + campos expuestos o preajustes de pestañas"
740+
helpText: "Barra de filtros rápidos: estilo de elemento (dropdown / tabs / toggle) + campos expuestos o preajustes de pestañas"
741741
},
742742
tabs: {
743743
label: "Pestañas",

‎packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
737737
},
738738
userFilters: {
739739
label: "ユーザーフィルター",
740-
helpText: "クイックフィルターバー:要素スタイル(ドロップダウン / タブ / トグル)+ 公開フィールドまたはタブプリセット"
740+
helpText: "クイックフィルターバー:要素スタイル(dropdown / tabs / toggle)+ 公開フィールドまたはタブプリセット"
741741
},
742742
tabs: {
743743
label: "タブ",
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// The `view` panel's `userFilters` tooltip names the legal values of a STRICT
4+
// enum, and it is the only place that panel names them at all. So that
5+
// sentence is not ordinary prose: the tokens inside it are what an author —
6+
// or an AI reading the panel — types into `userFilters.element`, and
7+
// `UserFiltersSchema` refuses anything else.
8+
//
9+
// All three translated catalogs used to render those tokens as ordinary words
10+
// (下拉 / 标签页 / 开关 · ドロップダウン / タブ / トグル ·
11+
// desplegable / pestañas / interruptor), so a translated-locale author was
12+
// shown a value the schema rejects. The repair keeps the enum values verbatim
13+
// English inside the translated sentence and leaves the prose around them
14+
// translated.
15+
//
16+
// This pins THAT leaf, in the three locales, and nothing wider: it reads one
17+
// key. What keeps it from passing vacuously — or passing while the defect
18+
// class walks back in — is four controls, each of which fails on its own:
19+
//
20+
// 1. THE SET IS DERIVED, NOT LISTED. `SCHEMA_ELEMENT_VALUES` is read off
21+
// `UserFiltersSchema.shape.element` (unwrapping `.default()`) and asserted
22+
// SET-EQUAL to the literal below. Every tooltip assertion then runs over
23+
// the DERIVED set. So enum GROWTH reddens — add a fourth member and this
24+
// control fails and every locale fails the presence leg — and not only
25+
// enum shrinkage. A hand-listed set catches shrinkage alone and lets a new
26+
// legal value go unnamed in all four catalogs with the pin green, which is
27+
// this card's own defect class reintroduced.
28+
// 2. THE SCHEMA LEG. Every derived token is `safeParse`d through
29+
// `UserFiltersSchema` and must be ACCEPTED; the words the translations
30+
// used to carry, and the capitalised spellings the neighbouring `page`
31+
// panel shows for its `filter-mode` widget, are `safeParse`d and must be
32+
// REFUSED. A tooltip rewritten to name some other word fails here.
33+
// 3. THE EXACT-SET LEG. The tooltip's parenthetical must name the derived
34+
// values and NOTHING ELSE, in order. Checking only "no fewer" lets
35+
// `(dropdown / tabs / toggle / chips)` through.
36+
// 4. THE STILL-TRANSLATED LEG. Each translated helpText must differ from the
37+
// `en` source and must still carry its own prose at BOTH ends of the
38+
// sentence. Anchoring the leading fragment alone lets the trailing half
39+
// revert to English — #19403's defect surviving on the larger half of the
40+
// sentence. "Fixing" a red by copying the whole English sentence into the
41+
// three catalogs fails here too.
42+
43+
import { describe, it, expect } from 'vitest';
44+
import { UserFiltersSchema } from '@objectstack/spec/ui';
45+
46+
import { enMetadataForms } from './en.metadata-forms.generated.js';
47+
import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js';
48+
import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js';
49+
import { esESMetadataForms } from './es-ES.metadata-forms.generated.js';
50+
51+
/**
52+
* The values `UserFiltersSchema.element` accepts, READ OFF THE SCHEMA.
53+
*
54+
* `element` is `z.enum([...]).default('dropdown')`, so the enum node sits
55+
* behind the default wrapper. Unwrapped here rather than reached for by a
56+
* fixed path, and the shape is asserted below — a derivation that silently
57+
* yielded `undefined` would make every assertion over it vacuous.
58+
*/
59+
const SCHEMA_ELEMENT_VALUES: readonly string[] = (() => {
60+
let node: any = (UserFiltersSchema as any).shape?.element;
61+
for (let hop = 0; hop < 5 && (node?.def ?? node?._def)?.type === 'default'; hop += 1) {
62+
node = (node.def ?? node._def).innerType;
63+
}
64+
return node?.options ?? [];
65+
})();
66+
67+
/** What that set is expected to be — the pin half of control 1. */
68+
const EXPECTED_ELEMENT_VALUES = ['dropdown', 'tabs', 'toggle'] as const;
69+
70+
/**
71+
* Spellings that must stay UNWRITABLE, so the presence assertions above cannot
72+
* be satisfied by a word the schema would refuse.
73+
*
74+
* Two groups. The per-locale rows are what each tooltip used to name. The
75+
* shared row is the capitalised set the neighbouring `page` Interface panel
76+
* shows for its `filter-mode` widget (`None / Tabs / Dropdown`): those are UI
77+
* mode names, not enum tokens — `z.enum` is case-sensitive, so the enum
78+
* refuses them, and `None` is not a member at all (it stands for the absence
79+
* of the config). Asserted here so the difference is a measurement rather than
80+
* a remark.
81+
*
82+
* `tabs` is deliberately absent from every per-locale row: its former word is
83+
* also the locale's ordinary word for the "tab presets" PROSE at the end of
84+
* the same sentence (标签页预设 · タブプリセット · preajustes de pestañas),
85+
* which is translated on purpose and must stay. Asserting its absence would
86+
* demand the prose be de-translated — the inverse defect again — so `tabs` is
87+
* covered by controls 1, 3 and the presence leg instead.
88+
*/
89+
const FORMER_TRANSLATIONS: Readonly<Record<string, readonly string[]>> = {
90+
'zh-CN': ['下拉', '开关'],
91+
'ja-JP': ['ドロップダウン', 'トグル'],
92+
'es-ES': ['desplegable', 'interruptor'],
93+
};
94+
const NEIGHBOURING_PANEL_LABELS = ['None', 'Tabs', 'Dropdown'] as const;
95+
96+
/**
97+
* Prose each locale must still render in its own language, at BOTH ends of the
98+
* sentence — the leading clause and the trailing one.
99+
*/
100+
const PROSE_ANCHORS: Readonly<Record<string, readonly [string, string]>> = {
101+
'zh-CN': ['快速筛选栏', '暴露的字段或标签页预设'],
102+
'ja-JP': ['クイックフィルターバー', '公開フィールドまたはタブプリセット'],
103+
'es-ES': ['Barra de filtros rápidos', 'campos expuestos o preajustes de pestañas'],
104+
};
105+
106+
const TRANSLATED_LOCALES: ReadonlyArray<readonly [string, Record<string, any>]> = [
107+
['zh-CN', zhCNMetadataForms as Record<string, any>],
108+
['ja-JP', jaJPMetadataForms as Record<string, any>],
109+
['es-ES', esESMetadataForms as Record<string, any>],
110+
];
111+
112+
const helpTextOf = (forms: Record<string, any>): string => {
113+
const help = forms?.view?.fields?.userFilters?.helpText;
114+
expect(typeof help, 'view.fields.userFilters.helpText is missing from this catalog').toBe('string');
115+
return help as string;
116+
};
117+
118+
/**
119+
* The tokens a tooltip names, read out of the parenthetical that carries them.
120+
* Both ASCII and full-width brackets, because each locale keeps its own
121+
* punctuation. Returns `[]` when no parenthetical names the first value — a
122+
* sentence reshaped past this reader's reach, which reddens rather than
123+
* silently measuring nothing.
124+
*/
125+
const namedTokens = (help: string): string[] => {
126+
const first = SCHEMA_ELEMENT_VALUES[0];
127+
if (first === undefined) return [];
128+
for (const match of help.matchAll(/[((]([^))]*)[))]/g)) {
129+
const inner = match[1] ?? '';
130+
if (inner.includes(first)) {
131+
return inner.split('/').map((part) => part.trim()).filter((part) => part.length > 0);
132+
}
133+
}
134+
return [];
135+
};
136+
137+
describe('view.fields.userFilters helpText names writable element tokens', () => {
138+
it('control: the asserted set IS the schema\'s set, derived not copied', () => {
139+
expect(
140+
Array.isArray(SCHEMA_ELEMENT_VALUES) && SCHEMA_ELEMENT_VALUES.length > 0,
141+
'could not read the enum off UserFiltersSchema.shape.element — every assertion below would be vacuous',
142+
).toBe(true);
143+
expect(
144+
[...SCHEMA_ELEMENT_VALUES].sort(),
145+
'UserFiltersSchema.element no longer accepts exactly these values — the tooltip names a set the schema has moved away from, in all four catalogs',
146+
).toEqual([...EXPECTED_ELEMENT_VALUES].sort());
147+
});
148+
149+
it('control: every value it names is accepted by the schema', () => {
150+
for (const value of SCHEMA_ELEMENT_VALUES) {
151+
expect(
152+
UserFiltersSchema.safeParse({ element: value }).success,
153+
`UserFiltersSchema refused '${value}' — the tooltip would be naming an unwritable value`,
154+
).toBe(true);
155+
}
156+
});
157+
158+
it('control: the words the translations used to name are refused', () => {
159+
for (const [locale, words] of Object.entries(FORMER_TRANSLATIONS)) {
160+
for (const word of words) {
161+
expect(
162+
UserFiltersSchema.safeParse({ element: word }).success,
163+
`UserFiltersSchema accepted '${word}' (${locale}) — this control no longer proves anything`,
164+
).toBe(false);
165+
}
166+
}
167+
});
168+
169+
it('control: the neighbouring panel\'s filter-mode labels are refused too', () => {
170+
for (const label of NEIGHBOURING_PANEL_LABELS) {
171+
expect(
172+
UserFiltersSchema.safeParse({ element: label }).success,
173+
`UserFiltersSchema accepted '${label}' — the page panel's capitalised labels would then be enum tokens, and this tooltip could borrow their shape`,
174+
).toBe(false);
175+
}
176+
});
177+
178+
it('en: the source names every accepted value, and only those', () => {
179+
const help = helpTextOf(enMetadataForms as Record<string, any>);
180+
for (const value of SCHEMA_ELEMENT_VALUES) {
181+
expect(help, `en helpText does not name '${value}'`).toContain(value);
182+
}
183+
expect(namedTokens(help), 'en helpText names a different set from the enum').toEqual([
184+
...SCHEMA_ELEMENT_VALUES,
185+
]);
186+
});
187+
188+
for (const [locale, forms] of TRANSLATED_LOCALES) {
189+
it(`${locale}: names every accepted value verbatim, and only those`, () => {
190+
const help = helpTextOf(forms);
191+
for (const value of SCHEMA_ELEMENT_VALUES) {
192+
expect(
193+
help,
194+
`${locale} helpText does not name '${value}' — a translated-locale author is shown a value the schema refuses, or is never shown a legal one`,
195+
).toContain(value);
196+
}
197+
expect(
198+
namedTokens(help),
199+
`${locale} helpText names a different set from the enum — an extra token here is a value the schema refuses`,
200+
).toEqual([...SCHEMA_ELEMENT_VALUES]);
201+
for (const word of FORMER_TRANSLATIONS[locale]!) {
202+
expect(help, `${locale} helpText still spells the enum value as '${word}'`).not.toContain(word);
203+
}
204+
});
205+
206+
it(`${locale}: the prose at both ends is still translated`, () => {
207+
const help = helpTextOf(forms);
208+
expect(help, `${locale} helpText is a byte copy of the en source`).not.toBe(
209+
helpTextOf(enMetadataForms as Record<string, any>),
210+
);
211+
const [leading, trailing] = PROSE_ANCHORS[locale]!;
212+
expect(help, `${locale} helpText lost its leading prose`).toContain(leading);
213+
expect(help, `${locale} helpText lost its trailing prose`).toContain(trailing);
214+
});
215+
}
216+
});

‎packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
737737
},
738738
userFilters: {
739739
label: "用户筛选器",
740-
helpText: "快速筛选栏:控件样式(下拉 / 标签页 / 开关)+ 暴露的字段或标签页预设"
740+
helpText: "快速筛选栏:控件样式(dropdown / tabs / toggle)+ 暴露的字段或标签页预设"
741741
},
742742
tabs: {
743743
label: "标签页",

0 commit comments

Comments
 (0)