Skip to content

Commit 7c164cf

Browse files
i18n(plugin-approvals): ship the approval objects, notifications, record page and e-mail link line in zh-Hant, ar, fr and pt-BR (#30790)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D7v5H7BaFmL7sUCoN7tSxhm)_ domain:server#1 · os-elon-musk · session_016WV4cya5ej5GQV86RPbRPK Part of #30512 — package 9 of 11, first half (`@objectstack/plugin-approvals` in `zh-Hant`, `ar`, `fr`, `pt-BR`). The card stays open: the second half (`ru`, `de`, `ko`), settings and platform-objects follow, and the last PR of the series makes the bundle check read the platform locale table. Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么:** 审批插件的 3 个对象(审批请求、审批动作、审批委派)的名称、字段、选项、帮助和动作,审批通知的 40 条句子,审批请求记录页的标题和「详情 / 时间线」两个标签,以及审批邮件里「打开审批」那一行,补上繁中、阿语、法语、巴葡 4 种。俄语、德语、韩语在下半包。 - **为什么改:** 平台语言表要求每个界面带齐 11 种语言;这是第 3 期按包补齐的第 9 个包的上半(每种语言 164 条对象文本 + 40 条消息)。用这 4 种语言的审批人和提交人看到的审批界面、通知和邮件不再是英文。 - **风险与回滚:** 只加翻译数据和注册行,不动逻辑、schema、接口;邮件模板的结构、选模板的规则和 `EMAIL_TEMPLATE_FLOOR_LOCALE` 都没动,只给「打开审批」那一行加了 4 种语言(按裁决 A)。原有 4 种语言一字未改。测试改动只是各语言测试把 4 种新语言加进列表(覆盖变宽,没有放宽任何断言)。回滚即撤 PR。 - **先看:** `src/translations/index.ts`(按表标签注册,zh-CN 在 zh-Hant 前,每种语言带页面标题和消息)、`src/approval-email-templates.ts` 的 `APPROVAL_EMAIL_LINK_LINES`、`src/translations/messages.ts`,再看 `sys-approval-request-page.test.ts`。 - **两分钟验证:** `node scripts/check-i18n-bundles.mjs --filter=approvals` 输出 `plugins/plugin-approvals in sync`;`pnpm --filter @objectstack/plugin-approvals test` 全绿。 - **需要你做的:** 无,席位落地。 ## What changes - Four new locales under the platform locale table's tags (`zh-Hant`, `ar`, `fr`, `pt-BR`): each a `TAG.objects.generated.ts` with all 164 object leaves of `sys_approval_request`, `sys_approval_action` and `sys_approval_delegation`, plus its `TAG.source-hashes.generated.ts`, written by the repo's own `os i18n extract --fill=default --source-hashes` and served through `withSourceFallback` like the existing three. `zh-CN` stays ahead of `zh-Hant` in `index.ts`. - The hand-written records gain an entry per new locale: `messages.ts` (the 40 approval notification sentences; `approvalMessages` widens to eight locales), `pages.ts` (the record page label `sys_approval_request_detail`, recorded against the same `en` digest the existing three carry), and the record page's inline Details / Timeline labels in `sys-approval-request.page.ts`. - `approval-email-templates.ts`: `APPROVAL_EMAIL_LINK_LINES` (the line that opens the request from its e-mail, `{{approvalUrl}}` kept byte-identical) gains the four locales. This follows the seat's ruling A, as recorded in the handover commit `7db48bdd`: the link line is added per locale; the e-mail templates' shape, the template selection and `EMAIL_TEMPLATE_FLOOR_LOCALE` are unchanged. - The extract command in `scripts/i18n-extract.config.ts` lists the four tags and its merge baseline imports the four bundles with their page entries, so `check:i18n` covers them from this PR on. ## Tests - `sys-approval-request-page.test.ts` pins the served locale set; it now lists the eight locales this PR serves, and every served locale is still held to carrying the authored page label. - `approval-status-vocabulary.test.ts`, `approve-decision-question.test.ts`, `decision-question-i18n.test.ts`, `success-copy-names-request-i18n.test.ts` and `admin-copy-vocabulary.test.ts` add the four new bundles to their locale lists. Coverage widens; no assertion changed. ## Acceptance notes - **Quality bar (#17508 / PR #19345):** every leaf written for the reader; placeholders, code spans and identifiers byte-identical, checked mechanically per leaf against `en` for the object bundles and for `messages.ts` (key set, `{{holes}}`, `{holes}`, backtick spans, angle tokens: all four locales OK, zero diffs). Arabic is RTL prose with identifiers left in Latin script. `zh-Hant` was checked character by character against OpenCC's Traditional (Taiwan) conversion: no Simplified-only character remains (核准 keeps 准, the Taiwan usage). - **Leaves equal to English, on purpose, each recorded by the extractor as a source copy:** `fr` `Source`, `Note`, `Action`; `pt-BR` `Status`. All are the native words. In `messages.ts`, `pt-BR`'s reason separator `"; "` equals the English one. - No cross-locale coincidence: `check:i18n-stale-fill` passes with the baseline untouched. - Carried unchanged from the released handover commit `7db48bdd` (session `session_01Utp6Cai5CRdAs4u9gTipWU`): the patch id of this PR's diff equals that commit's (`7e31c844`). The serial row "after #30208 lands" was cleared by the seat (6672288503): #30208's rows that touch this package landed in PR #30551. - The existing `ja-JP` / `es-ES` bundles still carry English source copies on `sys_approval_delegation`; this PR does not touch the existing three locales. ## Verification <details><summary>Package suite, typecheck, build (verify lock)</summary> ``` turbo run build --filter=!@objectstack/docs: 118/118 tasks successful, exit 0 pnpm --filter @objectstack/plugin-approvals test && typecheck (at head 63d63a1, on main 561d532) Test Files 115 passed (115) Tests 1492 passed (1492) check:test-typecheck: OK (tsconfig.test.json) os-verify-lock: VERDICT command-exit 0 same suite at 4a8b606d (the same patch on main adf6ee0): 115 files, 1492 passed, VERDICT command-exit 0 ``` </details> <details><summary>i18n gates and the derived families</summary> ``` node scripts/check-i18n-bundles.mjs --filter=approvals (at 63d63a1) plugins/plugin-approvals in sync (15 bundle(s)) EXIT 0 node scripts/check-i18n-stale-fill.mjs (at 63d63a1) check-i18n-stale-fill: OK (11 bundle set(s) — no new stale fills, 1 baselined). EXIT 0 dispatch-gates --commands (at 4a8b606d): 61 families, each run and recorded with its exit code: 61 x exit 0 dispatch-gates --ran: 61 derived, 61 run, 0 NOT-MEASURED (a derived zero) re-derived at 63d63a1: the identical 61-command list pnpm lint (repo-wide, at 4a8b606d): exit 0 eslint --no-inline-config on the changed .ts files: exit 0 changed lines 1962 (+1950 / -12), 21 files: under the 3000-line human-merge threshold ``` </details> The branch is one commit on `main` `561d532c` (after #30767 landed). Its diff has the same patch id (`7e31c844`) as `4a8b606d`, where the derived families ran on main `adf6ee09`; none of the six main commits in between touches `plugin-approvals`. The package suite and both i18n checks were re-run on this head. Dogfood: not run — content-only package PR, no `!` in the title and no `(narrowing)` arm. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cya5ej5GQV86RPbRPK)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 172eebb commit 7c164cf

21 files changed

Lines changed: 1950 additions & 12 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
---
4+
5+
i18n(plugin-approvals): the approval objects, notifications, record page and e-mail link line ship in Traditional Chinese, Arabic, French and Brazilian Portuguese
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The approval objects (`sys_approval_request`, `sys_approval_action`, `sys_approval_delegation`), the approval notifications' sentences, the approval request record page (its label and its Details / Timeline tabs) and the line that opens an approval from its e-mail now carry `zh-Hant`, `ar`, `fr` and `pt-BR` beside the existing `en`, `zh-CN`, `ja-JP` and `es-ES`, so an approver or submitter in one of those languages reads the approval screens, notices and e-mails in their own language instead of English. Each new locale is registered under its platform-table tag with its source-hash companion, its page entry and its messages, and the package's extract command lists the tags, so the existing `check:i18n` bundle check covers them. The e-mail templates' shape, the template selection and `EMAIL_TEMPLATE_FLOOR_LOCALE` are unchanged; only the link line is added per locale. First half of package 9 of #30512; `ru`, `de` and `ko` follow in the second half.

‎packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* strings were seeded from @objectstack/platform-objects.)
99
*
1010
* os i18n extract packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts \
11-
* --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \
11+
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr,pt-BR --fill=default --objects-only --no-metadata-forms \
1212
* --source-hashes \
1313
* --out=packages/plugins/plugin-approvals/src/translations
1414
*
@@ -60,6 +60,10 @@ import { enObjects } from '../src/translations/en.objects.generated.js';
6060
import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js';
6161
import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js';
6262
import { esESObjects } from '../src/translations/es-ES.objects.generated.js';
63+
import { zhHantObjects } from '../src/translations/zh-Hant.objects.generated.js';
64+
import { arObjects } from '../src/translations/ar.objects.generated.js';
65+
import { frObjects } from '../src/translations/fr.objects.generated.js';
66+
import { ptBRObjects } from '../src/translations/pt-BR.objects.generated.js';
6367
import { approvalsPageTranslations } from '../src/translations/pages.js';
6468

6569
/**
@@ -84,6 +88,10 @@ const config: ObjectStackDefinition = defineStack({
8488
{ 'zh-CN': { objects: zhCNObjects, pages: approvalsPageTranslations['zh-CN'] } },
8589
{ 'ja-JP': { objects: jaJPObjects, pages: approvalsPageTranslations['ja-JP'] } },
8690
{ 'es-ES': { objects: esESObjects, pages: approvalsPageTranslations['es-ES'] } },
91+
{ 'zh-Hant': { objects: zhHantObjects, pages: approvalsPageTranslations['zh-Hant'] } },
92+
{ ar: { objects: arObjects, pages: approvalsPageTranslations['ar'] } },
93+
{ fr: { objects: frObjects, pages: approvalsPageTranslations['fr'] } },
94+
{ 'pt-BR': { objects: ptBRObjects, pages: approvalsPageTranslations['pt-BR'] } },
8795
],
8896
});
8997

‎packages/plugins/plugin-approvals/src/admin-copy-vocabulary.test.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import { enObjects } from './translations/en.objects.generated.js';
2929
import { zhCNObjects } from './translations/zh-CN.objects.generated.js';
3030
import { jaJPObjects } from './translations/ja-JP.objects.generated.js';
3131
import { esESObjects } from './translations/es-ES.objects.generated.js';
32+
import { zhHantObjects } from './translations/zh-Hant.objects.generated.js';
33+
import { arObjects } from './translations/ar.objects.generated.js';
34+
import { frObjects } from './translations/fr.objects.generated.js';
35+
import { ptBRObjects } from './translations/pt-BR.objects.generated.js';
3236

3337
/** The design-record vocabulary an admin-facing string must not carry. */
3438
const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [
@@ -93,7 +97,7 @@ function isObjectSchema(v: unknown): v is ObjectLike {
9397
&& !!(v as ObjectLike).fields && typeof (v as ObjectLike).fields === 'object';
9498
}
9599

96-
const OBJECT_BUNDLES = { en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects } as const;
100+
const OBJECT_BUNDLES = { en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, 'ar': arObjects, 'fr': frObjects, 'pt-BR': ptBRObjects } as const;
97101

98102
describe('approval objects: admin-facing copy carries no design-record vocabulary', () => {
99103
it('the matcher recognises each kind of vocabulary it exists to keep out', () => {

‎packages/plugins/plugin-approvals/src/approval-email-templates.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,15 @@ export const APPROVAL_EMAIL_CHANNELS: readonly string[] = Object.freeze(['inbox'
5252
* The line that opens the request, per locale the approval catalog carries
5353
* (`translations/messages.ts`). `{{approvalUrl}}` is the payload member.
5454
*/
55-
export const APPROVAL_EMAIL_LINK_LINES: Readonly<Record<'en' | 'zh-CN' | 'ja-JP' | 'es-ES', string>> = {
55+
export const APPROVAL_EMAIL_LINK_LINES: Readonly<Record<'en' | 'zh-CN' | 'ja-JP' | 'es-ES' | 'zh-Hant' | 'ar' | 'fr' | 'pt-BR', string>> = {
5656
en: 'Open the approval: {{approvalUrl}}',
5757
'zh-CN': '打开审批:{{approvalUrl}}',
5858
'ja-JP': '承認を開く: {{approvalUrl}}',
5959
'es-ES': 'Abrir la aprobación: {{approvalUrl}}',
60+
'zh-Hant': '開啟核准:{{approvalUrl}}',
61+
ar: 'افتح الموافقة: {{approvalUrl}}',
62+
fr: 'Ouvrir l’approbation : {{approvalUrl}}',
63+
'pt-BR': 'Abrir a aprovação: {{approvalUrl}}',
6064
};
6165

6266
/** The e-mail templates: every emailed topic in every catalog locale. */

‎packages/plugins/plugin-approvals/src/sys-approval-request-page.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ describe('sys_approval_request detail page — its label in the translation bund
308308

309309
it('every served locale carries the label, and a translated locale serves its translation', () => {
310310
const locales = Object.keys(ApprovalsTranslations).sort();
311-
expect(locales).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN']);
311+
expect(locales).toEqual(['ar', 'en', 'es-ES', 'fr', 'ja-JP', 'pt-BR', 'zh-CN', 'zh-Hant']);
312312
for (const locale of locales) {
313313
const served = (ApprovalsTranslations as AnyRec)[locale]?.pages?.[NAME]?.label;
314314
const authored = (approvalsPageTranslations as AnyRec)[locale]?.[NAME]?.label;

‎packages/plugins/plugin-approvals/src/sys-approval-request.page.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ export const SysApprovalRequestDetailPage: Page = {
122122
position: 'top',
123123
items: [
124124
{
125-
label: { en: 'Details', 'zh-CN': '详情', 'ja-JP': '詳細', 'es-ES': 'Detalles' },
125+
label: { en: 'Details', 'zh-CN': '详情', 'ja-JP': '詳細', 'es-ES': 'Detalles', 'zh-Hant': '詳細資料', 'ar': 'التفاصيل', 'fr': 'Détails', 'pt-BR': 'Detalhes' },
126126
icon: 'file-text',
127127
children: [{ type: 'record:details' }],
128128
},
129129
{
130-
label: { en: 'Timeline', 'zh-CN': '时间线', 'ja-JP': 'タイムライン', 'es-ES': 'Cronología' },
130+
label: { en: 'Timeline', 'zh-CN': '时间线', 'ja-JP': 'タイムライン', 'es-ES': 'Cronología', 'zh-Hant': '時間軸', 'ar': 'المخطط الزمني', 'fr': 'Chronologie', 'pt-BR': 'Linha do tempo' },
131131
icon: 'history',
132132
children: [
133133
{
@@ -139,7 +139,7 @@ export const SysApprovalRequestDetailPage: Page = {
139139
sort: [{ field: 'created_at', order: 'desc' }],
140140
limit: 50,
141141
showViewAll: true,
142-
title: { en: 'Timeline', 'zh-CN': '时间线', 'ja-JP': 'タイムライン', 'es-ES': 'Cronología' },
142+
title: { en: 'Timeline', 'zh-CN': '时间线', 'ja-JP': 'タイムライン', 'es-ES': 'Cronología', 'zh-Hant': '時間軸', 'ar': 'المخطط الزمني', 'fr': 'Chronologie', 'pt-BR': 'Linha do tempo' },
143143
},
144144
},
145145
],

‎packages/plugins/plugin-approvals/src/translations/approval-status-vocabulary.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('approval status vocabulary (#7232)', () => {
115115
// Ratchet for statuses and locales added later: a new entry that reaches a
116116
// bundle un-translated is seeded from the source text, which is the enum
117117
// value itself, so this case catches it without naming it.
118-
for (const locale of ['en', 'zh-CN', 'ja-JP', 'es-ES']) {
118+
for (const locale of ['en', 'zh-CN', 'ja-JP', 'es-ES', 'zh-Hant', 'ar', 'fr', 'pt-BR']) {
119119
const labels = resolvedStatusLabels(locale);
120120
expect(Object.keys(labels).sort()).toEqual([...APPROVAL_STATUSES].sort());
121121
const raw = Object.entries(labels)

‎packages/plugins/plugin-approvals/src/translations/approve-decision-question.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import { enObjects } from './en.objects.generated.js';
2222
import { zhCNObjects } from './zh-CN.objects.generated.js';
2323
import { jaJPObjects } from './ja-JP.objects.generated.js';
2424
import { esESObjects } from './es-ES.objects.generated.js';
25+
import { zhHantObjects } from './zh-Hant.objects.generated.js';
26+
import { arObjects } from './ar.objects.generated.js';
27+
import { frObjects } from './fr.objects.generated.js';
28+
import { ptBRObjects } from './pt-BR.objects.generated.js';
2529
import { SysApprovalRequest } from '../sys-approval-request.object.js';
2630

2731
interface CopyAction {
@@ -45,6 +49,10 @@ const TRANSLATED = [
4549
['zh-CN', zhCNObjects],
4650
['ja-JP', jaJPObjects],
4751
['es-ES', esESObjects],
52+
['zh-Hant', zhHantObjects],
53+
['ar', arObjects],
54+
['fr', frObjects],
55+
['pt-BR', ptBRObjects],
4856
] as const;
4957

5058
describe('approval_approve — the decision question', () => {

0 commit comments

Comments
 (0)