|
103 | 103 | * pages.<page>.label / .description |
104 | 104 | * pages.<page>.title / .subtitle (from the page's `page:header` component) |
105 | 105 | * pages.<page>.components.<id>.<key> (per-component copy, #6080) |
| 106 | + * pages.<page>.components.<tabsId>.items.<value>.label (a `page:tabs` item label) |
106 | 107 | * flows.<flow>.label |
107 | 108 | * flows.<flow>.screens.<node_id>.title (#7646 / #11287) |
108 | 109 | * flows.<flow>.screens.<node_id>.description (#22507) |
@@ -137,7 +138,9 @@ import { |
137 | 138 | FLOW_SCREEN_COPY_KEYS, |
138 | 139 | FLOW_SCREEN_FIELD_COPY_KEYS, |
139 | 140 | FLOW_TERMINAL_MESSAGE_KEYS, |
| 141 | + PAGE_TABS_COMPONENT_TYPE, |
140 | 142 | globalFilterKey, |
| 143 | + pageTabItemKey, |
141 | 144 | walkAddressedPageComponents, |
142 | 145 | } from '@objectstack/spec/system'; |
143 | 146 | import { FLOW_REGION_SLOTS_BY_TYPE, flowScreenFieldOptionKey } from '@objectstack/spec/automation'; |
@@ -421,6 +424,39 @@ function pushViewEntries(out: ExpectedEntry[], objectName: string, viewName: str |
421 | 424 | pushBulkActionDefs(out, root, view, objectName); |
422 | 425 | } |
423 | 426 |
|
| 427 | +/** |
| 428 | + * Emit `_views.<view>.title` for every FORM view a view container serves — |
| 429 | + * each `formViews.<key>` entry and the default `form` — the key |
| 430 | + * `translateView` overlays a form's `title` (`FormViewSchema.title`) from. |
| 431 | + * |
| 432 | + * **The key is the composer's, asked, never re-derived.** A container form is |
| 433 | + * served as the item `expandViewContainer` expands it to, and `translateView` |
| 434 | + * keys its title by that item's registry name with the object prefix |
| 435 | + * stripped: `formViews.<key>` is `<key>` (renamed on a collision, e.g. |
| 436 | + * `default_2` beside a default list), the default `form` is its `name` or |
| 437 | + * `form`. Reading the expanded items is the same discipline |
| 438 | + * {@link defaultListViewKey} keeps for the default list, so the key written |
| 439 | + * here is the key the runtime reads. |
| 440 | + * |
| 441 | + * **Title only.** The other `_views` tails stay unemitted for a form: a |
| 442 | + * container's form entry carries no `label`, and its `description` is not |
| 443 | + * overlaid by anything a form renders. A form that authors no title still |
| 444 | + * records the key (`pushOptional`), so the coverage gate notices a bundle |
| 445 | + * that translates one the source never wrote. |
| 446 | + */ |
| 447 | +function pushContainerFormTitles(out: ExpectedEntry[], containerObject: string, container: any): void { |
| 448 | + if (!container?.form && !container?.formViews) return; |
| 449 | + const prefix = `${containerObject}.`; |
| 450 | + for (const item of expandViewContainer(containerObject, container)) { |
| 451 | + if (item.viewKind !== 'form') continue; |
| 452 | + const key = item.name.startsWith(prefix) ? item.name.slice(prefix.length) : item.name; |
| 453 | + const config = item.config as { title?: unknown } | undefined; |
| 454 | + pushOptional(out, ['objects', containerObject, '_views', key, 'title'], config?.title, 'view', { |
| 455 | + objectName: containerObject, |
| 456 | + }); |
| 457 | + } |
| 458 | +} |
| 459 | + |
424 | 460 | /** |
425 | 461 | * Emit `_views.<view>.bulkActions.<def>.*` for a list view's authored |
426 | 462 | * `bulkActionDefs[]` (#14253's resolver, #14376's walk). |
@@ -1169,6 +1205,22 @@ function emitPageComponentCopy(out: ExpectedEntry[], page: any, name: string): v |
1169 | 1205 | // records nothing (#14749). |
1170 | 1206 | pushEntry(out, ['pages', name, 'components', id as string, key], value, 'page'); |
1171 | 1207 | } |
| 1208 | + // A `page:tabs` component's tab labels, keyed by each item's `value` |
| 1209 | + // under the tabs component's id — the key `translatePage` reads. An |
| 1210 | + // item without a `value` gets no slot: its renderer token is |
| 1211 | + // index-derived, and an array index is never a bundle key (#14749). |
| 1212 | + if (component.type === PAGE_TABS_COMPONENT_TYPE && Array.isArray(props.items)) { |
| 1213 | + for (const item of props.items as unknown[]) { |
| 1214 | + const value = pageTabItemKey(item); |
| 1215 | + if (value === undefined) continue; |
| 1216 | + pushEntry( |
| 1217 | + out, |
| 1218 | + ['pages', name, 'components', id as string, 'items', value, 'label'], |
| 1219 | + (item as Record<string, unknown>).label, |
| 1220 | + 'page', |
| 1221 | + ); |
| 1222 | + } |
| 1223 | + } |
1172 | 1224 | } |
1173 | 1225 | return component; |
1174 | 1226 | }); |
@@ -1315,19 +1367,29 @@ export function collectExpectedEntries( |
1315 | 1367 | // `i18n/missing-view` had zero producers repo-wide while the ratchet |
1316 | 1368 | // reported green (#4123). |
1317 | 1369 | // |
1318 | | - // `formViews` stays uncovered: form views have no counterpart in the |
1319 | | - // `viewLabel` / `_views.*` resolver convention, so emitting keys for them |
1320 | | - // would expect translations nothing reads. |
| 1370 | + // A container's form views (`formViews.*`, the default `form`) emit one |
| 1371 | + // `_views` tail, `title` — the form heading `translateView` overlays (see |
| 1372 | + // `pushContainerFormTitles`). Their `label` / `description` stay |
| 1373 | + // unemitted: a container form entry carries no label, and nothing a form |
| 1374 | + // renders reads a `_views` description. |
1321 | 1375 | const views: any[] = Array.isArray(config?.views) ? config.views : []; |
1322 | 1376 | for (const view of views) { |
1323 | 1377 | const containerObject = viewObjectName(view); |
1324 | 1378 | if (!containerObject) continue; |
1325 | 1379 |
|
1326 | 1380 | if (view.name) { |
1327 | 1381 | pushViewEntries(out, containerObject, view.name, view); |
| 1382 | + // A `viewKind: 'form'` item carries its form under `config`, title included. |
| 1383 | + if (view.viewKind === 'form' && view.config && typeof view.config === 'object') { |
| 1384 | + pushOptional(out, ['objects', containerObject, '_views', view.name, 'title'], view.config.title, 'view', { |
| 1385 | + objectName: containerObject, |
| 1386 | + }); |
| 1387 | + } |
1328 | 1388 | continue; |
1329 | 1389 | } |
1330 | 1390 |
|
| 1391 | + pushContainerFormTitles(out, containerObject, view); |
| 1392 | + |
1331 | 1393 | // The container's default list, keyed exactly as the runtime registry keys |
1332 | 1394 | // it — see `defaultListViewKey`. `undefined` means the container declares |
1333 | 1395 | // no default list; a key the container's own `listViews` also declares |
|
0 commit comments