Skip to content

Commit af3873b

Browse files
docs(spec): a form section's translation TSDoc names both addresses — objects.OBJECT._sections for an object form view, metadataForms for a metadata form (#23815)
Fixes #23756 Clause-②: no ## What changed These are TSDoc and comment edits only, at the three locations the card and comment `6205162974` name, plus a fourth docblock the seat's review found (item 4). No `.describe()` text, schema or code changes. Measured at base `74b436263`. 1. **`packages/spec/src/ui/view.zod.ts`, `FormSectionSchema.name`.** The TSDoc named one address, `metadataForms.TYPE.sections.NAME`. It now names both addresses and which form each one serves: - **Object-bound form view:** `objects.OBJECT._sections.NAME`. OBJECT is the form's own `data.object`, else its view's. The i18n extractor (`walkObjectSections`) scaffolds the key, the translation linter (`collectViewRecord`) accepts it, and `translateView` serves it. A section without a `name` has no key and keeps its authored text. - **Metadata-editing form** (`defineForm({ schemaId })`): `metadataForms.TYPE.sections.NAME`, read by `resolveMetadataFormLabels`. A nameless section is looked up there under a slug of its `label`. The group-key comment on the same schema (the `:3862` site) also named `metadataForms.…` as the anchor. A section's `group` points at an object's `fieldGroups`, so its key is `deriveFieldGroupLayout`'s `key` at `objects.OBJECT._sections.KEY`, as `field-group-layout.ts` declares. The comment now says that. 2. **`packages/spec/src/system/translation.zod.ts`, `_sections`.** The TSDoc named `record:details` as the only reader. It now names: - the three authoring surfaces that put a name under this key: form-view `sections[].name`, `record:details` `sections[].name`, and object `fieldGroups[].key`; - the three readers on `main`: `walkObjectSections`, which scaffolds the key from all three; `collectViewRecord`, which accepts a form view's names; and `translateView`, which overlays a served form view's `config.sections`; - the `description` leaf, which the schema already declares; - the metadata-form address, which is a different key. 3. **`packages/spec/src/system/i18n-resolver.ts`, `translateFormSections`.** The docblock said "Three rules, each the one its sibling translators already follow". It now says that rules 1 (own `name` key) and 3 (a diverged string beats the catalog) are shared with `translateBulkActionDefs`, and that rule 2 (only authored copy is translated) belongs to sections alone. `resolveViewDescription` returns a catalog description the view never authored, and `translateBulkActionDefs` has no authored-copy guard. 4. **`packages/spec/src/system/i18n-resolver.ts`, `resolveMetadataFormLabels`** (patch round, head `e7ac53b9a1`). The docblock said a section without a stable `name` keeps its section-level text, which contradicted item 1's new text and the function's own `translateSection`. It now says what the code does: the lookup key is the section's `name`, else a slug of its string `label` (lowercased, `&` → `and`, each run of non-alphanumerics → `_`, edge underscores trimmed). Only a section with neither keeps its section-level text, and its fields are translated either way. The changeset gained one sentence for it. Every reader named above was found by its function name on `main` at `74b436263`: - `walkObjectSections`: `packages/cli/src/utils/i18n-extract.ts:914` - `collectViewRecord`: `packages/lint/src/validate-translation-references.ts:484` - `translateView` and `translateFormSections`: `i18n-resolver.ts` - `resolveMetadataFormLabels`: `i18n-resolver.ts:3926` - `defineForm`: `view.zod.ts:7145` The card body also lists objectui's form renderers as a reader. This repo does not contain them, so the new text does not name them as a reader. ## Changeset: `patch`, not `skip-changeset` The dispatch hypothesis was that comments do not publish. That was measured false for this diff: - `@objectstack/spec`'s `files[]` includes `src/**/*.zod.ts` and `dist`. - After `pnpm --filter @objectstack/spec build`, the new `view.zod.ts` text ("one of two addresses, decided by what the form edits") and the new `translation.zod.ts` text ("Three authoring surfaces name a section under this key") both appear in `dist/**` bundles. The `i18n-resolver.ts` text appears only in `src`. - Positive control: unchanged TSDoc from the same docblock is also present in `dist/**`. So the diff publishes text, and `.changeset/23756-section-translation-tsdoc.md` is a `patch` for `@objectstack/spec`. The `skip-changeset` label is not applied. ## Generated artifacts None carry this text. `git grep` for each replaced sentence returns zero hits after the edit. `check:docs` ran green against the rebuilt `dist`. ## Verification (head `58c3d6070f`; the patch round's re-runs at `e7ac53b9a1` are in report `6208324935`) - `pnpm --filter @objectstack/spec run typecheck` passes. - `vitest run src/system/i18n-resolver.test.ts src/system/translation.test.ts src/ui/view.test.ts`: 3 files, 974 tests passed. - `node scripts/pm/dispatch-gates.mjs --commands` derived 79 families; 78 ran and exited 0. - `pnpm check:dual-build-cjs-loads` exited 3, which means its prerequisite was not met: it needs every workspace package's `dist`, and only spec's build closure was built here. It is NOT MEASURED locally and is left to CI. - `--ran` reconciliation: "79 derived famil(ies) accounted for — 78 run, 1 NOT-MEASURED". ## Acceptance notes - `check-plugin-teardown-shape --self-test` needs the full history, because its positive control is pinned to a commit. It ran green after `git fetch --unshallow`. --- _Generated by [Claude Code](https://claude.ai/code/session_01RD4nZChNGxRzyyWJtvZ8NJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 59f9763 commit af3873b

4 files changed

Lines changed: 48 additions & 10 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Clause-②: no
6+
7+
The published TSDoc now sends a form section's translations to the right key. `FormSectionSchema.name` used to name only `metadataForms.<type>.sections.<name>`, which is the key for a metadata-editing form (`defineForm({ schemaId })`, read by `resolveMetadataFormLabels`). A section of an object-bound form view is keyed at `objects.<object>._sections.<name>`, where `<object>` is the form's own `data.object`, else its view's. The docblock now names both keys and which form each one serves. `TranslationDataSchema._sections` now names the three surfaces that author its key (form-view sections, `record:details` sections, object `fieldGroups`) and its readers (`walkObjectSections`, `collectViewRecord`, `translateView`). `resolveMetadataFormLabels`' docblock no longer says that a section without a `name` keeps its section-level text: such a section is looked up under a slug of its `label`, as the function has always done. These are comment changes only: no key, `.describe()` text or behaviour changes. An object-form translation bundle written from the old docblock under `metadataForms.…` never translated anything. Move its section entries to `objects.<object>._sections.<name>`.

‎packages/spec/src/system/i18n-resolver.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,11 @@ function formSectionObjectName(config: Record<string, unknown>, viewObject: stri
10521052
* nothing matched (so {@link translateView} rebuilds `config` only when a
10531053
* section moved).
10541054
*
1055-
* Three rules, each the one its sibling translators already follow:
1055+
* Three rules. The first and the third are the ones its sibling translators
1056+
* already follow ({@link translateBulkActionDefs} keys each def by its own
1057+
* `name` and keeps a diverged string); the second is the sections' own —
1058+
* {@link resolveViewDescription} returns a catalog description the view never
1059+
* authored, and {@link translateBulkActionDefs} has no authored-copy guard:
10561060
*
10571061
* - **The key is the section's own `name`**, ⛔ never a slug of its label: a
10581062
* section without a `name` has no `_sections` key (`FormSectionSchema.name`
@@ -3912,9 +3916,13 @@ function translateFormField(
39123916
* Translate the labels, descriptions, helpTexts, and placeholders of a
39133917
* metadata-type configuration form against the active translation bundle.
39143918
*
3915-
* Sections without a stable `name` are returned unchanged for section-level
3916-
* attributes — only field labels are translated (field names are always
3917-
* stable identifiers).
3919+
* A section's `label` and `description` are looked up at
3920+
* `metadataForms.<type>.sections.<section_name>`, where `<section_name>` is
3921+
* the section's own `name` or, when it has none, a slug of its string `label`
3922+
* (lowercased, `&` → `and`, each run of non-alphanumerics → `_`, edge
3923+
* underscores trimmed). Only a section with neither keeps its section-level
3924+
* text. Its fields are translated either way, since field names are always
3925+
* stable identifiers.
39183926
*
39193927
* Returns a new form object; the input is not mutated.
39203928
*

‎packages/spec/src/system/translation.zod.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,19 @@ export const ObjectTranslationDataSchema = lazySchema(() => strictObject({
392392
* Section translations keyed by section name (snake_case).
393393
* Convention:
394394
* objects.<object>._sections.<section_name>.label
395-
* Used by `record:details` to translate per-section labels on detail pages
396-
* (e.g. "Opportunity Information" → "商机信息"). Each section in the
397-
* page schema must declare a stable `name` for the lookup to fire.
395+
* objects.<object>._sections.<section_name>.description
396+
* Three authoring surfaces name a section under this key (e.g.
397+
* "Opportunity Information" → "商机信息"): an object-bound form view's
398+
* `sections[].name` (`FormSectionSchema.name`), a `record:details`
399+
* component's `sections[].name` on a record page, and an object's
400+
* `fieldGroups[].key`. A section without a stable name has no key here.
401+
*
402+
* Read by the i18n extractor (`walkObjectSections`, which scaffolds the key
403+
* from all three surfaces), the translation linter (`collectViewRecord`,
404+
* which accepts the names a form view declares) and `translateView` (which
405+
* overlays a served form view's `config.sections`). A metadata-editing
406+
* form's sections are keyed elsewhere, at
407+
* `metadataForms.<type>.sections.<section_name>`.
398408
*/
399409
_sections: z.record(z.string(), strictObject({
400410
surface: 'this section translation',

‎packages/spec/src/ui/view.zod.ts‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3676,8 +3676,20 @@ export const FormSectionSchema = lazySchema(() => strictObject({
36763676
}, {
36773677
/**
36783678
* Stable identifier for translation lookup. snake_case convention.
3679-
* When provided, translation bundles can target this section's `label`
3680-
* and `description` via `metadataForms.<type>.sections.<name>`.
3679+
* Translation bundles target this section's `label` and `description` at
3680+
* one of two addresses, decided by what the form edits:
3681+
*
3682+
* - **An object-bound form view**: `objects.<object>._sections.<name>`,
3683+
* where `<object>` is the form's own `data.object`, else its view's. The
3684+
* i18n extractor (`walkObjectSections`) scaffolds this key, the
3685+
* translation linter (`collectViewRecord`) accepts it and `translateView`
3686+
* serves it. A section without a `name` has no key there and keeps its
3687+
* authored text in every locale.
3688+
* - **A metadata-editing form** (`defineForm({ schemaId })`):
3689+
* `metadataForms.<type>.sections.<name>`, read by
3690+
* `resolveMetadataFormLabels`. A section without a `name` is looked up
3691+
* there under a slug of its `label`.
3692+
*
36813693
* Optional for backward-compat with sections that only have a `label`.
36823694
*/
36833695
name: z.string().optional().describe('Stable section identifier for i18n lookup (snake_case)'),
@@ -3859,7 +3871,8 @@ export const FormSectionSchema = lazySchema(() => strictObject({
38593871
surface: 'this form section',
38603872
// Exactly the keys `deriveFieldGroupLayout` fills from the group. `name` is
38613873
// in the list because the derived section's `key` IS the group key, which is
3862-
// already this surface's i18n anchor (`metadataForms.<type>.sections.<name>`)
3874+
// already the object's i18n anchor for that heading
3875+
// (`objects.<object>._sections.<key>`, `deriveFieldGroupLayout`'s `key`)
38633876
// — a second name would give one section two lookup identities. `visibleOn`
38643877
// rides along because this refinement runs BEFORE the `.transform` that folds
38653878
// it onto `visibleWhen`, so the deprecated spelling must be named here or it

0 commit comments

Comments
 (0)