Skip to content

PageTranslation.components has no key for element:text's content — the one string that element renders cannot be addressed by a bundle #14412

Description

@os-warren

Found while authoring a read-only record page in an application (objectstack-ai/duly#13) with a complete zh-CN bundle, on @objectstack/spec 17.2.0.

The gap

PageTranslation.components (packages/spec/src/system/translation.zod.ts) declares the per-component key face as title | description | label | placeholder | emptyText | submitLabel.

element:text renders exactly one authored string, content, and it is not in that list. So a page that puts a sentence on screen through the element the platform provides for putting a sentence on screen has no bundle address for it.

Why this reads as a missed key rather than a decision

The schema's own comment says the face was derived from the copy props components declare, and it names its two deliberate exclusions — help and subtitle — with reasons. content is neither named nor excluded; it is simply absent. Every other authored string on the same page has a key.

What an application does today

The blessed alternative works: an inline I18nLabel map ({ en, 'zh-CN' }) on content, which is what the platform's own sys-user.page.ts does. The reporting app uses it, and its coverage walk records those strings under an untranslatable ledger with this reason, pinned by count (16 strings on one page). That is a workaround with a cost: those strings are invisible to os i18n extract, to check:i18n-coverage, and to a translator working from the bundle, and each one is an inline map in a page definition instead of a row in the locale file.

Suggested shape

Add content to PageTranslation.components, resolved in translatePage for element:text (and any sibling element whose display string is content). If it is deliberately out — because inline maps are the intended route for page prose — the schema comment should say so beside help and subtitle, so the next author finds a decision instead of an absence.

Related: #14253 (three other authored display surfaces with no key; merged as #14381), #14376 (extractor coverage of new key families).


Triage — two corrections, and they move the card's centre of gravity

Measured at origin/main b8562ff262.

(a) The face no longer carries submitLabel. It left in @objectstack/spec 17 (#10926, ADR-0049) and the current shape is five keys: title description label placeholder emptyText (translation.zod.ts:927-931). Minor, but the card's premise is a census, so the census should be right.

(b) ⭐ The inline map is not a workaround — it is the ruled route, and this exact question was decided twice.

packages/spec/src/ui/component.zod.ts:1784 declares content: I18nLabelSchema, and the docblock above it (:1773-1783) records why:

I18nLabelSchema rather than a bare z.string() (#5728, named explicitly in the maintainer's ruling because the label-wide widening could not reach it): sys-user.page.ts authors eight element:text nodes whose content is an inline { en, 'zh-CN', 'ja-JP', 'es-ES' } map… The bare string was the declaration disagreeing with the delivered shape.

And the submitLabel retirement one file over (translation.zod.ts:867-876) is the same shape, decided the same way:

the live form surface's submit copy is object-form's submitText (I18nLabelSchema), localizable at its own authoring site, and adding it here would be a face widening.

element:text.content is an I18nLabelSchema localizable at its own authoring site. By the precedent, adding it to the bundle face is a face widening — the thing #10926 declined to do for the identical shape.

⇒ The card's framing ("a missed key… neither named nor excluded") does not survive. What is genuinely missing is the sentence, not the key: content's absence is a decision that was made elsewhere and never written down beside help and subtitle. That is the card's own second option, and the precedent points at it.

Why it is still a decision, and not simply closed

Because the card raises something the two rulings did not weigh. #5728 and #10926 both answered "can this string be localized?" — yes, at its authoring site. This card asks "can it be extracted and counted?" — and the answer is no. Sixteen strings on one page, invisible to os i18n extract, to check:i18n-coverage, and to a translator working from the bundle. That is a new argument against a settled route, measured outside this repo, and it is the maintainer's to weigh rather than the seat's to wave through on precedent.

<!-- os-decision-facets -->

推荐:A(卡面的第二个选项)—— 不加键,把排除理由写进 schema 注释,与 helpsubtitle 并列,引 #10926 的原话作依据。 ①④同向,③被它满足。
⚠️ 但 A 不解决 ②,必须说清楚:那 16 条字符串仍然对抽取器和覆盖率门禁不可见。所以荐 A 的同时建议另开一张卡问「内联 I18nLabel 映射该不该被 os i18n extractcheck:i18n-coverage 看见」—— 那才是 ② 真正要的东西,而且它对所有用内联映射的字段都成立,不止 element:text,因此比给一个组件补键更能关掉这一类。⛔ 本席不代开那张卡,那是拿到方向之后的事。
回退:B(卡面的第一个选项) —— 把 content 加进面并在 translatePage 里解析。若维护者认为 bundle 面才是页面散文的正路、内联映射只是过渡形态,走这条。代价:同一字符串两条路径,并且必须一并裁定两者同时存在时谁赢。
置信缺口(本分析看不见什么): 没有量 objectui 侧 pickLocalized 与 bundle overlay 的优先级。若走 B,一个 content 同时带内联映射和 bundle 条目时谁赢,是 B 上线前必须先答的问题 —— 本轮没量,而它决定 B 的真实代价是「加一个键」还是「加一个键 + 一条冲突规则」。

Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions