Skip to content
Merged
28 changes: 28 additions & 0 deletions .changeset/operation-message-zhcn-fullwidth-comma.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@objectstack/spec": patch
---

fix(spec): punctuate the `zh-CN` Operation Message Catalog with the full-width comma (#12717)

Every `zh-CN` entry in `BUILTIN_OPERATION_MESSAGES` separated its clauses with
U+002C (the ASCII comma) while ending the same sentence with U+3002 (the
full-width period) — two punctuation systems inside one sentence. In Chinese
typography the half-width comma reads as a lapse rather than as a style, and
these are strings a business user reads in a toast.

The two surfaces genuinely sit together on one screen: an approval action's own
description renders full-width (`plugin-approvals`'
`zh-CN.objects.generated.ts`, U+FF0C / U+FF1F) while a refusal from the same
feature rendered ASCII.

Twelve U+002C across the nine `zh-CN` entries — the five situation keys
(`permission_denied`, `record_access_denied`, `record_change_not_allowed`,
`record_write_denied`, `approval_recall_not_submitter`) and the four
`delete_restricted*` variants — become U+FF0C. Nothing else moves: the U+3002
sentence finals were already correct, the two U+3001 (、) enumeration commas in
the `_required` variants are correct Chinese and stay, and the `en`, `ja-JP` and
`es-ES` rows are untouched.

Text-face only — no key, placeholder, wire code, status or accept/reject
behaviour changes. A deployment that overrides `errors.<messageKey>` through a
`translation` is unaffected; only the built-in default moves.
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ describe('#7307 DELETE_RESTRICTED — user copy vs developer guidance', () => {
it('renders the toast sentence in the caller locale, with TRANSLATED labels', async () => {
const err = await refuseDelete(engine, 'zh-CN');
expect(err.message).toBe(
'该部门正被 1 条零星申请记录通过「申报部门」引用,且该字段为必填、无法清空,请先删除或改派这些记录。',
'该部门正被 1 条零星申请记录通过「申报部门」引用且该字段为必填、无法清空请先删除或改派这些记录。',
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const ADMIN_ZH = {
* itself cannot tell Chinese from English. Its twin lives in
* `packages/spec/src/system/operation-message.test.ts`; the two move together.
*/
const ZH_SENTENCE = '只有提交人可以撤回这条审批请求,如需撤回请联系提交人或管理员。';
const ZH_SENTENCE = '只有提交人可以撤回这条审批请求如需撤回请联系提交人或管理员。';

/** What `@objectstack/rest`'s `handleApprovalError` does to a thrown message. */
const WIRE_CODE = (msg: string) => /^FORBIDDEN/.test(msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe('#7414 — the 403 an end user reads', () => {
it('speaks the caller locale, not English', async () => {
const zh = await denyDelete('zh-CN');
expect(zh.message).toBe(BUILTIN_OPERATION_MESSAGES['zh-CN'].permission_denied);
expect(zh.message).toBe('您没有执行此操作的权限,如需访问请联系管理员。');
expect(zh.message).toBe('您没有执行此操作的权限如需访问请联系管理员。');

const ja = await denyDelete('ja-JP');
expect(ja.message).toBe(BUILTIN_OPERATION_MESSAGES['ja-JP'].permission_denied);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ const OWNER_ZH = ctxFor(U_OWNER, 'zh-CN');
* in `packages/spec/src/system/operation-message.test.ts`; the two move
* together.
*/
const ZH_SENTENCE = '您无权修改或删除这条记录,如需修改请联系该记录的负责人或管理员。';
const ZH_SENTENCE = '您无权修改或删除这条记录如需修改请联系该记录的负责人或管理员。';

/** The legacy hardcoded reason, kept as a literal so its ABSENCE is pinned. */
const LEGACY_EN = 'insufficient privileges to';
Expand Down
12 changes: 6 additions & 6 deletions packages/spec/src/system/operation-message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('operation message catalog', () => {

it('renders the caller locale, not English', () => {
const zh = renderOperationMessage({ messageKey: 'delete_restricted', params: PARAMS }, { locale: 'zh-CN' });
expect(zh).toBe('该部门正被 1 条零星申请记录通过「申报部门」引用,请先删除或改派这些记录。');
expect(zh).toBe('该部门正被 1 条零星申请记录通过「申报部门」引用请先删除或改派这些记录。');
});

it('falls back to en for an unknown locale, and en is the declared fallback', () => {
Expand Down Expand Up @@ -115,7 +115,7 @@ describe('operation message catalog — permission_denied (#7414)', () => {

it('renders the caller locale, not English', () => {
expect(renderOperationMessage({ messageKey: 'permission_denied' }, { locale: 'zh-CN' }))
.toBe('您没有执行此操作的权限,如需访问请联系管理员。');
.toBe('您没有执行此操作的权限如需访问请联系管理员。');
expect(renderOperationMessage({ messageKey: 'permission_denied' }, { locale: 'en' }))
.toBe('You do not have permission to perform this action. Contact your administrator if you need access.');
});
Expand Down Expand Up @@ -205,11 +205,11 @@ describe('operation message catalog — the row-level user copy (#7451)', () =>

it('renders the caller locale, not English', () => {
expect(renderOperationMessage({ messageKey: 'record_access_denied' }, { locale: 'zh-CN' }))
.toBe('您无权访问这条记录,如需访问请联系该记录的负责人或管理员。');
.toBe('您无权访问这条记录如需访问请联系该记录的负责人或管理员。');
expect(renderOperationMessage({ messageKey: 'record_access_denied' }, { locale: 'en' }))
.toBe('You do not have access to this record. Contact the person who owns it, or your administrator, if you need access.');
expect(renderOperationMessage({ messageKey: 'record_change_not_allowed' }, { locale: 'zh-CN' }))
.toBe('您无权将这条记录保存为当前填写的内容,请修改后重试,或联系管理员。');
.toBe('您无权将这条记录保存为当前填写的内容请修改后重试或联系管理员。');
});

it('matches a base language against a regional catalog key (ja → ja-JP)', () => {
Expand Down Expand Up @@ -322,11 +322,11 @@ describe('operation message catalog — sharing write denial and approvals recal

it('renders the caller locale, not English', () => {
expect(renderOperationMessage({ messageKey: 'record_write_denied' }, { locale: 'zh-CN' }))
.toBe('您无权修改或删除这条记录,如需修改请联系该记录的负责人或管理员。');
.toBe('您无权修改或删除这条记录如需修改请联系该记录的负责人或管理员。');
expect(renderOperationMessage({ messageKey: 'record_write_denied' }, { locale: 'en' }))
.toBe('You do not have access to change or delete this record. Contact the person who owns it, or your administrator, if you need to make changes.');
expect(renderOperationMessage({ messageKey: 'approval_recall_not_submitter' }, { locale: 'zh-CN' }))
.toBe('只有提交人可以撤回这条审批请求,如需撤回请联系提交人或管理员。');
.toBe('只有提交人可以撤回这条审批请求如需撤回请联系提交人或管理员。');
expect(renderOperationMessage({ messageKey: 'approval_recall_not_submitter' }, { locale: 'en' }))
.toBe('Only the person who submitted this approval request can recall it. Contact the submitter, or your administrator, if it needs to be recalled.');
});
Expand Down
18 changes: 9 additions & 9 deletions packages/spec/src/system/operation-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,19 +221,19 @@ export const BUILTIN_OPERATION_MESSAGES: Record<string, Record<string, string>>
'This {{object}} is still referenced by {{dependentObject}} record(s) through “{{field}}”, which is required and cannot be cleared. Delete or reassign them first.',
},
'zh-CN': {
permission_denied: '您没有执行此操作的权限,如需访问请联系管理员。',
record_access_denied: '您无权访问这条记录,如需访问请联系该记录的负责人或管理员。',
record_change_not_allowed: '您无权将这条记录保存为当前填写的内容,请修改后重试,或联系管理员。',
record_write_denied: '您无权修改或删除这条记录,如需修改请联系该记录的负责人或管理员。',
approval_recall_not_submitter: '只有提交人可以撤回这条审批请求,如需撤回请联系提交人或管理员。',
permission_denied: '您没有执行此操作的权限如需访问请联系管理员。',
record_access_denied: '您无权访问这条记录如需访问请联系该记录的负责人或管理员。',
record_change_not_allowed: '您无权将这条记录保存为当前填写的内容请修改后重试或联系管理员。',
record_write_denied: '您无权修改或删除这条记录如需修改请联系该记录的负责人或管理员。',
approval_recall_not_submitter: '只有提交人可以撤回这条审批请求如需撤回请联系提交人或管理员。',
delete_restricted:
'该{{object}}正被 {{count}} 条{{dependentObject}}记录通过「{{field}}」引用,请先删除或改派这些记录。',
'该{{object}}正被 {{count}} 条{{dependentObject}}记录通过「{{field}}」引用请先删除或改派这些记录。',
delete_restricted_required:
'该{{object}}正被 {{count}} 条{{dependentObject}}记录通过「{{field}}」引用,且该字段为必填、无法清空,请先删除或改派这些记录。',
'该{{object}}正被 {{count}} 条{{dependentObject}}记录通过「{{field}}」引用且该字段为必填、无法清空请先删除或改派这些记录。',
delete_restricted_opaque:
'该{{object}}正被{{dependentObject}}记录通过「{{field}}」引用,请先删除或改派这些记录。',
'该{{object}}正被{{dependentObject}}记录通过「{{field}}」引用请先删除或改派这些记录。',
delete_restricted_required_opaque:
'该{{object}}正被{{dependentObject}}记录通过「{{field}}」引用,且该字段为必填、无法清空,请先删除或改派这些记录。',
'该{{object}}正被{{dependentObject}}记录通过「{{field}}」引用且该字段为必填、无法清空请先删除或改派这些记录。',
},
'ja-JP': {
permission_denied: 'この操作を実行する権限がありません。アクセスが必要な場合は管理者にお問い合わせください。',
Expand Down
Loading