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-sys xuyushun441-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 顶层 object viewKind objectName 顶层 data config.name config.data.object
crm_lead.all crm_lead list 不存在 不存在 不存在 crm_lead
crm_lead.pipeline crm_lead list 不存在 不存在 不存在 crm_lead
crm_lead.web_to_lead crm_lead form 不存在 不存在 不存在 crm_lead
crm_lead.default crm_lead form 不存在 不存在 不存在 不存在

结论:

  • ✅ 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

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 7:26am

Request Review

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

github-actions Bot commented Aug 4, 2026

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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/m tests tooling

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