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

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 pending → Pending) green — merge preserves it
drop an option key (returned) from the en bundle red — DRIFTED (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-CN — status.options.pending 待处理 → 待审批; my_pending view 我的待办 → 待我审批; empty-state title 暂无待办审批 → 暂无待审批的请求. The other four statuses already matched the glossary and were left alone.
  • en — Pending / Approved / Rejected / Recalled / Returned. my_pending stays "My Pending" per the glossary.
  • ja-JP / es-ES — my_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 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 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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 10, 2026 04:36
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit 5d12675 Aug 10, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7232-approval-status-vocab branch August 10, 2026 04:58
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…omponent (objectstack-ai#7234) (objectstack-ai#7298)

The account menu's Approvals entry opened the raw sys_approval_request grid,
which cannot show an approver a single decision button: all eight actions on
that object gate on `record.viewer.can_act || record.viewer.can_override`, and
the `viewer` block is attached only by the approvals REST path, never by the
generic data API an object route reads. A correct-looking, completely inert
list.

The entry point does not move — same id, label and icon in every locale, which
also keeps objectstack-ai#7271's cross-package parity pin green. Only the DESTINATION changes,
to `{ type: 'component', componentRef: 'approvals:inbox' }`, gated by
`requiresService: 'approvals'` (requiresObject cannot gate a component entry;
the service gate is enforced server-side by filterAppForUser, ADR-0057 D10).

Setup gains an Approvals Inbox entry above the three raw tables, which stay put
as the admin/diagnostic surface. Docs document the approver's real surface and
how to mount the inbox in any business app.


Claude-Session: https://claude.ai/code/session_01L9U1G2piXmYrhYQX96XUyv

Co-authored-by: Claude <noreply@anthropic.com>
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