Skip to content

fix(spec): 视图标签按运行时真实下发的文档形状解析(#4854) - #5165

Merged
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4854-view-label-resolve
Aug 4, 2026
Merged

fix(spec): 视图标签按运行时真实下发的文档形状解析(#4854)#5165
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4854-view-label-resolve

Conversation

@xuyushun441-sys

@xuyushun441-sysxuyushun441-sys commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes#4854

resolveViewLabel / resolveViewDescription 读的两个字段,运行时下发的视图文档一个都没有,所以任何按 defineView 正常方式编写的视图,标签在任何语言下都只会落回英文字面量。列表视图切换器横在每个对象列表页顶部,在纯中文部署里这是屏幕上最显眼的残留英文。

说明:本文中形如 < object >.< key > 的写法,左尖括号后的空格是为了绕开 issue/PR 正文存储时的 HTML 标签清洗(左尖括号紧跟字母会被当作标签整段吞掉 —— 本 PR 正文初版就因此被截断了一半),实际字符串里没有这个空格。

先测量,再动手 —— 下发形状确认属实(且有一处与 issue 描述不符)

没有采信 issue 里贴的 JSON,而是回溯到真正组装下发文档的代码,并用真实的应用元数据跑了一遍。

组装者:expandViewContainerWithDiagnostics —— packages/spec/src/ui/view.zod.ts:2203。每个展开项都由同一行产出,字段依次是 name / object / viewKind / label / config(作者原始配置的深拷贝)/ order / scope,其中 name${object}.${k} 拼成,klistViews / formViews映射键

链路后续都不改形状:

  • packages/objectql/src/engine.ts:1875 —— 引擎把展开项原样 registerItem 进注册表;
  • packages/rest/src/rest-server.tsGET /meta/:type —— 仅按 viewKind 存在且 object 等于查询参数来过滤、排序,再交给 translateMetadataDocument 翻译。

examples/app-crm/src/views/lead.view.ts(真实编写的视图)实测下发结果:

顶层 name顶层 objectviewKindobjectName顶层 dataconfig.nameconfig.data.object
crm_lead.allcrm_leadlist不存在不存在不存在crm_lead
crm_lead.pipelinecrm_leadlist不存在不存在不存在crm_lead
crm_lead.web_to_leadcrm_leadform不存在不存在不存在crm_lead
crm_lead.defaultcrm_leadform不存在不存在不存在不存在

结论:

  • ✅ issue 说的顶层 object 属实 —— objectName 与顶层 data 确实都不存在,函数在 if (!bundle || !objectName) 处就返回了字面量,根本走不到查找;
  • ✅ issue 说的 name 被命名空间化为 < object >.< 裸名 > 属实;
  • 与 issue 描述不符:config.name 并不存在。 上表四条真实视图没有一条带 config.name,仓库内所有示例应用的视图配置也都没有写内层 name。裸名的唯一可靠来源是顶层 name 去掉 < object >. 前缀之后的部分(即容器映射键);config.name 只是作者可选字段。最后那条默认 form 更能说明问题:它连 config.data.object 都没有,顶层 object 是唯一能识别其对象的字段

因此实现按真实形状做,而不是按 issue 的措辞:查找键由顶层 name 剥前缀推导,不读 config.name。理由写进了代码注释 —— 当发生重名改写(组装器会把冲突的 < object >.< key > 改写成 ..._2)时,config.name 指向的是另一个视图,优先读它会把译文张冠李戴。

裸键这一侧同样有两个独立佐证:i18n 提取器 pushViewEntries(packages/cli/src/utils/i18n-extract.ts:180)写出的就是裸键;已发布的平台包 packages/platform-objects/src/apps/translations/*.objects.generated.ts 里也全是 sys_user._views.all_users 这样的裸键,没有任何带前缀的。

改动

  1. viewObjectName 依次取 objectNameobjectdata.objectconfig.data.object。这个顺序刻意与 i18n 提取器自己的 viewObjectName(i18n-extract.ts:163)保持一致 —— _views 键的那一端和它的这一端,从此对「哪个字段代表对象」给出同一个答案。
  2. 新增 viewTranslationKey:查找前剥掉 ${objectName}. 前缀。这是对组装规则的反解,不是容错别名 —— 翻译包按裸键存放,而 name 携带的是注册表分配的全局唯一身份(ViewItemNameSchema)。没有前缀的名字(手工构造的视图)原样使用,行为不变。
  3. ViewLike 只新增两个可选字段(objectconfig),纯类型增量,非破坏性。
  4. resolveViewDescriptionresolveViewLabel 共用这两个辅助函数,同一处缺陷一并修好 —— 只修标签会留下描述仍然失效。

测试

packages/spec/src/system/i18n-resolver.test.ts 新增一组用例,fixture 由 expandViewContainer 现场生成,而不是从 issue 里抄 JSON,所以它永远不会与真实下发路径漂移:组装器若改变身份拼写,断言随之改变,而不是继续描述一个运行时早已不产出的形状。

覆盖:身份 pin(断言旧代码读的两个字段在真实文档里不存在,config.name 也不存在)、顶层 object 加裸键解析出译文、描述同样解析、locale 回退链、负向对照(对象存在但无对应 _views 条目 → 落回字面量)、无 data 的默认 form、经 translateMetadataDocument 的 REST 边界入口(并断言 name / object / config 原样保留)、前缀不属于本视图对象时不截断。

反向验证(证明用例真的能抓到 bug):把 i18n-resolver.ts 还原成修复前版本重跑,4 条行为用例全部失败,修复后全绿。

× resolves a translation from top-level object + the bare view key
× resolves the description the same way
× translates through the REST boundary entry point
× still resolves the retargeting case via config.data.object
Tests 4 failed | 95 passed (99)

门禁实测结果:

pnpm --filter @objectstack/spec test Test Files 303 passed (303) · Tests 7729 passed (7729)
pnpm --filter @objectstack/spec typecheck tsc --noEmit(无输出,通过)
pnpm --filter @objectstack/spec check:generated All 8 generated artifacts are up to date.
eslint(改动的两个文件) 无输出,通过

未新增导出、未改动任何 zod schema 的 .describe(),故 8 项生成物无需重新生成 —— 这是用 check:generated 实测确认的,不是推断。

范围

按派发要求,未触碰 issue 中「report / dataset 没有 translator」一节 —— 那是独立的 surface 设计题,由 PM 另立单跟踪。本 PR 不新增翻译面,不改 METADATA_DOCUMENT_TRANSLATORS

顺带发现(已另立 issue,未在本 PR 修复)

#5164 —— _views 键的拼写有三个生产方互不一致:只声明默认 list(没有 listViews)的容器,提取器写出的键是 list,而组装器给它的注册名是 < object >.default,两边对不上,本 PR 之后这一种形状仍解析不出译文。这是生产方之间的分歧,必须在生产端统一;在消费端再加一层 defaultlist 的兼容,正是 Prime Directive #12 明令禁止的做法,所以留给该 issue 决策(倾向:以运行时身份的裸键 default 为准,并同步收紧 lint)。examples/app-crm 之所以没暴露这个问题,只是因为它的默认 listlistViews.all 结构完全相同、被组装器的签名去重合并了,活下来的键正好是 all

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

…#4854)
`resolveViewLabel` / `resolveViewDescription` read two fields the runtime's
view document does not carry, so every view authored through `defineView`
fell back to its English literal in every locale.
Measured against the composer, not the bug report: `expandViewContainer`
(`ui/view.zod.ts`) emits `{ name: '<object>.<key>', object, viewKind, label,
config, order, scope }`, the ObjectQL engine registers that verbatim, and
`RestServer` hands it to `translateMetadataDocument` unchanged. Two
independent mismatches, either one sufficient:
- the object is at top-level `object` (nested config data under `config`),
not `objectName` / `data.object`, so the lookup bailed at the
`!objectName` guard before consulting the bundle;
- the lookup key was the namespaced registry identity `<object>.<key>`,
while bundles key on the bare authoring key.
`viewObjectName` now reads `objectName -> object -> data.object ->
config.data.object`, matching the i18n extractor's own resolution order, and
the lookup derives the bare key by stripping the `<object>.` prefix — a
decode of the composer's rule, not a tolerant alias. `config.name` is
deliberately not consulted: it is absent from every real view and, on a
renamed colliding view, names a different view than the one being resolved.
`ViewLike` gains two optional fields only; a bare `name` still resolves
unchanged. Tests derive their fixture FROM `expandViewContainer` so they
cannot drift from the serving path, and pin that the two old fields are
absent from a real document. The four behavioural cases fail against the
unfixed resolver.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 4, 2026 7:26am

Request Review

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation protocol:system tests tooling labels Aug 4, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentationImprovements or additions to documentationprotocol:systemsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

View labels never resolve a translation — resolveViewLabel reads two fields the served view document does not have

2 participants

@xuyushun441-sys@claude