Repository navigation
Commit dd5e355
feat(spec,cli): reports are translatable —
<!-- 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>reports in the translation bundle (#25826)1 parent 2a728ba commit dd5e355
35 files changed
Lines changed: 842 additions & 51 deletions
File tree
- .changeset
- content/docs
- protocol/kernel
- references
- api
- system
- ui
- docs/audits/2026-07-unknown-key-strictness-ledger.counts
- examples
- app-showcase/src/system/translations
- app-todo/src/translations
- packages
- cli
- src/utils
- test
- lint/src
- metadata-protocol/src
- objectql/src
- rest/src
- runtime/src/domains
- spec
- api-surface
- authorable-surface
- export-origins
- liveness
- state-counts
- src/system
- scripts
- fixtures/i18n-walk-parity
- ui/packages
- app-shell/src/views/metadata-admin
- previews
- i18n/src/utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
399 | 399 | | |
400 | 400 | | |
401 | 401 | | |
402 | | - | |
| 402 | + | |
403 | 403 | | |
404 | 404 | | |
405 | 405 | | |
| |||
483 | 483 | | |
484 | 484 | | |
485 | 485 | | |
486 | | - | |
| 486 | + | |
487 | 487 | | |
488 | 488 | | |
489 | 489 | | |
| |||
1063 | 1063 | | |
1064 | 1064 | | |
1065 | 1065 | | |
| 1066 | + | |
1066 | 1067 | | |
1067 | 1068 | | |
1068 | 1069 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1776 | 1776 | | |
1777 | 1777 | | |
1778 | 1778 | | |
| 1779 | + | |
1779 | 1780 | | |
1780 | 1781 | | |
1781 | 1782 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
| 180 | + | |
180 | 181 | | |
181 | 182 | | |
182 | 183 | | |
| |||
247 | 248 | | |
248 | 249 | | |
249 | 250 | | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
250 | 258 | | |
251 | 259 | | |
252 | 260 | | |
| |||
397 | 405 | | |
398 | 406 | | |
399 | 407 | | |
| 408 | + | |
400 | 409 | | |
401 | 410 | | |
402 | 411 | | |
| |||
466 | 475 | | |
467 | 476 | | |
468 | 477 | | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
469 | 485 | | |
470 | 486 | | |
471 | 487 | | |
| |||
566 | 582 | | |
567 | 583 | | |
568 | 584 | | |
| 585 | + | |
569 | 586 | | |
570 | 587 | | |
571 | 588 | | |
| |||
645 | 662 | | |
646 | 663 | | |
647 | 664 | | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
648 | 672 | | |
649 | 673 | | |
650 | 674 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
81 | 82 | | |
82 | 83 | | |
83 | 84 | | |
| |||
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
92 | | - | |
| 93 | + | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1337 | 1337 | | |
1338 | 1338 | | |
1339 | 1339 | | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
| 1344 | + | |
| 1345 | + | |
| 1346 | + | |
| 1347 | + | |
1340 | 1348 | | |
1341 | 1349 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
94 | 103 | | |
95 | 104 | | |
96 | 105 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
102 | 111 | | |
103 | 112 | | |
104 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| |||
352 | 353 | | |
353 | 354 | | |
354 | 355 | | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
355 | 361 | | |
356 | 362 | | |
357 | 363 | | |
| |||
396 | 402 | | |
397 | 403 | | |
398 | 404 | | |
| 405 | + | |
399 | 406 | | |
400 | 407 | | |
401 | 408 | | |
| |||
440 | 447 | | |
441 | 448 | | |
442 | 449 | | |
| 450 | + | |
443 | 451 | | |
444 | 452 | | |
445 | 453 | | |
| |||
0 commit comments