Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/26820-i18n-skill-books-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/skills': patch
---

docs(skills): objectstack-i18n teaches the `books` translation group, with an eval for a two-language doc book

Clause-②: no
Metadata-protocol: none

- **Before.** The top-level group list in `skills/objectstack-i18n/SKILL.md` named fourteen of the bundle's fifteen groups and left out `books`, the group the spec added so a doc book's title and section titles follow the reader's locale. An agent writing a zh-CN bundle from the skill left the docs sidebar in one language or invented a key under `pages`.
- **Now.** The list names `books` with its three keys — `books.{book}.label` / `.description` by `Book.name` and `books.{book}.groups.{group_key}.label` by each group's stable `key` — says the pages themselves localize through `doc.translations` and that a book's inline `translations` map stays retired; `os i18n extract --no-objects-only` seeds the group like every other. Eval 9 in `evals/bundles-locales-coverage.json` checks that an answer for a two-language manual spells the book label and a group label by key and never a `pages.*` key. Five eval sentences that restated a rule the skill already teaches were cut to pay for it.
- No schema, key or behaviour changes.
20 changes: 19 additions & 1 deletion scripts/check-skills-token-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,25 @@ export const CEILINGS = new Map([
// pin duplicated case 5's (77 bytes cut, 20 tokens); the remainder is the new
// case; pinned AT the measurement, zero headroom. The raising PR's body carries
// the same arithmetic.
['skills/objectstack-i18n/evals/bundles-locales-coverage.json', 1642],
// 1642 -> 1790 (2026-10-28, the owed Skills-docs follow-up of the spec PR that
// added the `books` translation group): one eval prompt for a doc book's
// spine copy — `books.<book>.label` / `.description` and
// `books.<book>.groups.<key>.label`. Same ruling as the rows above, tap label
// verbatim and untranslated: 「A:先削重复,余额按实测提高」. PAID DOWN FIRST: five
// sentences restated a rule whose home the reader already reads — case 1's
// "no ICU plural/select" (SKILL.md "No ICU MessageFormat" + its own
// `{count, plural` pin), case 3's "retired o.* dialect" (SKILL.md "The
// Retired o.* Shape" + its own `o: {` pin), case 5's "extract --check is the
// freshness gate" (SKILL.md "os i18n extract --check — freshness, not
// coverage"), case 6's "inherited by every field referencing the picklist"
// (SKILL.md's `picklists` entry in the top-level group list), case 8's
// "neither is a pages.*.components key" (its own `components.open_block`
// pin): 227 bytes = 57 units (6567 -> 6340 bytes, 1642 -> 1585); the new case
// adds 817 bytes = 205 units (6340 -> 7157 bytes, 1585 -> 1790):
// ceil($(wc -c < skills/objectstack-i18n/evals/bundles-locales-coverage.json) / 4) == ceil(7157 / 4)
// pinned AT the measurement, zero headroom. The raising PR's body carries the
// same arithmetic.
['skills/objectstack-i18n/evals/bundles-locales-coverage.json', 1790],
// 1211 -> 1458 (card #26302): one eval for a demo whose seed names people —
// expected: both in `demoStaff` with a declared `position` /
// `permissionSets`, created by `os dev`; NO `role:` alias and NO platform
Expand Down
11 changes: 7 additions & 4 deletions skills/objectstack-i18n/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,13 @@ Top-level groups alongside `objects`: `apps` (label, description, navigation),
an **error** whose message names the `objects.{owner}._actions.{action_name}` key to write
instead), `picklists` (`label`, `options` by stored value — every field referencing the
picklist inherits them), `dashboards`, `datasets`, `reports`, `pages`, `flows`, `positions`,
`permissions` (permission sets, spelled as the stack's `permissions[]`), `metadataForms`,
`settingsCommon`; `reports`, `positions` and `permissions` hold `label` + `description` by name,
and a joined report's block headings sit at `reports.{report}.blocks.{block_name}.{label,description}`,
keyed by the block's required `name`.
`permissions` (permission sets, spelled as the stack's `permissions[]`), `books` (a doc book's
spine, by `Book.name`: `books.{book}.label` / `.description`, and `groups.{group_key}.label` by
each group's stable `key`, never its position — `books.crm_manual.groups.getting_started.label`;
the pages themselves localize through `doc.translations`, and the inline `translations` map a
book once carried stays retired), `metadataForms`, `settingsCommon`; `reports`, `positions` and
`permissions` hold `label` + `description` by name, and a joined report's block headings sit at
`reports.{report}.blocks.{block_name}.{label,description}`, keyed by the block's required `name`.
Two keys sit deeper than their group's `label`: a `page:tabs` item at
`pages.{page}.components.{tabs_id}.items.{value}.label` (by the item's `value`; an item without
one has no key) and an approval step at `flows.{flow}.approvals.{node_id}.label`.
Expand Down
20 changes: 15 additions & 5 deletions skills/objectstack-i18n/evals/bundles-locales-coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"id": 1,
"prompt": "Add Simplified Chinese for the `support_case` object: its label and plural, the `status` field with options New / Open / Closed, the `close` action, and a welcome message that greets the user by name.",
"expected_output": "A `TranslationData` per locale under `objects.support_case` — `label`, `pluralLabel`, `fields.status.label`, `fields.status.options` keyed by the STORED option value (`new`, `open`, `closed`, never the display label), `_actions.close.label` — assembled with `defineTranslationBundle({ en, 'zh-CN': zhCN })` and registered under `defineStack({ translations })`. The message uses double-brace interpolation, `'welcome.user': '欢迎,{{userName}}!'`; there is no ICU plural/select.",
"expected_output": "A `TranslationData` per locale under `objects.support_case` — `label`, `pluralLabel`, `fields.status.label`, `fields.status.options` keyed by the STORED option value (`new`, `open`, `closed`, never the display label), `_actions.close.label` — assembled with `defineTranslationBundle({ en, 'zh-CN': zhCN })` and registered under `defineStack({ translations })`. The message uses double-brace interpolation, `'welcome.user': '欢迎,{{userName}}!'`.",
"files": [],
"assertions": {
"must_contain": ["defineTranslationBundle", "objects", "pluralLabel", "options", "_actions", "{{"],
Expand All @@ -24,7 +24,7 @@
{
"id": 3,
"prompt": "A customer admin wants to relabel `account` as 客户 in their Studio without waiting for a redeploy.",
"expected_output": "A `translation` metadata item (author with `defineTranslation`, shape `TranslationItemSchema`) carrying `locale: 'zh-CN'` and the same `objects.account.label` group as a file bundle; published items layer over the shipped bundles at boot and on every publish, and deleting the item restores the shipped value. One locale per item; the retired `o.*` dialect is rejected on this door too.",
"expected_output": "A `translation` metadata item (author with `defineTranslation`, shape `TranslationItemSchema`) carrying `locale: 'zh-CN'` and the same `objects.account.label` group as a file bundle; published items layer over the shipped bundles at boot and on every publish, and deleting the item restores the shipped value. One locale per item.",
"files": [],
"assertions": {
"must_contain": ["translation", "locale", "objects", "defineTranslation"],
Expand All @@ -44,7 +44,7 @@
{
"id": 5,
"prompt": "`os i18n check` reports zh-CN is missing app, navigation and dashboard keys even though we just ran `os i18n extract`. What is wrong?",
"expected_output": "Diagnoses that `--objects-only` is on by default, so extract wrote only the `objects` / `globalActions` subtree while check also demands `apps`, `navigation`, `dashboard`, `widget`, `page` and `flow` keys. Re-runs `os i18n extract --no-objects-only` (or hand-authors those groups), re-checks with `os i18n check --locales=zh-CN`, and adds `--strict` / `--threshold` in CI; `extract --check` is the separate freshness gate.",
"expected_output": "Diagnoses that `--objects-only` is on by default, so extract wrote only the `objects` / `globalActions` subtree while check also demands `apps`, `navigation`, `dashboard`, `widget`, `page` and `flow` keys. Re-runs `os i18n extract --no-objects-only` (or hand-authors those groups), re-checks with `os i18n check --locales=zh-CN`, and adds `--strict` / `--threshold` in CI.",
"files": [],
"assertions": {
"must_contain": ["--no-objects-only", "os i18n check", "apps", "--strict"],
Expand All @@ -54,7 +54,7 @@
{
"id": 6,
"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.",
"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[]`, not the camelCase name), `picklists.deal_stage.options` by stored value, inherited by every field referencing the picklist.",
"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[]`, not the camelCase name), `picklists.deal_stage.options` by stored value.",
"files": [],
"assertions": {
"must_contain": ["reports", "permissions", "positions", "picklists"],
Expand All @@ -74,12 +74,22 @@
{
"id": 8,
"prompt": "In zh-CN the `Products` grid heading on the `crm_opportunity` form and the `Open Tasks` block heading of the joined `showcase_task_overview` report are still English.",
"expected_output": "`objects.crm_opportunity_line_item.fields.opportunity.inlineTitle` — the field lives on the child object, so the key is the child's, and an untranslated `inlineTitle` keeps its authored text (no fallback to the child's plural label); `reports.showcase_task_overview.blocks.open_block.label`, keyed by the block's required `name`. Neither is a `pages.*.components` key.",
"expected_output": "`objects.crm_opportunity_line_item.fields.opportunity.inlineTitle` — the field lives on the child object, so the key is the child's, and an untranslated `inlineTitle` keeps its authored text (no fallback to the child's plural label); `reports.showcase_task_overview.blocks.open_block.label`, keyed by the block's required `name`.",
"files": [],
"assertions": {
"must_contain": ["inlineTitle", "crm_opportunity_line_item", "blocks", "open_block"],
"must_not_contain": ["fields.opportunity_line_item", "components.open_block"]
}
},
{
"id": 9,
"prompt": "Our `crm_manual` doc book ships in English and zh-CN, but the docs sidebar still shows the book title and its `getting_started` section title in English.",
"expected_output": "The `books` group, keyed by `Book.name`: `books.crm_manual.label` / `.description` and `books.crm_manual.groups.getting_started.label`, by the group's stable `key` (never its position); `os i18n extract --no-objects-only` seeds all three. The pages' own titles and bodies localize through `doc.translations`, and the inline `translations` map a book once carried stays retired.",
"files": [],
"assertions": {
"must_contain": ["books", "groups", "getting_started", "doc.translations"],
"must_not_contain": ["pages.crm_manual", "sections.getting_started"]
}
}
]
}
Loading