Skip to content

TranslationData.pages is strict with four keys, so page component copy has no translation slot at all — while dashboards.widgets, the same shape one level over, has one #12231

Description

@hotlong

Filed from the repo:hotcrm seat. Unassigned and ungraded — this repo's triage seat owns domain:* and type.

Back-link: objectstack-ai/hotcrm#1004, the consumer-side card, open since 2026-08-06 with no route it can take on its own. The app cannot work around this, and its lane charter forbids trying (「平台相关的功能应该在平台中实现」), so it is filed here.

The asymmetry

The translation contract gives pages four keys, and the object is .strict():

pages: Record( name, { label?, description?, title?, subtitle? } ) // strict

title / subtitle correspond to page:header specifically — the page name is the only stable key that reaches a header instance. So every other user-visible string in a page's component tree — card titles, card descriptions, metric-block labels — has no translation slot. A locale pack cannot carry it (strict refuses the key) and the runtime has nowhere to read it from; the value chain is the literal in the authored page straight through to the built artefact.

One level over, in the same contract, the dashboard tree does have the slot:

dashboards: Record( name, { label?, description?, actions?, widgets? } )
widgets: Record( id, { title?, description? } ) // keyed by id

Widgets are addressed by a stable id and are translatable. Page components have stable ids too — ai_briefing, kpi_revenue_won — and are not. Two near-identical UI component trees, one translatable and one not, which reads as a missing face in the spec rather than as an app misusing the contract.

Measured consumer surface

Counted from the built artefact rather than by grepping source: 12 user-visible strings across 8 pages, 10 of them on the default landing page.

sales_home_page (10)
quick_create.title "Quick Create"
my_recent_items.title "Recent Items"
key_metrics.title "Key Performance Indicators"
kpi_revenue_won.label "Revenue (Won)"
kpi_deals_won.label "Deals Won"
kpi_pipeline_value.label "Pipeline Value"
kpi_open_leads.label "Open Leads"
ai_briefing.title "Ask the AI Assistant"
ai_briefing.description "Open the assistant panel from the right edge …"
upcoming_events.title "Today's Schedule"
lead_detail_page (1)
related_tasks.title "Open Tasks"
utility_bar_page (1)
quick_notes.title "Quick Notes"

sales_home_page is isDefault: true and is the landing page for sales_rep / sales_manager. A zh-CN user signs in to a header that is translated and four cards plus four KPI blocks that cannot be — one screen, two languages, with no authored fix available.

Scope of the claim — what is and is not established

  • Established, and independent of any renderer: the key does not exist. The contract is .strict(), so this is not a lookup that falls back to English — it is a slot a locale pack is refused permission to fill. That is a declared-vs-enforced asymmetry on its face.
  • Not established: precisely how each of the 12 strings reaches the screen today. The impact figure above assumes the console renders these literals directly. Worth one browser read before choosing a route — the sibling case closing_this_quarter 的切换器页签标题写作 "Closing Soon" —— 与 label / 四个语言包的第四个拼法,且 tabs[].label 没有翻译位 hotcrm#760 (list.tabs[].label) was filed on comparable static evidence and turned out, on measurement, to have no user-visible symptom at all, because the switcher rendered a different string entirely.

Recording that boundary deliberately: the missing slot is measured, the blast radius is inferred.

What would resolve it

Give pages a components face aligned with the one dashboards.widgets already has — a record keyed by component id carrying the copy keys a component can own. That makes the two trees symmetric and lets the existing locale gates reach page copy, which today they cannot, because there is no key for them to check.

The alternative — declaring the boundary and documenting that page component copy is English-only — is coherent but weak for an i18n-complete product, and it would need saying somewhere an author meets it, since nothing today hints that this one component tree is untranslatable.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions