Skip to content

i18n: two surfaces a translation bundle cannot address at all — walkAddressedPageComponents misses a slotted page's slots and a tabs node's items[].children, and dashboards.NAME has no globalFilters group #16772

Description

@os-zhuang

Split off #16735 (findings 2 and 3) by the triage seat. They are one card because both are the same defect — a surface the console renders but a bundle has no key for — both live in packages/spec, and both would otherwise produce two PRs touching the same package.

Original provenance: filed from an app repo (objectstack-ai/hotclm, @objectstack/* 17.3.0) after translating it end to end into en + zh-CNobjectstack-ai/hotclm#32, PR objectstack-ai/hotclm#33. The reporter states they did not personally re-drive these; see Provenance and boundaries below.


Finding A — pages.NAME.components.* addresses nothing on a kind: 'slotted' page, and a tab label has no key on any page kind

let n = 0;
walkAddressedPageComponents(ContractDetailPage, (c) => (n++, c));   // -> 0

against 21 authored components and tab labels (@objectstack/spec 17.3.0). walkAddressedPageComponents is the traversal translatePage itself runs, so this is unresolvable rather than merely ungated.

Two independent causes, either sufficient on its own:

  • its roots are regions[].components[], and a slotted page authors slots;
  • its descent is properties.children, and a page:tabs carries properties.items[].children.

pages.contract_detail carries exactly two addressable keys, label and description, while 7 tab labels, 7 path stages, 7 related-list titles and 1 block label cannot be reached from a bundle at all. Worked around in the app with 22 inline locale maps.

Triage's own reading, on origin/main — the roots half is confirmed from the signature and the first pass:

packages/spec/src/system/i18n-resolver.ts:1568  export function walkAddressedPageComponents(
:1569    doc: Pick<PageLike, 'regions'>,
:1578    if (Array.isArray(doc.regions)) {
:1579      for (const region of doc.regions) {
:1580        if (!region || typeof region !== 'object' || !Array.isArray(region.components)) continue;

⇒ the parameter type itself is Pick<PageLike, 'regions'> — a slotted page's slots is not merely skipped, it is outside the declared input. ⚠️ The properties.children descent half was not separately verified by triage; re-derive it.


Finding B — dashboards.NAME has no group for globalFilters

A dashboard's global-filter labels and their option labels are not addressable — and the filter bar draws directly above the widget titles the bundle does translate, so the mixed-language result is maximally visible:

类别: 全部        Requesting Department: 全部
[six Chinese widget titles below]

5 filter labels + 29 option labels in that app. Worked around with inline maps.

Triage's own reading, on origin/main — confirmed, the group has exactly four members:

packages/spec/src/system/translation.zod.ts:694  dashboards: z.record(z.string(), strictObject({
:699    label: z.string().optional().describe('Translated dashboard title'),
:700    description: z.string().optional().describe('Translated dashboard description'),
:701    actions: z.record(...)
:708    widgets: z.record(...)

label · description · actions · widgets. No globalFilters.


Why these two are Bug and not Feature

Both are the benign direction of the same seam #16735's finding 1 describes: there is nothing to address, so nothing claims coverage, so os i18n check does not lie about them. That is why they are p2 and not higher.

They are still Bug rather than Feature because the console already renders these strings and the platform already declares that a bundle is how you translate a page or a dashboard. A declared translation surface that cannot reach a rendered string is a contract not delivered, not a capability nobody promised.

Suggested shape (⛔ not a prescription)

  • A: widen the traversal's roots to include a slotted page's slots, and its descent to include properties.items[].children. ⚠️ The parameter type changes with it (Pick<PageLike, 'regions'> no longer describes the input), so this is a published-signature change in packages/spec — expect contract review.
  • B: add a globalFilters group to the dashboard translation schema, keyed the way widgets and actions already are, covering both the filter label and its option labels.

⛔ Do not collapse A and B into one traversal change; they are different files and different shapes and only share a lane.

Acceptance

  • A slotted page with tab labels yields a non-zero walkAddressedPageComponents count, and each authored key resolves.
  • A dashboard's global-filter labels and option labels are addressable from a bundle and render translated.
  • The 22 + 34 inline locale maps in the reporting app become removable (⛔ not this card's job to remove them — the app's).

Provenance and boundaries

  • ⚠️ Measured in a browser by the reporting app's implementation run. The filer states plainly: "I did not personally re-drive these four." They also record that a fifth finding from the same run was independently re-measured and came back inverted (record:details: the #13855 sections[].group reference form crashes the renderer, and the enumerated fields form renders raw field keys instead of declared labels #16695) — so this run's output is not offered as unexamined, but it is also not independently confirmed end to end.
  • ⚠️ Triage verified the two code readings quoted above and nothing else — not the counts (21 components, 5 filter labels, 29 option labels), not the browser behaviour.
  • ⛔ Dedup could not be run from the reporting session: search_issues returned total_count: 0 for the app's own repo even on a control term certainly present in an open issue's title, so the index was not answering. The run correctly reported this rather than filing blind. ⚠️ Whoever claims this should re-run the duplicate search.
  • One app, one console build, one dialect. Re-confirm before acting.

Split siblings

https://claude.ai/code/session_01SwJQDFKe8tVit3BXQ9EfR5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions