Skip to content

Commit f18a2f5

Browse files
committed
fix(spec): drop the tabs alias on a page component translation entry; the docs table names the tab-item key
The alias put a `tabs` key beside `emptyState` in one object literal, which the list-view tabs retirement's tree-scoped absence pin reads as a list-view payload carrying `tabs` (translation.zod.ts:1191). The alias was not part of the card's ask, so it goes rather than widening the pin's exclusions. content/docs/ui/translations.mdx gains the pages.NAME.components.TABS.items.VALUE.label row. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
1 parent 7e29242 commit f18a2f5

4 files changed

Lines changed: 3 additions & 8 deletions

File tree

‎.changeset/24968-page-tabs-item-label-key.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ feat(spec): a `page:tabs` item label gains a translation key, `pages.<page>.comp
99
Clause-②: yes (widening)
1010

1111
- **Before.** `pages.<page>.components` is keyed by component id, and a `page:tabs` item is not a component and carries no id, so a tab label had no key. `items` was refused as an unknown key, and `translatePage` never read `items[].label`. A localized page kept its tab strip in the authoring language.
12-
- **Now.** `pages.<page>.components.<tabsId>.items.<value>.label` is a declared key. It is addressed by the tabs component's own `id` and the item's `value` (the stable `?tab=` token), and `translatePage` overlays it onto that item's `label`, across the locale chain. The entry is closed: `title`, `name` and `text` are renames onto `label`, and `tabs` on a component entry is the rename onto `items`. An item without a `value` has no key, because its renderer token is index-derived and an array index is never a bundle key. Author a `value` on the item, or keep localizing that label with an inline locale map.
12+
- **Now.** `pages.<page>.components.<tabsId>.items.<value>.label` is a declared key. It is addressed by the tabs component's own `id` and the item's `value` (the stable `?tab=` token), and `translatePage` overlays it onto that item's `label`, across the locale chain. The entry is closed: `title`, `name` and `text` are renames onto `label`. An item without a `value` has no key, because its renderer token is index-derived and an array index is never a bundle key. Author a `value` on the item, or keep localizing that label with an inline locale map.
1313
- **`walkAddressedPageComponents`.** A visitor's edit to a panel's own keys (its `label`) now stands when the visitor keeps the panel list's authored shape. Panel children are still descended from the authored document.
1414
- **`objectstack validate`.** `@objectstack/lint`'s translation-reference check accepts the key over a declared `page:tabs` item `value`. It refuses it as `translation-target-unknown` over an unknown value (with "Did you mean"), over a component of another type (a `page:accordion` is pointed at the inline locale map, since its panels have no stable key), and under a component id the page does not declare.
1515
- **`os i18n extract` / coverage.** The extractor writes a slot for every valued tab item, and the coverage check demands it. An inline locale map on the item label is counted as authored, as before.

‎content/docs/ui/translations.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export default defineStack({
8080
| Dataset dimension and measure labels | `datasets.<name>.dimensions.<dimension>.label` / `datasets.<name>.measures.<measure>.label` |
8181
| Page labels and `page:header` copy | `pages.<name>.label` / `description` / `title` / `subtitle` — on a `kind: 'slotted'` page the header under `slots.header` is the page's header |
8282
| Page component copy, by component id | `pages.<name>.components.<id>.title` / `description` / `label` / `placeholder` / `emptyText` — reached under `regions[].components[]` and `slots.<slot>`, through `properties.children` and a `page:tabs` / `page:accordion` panel's `items[].children` |
83+
| Page tab labels (`page:tabs` items) | `pages.<name>.components.<tabsId>.items.<value>.label` — keyed by the tabs component's `id` and the item's `value`; an item without a `value` has no key (localize it with an inline locale map), and `page:accordion` panels have none |
8384
| Screen-flow wizards (flow label, screen headings and body text, screen field copy) | `flows.<flow>.label` / `flows.<flow>.screens.<node_id>.title` / `.description` / `.fields.<field>.label` / `.placeholder` / `.inlineHelpText` — a screen field's help text is `inlineHelpText`, the key the field itself declares, not `help`; see the boundary note below |
8485
| Global actions, messages | `globalActions`, `messages` |
8586
| Settings UI shell copy (the source badge on a settings row) | `settingsCommon.sourceLabels.<layer>` — the per-namespace settings copy under `settings` is **platform-only**: an app bundle carrying it is refused by name, and the platform's own strings are translated in `@objectstack/service-settings`'s bundle |

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,12 +1007,6 @@ describe('translation unknown-key strictness', () => {
10071007
expect(result.error?.issues.find((i) => i.code === 'unrecognized_keys')?.message).toContain('`title` → `label`');
10081008
});
10091009

1010-
it('names `tabs` on a component entry as the rename onto `items`', () => {
1011-
const result = parse({ home_tabs: { tabs: { my_leads: { label: 'My Leads' } } } });
1012-
expect(result.success).toBe(false);
1013-
expect(result.error?.issues.find((i) => i.code === 'unrecognized_keys')?.message).toContain('`tabs` → `items`');
1014-
});
1015-
10161010
it('carries the `label`/`title` trap alias the dashboard widget face carries', () => {
10171011
// A page's headline is `label`; a component's is `title`. One level
10181012
// apart, opposite spellings — the same trap `dashboards.widgets` names.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1188,7 +1188,7 @@ const appTranslationDataShape = () => ({
11881188
// A page component's headline is `title`; the PAGE's is `label`. Same
11891189
// document one level apart with opposite spellings — the same trap
11901190
// `dashboards.widgets` names, so it gets the same alias table.
1191-
aliases: { name: 'title', heading: 'title', text: 'label', caption: 'description', help: 'description', helpText: 'description', empty: 'emptyText', emptyState: 'emptyText', tabs: 'items' },
1191+
aliases: { name: 'title', heading: 'title', text: 'label', caption: 'description', help: 'description', helpText: 'description', empty: 'emptyText', emptyState: 'emptyText' },
11921192
guidance: {
11931193
submitLabel:
11941194
'`submitLabel` was removed in @objectstack/spec 17 (ADR-0049) — its only '

0 commit comments

Comments
 (0)