Skip to content

fix(i18n): 补齐 en 的列表视图标签,并为「视图标签跨语言一致性」加守卫 (#783, #767) - #787

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-783-views-locale-key-parity
Aug 5, 2026
Merged

yinlianghui merged 2 commits into
mainfrom
claude/issue-783-views-locale-key-parity

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #783
Fixes #767

先说复核结论:#783 的 premise 只成立一半

单 声称 在 origin/main (fb48ab5) 的复核结果
#783 en.ts 的 crm_task._views 缺 todays_tasks / overdue_tasks ✅ 成立
#783 ja-JP.ts 同样缺这两项 ❌ 不成立
#767 en.ts 缺 renewals_due / at_risk_accounts / hot_leads ✅ 三项全部成立

ja-JP.ts:943-944 早已有这两条(📅 私の優先タスク / ⏰ オープンタスク · 期限超過が長い順),是 #679 补齐四语言包时一起落地的。#783 引用的基线 7667c9b4 上就已经是这样——git show 7667c9b4:src/translations/ja-JP.ts 的 _views 块从 937 行开到 946 行,八项齐全。issue 正文写的 ja-JP.ts:937-942 只读到 my_open_tasks 那一行就收尾了,漏看了后两行。因此本 PR 不动 ja-JP.ts,只有 #783 的 en 那一半是真的。

#767 的三项全部仍在,且 #774(对齐 hot 阈值到 rating ≥ 4)没有碰任何语言包,所以 hot_leads 的四语言现状与 #767 记录时一致。canonical label 是 🔥 Hot Leads,不含任何阈值措辞,照抄即可——没有 4.5 星的旧口径可以复活。

缺口的实际范围:9 条,不是 5 条

守卫的派生(不是人工清单)在 origin/main 上跑出 9 条,全部在 en:

en: crm_lead._views.hot_leads.label            (#767)
en: crm_account._views.renewals_due.label      (#767)
en: crm_account._views.at_risk_accounts.label  (#767)
en: crm_task._views.todays_tasks.label         (#783)
en: crm_task._views.overdue_tasks.label        (#783)
en: crm_opportunity._views.stale_opportunities.label
en: crm_opportunity._views.closing_this_quarter.label
en: crm_case._views.my_open_cases.label
en: crm_case._views.sla_at_risk.label

后四条一并在本 PR 修掉,而不是另开 issue。 这是一个需要说明的越界,理由是:它们与两单是同一文件、同一缺陷、同一成因,而本单要交付的守卫无论如何都需要对它们有个处置。剩下的唯一选择是引入豁免清单——而本文件里 select fields are translated in every locale 那段注释已经把这条路判为 regression(#679 退役 PENDING_SELECT_LABELS 时写的原话:「空清单就是在邀请别人加一行」)。带 4 行豁免的守卫等于出厂即破。

新增的 9 条全部是各视图 *.view.ts 里 label 的逐字节照抄——不是翻译,没有编辑余地,用户读到的任何名字都没有变。stale_opportunities 的 · 是 U+00B7、⚠️ 是 U+26A0 U+FE0F,与 metadata 一致(第三条断言会替你核对)。

顺带删掉了 closing_this_quarter 上「No label here on purpose」那段注释。它是 #746 写的,而 #679 早已对其它每个 locale 面做了相反的裁定;它给出的理由(en 不需要条目,因为 resolver 会回落到 metadata label)恰恰就是让这 9 条长期无人发现的那种隐身性。

守卫(本单的核心交付)

两单都指出「没有任何 gate 看得见」,复核属实:pnpm lint 硬编码 --skip-i18n;把 flag 去掉重跑也不报这些——只会输出一行 platform built-ins: 2298 i18n issue(s) hidden,因为 app 自有的 _views 完整性根本不在被检查的集合里。所以这不属于 #494 那一类「被 flag 藏起来的告警」,而是压根没人检查的面。

test/metadata-references.test.ts 的 every locale is complete on every authored surface 块里加三条断言,视图集从编译后的 stack 派生(与 emptyState 守卫同一套 container 遍历),不是硬编码清单,所以明天新增的视图在引入它的那个 PR 上就被卡住:

  1. 每个 canonical 视图在每个语言包都有 label(70 视图 × 4 语言包);
  2. 没有任何语言包为「stack 已不再 ship 的视图」保留 _views 条目——孤儿条目比缺口更坏,它看起来像覆盖,其实什么都没翻译;
  3. en 的条目与它所代表的 metadata label 逐字节相同。

第三条是 key 一致性做不到、而 #767 真正担心的那件事:当四个包的 key 都在时,改掉 metadata label,过去 en 会白白跟着变(它的条目本就是同一串英文的副本),而 zh-CN / ja-JP / es-ES 继续翻译旧名字,没有任何东西会红。诚实地说清这条断言的边界:它只证明英文这一对是一致的,无法验证另外三门语言随后被重新翻译过——它让改名变响,不能让改名变对。这不算新规矩:本次改动前 en 已有的 61 条视图标签本来就 61/61 逐字节相同,因为 #679 是从 objectstack.config 里程序化提取而非手抄的。

反真空断言守着派生本身:至少 50 个 canonical 视图、覆盖至少 10 个对象、每条都解析出 object 与 name,且每个语言包至少解析出一个 _views 表。

反向验证(四个方向,都是先声明后跑)

操作 预测 实测
删掉刚补的 en.crm_task.overdue_tasks 一致性断言红并点名 ✅ en: crm_task._views.overdue_tasks.label;逐字节断言保持绿(无可比对象)——正是两条断言应有的分工
把该 label 改成看似合理的 ⏰ Overdue Tasks 逐字节断言红、一致性断言绿 ✅ 红并同时报出两串;一致性绿——这正是 key 一致性看不见的那一类
加一条 stack 中不存在的 renamed_last_release 条目 孤儿断言红 ✅ en: crm_task._views.renamed_last_release
保留守卫、把 en.ts 退回 origin/main 报出全部 9 条且全在 en ✅ 9 条,含 #783 的 2 条与 #767 的 3 条;ja-JP 零命中——即上面 premise 复核的机器证据

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Testing

  • pnpm validate — Validation passed
  • pnpm typecheck — clean
  • pnpm lint — 13 warning(s), 14 suggestion(s)(与改动前一致,无新增)
  • pnpm hygiene — source hygiene clean(含 no raw control bytes)
  • pnpm build — Build complete
  • pnpm test — 63 files / 1517 passed / 1 skipped
  • 控制字符自扫(grep -naP 覆盖 gate 的盲区)零命中
  • 新增测试 3 条 + 反真空 1 条

Checklist

  • 已添加 changeset(.changeset/view-label-locale-parity.md,patch)
  • 未改动 canonical 视图 label 本身
  • 未改动 zh-CN / es-ES / ja-JP 的既有条目
  • 未升级 @objectstack/*(锁 17.0.0-rc.2)
  • 未改动 content/docs/releases/
  • 未改动 lint 的 --skip-i18n 配置

留给 reviewer 的两个判断点

  1. 越界修了 4 条(crm_case ×2、crm_opportunity ×2)。理由见上;若认为应当拆走,删掉这 4 条 en 条目后守卫会立刻变红,所以拆分必然要配一个豁免清单。
  2. 第三条断言(en 逐字节 pin)超出了派单里「键集一致性」的字面要求。 它当前是绿的,成本是一个 it() 块;若认为把 en 钉死在 metadata 上过度,删掉它不影响前两条。我的建议是留下:en.ts 的 _views 少了三个视图标签(renewals_due / at_risk_accounts / hot_leads),而其余三个语言包都有;_views 没有任何跨语言覆盖守卫 #767 列的第一条风险(改名后静默分叉)只有它能变成一次 CI 失败,而真要出现「英文该与 metadata 不同」的情形,contract-first 的解法是去改 *.view.ts 里那个唯一的生产者,而不是容忍两串英文各走各的。

Generated by Claude Code

…ity across locales (#783, #767)

Nine saved list views had no `_views` label entry in the `en` bundle while
zh-CN, ja-JP and es-ES all carried the full set: `crm_task.todays_tasks` /
`overdue_tasks` (#783), `crm_lead.hot_leads` and `crm_account.renewals_due` /
`at_risk_accounts` (#767), plus `crm_case.my_open_cases` / `sla_at_risk` and
`crm_opportunity.stale_opportunities` / `closing_this_quarter`, which the
guard's derivation turned up beside them.

Each entry added here is the view's own metadata `label` verbatim — no name a
user reads changes, and no locale invents a reading of its own. The four
`crm_case` / `crm_opportunity` rows are closed in the same PR rather than
filed: they are the identical defect in the identical file, and the guard this
issue asks for cannot ship green without a disposition for them. The only
alternative was an exemption ledger, which the select-field guard's own comment
in this file calls a regression.

The `closing_this_quarter` comment claiming "no label here on purpose" is
removed with it. It was written by #746 after #679 had already ruled the other
way for every other locale surface, and its premise — that `en` needs no entry
because the resolver falls back to the metadata label — is exactly the
invisibility that let these nine sit unnoticed.

## The guard

Nothing mechanical could have found any of this. `pnpm lint` hard-codes
`--skip-i18n`, and dropping the flag does not report them either: the run emits
one line about 2298 hidden platform built-ins, because app-authored `_views`
completeness is not in the set the linter checks. So this is not the #494
family (real warnings suppressed by a flag) — it is a surface nobody checked,
and both gaps were found by a human comparing four bundles column by column.

Three assertions in `test/metadata-references.test.ts`, derived from the
compiled stack rather than a hand-kept list, so a view added tomorrow is held
to the bar on the PR that adds it:

- every canonical view has a `label` in every locale (70 views x 4 locales);
- no locale carries a `_views` entry for a view the stack no longer ships — an
  orphan reads as coverage while translating nothing;
- the `en` label is byte-identical to the metadata label it stands in for.

The third is what parity alone cannot do, and it is #767's actual concern:
with the key present in all four bundles, editing the metadata label used to
let `en` track the rename for free while the other three kept translating the
old name, with nothing red. It proves the ENGLISH pair agrees and nothing more
— it makes a rename loud, it cannot make the other three correct. All 61 `en`
view labels that existed before this change were already byte-identical,
because #679 extracted them programmatically rather than transcribing them.

An anti-vacuum assertion guards the derivation itself: >= 50 canonical views
over >= 10 objects, every one resolving an object and a name, and at least one
`_views` table parsed out of every locale pack.

## Verification

Reverse-verified in four directions, each predicted before it was run:

- delete `en.crm_task.overdue_tasks` -> parity RED naming
  `en: crm_task._views.overdue_tasks.label`; byte-identity stays green (it has
  nothing to compare), which is the correct split of duties;
- rewrite that label to a plausible `⏰ Overdue Tasks` -> byte-identity RED
  naming both strings, parity GREEN — the case parity cannot see;
- add a `_views` entry for a view that does not exist -> orphan check RED;
- run the guard against `origin/main`'s `en.ts` -> exactly the 9 rows above,
  all in `en`, none in ja-JP.

That last run also disproves half of #783's premise: it claims ja-JP is missing
`todays_tasks` / `overdue_tasks`, but ja-JP has carried both since #679, at
`ja-JP.ts:943-944` — already true at the `7667c9b4` baseline the issue cites.
Only the `en` half of #783 was real, so no ja-JP edit was needed.

Full suite green: validate, typecheck, lint (13 warnings, unchanged), hygiene,
build, and 1517 tests across 63 files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa
@vercel

vercel Bot commented Aug 5, 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)
hotcrm Ignored Ignored Aug 5, 2026 7:55pm

Request Review

@github-actions github-actions Bot added ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces labels Aug 5, 2026
It said "both assertions below" while three now sit under it (parity, orphan,
and the en byte-identity pin). A guard's comment that undercounts what it
covers is how the next reader concludes one of them is unprotected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa
@yinlianghui
yinlianghui marked this pull request as ready for review August 5, 2026 19:58
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 0ed957a Aug 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants