Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/zh-approvals-plugin-view-names.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 12 additions & 12 deletions content/docs/revenue/approvals.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ HotCRM 应用里审批只有一个入口,它挂在 **My Work**(我的工作

| 页签 | 内容 |
| --- | --- |
| **My Pending**(待我审批) | 等你做决定的请求,页签上带数量角标 |
| **Submitted by me**(我发起的) | 你发起的请求,不分状态 |
| **All**(全部) | 你有权看到的全部请求 |
| **待我审批** | 等你做决定的请求,页签上带数量角标 |
| **我发起的** | 你发起的请求,不分状态 |
| **全部** | 你有权看到的全部请求 |

在任一页签内部,还有一个独立的**状态**(Status)筛选可以继续收窄:*全部状态*、**待审批**、**已通过**、**已拒绝**、**已撤回**、**已退回修改**。

Expand All @@ -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` 指回它所属的请求。缺的只是入口:本应用里没有任何导航条目能打开这个对象。

Expand Down
26 changes: 13 additions & 13 deletions content/docs/revenue/approvals.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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**(簡體介面依序為「全部状态」「待审批」「已通过」「已拒绝」「已撤回」「已退回修改」)。

Expand All @@ -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` 指回它所屬的請求。缺的只是入口:本應用裡沒有任何導覽條目能打開這個物件。

Expand Down
69 changes: 60 additions & 9 deletions test/docs-revenue-approvals-navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string, AnyRec>,
).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
Expand Down Expand Up @@ -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/,
Expand Down Expand Up @@ -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\*/,
/哪里都不存在/,
Expand All @@ -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',
'待我审批',
'我发起的',
Expand All @@ -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\*/,
/哪裡都不存在/,
Expand All @@ -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',
'待我审批',
'我发起的',
'全部状态',
'待审批',
'已通过',
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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 ` +
Expand Down
Loading