Skip to content

Commit 5507225

Browse files
docs(skills): objectstack-i18n lists every top-level translation group and the nested form, tab and approval keys (#26388)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8Gmf9pt5QnfhyXoGbpSn5Bn)_ Fixes #26296 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`skills/objectstack-i18n/SKILL.md` 的对象子键表补上表单视图自己的 `title`(`_views`)和表单分区条目的 `label` / `placeholder` / `helpText`(`_sections.{section}.fields`);顶层分组清单补齐 `picklists`、`reports`、`positions`、`permissions`,并各用一行写明页面 `page:tabs` 条目标签与审批步骤标签的键路径,以及 `os i18n extract --no-objects-only` 会把它们一并生成。评测文件加两道题(报表 + 权限集 + 岗位 + 选项列表;四个嵌套键)。评测文件 token 天花板按你的裁决从 1004 提到 1451。 - 为什么改:18.2 的翻译包多了 `reports`、`positions`、`permissions` 分组和这几个嵌套键,技能里的清单读起来像是完整的,AI 作者照着写 zh-CN 包就会漏掉报表、岗位和权限集,而且没有任何提示。 - 风险与回滚:只改一份已发布技能的文字、它的评测文件和棘轮脚本里的一行天花板数;不发包、不改代码、不改 schema。回滚 = revert 本 PR 的一个提交。 - 先看:① `skills/objectstack-i18n/SKILL.md` L169–L181(顶层分组清单与两条嵌套键);② 同文件 L163、L165(`_views` 的 `title`、`_sections` 的 `fields`);③ `scripts/check-skills-token-ratchet.mjs` L657–L667(天花板行 1004 → 1451 与引用的裁决)。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期退出 0,且印出 `bundles-locales-coverage.json is 1451 tokens (ceiling 1451; headroom 0)` 与 `SKILL.md is 5282 tokens (ceiling 6333; headroom 1051)`。 - 需要你做的:批准(Tier H,`skills/**` 为对外发布面;席位不会 ready、不会入队)。 ## What changes `skills/objectstack-i18n/SKILL.md` (503 → 512 lines; 5028 → 5282 tokens, headroom 1305 → 1051, no ceiling moved): - `_views.{view_name}` row: adds `title` — a form view's own heading, translated only where the form authors one; a list view has none (`translation.zod.ts` 303–320). - `_sections.{section_name}` row: adds `fields.{field_name}` with the `label`, `placeholder`, `helpText` an entry overrides for that form alone, and says why the spelling is `helpText` here and `help` one row up (`translation.zod.ts` 455–490: each spells what its source does). - Top-level group list: now names every group the per-app bundle declares — `picklists` (pre-18.0, previously unlisted), `reports`, `positions`, `permissions` added, each with the one line an author needs (`reports` / `positions` / `permissions` hold `label` + `description` by name; `picklists` holds `label` + `options` by stored value, inherited by every field referencing the picklist; `permissions` is spelled as the stack's `permissions[]`). - One line each for the two keys that sit deeper than their group's `label`: `pages.{page}.components.{tabs_id}.items.{value}.label` (keyed by the item's `value`; no `value`, no key) and `flows.{flow}.approvals.{node_id}.label`, plus the scaffold that writes them: `os i18n extract --no-objects-only` — every group above but `messages` and `settingsCommon`, which the extractor never emits — (the default `--objects-only` emits only `objects` / `globalActions`; `packages/cli/src/utils/i18n-extract.ts` 63–131 lists the walk). `skills/objectstack-i18n/evals/bundles-locales-coverage.json` (55 → 75 lines; 1004 → 1451 tokens): - The `--objects-only` warning paragraph names the `dataset`, `report`, `picklist`, `position` and `permission` coverage kinds `os i18n check` reports beside the six it listed (review item 3). - Eval 6 — the card's acceptance eval: a report, a permission set, a position and a picklist; negative pins `permissionSets` and the singular `report: {` alias. - Eval 7 — the four nested keys (form `title`, section entry `placeholder`, `page:tabs` item, approval step); negative pins `stepLabel` and an object-level `_tabs.overview` key. `scripts/check-skills-token-ratchet.mjs`: the eval file's CEILINGS row 1004 → 1451 (+447, the measured size of the two prompts), with the ruling quoted on the row. Package reading (SKILL.md + `references/**/*.md` + `rules/**/*.md`): 533 → 542 lines (`references/_index.md` 30 → 30, generated, unchanged). Authored tokens 6032 → 6733. Nothing is removed: the card names no stale line, and the line that existed (the group list) is extended, not replaced. ## Why The skill's enumerated lists read as complete. The bundle gained `reports`, `positions` and `permissions` as top-level groups, the section entry overrides, the form view title, the page tab item and the approval step label, and none of them was taught — so an AI author shipping a zh-CN pack from this page leaves reports, positions and permission sets in English, and nothing reports a missing key (audit row I1–I5 of the skills gap audit; `priority:p1`, 18.2 headline rows). ### The token ceiling The eval file sat at zero headroom (1004 / 1004) under the shrink-only `check-skills-token-ratchet`. The maintainer ruled on the seat thread's decision card, question 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap label verbatim and untranslated: > 「A:先削重复,余额按实测提高」 Paid down first: the eval file carried no duplicated content to delete (five prompts, five distinct surfaces — bundle authoring, a new locale, the `translation` item, a plugin's generated bundle, the `--objects-only` diagnosis), so the remainder is the whole addition, 447 tokens, and the row is pinned at the measurement with zero headroom. SKILL.md needed no raise (1075 tokens of headroom remain). Arithmetic: evals +447 (ceiling +447), SKILL.md +254 (ceiling +0); bundle +701, ceilings +447. ## Tests No package is touched, so no package test or typecheck is owed. The gate script edited (`check-skills-token-ratchet.mjs`) carries its own `--self-test`, which ran green on the final tree; no `*.test.*` file names that script (`git grep -l check-skills-token-ratchet -- '*.test.*'` returns nothing). <details> <summary>Ratchet and self-test on 6181b12</summary> ```text node scripts/check-skills-token-ratchet.mjs :: exit 0 ✓ skills/objectstack-i18n/SKILL.md is 5282 tokens (ceiling 6333; headroom 1051) ✓ skills/objectstack-i18n/evals/bundles-locales-coverage.json is 1451 tokens (ceiling 1451; headroom 0) ✓ 81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted. node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0 ✓ check-skills-token-ratchet self-test: 79 cases pass. node -e "JSON.parse(readFileSync('skills/objectstack-i18n/evals/bundles-locales-coverage.json'))" :: JSON ok ``` </details> ## Acceptance notes - The `--objects-only` warning paragraph in the same file (L394–L398 before this PR) still lists the coverage kinds as `app`, `navigation`, `dashboard`, `widget`, `page` and `flow`; `os i18n check` also reports `report`, `picklist`, `position` and `permission` buckets (`packages/cli/src/utils/i18n-coverage.ts` 356–384, 450–454). Fixed in this PR at the review's suggestion (rework `6181b122`, inside SKILL.md's headroom; now L402–406). - The spec line references in the card body (`translation.zod.ts:602-608, 724, 1140-1143`) do not match `origin/main` at 756220c; the readings used here are `reports` 1161–1168, `positions` 1698–1705, `permissions` 1721–1728, `picklists` 902–911, `_views.title` 320, tab items 1353–1359, `approvals` 1668–1680, `_sections.fields` 476–490. - The card's「`:165` adds `fields.FIELD` (`label`, `placeholder`, `helpText`)」is right as written: under `_sections.SECTION.fields.FIELD` the canonical key is `helpText` (`help` is the alias); only the object-level `fields.FIELD` spells it `help`. No duplicate object-level `fields` row was added. - `skip-changeset`: the skills package carries no `package.json`; `scripts/**` ships in no package's `files[]`. ## Verification Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on the final tree (6181b12), every command run with its exit code captured before any pipe, reconciled with `--ran`. <details> <summary>Reconciliation: 52 derived, 51 green, 1 NOT MEASURED (exit 3)</summary> ```text ✓ dispatch-gates --ran: 52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3). ``` Green (exit 0): check-ci-filter-parity, check-closing-keyword-parity (+ self-test), check-comment-mask-corpus, check-declaration-mirrors (+ self-test), check-doc-route-spelling --advisory (+ self-test), check-dts-emitted --self-test, check-scripts-symbol-anchors (+ self-test), check-self-test-wired (+ self-test), check-self-test-workflow-commands (+ self-test), check-skills-token-ratchet (+ self-test), check-whole-set-label-write (+ self-test), ci/assert-turbo-remote-cache --self-test, ci/prune-turbo-cache --self-test, pm/bare-root-worklist --self-test, spec check:skill-docs, check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:corpus-claim-drift, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:entry-guard, check:gitlink-declared, check:merge-driver, check:nul-bytes, check:parse-guard, check:pm-dispatch-gates, check:pm-governed-merges, check:pm-skill-id-lint, check:pnpm-filter-targets, check:pnpm-flag-spellings, check:ratchet-remedy-authority, check:refd-timer-probe, check:role-word, check:skill-compatibility, check:skill-frame-sync, check:skill-identifier-liveness, check:watch-hint-literal, lint check:doc-formula-expressions, check:dts-closure, check:lean-entry-closure, check:sourcemap-no-sources-content (the four after building the closures they read, under the shared verify lock). NOT MEASURED locally (exit 3, `PREREQUISITE NOT MET`: it reads every workspace package's built `dist`, which a fresh worktree does not have; this diff touches no package, so no whole-tree build is owed and CI runs it on a built tree): check:dual-build-cjs-loads. `check:skill-refs` is not in the derived set: `references/_index.md` is generated from the SKILL.md frontmatter and the schema exports, neither of which moved, and the file is byte-identical to `origin/main`. </details> ## Human Minimum Each item is one rule from [CONTRIBUTING.md › Human Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum), which links the AGENTS.md text that states it. - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (No schema or runtime file is touched.) - [x] 2. Every name I added uses the casing for its kind. (Only eval fixture names, all `snake_case` machine names.) - [ ] 3. No formatter ran (none did); `pnpm lint` is the repo-level scan CI's `Lint & Repo Gates` owns — not run locally. - [x] 4. One issue, one branch off an up-to-date `main` (756220c), one pull request. - [x] 5. A change that publishes carries a changeset — this one publishes nothing (`skip-changeset`); both declaration lines are filled in above. - [x] 6. `packages/spec` touched: no. - [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 维护者速读 brief is Chinese by the repo's communication rule for maintainer-facing summaries). - [ ] The *Before Submitting* list in CONTRIBUTING.md is done. (Every item holds except `pnpm lint`, which is not run locally; the change reaches no package test, updates the documentation it is about, publishes nothing, carries nothing unrelated, and touches no auth surface.) --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxbXbFVr7jEKWyQKnPm9)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 591a796 commit 5507225

3 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,17 @@ export const CEILINGS = new Map([
713713
['skills/objectstack-data/evals/hooks-security-seeds-datasources.json', 1095],
714714
['skills/objectstack-data/evals/objects-fields-relationships.json', 946],
715715
['skills/objectstack-formula/evals/cel-predicates-formulas.json', 1046],
716-
['skills/objectstack-i18n/evals/bundles-locales-coverage.json', 1004],
716+
// 1004 -> 1451 (2026-10-27, the objectstack-i18n fix card of the skills gap
717+
// audit): two eval prompts for the teaching that card adds — the `reports`,
718+
// `positions`, `permissions` and `picklists` groups; a form view `title`, a
719+
// section entry's own copy, a `page:tabs` item, an approval step. Maintainer
720+
// ruling on the seat thread's decision card 「skills 修复卡的教学和 eval 都撞
721+
// token 天花板,怎么付?」, tap label verbatim and untranslated:
722+
// 「A:先削重复,余额按实测提高」. PAID DOWN FIRST: the file carried no duplicated
723+
// content to delete (five prompts, five distinct surfaces), so the remainder
724+
// is the whole addition; pinned AT the measurement, zero headroom. The
725+
// raising PR's body carries the same arithmetic.
726+
['skills/objectstack-i18n/evals/bundles-locales-coverage.json', 1451],
717727
['skills/objectstack-platform/evals/config-plugins-ops.json', 1211],
718728
['skills/objectstack-query/evals/filters-pagination-search.json', 955],
719729
// 1505 -> 1789 (#26295): two eval prompts — list grouping with `orderBy`, and the copy hole

‎skills/objectstack-i18n/SKILL.md‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,24 @@ All translatable content for a single object is aggregated under
160160
|:--------|:------|
161161
| `label` / `pluralLabel` / `description` | Object-level text (every key optional) |
162162
| `fields.{field_name}` | `label`, `help`, `placeholder`, `options` (option value → label) per field |
163-
| `_views.{view_name}` | `label`, `description`, `emptyState.title` / `emptyState.message`, `bulkActions.{def_name}` (`label`, `confirmText`, `confirmLabel`, `params`) |
163+
| `_views.{view_name}` | `label`, `description`, `title` (a form view's own heading — only a form that authors one is translated; a list view has none), `emptyState.title` / `emptyState.message`, `bulkActions.{def_name}` (`label`, `confirmText`, `confirmLabel`, `params`) |
164164
| `_actions.{action_name}` | `label`, `description`, `confirmText`, `successMessage`, `params.{param_name}` (and `.options.{value}` beneath it — an option's stored `value` in that param's inline `options`), `outcomeMessages.{outcome}` (an outcome the action's own `outcomeMessages` declares), `resultDialog.title` / `.description` / `.acknowledge` (only under an action that declares a `resultDialog`), `resultDialog.fields.{path}` (the literal `path` of a field in `resultDialog.fields[]`, dots included — `client.secret` is one key); the same keys hold under `globalActions.{action_name}` |
165-
| `_sections.{section_name}` | Form section `label`, `description` |
165+
| `_sections.{section_name}` | Form section `label`, `description`; `fields.{field_name}` — the `label`, `placeholder`, `helpText` an entry overrides for that form alone (`helpText` here, `help` under `fields.{field_name}` above: each spells what its source does) |
166166
| `_tabs.{tab_name}` | Filter-preset tab `label` (`ViewTabSchema.name`) |
167167
| `_validations.{rule_name}` | `message` only — the sentence a rejected write returns (replaces the retired `validationMessages`) |
168168

169169
Top-level groups alongside `objects`: `apps` (label, description, navigation),
170170
`messages`, `globalActions` (actions with no `objectName` — a key naming a bound action is
171171
an **error** whose message names the `objects.{owner}._actions.{action_name}` key to write
172-
instead), `dashboards`, `datasets`, `pages`, `flows`, `metadataForms`, `settingsCommon`.
172+
instead), `picklists` (`label`, `options` by stored value — every field referencing the
173+
picklist inherits them), `dashboards`, `datasets`, `reports`, `pages`, `flows`, `positions`,
174+
`permissions` (permission sets, spelled as the stack's `permissions[]`), `metadataForms`,
175+
`settingsCommon`; `reports`, `positions` and `permissions` hold `label` + `description` by name.
176+
Two keys sit deeper than their group's `label`: a `page:tabs` item at
177+
`pages.{page}.components.{tabs_id}.items.{value}.label` (by the item's `value`; an item without
178+
one has no key) and an approval step at `flows.{flow}.approvals.{node_id}.label`.
179+
`os i18n extract --no-objects-only` scaffolds every group above but `messages` and
180+
`settingsCommon`, both keys included.
173181
`settings` is **platform-only** — it is keyed by a settings manifest's namespace and only
174182
the platform declares one, so an app bundle carrying it is refused by name.
175183

@@ -393,7 +401,8 @@ the default locale is filled from schema labels, other locales follow `--fill`
393401

394402
⚠️ **`--objects-only` is on by DEFAULT**, so extract writes only the `objects` /
395403
`globalActions` subtree — while `os i18n check` also demands `app`, `navigation`,
396-
`dashboard`, `widget`, `page` and `flow` keys. Run the workflow as written and coverage
404+
`dashboard`, `widget`, `dataset`, `report`, `picklist`, `page`, `flow`, `position` and
405+
`permission` keys. Run the workflow as written and coverage
397406
reports gaps the extract never scaffolded. Pass `--no-objects-only` to include those
398407
groups, or author them by hand.
399408

‎skills/objectstack-i18n/evals/bundles-locales-coverage.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@
5050
"must_contain": ["--no-objects-only", "os i18n check", "apps", "--strict"],
5151
"must_not_contain": ["messageFormat"]
5252
}
53+
},
54+
{
55+
"id": 6,
56+
"prompt": "Add zh-CN for the `pipeline_by_stage` report, the `sales_manager` permission set, the `regional_lead` position and the `deal_stage` picklist's option labels.",
57+
"expected_output": "Top-level groups keyed by name: `reports.pipeline_by_stage` and `positions.regional_lead` (`label`, `description`), `permissions.sales_manager` (the same, spelled as the stack's `permissions[]`, never `permissionSets`), `picklists.deal_stage.options` by stored value, inherited by every field referencing the picklist. `os i18n extract --no-objects-only` scaffolds all four.",
58+
"files": [],
59+
"assertions": {
60+
"must_contain": ["reports", "permissions", "positions", "picklists", "--no-objects-only"],
61+
"must_not_contain": ["permissionSets", "report: {"]
62+
}
63+
},
64+
{
65+
"id": 7,
66+
"prompt": "In zh-CN the `contact` quick-create form still shows its English heading and the placeholder its `contact_details` section sets on `email`; the `home` page's `overview` tab and the `manager_review` step of the `expense_claim` flow are English too.",
67+
"expected_output": "Each key spelled as its source: `objects.contact._views.quick_create.title` (a form's own heading), `objects.contact._sections.contact_details.fields.email.placeholder` (that form's override; `helpText` there, `help` at `fields.email`), `pages.home.components.{tabs_id}.items.overview.label` (by the item's `value`), `flows.expense_claim.approvals.manager_review.label` (the node id). Not a view `label`, an object `_tabs` key or a screen `title`.",
68+
"files": [],
69+
"assertions": {
70+
"must_contain": ["_views", "title", "_sections", "fields", "items", "approvals"],
71+
"must_not_contain": ["stepLabel", "_tabs.overview"]
72+
}
5373
}
5474
]
5575
}

0 commit comments

Comments
 (0)