From 7cd0b0e60a814d58cb0e95bd9df2c1f73ea16607 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 07:38:03 +0000 Subject: [PATCH 1/2] docs(zh): approvals spells plugin-owned view and tab names from the owner's zh-CN pack (#1552) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X8U3asekbiC7yWoEPWR4Dg --- .changeset/zh-approvals-plugin-view-names.md | 4 +++ content/docs/revenue/approvals.zh-Hans.mdx | 24 +++++++++--------- content/docs/revenue/approvals.zh-Hant.mdx | 26 ++++++++++---------- 3 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 .changeset/zh-approvals-plugin-view-names.md diff --git a/.changeset/zh-approvals-plugin-view-names.md b/.changeset/zh-approvals-plugin-view-names.md new file mode 100644 index 00000000..1299f9c1 --- /dev/null +++ b/.changeset/zh-approvals-plugin-view-names.md @@ -0,0 +1,4 @@ +--- +--- + +docs(zh): `revenue/approvals` spells the approval plugin's list views and the approval centre's tabs with their owning package's zh-CN strings (待我审批 / 我提交的 / 已完成 / 全部; tabs 待我审批 / 我发起的 / 全部), zh-Hant in Traditional script, dropping the English-plus-parenthesis form (#1552). Docs only; nothing ships. diff --git a/content/docs/revenue/approvals.zh-Hans.mdx b/content/docs/revenue/approvals.zh-Hans.mdx index 4a49eb64..9f3ffcf1 100644 --- a/content/docs/revenue/approvals.zh-Hans.mdx +++ b/content/docs/revenue/approvals.zh-Hans.mdx @@ -91,9 +91,9 @@ HotCRM 应用里审批只有一个入口,它挂在 **My Work**(我的工作 | 页签 | 内容 | | --- | --- | -| **My Pending**(待我审批) | 等你做决定的请求,页签上带数量角标 | -| **Submitted by me**(我发起的) | 你发起的请求,不分状态 | -| **All**(全部) | 你有权看到的全部请求 | +| **待我审批** | 等你做决定的请求,页签上带数量角标 | +| **我发起的** | 你发起的请求,不分状态 | +| **全部** | 你有权看到的全部请求 | 在任一页签内部,还有一个独立的**状态**(Status)筛选可以继续收窄:*全部状态*、**待审批**、**已通过**、**已拒绝**、**已撤回**、**已退回修改**。 @@ -103,21 +103,21 @@ HotCRM 应用里审批只有一个入口,它挂在 **My Work**(我的工作 | 对象内置视图 | 内容 | | --- | --- | -| **My Pending**(待我审批) | 仍在待批、且你是审批人之一的请求 | -| **I Submitted**(我提交的) | 你提交过的全部请求,不分状态 | -| **Completed**(已完成) | 已结束的请求——已批准、已拒绝或已撤回 | -| **All**(全部) | 全部请求,按最近更新排序 | +| **待我审批** | 仍在待批、且你是审批人之一的请求 | +| **我提交的** | 你提交过的全部请求,不分状态 | +| **已完成** | 已结束的请求——已批准、已拒绝或已撤回 | +| **全部** | 全部请求,按最近更新排序 | 请把这四个当成第二块界面,不要和上面的页签混着读。有两个名字在两边一模一样,还有一对只差一个词: | 对象内置视图 | 审批中心页签 | | --- | --- | -| **My Pending**(待我审批) | **My Pending**(待我审批)——同名,但不是同一块界面 | -| **I Submitted**(我提交的) | **Submitted by me**(我发起的) | -| **Completed**(已完成) | *没有对应页签*——改用**状态**筛选 | -| **All**(全部) | **All**(全部)——同名,但不是同一块界面 | +| **待我审批** | **待我审批**——同名,但不是同一块界面 | +| **我提交的** | **我发起的** | +| **已完成** | *没有对应页签*——改用**状态**筛选 | +| **全部** | **全部**——同名,但不是同一块界面 | -本页过去列出的三个名字——*Pending My Approval*、*Submitted by Me*、*Recently Approved*——**哪里都不存在**:`src/` 与已安装的审批插件双面零命中,换任何拼写都一样。它们想描述的其实就是 **My Pending**、**I Submitted** 和 **Completed**。有一点是随这次改指向新出现的:审批中心的 **Submitted by me** 页签,与幻影名字 *Submitted by Me* 只差一个字母的大小写。那个页签是审批中心里真实存在的一块界面,但它从来不是对象上的视图。 +本页过去列出的三个名字——*Pending My Approval*、*Submitted by Me*、*Recently Approved*——**哪里都不存在**:`src/` 与已安装的审批插件双面零命中,换任何拼写都一样。它们想描述的其实就是 **待我审批**、**我提交的** 和 **已完成**。有一点是随这次改指向新出现的:审批中心的 **我发起的** 页签在英文界面上叫 *Submitted by me*,与幻影名字 *Submitted by Me* 只差一个字母的大小写。那个页签是审批中心里真实存在的一块界面,但它从来不是对象上的视图。 **Action History**(操作历史)同样不在这个分组里,本应用侧边栏上也没有任何条目叫这个名字。审批的操作留痕本身是真实数据——审批插件把每一次操作存为 `sys_approval_action`,还为它内置了 **Recent**(最近)/ **By Actor**(按执行人)/ **All**(全部)三个视图,每条操作都通过 `request_id` 指回它所属的请求。缺的只是入口:本应用里没有任何导航条目能打开这个对象。 diff --git a/content/docs/revenue/approvals.zh-Hant.mdx b/content/docs/revenue/approvals.zh-Hant.mdx index 9c16a48e..29d5c737 100644 --- a/content/docs/revenue/approvals.zh-Hant.mdx +++ b/content/docs/revenue/approvals.zh-Hant.mdx @@ -89,13 +89,13 @@ HotCRM 應用裡審批只有一個入口,它掛在 **My Work**(我的工作 該條目沒有綁定自己的檢視。它過去落在 `sys_approval_request` 物件的列表頁上,而那個列表是唯讀的——看得到請求,卻動不了它——所以現在改成直接打開審批中心,也就是帶**批准** / **拒絕**按鈕的那塊介面。物件名仍然留在條目上,作用是在沒裝審批外掛的環境裡把整個條目隱藏掉,但它已經不是這一項的落點了。 -審批中心自帶三個頁籤(審批中心同樣沒有 Hant 語言包,簡體介面下的字樣一併列出): +審批中心自帶三個頁籤: | 頁籤 | 內容 | | --- | --- | -| **My Pending**(簡體介面「待我审批」) | 等你做決定的請求,頁籤上帶數量角標 | -| **Submitted by me**(簡體介面「我发起的」) | 你發起的請求,不分狀態 | -| **All**(簡體介面「全部」) | 你有權看到的全部請求 | +| **待我審批** | 等你做決定的請求,頁籤上帶數量角標 | +| **我發起的** | 你發起的請求,不分狀態 | +| **全部** | 你有權看到的全部請求 | 在任一頁籤內部,還有一個獨立的**狀態**(Status,簡體介面「状态」)篩選可以繼續收窄:*All statuses*、**Pending**、**Approved**、**Rejected**、**Recalled**、**Returned for revision**(簡體介面依序為「全部状态」「待审批」「已通过」「已拒绝」「已撤回」「已退回修改」)。 @@ -105,21 +105,21 @@ HotCRM 應用裡審批只有一個入口,它掛在 **My Work**(我的工作 | 物件內建檢視 | 內容 | | --- | --- | -| **My Pending**(簡體介面「待我审批」) | 仍在待批、且你是審批人之一的請求 | -| **I Submitted**(簡體介面「我提交的」) | 你提交過的全部請求,不分狀態 | -| **Completed**(簡體介面「已完成」) | 已結束的請求——已批准、已拒絕或已撤回 | -| **All**(簡體介面「全部」) | 全部請求,按最近更新排序 | +| **待我審批** | 仍在待批、且你是審批人之一的請求 | +| **我提交的** | 你提交過的全部請求,不分狀態 | +| **已完成** | 已結束的請求——已批准、已拒絕或已撤回 | +| **全部** | 全部請求,按最近更新排序 | 請把這四個當成第二塊介面,不要和上面的頁籤混著讀。有兩個名字在兩邊一模一樣,還有一對只差一個詞: | 物件內建檢視 | 審批中心頁籤 | | --- | --- | -| **My Pending** | **My Pending**——同名,但不是同一塊介面 | -| **I Submitted**(簡體介面「我提交的」) | **Submitted by me**(簡體介面「我发起的」) | -| **Completed** | *沒有對應頁籤*——改用**狀態**篩選 | -| **All** | **All**——同名,但不是同一塊介面 | +| **待我審批** | **待我審批**——同名,但不是同一塊介面 | +| **我提交的** | **我發起的** | +| **已完成** | *沒有對應頁籤*——改用**狀態**篩選 | +| **全部** | **全部**——同名,但不是同一塊介面 | -本頁過去列出的三個名字——*Pending My Approval*、*Submitted by Me*、*Recently Approved*——**哪裡都不存在**:`src/` 與已安裝的審批外掛雙面零命中,換任何拼寫都一樣。它們想描述的其實就是 **My Pending**、**I Submitted** 和 **Completed**。有一點是隨這次改指向新出現的:審批中心的 **Submitted by me** 頁籤,與幻影名字 *Submitted by Me* 只差一個字母的大小寫。那個頁籤是審批中心裡真實存在的一塊介面,但它從來不是物件上的檢視。 +本頁過去列出的三個名字——*Pending My Approval*、*Submitted by Me*、*Recently Approved*——**哪裡都不存在**:`src/` 與已安裝的審批外掛雙面零命中,換任何拼寫都一樣。它們想描述的其實就是 **待我審批**、**我提交的** 和 **已完成**。有一點是隨這次改指向新出現的:審批中心的 **我發起的** 頁籤在英文介面上叫 *Submitted by me*,與幻影名字 *Submitted by Me* 只差一個字母的大小寫。那個頁籤是審批中心裡真實存在的一塊介面,但它從來不是物件上的檢視。 **Action History**(操作歷史)同樣不在這個群組裡,本應用側邊欄上也沒有任何條目叫這個名字。審批的操作留痕本身是真實資料——審批外掛把每一次操作存為 `sys_approval_action`,還為它內建了 **Recent**(最近)/ **By Actor**(按執行人)/ **All**(全部)三個檢視,每條操作都透過 `request_id` 指回它所屬的請求。缺的只是入口:本應用裡沒有任何導覽條目能打開這個物件。 From c194a60c09701e4af6d11ba04019e166d78f0704 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 02:04:15 +0000 Subject: [PATCH 2/2] test(docs-nav): approvals pins each face's request views in its own language (#1552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ruling C spells plugin-owned view names from the owner's zh-CN pack, so the zh pages no longer carry the English view labels or the English-plus-gloss form. The section check now takes each face's labels: en from the schema, zh-Hans from the plugin's zh-CN block (read off the decoded bundle, as docs-search-navigation-views does), zh-Hant by character conversion. The zh quotedFromConsole fixtures drop the retired English 'My Pending' and, on zh-Hant, the simplified 我发起的 the page no longer spells. No guard added. Maintainer scope: 「1971 可以改测试,繁体用繁体」. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X8U3asekbiC7yWoEPWR4Dg --- .../docs-revenue-approvals-navigation.test.ts | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/test/docs-revenue-approvals-navigation.test.ts b/test/docs-revenue-approvals-navigation.test.ts index 51ac5aa8..42a88075 100644 --- a/test/docs-revenue-approvals-navigation.test.ts +++ b/test/docs-revenue-approvals-navigation.test.ts @@ -3,6 +3,7 @@ import { describe, it, expect } from 'vitest'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; +import { createRequire } from 'node:module'; import { SysApprovalAction, SysApprovalRequest } from '@objectstack/plugin-approvals'; import { REPO_ROOT } from './helpers/repo-root'; import { CrmApp } from '../src/sales/apps/crm.app'; @@ -120,6 +121,44 @@ const viewLabels = (schema: AnyRec): string[] => const REQUEST_VIEWS = viewLabels(SysApprovalRequest as unknown as AnyRec); const ACTION_VIEWS = viewLabels(SysApprovalAction as unknown as AnyRec); +/** + * The zh-CN labels of the request views, from the plugin's own pack — a Chinese + * page spells a view name as the zh-CN value of the pack that owns the object + * (#1552). The package exports its schemas but not its locale packs, so the + * labels are read off the built bundle with its `\uXXXX` escapes decoded, the + * same read `docs-search-navigation-views.test.ts` makes, and only from the + * zh-CN block so a ja-JP or es-ES label can never satisfy it. + */ +const ZH_CN_PLUGIN_OBJECTS: string = (() => { + const entry = createRequire(import.meta.url).resolve('@objectstack/plugin-approvals'); + const raw = readFileSync(entry, 'utf8').replace(/\\u([0-9a-fA-F]{4})/g, (_m, hex) => + String.fromCharCode(parseInt(hex, 16)), + ); + const start = raw.indexOf('zhCNObjects ='); + if (start < 0) { + throw new Error('the approvals plugin bundle has no zhCNObjects block — this pin is out of date'); + } + const end = raw.slice(start + 1).search(/\b[a-z]{2}[A-Z]{2}Objects =/); + return end < 0 ? raw.slice(start) : raw.slice(start, start + 1 + end); +})(); + +const ZH_HANS_REQUEST_VIEWS: string[] = Object.keys( + ((SysApprovalRequest as unknown as AnyRec).listViews ?? {}) as Record, +).map((key) => { + const m = ZH_CN_PLUGIN_OBJECTS.match(new RegExp(`\\b${key}:\\s*\\{\\s*label:\\s*"([^"]+)"`)); + if (!m) throw new Error(`the approvals plugin ships no zh-CN label for view '${key}'`); + return m[1]; +}); + +/** + * No pack ships Traditional script, so the zh-Hant page spells the same labels + * by character conversion only (no word swap) — the convention AGENTS.md sets + * for zh-Hant view names. Only the characters these labels use are mapped. + */ +const ZH_HANT_REQUEST_VIEWS = ZH_HANS_REQUEST_VIEWS.map((l) => + l.replace(/审/g, '審').replace(/发/g, '發'), +); + /** * Which destination the Inbox entry actually carries, reduced to the * discriminant the doc pins key on. Anything unrecognised stays distinct, so a @@ -194,6 +233,8 @@ const PAGES = [ file: 'content/docs/revenue/approvals.mdx', lang: 'en', heading: '## Where to find pending approvals', + /** The request views' labels, in this page's language. */ + requestViews: REQUEST_VIEWS, /** The denials the section must actually make, not merely imply. */ denials: [ /\*Approval Requests\* is not a navigation entry anywhere in this app/, @@ -226,6 +267,7 @@ const PAGES = [ file: 'content/docs/revenue/approvals.zh-Hans.mdx', lang: 'zh-Hans', heading: '## 在哪里找到待处理的审批', + requestViews: ZH_HANS_REQUEST_VIEWS, denials: [ /应用里没有任何导航条目叫 \*Approval Requests\*/, /哪里都不存在/, @@ -240,7 +282,6 @@ const PAGES = [ // actually renders, so those are pinned too — 全部 is left out on purpose, // a bare "all" matches any bundle and would pin nothing. quotedFromConsole: [ - 'My Pending', 'Submitted by me', '待我审批', '我发起的', @@ -262,6 +303,7 @@ const PAGES = [ file: 'content/docs/revenue/approvals.zh-Hant.mdx', lang: 'zh-Hant', heading: '## 在哪裡找到待處理的審批', + requestViews: ZH_HANT_REQUEST_VIEWS, denials: [ /應用裡沒有任何導覽條目叫 \*Approval Requests\*/, /哪裡都不存在/, @@ -272,14 +314,13 @@ const PAGES = [ 'approval-centre': '進入平台的**審批中心**', 'request-object-list': '所以點開後落在物件的列表頁上', }, - // Neither the app nor the console ships a Hant pack, so this page quotes the - // simplified strings verbatim inside 「」 — the same convention the rest of - // the file uses, and the reason these are the simplified spellings. + // Neither the app nor the console ships a Hant pack. Strings quoted inside + // 「」 stay in the simplified spelling the console renders; the tab and view + // names themselves are spelled in Traditional script (#1552), so the views + // are pinned by `requestViews` rather than against the console bundle. quotedFromConsole: [ - 'My Pending', 'Submitted by me', '待我审批', - '我发起的', '全部状态', '待审批', '已通过', @@ -483,7 +524,17 @@ describe('the source facts the approvals navigation section rests on (#963)', () describe('revenue/approvals names the navigation that exists (#963, #1162)', () => { describe.each(PAGES)( '$file', - ({ file, heading, denials, attribution, destinationClaims, quotedFromConsole, collision, retired }) => { + ({ + file, + heading, + requestViews, + denials, + attribution, + destinationClaims, + quotedFromConsole, + collision, + retired, + }) => { const section = () => sectionOf(file, heading); it('names the real sidebar item', () => { @@ -550,9 +601,9 @@ describe('revenue/approvals names the navigation that exists (#963, #1162)', () expect(section(), `${file}: no collision warning between the two lists`).toMatch(collision); }); - it('still lists every built-in view of the approval request, by its source label', () => { + it('still lists every built-in view of the approval request, in its own language', () => { const text = section(); - const missing = REQUEST_VIEWS.filter((l) => !text.includes(l)); + const missing = requestViews.filter((l) => !text.includes(l)); expect( missing, `${file}: the section omits list view(s) the approvals plugin still ships — they are no ` +