Skip to content

Commit dd5e355

Browse files
feat(spec,cli): reports are translatable — reports in the translation bundle (#25826)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSi3q9Cxq5D2UNgBbGKBFEtdp)_ Fixes #25788 Clause-②: yes Metadata-protocol: added Ruling-ref: none; triage first grade 6397355491 (a protocol addition under the standing order, p1) _Body refreshed 2026-10-26T02:56Z, after the `ui/` merge (#25825): the console half now ships in this PR._ ## What A translation bundle can now carry reports: ```ts reports: { opps_by_stage: { label: '各阶段商机', description: '按阶段汇总的商机' } }, ``` Before, an app's reports listed in the authoring language on the console's report index and report page under any locale: the bundle had no key for a report (`reports` was a retired object-first dialect key, so `os validate` refused it), and `os i18n check` reported 100 % with zero report keys (HotCRM, `✓ All 1691 translation keys covered`). - **`@objectstack/spec` (`system/translation.zod.ts`):** a `reports` group on the shared per-app shape, so the bundle entry (`TranslationData`), the registered `translation` item and the platform bundle all accept it. Keyed by the report's `name` and closed at `label` / `description` (strict: `name` / `title` are refused with a message pointing at `label`, and the singular `report` with a message pointing at `reports`). - **`@objectstack/spec` (`system/i18n-resolver.ts`):** `translateReport`, registered in `METADATA_DOCUMENT_TRANSLATORS`. `TRANSLATABLE_METADATA_TYPES` is derived from that table and `@objectstack/rest` reads it, so `GET /meta/report` (list and item) is served in the request's locale. A key the bundle does not carry leaves the authored string, and an untouched document keeps its reference. - **`@objectstack/metadata-protocol` + `@objectstack/rest`:** a report is overlay-editable (ADR-0126 Regime O), so like `dashboard` and `view` its translator honours the packaged base (ADR-0029 D9.2a): `getPackagedReportBase(name)` returns the shipped document under any overlay, and `PACKAGED_BASE_ACCESSORS` hands it to the translator. A label an org re-authored is served as written; the strings it did not touch are still translated. - **`@objectstack/spec` (liveness ledger):** `translation/reports` added as `live`, with drilled `label` / `description` children, seeded with its reader. - **`@objectstack/cli`:** `os i18n extract` walks `reports[]` (`walkReports`), and `os i18n check` / `os lint` coverage report a gap in its own bucket (`i18n/missing-report`). Every key is recorded with `pushOptional`, so a description nobody wrote is never demanded. - **`@objectstack/lint`:** `translation-target-unknown` resolves each `reports.NAME` key against the stack's `reports[]` names, sibling packages' declarations included; an unknown name is an `error` with the nearest declared name. `os explain translation-target-unknown` names the leg. - **`scripts/fixtures/i18n-walk-parity/every-group.stack.json`:** authors one report, so `check:i18n-walk-parity` sees the group walked. - **`packages/runtime` meta-list projection census:** `report` / `reports` joined both axes; the reference-moves blocks pin the zh-CN label on both reads. - **`examples/app-todo` (zh-CN, ja-JP), `examples/app-showcase` (zh-CN):** translations for their 5 + 4 reports. The new walk counts them; deleting one todo key grows `check:i18n-coverage` 106 → 108, so the fix was to translate them, not to raise the ratchet. - **`ui/` (`@object-ui/app-shell`, `@object-ui/i18n`):** the translation designer's preview draws the `reports` group as a "Reports" / 「报表」 category beside the others, and the console's translation loader recognises a payload that carries only `reports`. - **Docs:** `protocol/kernel/i18n-standard.mdx` (group list, count, leg table) and `ui/translations.mdx`. ## Acceptance notes - **The `LEGACY_OBJECT_FIRST_KEYS` collision (the order's PM assumption), decided:** `reports` leaves the list. `authored-translation-sync` skips every stored translation row that carries one of those keys as a retired dialect, so a declared group still on the list would parse in a file and never load from a Studio-authored row. The legacy object-first dialect never used `reports` (zero footprint, #3778), so nothing loses its retired-key diagnostic. Its `TRANSLATION_KEY_GUIDANCE` entry goes with it. A test pins the removal. - **Deviation from the precedent's shape (and the reason):** #25701 touched spec, cli and lint only, because positions and permission sets are not overlay-editable. A report is (ADR-0126 Regime O), so a translator that ignored the packaged base would overwrite an org's re-authored label with the catalog string, the #20680 defect class. Hence the two extra packages (`metadata-protocol`, one accessor; `rest`, one table row), inside the claim's "runtime meta translation path". - **The console half ships in this PR (#25859, transferred here from objectui#13543).** Since #25825 moved objectui into this repo as `ui/`, `Type Check UI` and `Test UI` build against this tree's `packages/spec`, so the console's exhaustive copies of the group list have to move with the group or this PR is red. The copies are: - `TranslationPreview.tsx`'s `CATEGORY_DEFS` (`satisfies Record<keyof TranslationDataSchema['shape'], …>`), with its en / zh label keys in `metadata-admin/i18n.ts`; - `spec-translations.ts`'s `SPEC_TRANSLATION_GROUPS`, pinned by `loadLanguage.test.tsx`; - the two hand-pinned tests: `TranslationPreview.categories-11765` (`ONE_ENTRY`) and `adminPreviews.i18n-10862-s2` (its key list, and the `2/13` denominator becoming `2/14`). The report surfaces themselves (the report index `appReportIndex.ts`, the page heading `ReportView.tsx`, the header crumb `AppHeader.tsx`, the recent-items label `useRecentItemLabel.ts`) read the server-translated `GET /meta/report` and need no change. The earlier sequencing question (the scheduled `Console Pin Gate` going red until a `.objectui-sha` bump) went away with the pin (#25745). `ui/` reads neither `LEGACY_OBJECT_FIRST_KEYS` nor `TRANSLATION_KEY_GUIDANCE` (`git grep` at `da49afeb6c`: no hits). This PR does not carry `Fixes #25859`, because the claim gate requires a claim naming this branch; #25859 closes after the merge. - **Out of scope, recorded:** a joined report's `blocks[].label` (`ui/report.zod.ts`, an `I18nLabelSchema`) is not on the face, so `showcase_task_overview` will show a translated heading over its block labels as authored. The card asks for `label` / `description` only. - No `!` in the title and no `(narrowing)` arm, so no full dogfood run is owed. - **Receipts for the comments on this PR before this refresh:** - Docs drift 6400121514: `i18n-standard.mdx` and `translations.mdx` are updated in this PR. `concepts/metadata-lifecycle.mdx` names no packaged-base accessor, so `getPackagedReportBase` changes nothing it says. - Contract review 6400225090 ③.3 (a joined report's `blocks[].label` has no key) is a follow-up for the seat to file, recorded above as out of scope. - Contract review ③.4 and the seat's 6400446674 ("landing held for the owner's sequencing choice") are superseded. With the console half in this PR there is no main-red window to sequence: no `Console Pin Gate`, no `.objectui-sha`. ## Verification **After the `ui/` merge, at head `378f52336f`** (main merged a third time at `bbb49e33f1`, clean, then the `ui/` port `da49afeb6c` and the sdui re-record `378f52336f`). Every result is exit 0. - **ui vitest** over the touched surfaces (`TranslationPreview*`, `adminPreviews.i18n-10862-s2`, `packages/i18n/src`, `apps/console/src/loadLanguage.test.tsx`), run from `ui/`: 91 files, 1424 passed, 13 skipped. - Ablation: with the `ui/` half stashed, the three pinned files fail 19 tests in 2 files, and app-shell `tsc` reports TS1360 at `TranslationPreview.tsx:103` (`CATEGORY_DEFS` does not satisfy the record of 14 groups). With the port, that error is gone. The stash was popped and `git diff` was empty afterwards. - **ui i18n gates:** `check:i18n-keys`, `check:i18n-drift`, `check:i18n-designer-parity`, `check:i18n-dead-keys`, `check:spec-symbols` and `check:icon-record-names` passed. - **`check-sdui-manifest`:** the `ui/packages/i18n` source edit moved the in-tree registry digest, which reddened `Lint · repo & tooling gates` on `da49afeb6c`. `scripts/gen-sdui-manifest-node.mjs` regenerated it. `sdui.manifest.json` is byte-identical (sha256 `1f6e1ccb925a…`, 106 components); only the record's `registrySourcesSha256` and `generatedAt` moved. The gate now passes, locally and in CI. - **`dispatch-gates --commands`** derived 135 families, the same list at `da49afeb6c` and `378f52336f`. Each ran with its exit code captured before any pipe: 134 at `da49afeb6c` and the sdui gate again at `378f52336f`, all 135 exit 0. `--ran` reports 135 run, 0 NOT-MEASURED, 0 UNRUN. - **Local environment note:** this clone's incremental install (from before the ui merge) left `@types/react` unhoisted, so `@object-ui/components`' declaration build failed locally on three third-party typings in files this PR does not touch. The generator ran after `@types/react` and `@types/react-dom` were hoisted locally; no install change is committed. `ui/packages/components/` is unchanged from `main`, where `Type Check UI` builds it green. **Before the `ui/` merge,** all at head `ac2bcb0ba` (main merged twice, the second clean) unless noted. Every result is exit 0. - **Package suites** (spec, runtime, objectql, metadata-protocol and rest `test:repo` on `a79e5bc2`; rest `test`, lint and cli on `f6725a90`, before the first merge): - `@objectstack/spec`: `test` 21849 passed, `test:repo` 1269 passed. - `@objectstack/runtime`: `test` 7100 passed, `test:repo` 947 passed. - `@objectstack/objectql`: `test` 8723 passed. - `@objectstack/metadata-protocol`: `test` 21490 passed. - `@objectstack/rest`: `test` 6737 passed, `test:repo` 280 passed. - `@objectstack/lint`: `test` 7302 passed. - `@objectstack/cli`: unit project 4848 passed. - **After the second merge, at `ac2bcb0ba`:** spec `src/system` (1974), the cli report walk (4), lint `validate-translation-references` (209), rest explicit-override (76), runtime projection census (878) and the new objectql accessor file (5) re-ran green, and spec `tsc --noEmit` passed. - **`typecheck`** passed for `spec`, `cli`, `lint`, `rest`, `metadata-protocol`, `runtime`, `example-todo` and `example-showcase`. - **`dispatch-gates --commands`** at `ac2bcb0ba` derived 134 families. A full `turbo run build` came first. Each family ran with its exit code captured before any pipe, and all 134 exited 0. `--ran` reports 134 run, 0 NOT-MEASURED, 0 UNRUN. - `check-engine-split-ratio --days 90` first refused (exit 2) because the clone was shallow. After `git fetch --shallow-since=2026-07-20` it passed. - **`check:i18n-coverage`:** OK, 527 baselined, none new. `--update` moved nothing. - Control: deleting one `zh-CN` report key in app-todo turns it red (106 → 108), then the file was restored. - **`check:i18n`, `check:i18n-walk-parity`** (14 declared, 12 walked, 2 exempted), **`check:generated`:** green. - **Ablation.** Each step was run, then restored to the HEAD blob, with `git diff` empty afterwards: - Deleting `report: translateReport,` turns `i18n-report.test.ts` red (2 of 16 failed). - Deleting the `walkReports(config, out);` call turns `i18n-report-walk.test.ts` red (4 of 4 failed). - Deleting the `['report', 'getPackagedReportBase']` row turns the REST explicit-override file red (9 failed: §1 and every §4 edited-label cell). - **Independent review** (the spec rule): an isolated Fable subagent ran three rounds, each APPROVE_WITH_NITS. Every finding is fixed, filed or recorded. Rounds 1 and 2 (the whole diff, then its fixes) are #25788 comment 6399284479. Round 3 (the merge, the `ui/` port and the re-record) is #25788 comment 6407105388; its one nit is that `378f52336f`'s message credits app-shell as well as i18n for the digest move, when app-shell is not a registry package. --- _Generated by [Claude Code](https://claude.ai/code/session_01BUeRMD4VYCYUfzxTKcZFAF)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01BUeRMD4VYCYUfzxTKcZFAF --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>
1 parent 2a728ba commit dd5e355

35 files changed

Lines changed: 842 additions & 51 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/cli': minor
4+
'@objectstack/lint': minor
5+
'@objectstack/metadata-protocol': minor
6+
'@objectstack/rest': minor
7+
'@object-ui/app-shell': minor
8+
'@object-ui/i18n': minor
9+
---
10+
11+
feat(spec,cli): reports are translatable — `reports.<name>` in the translation bundle
12+
13+
Clause-②: yes
14+
Metadata-protocol: added
15+
16+
An app authored in one language listed its reports (`reports[].label`, `reports[].description`) in that language on the app's report index and the report page heading. Before this change the translation bundle had no key for them: `reports` was a retired object-first dialect key, so a bundle carrying it was refused by `os validate`, and `os i18n check` could not report the gap.
17+
18+
- **`@objectstack/spec`:** `TranslationData` (and the registered `translation` item and the platform bundle, which share its groups) gains `reports`, keyed by the report's `name`, carrying `label` and `description` (strict: any other key is refused — `name` or `title` with a message pointing at `label`, and the singular spelling `report` with a message pointing at `reports`). `reports` leaves `LEGACY_OBJECT_FIRST_KEYS`, so a stored translation row carrying it now loads through the authored-row sync instead of being skipped as a retired dialect; the key's measured footprint in the legacy dialect was zero (#3778). A new translator, `translateReport`, overlays the two strings and is registered in the metadata-document dispatch, so `GET /meta/report` (list and item) is served in the request's locale. A key the bundle does not carry keeps the authored string. A report is overlay-editable (ADR-0126 Regime O), so the translator honours the packaged base: a label or description an org re-authored is served as written (ADR-0029 D9.2a), and the strings it did not touch are still translated.
19+
- **`@objectstack/metadata-protocol`:** `getPackagedReportBase(name)` returns the shipped report document beneath any overlay, the base the REST read path hands the translator.
20+
- **`@objectstack/rest`:** the meta read path supplies that base for `report`, as it does for `dashboard` and `view`.
21+
- **`@objectstack/lint`:** `translation-target-unknown` reports a `reports.<name>` key that names no report a package of the stack declares, as an error with the nearest declared name.
22+
- **`@objectstack/cli`:** `os i18n extract` scaffolds `reports.<name>.label` / `.description` from the stack's `reports[]`, and `os i18n check` / `os lint` coverage report an untranslated one in its own bucket (`i18n/missing-report`).
23+
- **`@object-ui/app-shell`, `@object-ui/i18n`:** the translation designer's preview draws the `reports` group (a "Reports" / 「报表」 category beside the others), and the console's translation loader recognises a payload that carries only `reports`.
24+
25+
Example (`zh-CN` bundle of an English-authored app):
26+
27+
```ts
28+
reports: { opps_by_stage: { label: '各阶段商机', description: '按阶段汇总的商机' } },
29+
```

‎content/docs/protocol/kernel/i18n-standard.mdx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ Three rules the shape enforces, all of them closed since #4001:
399399

400400
- **The top level is the declared group set and nothing else** — `objects`,
401401
`picklists`, `apps`, `messages`, `globalActions`, `dashboards`, `datasets`,
402-
`pages`, `flows`, `positions`, `permissions`, `metadataForms`,
402+
`reports`, `pages`, `flows`, `positions`, `permissions`, `metadataForms`,
403403
`settingsCommon`. A group invented beside them
404404
(`"account"`, `"fields"`, `"list"`, `"industries"`) is rejected **by name**
405405
at both authoring doors, with the group to use instead named in the
@@ -483,7 +483,7 @@ what the overlay writes back as the node's `title`.
483483
The contract is `t(key, locale, params?)` — the target locale is always passed
484484
explicitly, and the key itself is returned when no translation is found (there
485485
is no `defaultValue` option). The key is a **dot path into `TranslationData`**,
486-
so it starts at one of the thirteen declared groups.
486+
so it starts at one of the fourteen declared groups.
487487

488488
```typescript
489489
const i18n = kernel.getService('i18n');
@@ -1063,6 +1063,7 @@ beneath an action entry are judged the same way under either.
10631063
| `flows.{flow}` | a flow's machine `name`, declared |
10641064
| `positions.{name}` | a position's `name`, declared, or an audience anchor (`everyone`, `guest`) |
10651065
| `permissions.{name}` | a permission set's `name`, declared |
1066+
| `reports.{name}` | a report's `name`, declared |
10661067
| `flows.{flow}.screens.{node}` | the `id` of a `type: 'screen'` node in that flow, nodes nested in loop, parallel and try/catch regions included |
10671068
| `flows.{flow}.screens.{node}.fields.{field}` | a `config.fields[].name` on that screen — an object-form screen (`config.objectName`) declares none, and its input labels live under `objects.{objectName}.fields` |
10681069

‎content/docs/references/api/protocol.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
17761776
| **globalActions** | `Record<string, { label?: string; description?: string; confirmText?: string; successMessage?: string; … }>` | optional | Global action translations keyed by action name |
17771777
| **dashboards** | `Record<string, { label?: string; description?: string; actions?: Record<string, object>; widgets?: Record<string, object>; … }>` | optional | Dashboard translations keyed by dashboard name |
17781778
| **datasets** | `Record<string, { label?: string; description?: string; dimensions?: Record<string, object>; measures?: Record<string, object> }>` | optional | Analytics dataset translations keyed by dataset name |
1779+
| **reports** | `Record<string, { label?: string; description?: string }>` | optional | Report translations keyed by report name |
17791780
| **pages** | `Record<string, { label?: string; description?: string; title?: string; subtitle?: string; … }>` | optional | Page translations keyed by page name |
17801781
| **flows** | `Record<string, { label?: string; successMessage?: string; errorMessage?: string; screens?: Record<string, object>; … }>` | optional | Screen-flow translations keyed by flow name |
17811782
| **positions** | `Record<string, { label?: string; description?: string }>` | optional | Position translations keyed by position name |

‎content/docs/references/system/translation.mdx‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ Platform translation data — the per-app groups plus the platform-only `setting
177177
| **globalActions** | `Record<string, { label?: string; description?: string; confirmText?: string; successMessage?: string; … }>` | optional | Global action translations keyed by action name |
178178
| **dashboards** | `Record<string, { label?: string; description?: string; actions?: Record<string, object>; widgets?: Record<string, object>; … }>` | optional | Dashboard translations keyed by dashboard name |
179179
| **datasets** | `Record<string, { label?: string; description?: string; dimensions?: Record<string, object>; measures?: Record<string, object> }>` | optional | Analytics dataset translations keyed by dataset name |
180+
| **reports** | `Record<string, { label?: string; description?: string }>` | optional | Report translations keyed by report name |
180181
| **pages** | `Record<string, { label?: string; description?: string; title?: string; subtitle?: string; … }>` | optional | Page translations keyed by page name |
181182
| **flows** | `Record<string, { label?: string; successMessage?: string; errorMessage?: string; screens?: Record<string, object>; … }>` | optional | Screen-flow translations keyed by flow name |
182183
| **positions** | `Record<string, { label?: string; description?: string }>` | optional | Position translations keyed by position name |
@@ -247,6 +248,13 @@ Translation data for a single object
247248
| **dimensions** | `Record<string, { label?: string }>` | optional | Dimension translations keyed by dimension name (`DatasetDimensionSchema.name`) |
248249
| **measures** | `Record<string, { label?: string }>` | optional | Measure translations keyed by measure name (`DatasetMeasureSchema.name`) |
249250

251+
### Nested Shape: `PlatformTranslationData.reports[string]`
252+
253+
| Property | Type | Required | Description |
254+
| :--- | :--- | :--- | :--- |
255+
| **label** | `string` | optional | Translated report label — overlays the report's own `label` |
256+
| **description** | `string` | optional | Translated report description — overlays the report's own `description` |
257+
250258
### Nested Shape: `PlatformTranslationData.pages[string]`
251259

252260
| Property | Type | Required | Description |
@@ -397,6 +405,7 @@ Per-app translation data for objects, apps, and UI messages
397405
| **globalActions** | `Record<string, { label?: string; description?: string; confirmText?: string; successMessage?: string; … }>` | optional | Global action translations keyed by action name |
398406
| **dashboards** | `Record<string, { label?: string; description?: string; actions?: Record<string, object>; widgets?: Record<string, object>; … }>` | optional | Dashboard translations keyed by dashboard name |
399407
| **datasets** | `Record<string, { label?: string; description?: string; dimensions?: Record<string, object>; measures?: Record<string, object> }>` | optional | Analytics dataset translations keyed by dataset name |
408+
| **reports** | `Record<string, { label?: string; description?: string }>` | optional | Report translations keyed by report name |
400409
| **pages** | `Record<string, { label?: string; description?: string; title?: string; subtitle?: string; … }>` | optional | Page translations keyed by page name |
401410
| **flows** | `Record<string, { label?: string; successMessage?: string; errorMessage?: string; screens?: Record<string, object>; … }>` | optional | Screen-flow translations keyed by flow name |
402411
| **positions** | `Record<string, { label?: string; description?: string }>` | optional | Position translations keyed by position name |
@@ -466,6 +475,13 @@ Translation data for a single object
466475
| **dimensions** | `Record<string, { label?: string }>` | optional | Dimension translations keyed by dimension name (`DatasetDimensionSchema.name`) |
467476
| **measures** | `Record<string, { label?: string }>` | optional | Measure translations keyed by measure name (`DatasetMeasureSchema.name`) |
468477

478+
### Nested Shape: `TranslationData.reports[string]`
479+
480+
| Property | Type | Required | Description |
481+
| :--- | :--- | :--- | :--- |
482+
| **label** | `string` | optional | Translated report label — overlays the report's own `label` |
483+
| **description** | `string` | optional | Translated report description — overlays the report's own `description` |
484+
469485
### Nested Shape: `TranslationData.pages[string]`
470486

471487
| Property | Type | Required | Description |
@@ -566,6 +582,7 @@ One locale of translations — the `translation` metadata type
566582
| **globalActions** | `Record<string, { label?: string; description?: string; confirmText?: string; successMessage?: string; … }>` | optional | Global action translations keyed by action name |
567583
| **dashboards** | `Record<string, { label?: string; description?: string; actions?: Record<string, object>; widgets?: Record<string, object>; … }>` | optional | Dashboard translations keyed by dashboard name |
568584
| **datasets** | `Record<string, { label?: string; description?: string; dimensions?: Record<string, object>; measures?: Record<string, object> }>` | optional | Analytics dataset translations keyed by dataset name |
585+
| **reports** | `Record<string, { label?: string; description?: string }>` | optional | Report translations keyed by report name |
569586
| **pages** | `Record<string, { label?: string; description?: string; title?: string; subtitle?: string; … }>` | optional | Page translations keyed by page name |
570587
| **flows** | `Record<string, { label?: string; successMessage?: string; errorMessage?: string; screens?: Record<string, object>; … }>` | optional | Screen-flow translations keyed by flow name |
571588
| **positions** | `Record<string, { label?: string; description?: string }>` | optional | Position translations keyed by position name |
@@ -645,6 +662,13 @@ Translation data for a single object
645662
| **dimensions** | `Record<string, { label?: string }>` | optional | Dimension translations keyed by dimension name (`DatasetDimensionSchema.name`) |
646663
| **measures** | `Record<string, { label?: string }>` | optional | Measure translations keyed by measure name (`DatasetMeasureSchema.name`) |
647664

665+
### Nested Shape: `TranslationItem.reports[string]`
666+
667+
| Property | Type | Required | Description |
668+
| :--- | :--- | :--- | :--- |
669+
| **label** | `string` | optional | Translated report label — overlays the report's own `label` |
670+
| **description** | `string` | optional | Translated report description — overlays the report's own `description` |
671+
648672
### Nested Shape: `TranslationItem.pages[string]`
649673

650674
| Property | Type | Required | Description |

‎content/docs/ui/translations.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export default defineStack({
7878
| Dashboard global-filter label and static option labels | `dashboards.<name>.globalFilters.<filterName>.label` / `.options.<value>` — the key is the filter's `name`, or its `field` when it authors no `name`; an option is keyed by its `value` spelled as a string |
7979
| Analytics dataset label / description | `datasets.<name>.label` / `description` |
8080
| Dataset dimension and measure labels | `datasets.<name>.dimensions.<dimension>.label` / `datasets.<name>.measures.<measure>.label` |
81+
| Report label / description | `reports.<name>.label` / `description` — keyed by the report's `name`; overlaid on every served report, which is what the console's report index lists and the report page is headed by. A label or description an org re-authored on a packaged report is served as edited |
8182
| 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 |
8283
| 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` |
8384
| 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 |
@@ -89,7 +90,7 @@ export default defineStack({
8990
| A label written as an inline locale map (`label: { en: 'Members', 'zh-CN': '成员' }`) | Nowhere — it is written on the metadata and resolved at render time; see **Current boundaries** below |
9091

9192
The metadata types resolved per request are **object, view, action, app,
92-
dashboard, dataset, page, picklist, position and permission** — a field's labels are translated as part of
93+
dashboard, dataset, report, page, picklist, position and permission** — a field's labels are translated as part of
9394
its object document, and so are the labels of any actions the object declares
9495
inline
9596
(#3370). Before that, an object document went out with its authored action

‎docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.
1919

2020
| Dir | Sites |
2121
|---|---|
22-
| `system/` | 381 |
22+
| `system/` | 382 |

‎examples/app-showcase/src/system/translations/index.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,5 +1337,13 @@ export const ShowcaseTranslationBundle = {
13371337
showcase_field_ops_delegate: { label: '现场运营委派管理员' },
13381338
showcase_client_liaison: { label: '示例客户联络人' },
13391339
},
1340+
// The reports, as the report index lists them and each report page is
1341+
// headed. Counted by `check-i18n-coverage` like the groups above.
1342+
reports: {
1343+
showcase_hours_by_status: { label: '按状态统计工时(汇总)', description: '按任务状态分组的预计工时。' },
1344+
showcase_status_priority_matrix: { label: '状态 × 优先级(矩阵)', description: '按状态和优先级交叉统计的预计工时。' },
1345+
showcase_task_overview: { label: '任务概览(联合)', description: '将多个任务子报表堆叠在一个联合视图中。' },
1346+
showcase_hours_by_status_chart: { label: '按状态统计工时(图表)', description: '以柱状图展示按状态统计的预计工时。' },
1347+
},
13401348
},
13411349
};

‎examples/app-todo/src/translations/ja-JP.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ export const jaJP: TranslationData = {
9191
description: '個人タスク管理アプリケーション',
9292
},
9393
},
94+
// The app's reports, as its report index lists them and each report page is
95+
// headed (`reports.<name>.label` / `.description`).
96+
reports: {
97+
tasks_by_status: { label: 'ステータス別タスク', description: 'ステータス別に集計したタスク' },
98+
tasks_by_priority: { label: '優先度別タスク', description: '優先度レベル別に集計したタスク' },
99+
tasks_by_owner: { label: '担当者別タスク', description: '担当者別のタスク集計' },
100+
completed_tasks: { label: '完了済みタスク', description: '作業時間の記録付きの完了済みタスク' },
101+
time_tracking: { label: '作業時間レポート', description: '見積時間と実績時間の分析' },
102+
},
94103
// The quick-add wizard: the flow's name in the runner header and completion
95104
// toast, each screen's heading and each field label.
96105
flows: {

‎examples/app-todo/src/translations/zh-CN.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ export const zhCN: TranslationData = {
9999
description: '个人任务管理应用',
100100
},
101101
},
102+
// The app's reports, as its report index lists them and each report page is
103+
// headed (`reports.<name>.label` / `.description`).
104+
reports: {
105+
tasks_by_status: { label: '按状态统计任务', description: '按状态分组的任务汇总' },
106+
tasks_by_priority: { label: '按优先级统计任务', description: '按优先级分组的任务汇总' },
107+
tasks_by_owner: { label: '按负责人统计任务', description: '按负责人汇总的任务' },
108+
completed_tasks: { label: '已完成任务', description: '所有已完成任务及其工时记录' },
109+
time_tracking: { label: '工时跟踪报表', description: '预计工时与实际工时分析' },
110+
},
102111
// The quick-add wizard: the flow's name in the runner header and completion
103112
// toast, each screen's heading and each field label.
104113
flows: {

‎packages/cli/src/utils/i18n-coverage.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface CoverageIssue {
6767
| 'dashboard'
6868
| 'widget'
6969
| 'dataset'
70+
| 'report'
7071
| 'picklist'
7172
| 'page'
7273
| 'flow'
@@ -352,6 +353,11 @@ const COVERAGE_SOURCE: Record<ExpectedEntry['source'], CoverageIssue['source']>
352353
// the `dashboard` bucket: the string is defined once, not once per
353354
// presentation.
354355
dataset: 'dataset',
356+
// Report copy (`reports.<r>.label` / `.description`) — the author's own
357+
// reports, listed by these two strings on the app's report index and headed
358+
// by the label on the report page, so it keeps its own bucket and reports as
359+
// `i18n/missing-report` rather than folding into `dashboard`.
360+
report: 'report',
355361
// Shared option-list copy (`picklists.<p>.label`, `.options.<value>`) — the
356362
// author's own vocabulary, translated once for every field that references
357363
// it, so it keeps its own bucket and reports as `i18n/missing-picklist`.
@@ -396,6 +402,7 @@ const SOURCE_NOUN: Record<CoverageIssue['source'], string> = {
396402
dashboard: 'Dashboard',
397403
widget: 'Widget',
398404
dataset: 'Dataset',
405+
report: 'Report',
399406
picklist: 'Picklist',
400407
page: 'Page',
401408
flow: 'Flow',
@@ -440,6 +447,7 @@ const SOURCE_SURFACE: Record<CoverageIssue['source'], string> = {
440447
dashboard: 'dashboards',
441448
widget: 'widgets',
442449
dataset: 'datasets',
450+
report: 'reports',
443451
picklist: 'picklists',
444452
page: 'pages',
445453
flow: 'flow screens, approval steps, toasts and refusals',

0 commit comments

Comments
 (0)