You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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():
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:
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.
Filed from the
repo:hotcrmseat. Unassigned and ungraded — this repo's triage seat ownsdomain:*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():title/subtitlecorrespond topage:headerspecifically — 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:
Widgets are addressed by a stable
idand 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_pageisisDefault: trueand is the landing page forsales_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
.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.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
pagesa components face aligned with the onedashboards.widgetsalready 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.