fix(plugin-approvals): unify the approval-status vocabulary across the i18n bundles - #7271
Conversation
…e i18n bundles (#7232) zh-CN said 待处理 for the status and 我的待办 for the my_pending view, while the Approvals Inbox and the account-app nav said 待审批 / 待我审批 for the same things, and the en bundle shipped the raw enum values as labels. Align the bundles to the Inbox wording and humanize en. The bundles are the source of truth for the leaf strings despite the `.generated.ts` name: the extractor generates the STRUCTURE and `--merge` preserves every hand-translated value. Measured both ways -- a hand-edited leaf value keeps `check:i18n` green, a dropped option key turns it red. Adds a pin that resolves every assertion through the real i18n resolver against the real object, including a cross-package parity case tying the my_pending view label to the account-app nav label -- the assertion that goes red if either layer is reworded alone. Status values are untouched; this is display wording only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9U1G2piXmYrhYQX96XUyv
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #7232 (problem 4 of #7213: three vocabularies for one request).
Maintainer directive, 2026-08-10 — the glossary itself is decided, not re-opened here:
Premise: verified on origin/main, both halves hold
zh-CN.objects.generated.ts:44really did saypending: "待处理", and:90really did saymy_pending→"我的待办", while the Approvals Inbox andplatform-objectsnav (zh-CN.ts:21) say 待审批 / 待我审批.en.objects.generated.ts:43-49really did ship the raw enum values (pending,approved, …) as labels.Where the labels actually live — the mechanism, measured
The dispatch hypothesis was that an extraction pipeline upstream owns these strings and the fix belongs at that producer. That is half wrong, and the measurement says which half.
os i18n extractgenerates the bundle structure; the leaf string values are hand-maintained in these files and--mergepreserves them — exactly what the file header states ("Edit translations in place… Do not hand-edit the structure — only the leaf string values"). Measured both directions onpnpm check:i18n:pending→Pending)returned) from the en bundleDRIFTED (1)So there is no upstream label producer to fix: the bundles are edited directly, and this PR does that. For completeness, the reason en was seeded with raw values is that
Field.select([...APPROVAL_STATUSES])normalizes each bare string to{ label: 'pending', value: 'pending' }— the declared label is the value, and the extractor seeds a new key from that source text.Changes
status.options.pending待处理 → 待审批;my_pendingview 我的待办 → 待我审批; empty-state title 暂无待办审批 → 暂无待审批的请求. The other four statuses already matched the glossary and were left alone.Pending/Approved/Rejected/Recalled/Returned.my_pendingstays "My Pending" per the glossary.my_pendingview label aligned to the nav wording: 自分の保留中 → 承認待ち, "Mis pendientes" → "Aprobaciones pendientes".Status values are untouched — display wording only, so no stored data, filter or API payload moves.
New pin
src/translations/approval-status-vocabulary.test.ts. The card asked for pinned label expectations to be flipped; there were none to flip — nothing pinned this wording at all, which is how three layers drifted apart in the first place. Two things it deliberately does not do:translateObject/resolveViewLabel) against the realSysApprovalRequest, so it also proves the bundle is reached. A guard-the-guard case asserts the declared option label is still the bare enum value, so a humanized result can only have come from the bundle.my_pendinglabel againstplatform-objects'nav_account_approvalslabel for zh/ja/es — the cross-package assertion that goes red if either layer is reworded alone. en is excluded on purpose: its nav reads "Approvals" (the destination) and its view reads "My Pending" (the filter), a split the glossary keeps.Reverse-verified in the predicted direction: reverting the four bundles and keeping the test turns 5 of 6 cases red (en options, zh options, the raw-enum ratchet, the per-locale view labels, and the parity case), while the guard-the-guard case stays green — it reads the object definition, which this PR does not touch.
bundle-ownership.test.tsneeded no change: it pins which objects the bundle covers, not label text, and the change stays insidesys_approval_request.approval-vocabularies.test.tslikewise — it pins option values against@objectstack/spec/contracts, and those are unchanged.Verification
pnpm --filter @objectstack/plugin-approvals test— 20 files / 452 tests passed (19 files before; the new pin is the 20th).pnpm --filter @objectstack/plugin-approvals typecheck— exit 0.pnpm check:i18n— exit 0, "OK (9 packages — all bundles in sync, no undeclared authoring keys)".pnpm check:i18n-coverage— exit 0, "OK (12 configs, 660 baselined untranslated strings, none new)". First two runs reported COULD NOT MEASURE for an unbuiltconnector-mcp/connector-openapiin the worktree — an environment prerequisite that compares nothing, not a content verdict; resolved by building the showcase dependency closure.node scripts/check-nul-bytes.mjs— exit 0.Changeset:
.changeset/hip-planes-shake.md(patch, user-visible wording).Scope note
ja-JP
status.options.pendingis 保留中 while the same bundle uses 承認待ち elsewhere. #7232's glossary scopes ja/es to the view label and says not to invent translations beyond it, so it is left as-is and flagged here rather than changed.Generated by Claude Code