Skip to content

fix(plugin-approvals): unify the approval-status vocabulary across the i18n bundles - #7271

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7232-approval-status-vocab
Aug 10, 2026
Merged

fix(plugin-approvals): unify the approval-status vocabulary across the i18n bundles#7271
os-zhuang merged 1 commit into
mainfrom
claude/issue-7232-approval-status-vocab

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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:44 really did say pending: "待处理", and :90 really did say my_pending"我的待办", while the Approvals Inbox and platform-objects nav (zh-CN.ts:21) say 待审批 / 待我审批.
  • en.objects.generated.ts:43-49 really 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 extract generates the bundle structure; the leaf string values are hand-maintained in these files and --merge preserves 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 on pnpm check:i18n:

probe gate
hand-edit a leaf value (en pendingPending) green — merge preserves it
drop an option key (returned) from the en bundle redDRIFTED (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

  • zh-CNstatus.options.pending 待处理 → 待审批; my_pending view 我的待办 → 待我审批; empty-state title 暂无待办审批 → 暂无待审批的请求. The other four statuses already matched the glossary and were left alone.
  • enPending / Approved / Rejected / Recalled / Returned. my_pending stays "My Pending" per the glossary.
  • ja-JP / es-ESmy_pending view 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:

  • It does not re-read the bundle constants. Every assertion goes through the real resolver (translateObject / resolveViewLabel) against the real SysApprovalRequest, 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.
  • It does not pin each locale in isolation. A parity case compares this plugin's my_pending label against platform-objects' nav_account_approvals label 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.ts needed no change: it pins which objects the bundle covers, not label text, and the change stays inside sys_approval_request. approval-vocabularies.test.ts likewise — it pins option values against @objectstack/spec/contracts, and those are unchanged.

Verification

  • pnpm --filter @objectstack/plugin-approvals test20 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 unbuilt connector-mcp / connector-openapi in 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.pending is 保留中 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

…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
@vercel

vercel Bot commented Aug 10, 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 10, 2026 4:15am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-approvals.

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

  • content/docs/automation/approvals.mdx (via @objectstack/plugin-approvals)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-approvals)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-approvals)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-approvals)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-approvals)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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 size/m tests tooling

Projects

None yet

2 participants