Repository navigation
页面组件文案没有翻译位:12 处用户可见字符串(首页占 10)在 zh/ja/es 下只能是英文,而 dashboards.widgets 有同形的翻译面 #1004
Description
Blocked-by: objectstack-ai/objectstack#12231 — ⛔ VOID, and it was never load-bearing. Removed from the reverse index at round 15. That upstream card was filed on 2026-08-25 asking for a face that had already shipped in @objectstack/spec 17.0.0-rc.6 (measured on this card 2026-08-11, verified upstream 2026-08-14); it was closed completed on 2026-08-28. This repo is pinned at 17.1.0, so the slot exists in the package actually installed. See the round-15 claim comment.
发现于 #1002 实施过程(改首页 ai_briefing 卡片文案时,按裁定 grep 四个语言包找对应键,
发现根本没有这个键面)。#1002 的文件面是那一张卡片,故单独立单。
现状(基线 origin/main = 86010ff)
翻译契约里 pages 只有四个键,而且是 .strict():
pages: Record( name, { label?, description?, title?, subtitle? } ) // strict
(node_modules/@objectstack/spec/dist/translation.zod-*.d.ts)
pages 现已带 components 面,见 2026-08-11 / 08-14 两条评论。保留原文是为了让这张卡的论证链可读,⛔ 不要把它当作今天的契约形状。
test/i18n-references.test.ts 的 every page has translated nav copy and header copy 也照着这四个键写,并且在注释里点明了 title / subtitle 的语义:它们对应
page:header,因为 header 实例没有稳定 id,页面名是唯一能够到它的键。
于是 page:header 以外的组件文案 —— 卡片标题、卡片描述、指标块 label —— 没有任何
翻译位。语言包写不进去(strict 会拒),运行时也无处可读,值链就是 *.page.ts 里的
字面量直通 dist/objectstack.json。
实测面(从 dist 产物统计,非源码 grep)
8 个页面,12 处这样的用户可见字符串:
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 是 isDefault: true、发给 sales_rep / sales_manager 的落地页。
一个 zh-CN 用户登录后看到的首页,页头是「销售看板 / 欢迎回来,…」(有翻译位),而右侧
和左侧四张卡片、四个 KPI 块全是英文 —— 同一屏两种语言。
静态证据立单,实测后判 premise-false。⛔ 不要把 12 这个数字当作已验证的影响面向前引用。
这与 AGENTS.md 的 i18n 约束(「No new feature ships without all 4 locales」)以及
仓里 9 个 locale 相关守卫的一贯口径是冲突的:那些守卫覆盖对象/字段/视图/动作/选项,
唯独页面组件文案覆盖不到,因为契约里没有那个键可查。
为什么这像是契约的不对称,而不是本应用漏写
同一个翻译契约里,仪表盘的组件是有翻译位的:
dashboards: Record( name, { label?, description?, actions?, widgets? } )
widgets: Record( id, { title?, description? } ) // 按 id 定位
widget 用 id 定位就能翻译 title/description;page 的组件同样有稳定 id
(ai_briefing、kpi_revenue_won …),却没有对应的 components 面。两个形状几乎
一样的 UI 组件树,一个能翻一个不能,更像是 spec 侧少了一个面,而不是本应用的用法问题。
处置建议(不预判,交分诊)
三条路成本差别很大:
- 上游补面:给
pages加components: Record( id, { title?, description?, label?, help? } ),
与dashboards.widgets对齐 —— 一致性最好,但要动@objectstack/spec; - 应用侧规避:把这类文案挪到有翻译位的表面(例如能承载它的组件类型),或接受
首页卡片只有英文并在守卫里把这一点写成明示豁免,免得下一个人再查一遍; - 先量再定:先确认控制台是否真的直接渲染这些字面量(与 closing_this_quarter 的切换器页签标题写作 "Closing Soon" —— 与 label / 四个语言包的第四个拼法,且 tabs[].label 没有翻译位 #760 同样的不确定性,
只是那一条是tabs[].label)。本条的静态证据比 closing_this_quarter 的切换器页签标题写作 "Closing Soon" —— 与 label / 四个语言包的第四个拼法,且 tabs[].label 没有翻译位 #760 强(strict schema 直接排除了
键的存在),但「渲染出来是什么」仍值得在浏览器里看一眼再定路线。
✅ 路线 1 已由上游交付(@objectstack/spec 17.0.0-rc.6,面比本卡所求更宽:
title / description / label / placeholder / emptyText / submitLabel)。
⛔ 路线 2 本车道宪章明令不可用(绕行把平台缺口藏进样板)。
关联
- Home page still ships a card titled "Ask the Sales Copilot" — a retired persona in live UI metadata, outside every docs guard's scan surface #1002(成因:改这张卡片的文案时按裁定找语言包键,发现无键;该单已如实记录取值链)
- closing_this_quarter 的切换器页签标题写作 "Closing Soon" —— 与 label / 四个语言包的第四个拼法,且 tabs[].label 没有翻译位 #760(同族:
tabs[].label也没有翻译位 —— 那条是视图切换器,本条是页面组件;
两条合起来说明「没有翻译位」不是个别键的遗漏) - i18n: 205 warnings hidden by --skip-i18n; broken option keys and untranslated navigation #494 /
test/i18n-references.test.ts(语言包内部的键漂移 —— 与本条不同,那是有键
但漂了,这里是根本没有键)
Activity
yinlianghui commentedon Aug 6, 2026
上游镜像已建:objectstack-ai/objectstack#6080(Blocked-by)
按上游协议处理:本单症结在
@objectstack/spec的翻译契约(pages为.strict()四键、无组件文案面),不是 hotcrm 侧可修的——语言包写不进(strict 拒收)、运行时无处读,应用侧任何"修法"都是平台 workaround,⛔ 不做。title/subtitle归page:header的语义口径、与dashboards.widgets的不对称对照、12 处 dist 实测面,并给了建议方向(pages.components面与 widgets 对齐);pm:blocked+upstream:objectstack,待上游补面后回来消化(届时语言包补 zh/ja/es 三语、test/i18n-references.test.ts扩面覆盖组件键);tabs[].label无翻译位)维持原状,镜像单里已互相关联——上游若补面,两条一起解锁。Generated by Claude Code
huangyiirene commentedon Aug 11, 2026
Re-measured on
@objectstack/*17.0.0-rc.6 during the rc.5 → rc.6 upgrade (#1059, PR #1066). The asymmetry this card identified is closed upstream — option 1 in the disposition list ("上游补面") is what shipped.pagesin the translation contract is no longer four keys. It now carries acomponentsface, keyed by componentid, exactly parallel todashboards.widgets:The face is wider than this card asked for — beyond
title/description/labelit addsplaceholder,emptyTextandsubmitLabel— so it covers component copy this card did not enumerate. (settingsandmetadataFormsgained comparable faces in the same contract.)So all 12 strings listed here now have a translation slot: the 10 on
sales_home_page(quick_create.title, the four KPIlabels,ai_briefing.title/.description, etc.),lead_detail_page.related_tasks.title, andutility_bar_page.quick_notes.title. A zh-CN user's landing page no longer has to be two languages on one screen.Not done here: this PR is the dependency bump only. Actually populating
componentsacross the four locale bundles — and extendingtest/i18n-references.test.tsto walk the new face so the coverage guard can finally see page component copy — is app-side work with a real review surface. Worth reopening this card's scope as that follow-up, or filing it fresh.Scope note: static read of the shipped
@objectstack/spectranslation schema. Not verified in a browser that the console actually resolvespages.<name>.components.<id>.titleat render time — the point #1004's option 3 raised, and still the right thing to check before writing 12 strings × 4 locales.Generated by Claude Code
hotlong commentedon Aug 14, 2026
Transfer sweep (#1156, part 1) — outcome 2: the fix lands in hotcrm. Relabelled and kept here.
The outcome test that was applied
Where would the fix land today? The
upstream:objectstacklabel was correct when applied on 2026-08-06 — the contract genuinely had no key to write into. It is stale now.Verdict: the upstream gap this card identified is closed; what is left is app-side
Platform half — shipped. The mirror objectstack#6080 ("翻译契约 pages 无组件文案面") is closed / completed, via merged PR objectstack#6466 ("feat(spec): translatable page component copy"). Verified upstream directly, not inferred from the on-card note.
The contract's
pagesface is no longer four strict keys. It now carries a component face keyed by componentid, parallel todashboards.widgets— which is precisely the asymmetry argument this card made, and option 1 of its own disposition list (「上游补面」) is what shipped. The delivered face is wider than this card asked for: beyondtitle/description/labelit also carriesplaceholder,emptyTextandsubmitLabel.So all 12 strings enumerated in the body now have a translation slot — the 10 on
sales_home_page,lead_detail_page.related_tasks.title, andutility_bar_page.quick_notes.title. The value chain this card described (literals in*.page.tsstraight through todist/objectstack.json, unwritable because.strict()rejects the key) no longer holds.App half — outstanding, and it is real work with a review surface. Populate the component copy across the four locale bundles, and extend
test/i18n-references.test.tsto walk the new face so the coverage guard can finally see page component copy — today it cannot, which is how this gap stayed invisible until #1002 tripped over it. That is app-side work in this repo, so there is no platform need left to route anywhere.Actions taken on this card
upstream:objectstack— removed. The upstream ask was made, accepted and shipped.pm:blocked— removed. The blocking condition (no key in the contract) is gone.prio:p2added. This card carried no priority grade at all. P2 (triaged backlog, no near-term schedule) is the honest grade: the user-visible symptom is real — a zh-CN user's landing page is two languages on one screen, on anisDefault: truepage shipped tosales_rep/sales_manager— but it is copy, not function, and nothing is broken.metadata— kept.The body's option 3 asked whether the console actually renders these literals — i.e. whether it resolves
pages.[ name ].components.[ id ].titleat render time, or only accepts the key in the schema. Everything established above is a static read of the shipped contract. Nobody has confirmed the render-time resolution in a browser.That matters practically: writing 12 strings × 3 locales against a key the console does not read would produce a silently ineffective change that still passes every guard. Check the render path first, then write the bundles.
⛔ And note what that check is not grounds for. If the console turns out not to resolve the key, that is a new finding against the console at that point — file it fresh, in
objectui, with the measurement. It is not a reason to keep this card labelledupstream:objectstacktoday on a defect nobody has observed.What was NOT done here
No app boot, no browser check, no code, no locale bundles touched. This sweep is routing and bookkeeping only. The upstream half was verified by reading objectstack#6080's state and its merged closing PR; the render-time question above is exactly the part that remains unmeasured, and it is named rather than assumed either way.
Related, unchanged
#760 (
tabs[].labelhas no translation slot) is the sibling case and was cross-linked from the upstream mirror on the expectation that both unlock together. This sweep did not touch it — it is not in part 1's six cards. Whoever picks up #1004 should re-check whether the shipped face covers #760 as well; if it does, #760 needs the same relabelling this card just got.Authority
This card is the ruling working as intended: the platform need was routed to the platform, the platform built the capability, and what returns is an app using it. The companion disposition 「关掉,只留指向镜像的指针」 does not apply — the mirror is already closed, and pointing a reader at a closed card would hide the app-side work that is the only thing left.
Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 25, 2026
Triage (代扫, vacancy clause) →
pm:blocked,Blocked-by: objectstack-ai/objectstack#12231(filed by this seat just now).Filed 2026-08-06 with
metadata+prio:p2and no pm-state — nineteen days invisible. Vacancy clause: triage actions only.The card asked triage to choose among three routes. Two of them are already decided by the charter.
That leaves nothing this repo can do until the slot exists, which is the definition of
pm:blocked.What is measured and renderer-independent: the key does not exist.
pagesis.strict()with four keys, so a locale pack is refused permission to carry component copy — this is not an English fallback, it is a closed door.What is inferred: that all 12 strings reach the screen as written. The sibling case #760 was filed on comparable static evidence about
list.tabs[].labeland was closed this week as premise-false — the switcher rendered a different string entirely and there was no user-visible symptom at all. Same family, same kind of static argument, opposite outcome on measurement.So the "同一屏两种语言" figure is the strong claim on this card and the least verified one. It is stated as such in objectstack#12231 rather than carried across as fact. ⛔ Whoever eventually implements this should not quote the 12-string count forward as a verified impact figure without a browser read.
prio:p2retained.Generated by Claude Code