Skip to content

Commit 2eb658e

Browse files
committed
fix: Setup serves administrator wording and Chinese queue, mail status and test-toast text
Rows 1-3: business wording in the help on permission set and position assignments, out-of-office rules, business units, organization and user forms (English source and zh-CN). Row 7: sys_queue and sys_queue_member join plugin-sharing's translation bundle, with zh-CN. Row 8: the mail live-status statement and the Send test email answer are written in the caller's display language; settings action handlers now receive that locale. Claude-Session: https://claude.ai/code/session_01Utp6CbDNK83SuSb8zsRt2s Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0f39075 commit 2eb658e

25 files changed

Lines changed: 442 additions & 96 deletions

‎packages/platform-objects/src/apps/translations/en.objects.generated.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
3939
},
4040
banned: {
4141
label: "Banned",
42-
help: "When true, the user cannot sign in. Toggle via Ban User / Unban User actions."
42+
help: "When on, the user cannot sign in. Use the Ban User / Unban User actions to change it."
4343
},
4444
ban_reason: {
4545
label: "Ban Reason"
@@ -70,7 +70,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
7070
},
7171
must_change_password: {
7272
label: "Must Change Password",
73-
help: "When true, the user is blocked (403 PASSWORD_RESET_REQUIRED) until they change their password. Stamped by the admin user-management routes; system-managed."
73+
help: "When on, the user must change their password before they can continue. Turned on when an administrator creates or imports the account, or resets its password; system-managed."
7474
},
7575
mfa_required_at: {
7676
label: "MFA Required At",
@@ -557,7 +557,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
557557
},
558558
require_mfa: {
559559
label: "Require Multi-Factor Auth",
560-
help: "When true, every member of this organization must enroll an authenticator app to access data."
560+
help: "Turn on to require every member of this organization to set up an authenticator app before they can access data."
561561
},
562562
timezone: {
563563
label: "Timezone",
@@ -995,7 +995,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
995995
},
996996
kind: {
997997
label: "Kind",
998-
help: "Categorisation hint — does not change graph semantics.",
998+
help: "For grouping and reporting only; it does not change where the unit sits in the organization or who it covers.",
999999
options: {
10001000
company: "Company",
10011001
division: "Division",
@@ -1006,7 +1006,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
10061006
},
10071007
parent_business_unit_id: {
10081008
label: "Parent Business Unit",
1009-
help: "Self-reference for the org tree. Null = root of tenant. A unit that still has child units cannot be deleted until they are re-parented or deleted."
1009+
help: "The unit this one sits under. Leave empty for a top-level unit. A unit that still has units under it cannot be deleted until they are moved under another unit or deleted."
10101010
},
10111011
timezone: {
10121012
label: "Timezone",
@@ -1022,19 +1022,19 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
10221022
},
10231023
active: {
10241024
label: "Active",
1025-
help: "When false, members are not expanded by graph queries."
1025+
help: "Turn off to retire the unit: sharing and other access given to this unit, or to a unit above it, no longer reaches its members or the units under it."
10261026
},
10271027
effective_from: {
10281028
label: "Effective From",
1029-
help: "When this business unit came into existence (HRIS sync)."
1029+
help: "When this business unit came into existence. Usually filled in by a sync from your HR system."
10301030
},
10311031
effective_to: {
10321032
label: "Effective To",
1033-
help: "When this business unit was retired (HRIS sync)."
1033+
help: "When this business unit was retired. Usually filled in by a sync from your HR system."
10341034
},
10351035
external_ref: {
10361036
label: "External Reference",
1037-
help: "ID in upstream HRIS (Workday / SAP HR / 北森)."
1037+
help: "The unit's ID in your HR system (such as Workday, SAP HR or 北森), used to match it on each sync."
10381038
},
10391039
id: {
10401040
label: "Business Unit ID"

‎packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
3939
},
4040
banned: {
4141
label: "已封禁",
42-
help: "为 true 时,用户无法登录。可通过“封禁用户 / 解除封禁”操作切换。"
42+
help: "开启后,用户无法登录。可通过“封禁用户 / 解除封禁”操作切换。"
4343
},
4444
ban_reason: {
4545
label: "封禁原因"
@@ -70,7 +70,7 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
7070
},
7171
must_change_password: {
7272
label: "须修改密码",
73-
help: "为 true 时,用户会被阻止访问(403 PASSWORD_RESET_REQUIRED),直到修改密码。由管理员用户管理接口写入;系统维护。"
73+
help: "开启后,用户必须先修改密码才能继续使用。管理员创建账号、导入用户或重置密码时会自动开启;系统维护。"
7474
},
7575
mfa_required_at: {
7676
label: "MFA 强制生效时间",
@@ -557,7 +557,7 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
557557
},
558558
require_mfa: {
559559
label: "强制多因素认证",
560-
help: "为 true 时,该组织的每位成员都必须注册身份验证器 App 才能访问数据。"
560+
help: "开启后,该组织的每位成员都必须先绑定身份验证器 App,才能访问数据。"
561561
},
562562
timezone: {
563563
label: "时区",
@@ -995,7 +995,7 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
995995
},
996996
kind: {
997997
label: "类型",
998-
help: "分类提示——不会改变图谱语义。",
998+
help: "仅用于分类和报表,不会改变该单元在组织中的位置和覆盖范围。",
999999
options: {
10001000
company: "公司",
10011001
division: "事业部",
@@ -1006,7 +1006,7 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
10061006
},
10071007
parent_business_unit_id: {
10081008
label: "上级业务单元",
1009-
help: "组织树的自关联字段。Null 表示租户根节点。仍有下级单元的业务单元无法删除,须先将下级单元改挂到其他上级或删除。"
1009+
help: "该单元的上级单元。留空表示顶级单元。仍有下级单元的业务单元无法删除,须先将下级单元改挂到其他上级或删除。"
10101010
},
10111011
timezone: {
10121012
label: "时区",
@@ -1022,19 +1022,19 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
10221022
},
10231023
active: {
10241024
label: "启用",
1025-
help: "为 false 时,图谱查询不会展开该业务单元成员。"
1025+
help: "关闭后即停用该单元:授予该单元或其上级单元的共享等访问权限,将不再覆盖其成员及下级单元。"
10261026
},
10271027
effective_from: {
10281028
label: "生效时间",
1029-
help: "该业务单元生效的时间(HRIS 同步)。"
1029+
help: "该业务单元成立的时间,通常由 HR 系统同步填写。"
10301030
},
10311031
effective_to: {
10321032
label: "失效时间",
1033-
help: "该业务单元停用的时间(HRIS 同步)。"
1033+
help: "该业务单元撤销的时间,通常由 HR 系统同步填写。"
10341034
},
10351035
external_ref: {
10361036
label: "外部引用",
1037-
help: "上游 HRIS 中的 ID(Workday / SAP HR / 北森)。"
1037+
help: "该单元在 HR 系统(如 Workday、SAP HR、北森)中的编号,每次同步时用它对应到本单元。"
10381038
},
10391039
id: {
10401040
label: "业务单元 ID"

‎packages/platform-objects/src/identity/sys-business-unit.object.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export const SysBusinessUnit = ObjectSchema.create({
121121
label: 'Kind',
122122
required: true,
123123
defaultValue: 'department',
124-
description: 'Categorisation hint — does not change graph semantics.',
124+
description: 'For grouping and reporting only; it does not change where the unit sits in the organization or who it covers.',
125125
group: 'Identity',
126126
options: [
127127
{ value: 'company', label: 'Company' },
@@ -145,7 +145,7 @@ export const SysBusinessUnit = ObjectSchema.create({
145145
required: false,
146146
deleteBehavior: 'restrict',
147147
description:
148-
'Self-reference for the org tree. Null = root of tenant. A unit that still has child units cannot be deleted until they are re-parented or deleted.',
148+
'The unit this one sits under. Leave empty for a top-level unit. A unit that still has units under it cannot be deleted until they are moved under another unit or deleted.',
149149
group: 'Hierarchy',
150150
}),
151151

@@ -224,29 +224,29 @@ export const SysBusinessUnit = ObjectSchema.create({
224224
label: 'Active',
225225
required: false,
226226
defaultValue: true,
227-
description: 'When false, members are not expanded by graph queries.',
227+
description: 'Turn off to retire the unit: sharing and other access given to this unit, or to a unit above it, no longer reaches its members or the units under it.',
228228
group: 'Lifecycle',
229229
}),
230230

231231
effective_from: Field.datetime({
232232
label: 'Effective From',
233233
required: false,
234-
description: 'When this business unit came into existence (HRIS sync).',
234+
description: 'When this business unit came into existence. Usually filled in by a sync from your HR system.',
235235
group: 'Lifecycle',
236236
}),
237237

238238
effective_to: Field.datetime({
239239
label: 'Effective To',
240240
required: false,
241-
description: 'When this business unit was retired (HRIS sync).',
241+
description: 'When this business unit was retired. Usually filled in by a sync from your HR system.',
242242
group: 'Lifecycle',
243243
}),
244244

245245
external_ref: Field.text({
246246
label: 'External Reference',
247247
required: false,
248248
maxLength: 200,
249-
description: 'ID in upstream HRIS (Workday / SAP HR / 北森).',
249+
description: 'The unit\'s ID in your HR system (such as Workday, SAP HR or 北森), used to match it on each sync.',
250250
group: 'Lifecycle',
251251
}),
252252

‎packages/platform-objects/src/identity/sys-organization.object.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ export const SysOrganization = ObjectSchema.create({
401401
required: false,
402402
defaultValue: false,
403403
group: 'Configuration',
404-
description: 'When true, every member of this organization must enroll an authenticator app to access data.',
404+
description: 'Turn on to require every member of this organization to set up an authenticator app before they can access data.',
405405
}),
406406

407407
// #14238 — maintainer ruling 2026-09-02, option A (verbatim 「同意」): the

‎packages/platform-objects/src/identity/sys-user.object.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ export const SysUser = ObjectSchema.create({
981981
defaultValue: false,
982982
readonly: true, // ADR-0092 — toggled via Ban/Unban actions (session side effects)
983983
group: 'Account',
984-
description: 'When true, the user cannot sign in. Toggle via Ban User / Unban User actions.',
984+
description: 'When on, the user cannot sign in. Use the Ban User / Unban User actions to change it.',
985985
}),
986986

987987
ban_reason: Field.text({
@@ -1085,8 +1085,8 @@ export const SysUser = ObjectSchema.create({
10851085
readonly: true,
10861086
group: 'Admin',
10871087
description:
1088-
'When true, the user is blocked (403 PASSWORD_RESET_REQUIRED) until they change ' +
1089-
'their password. Stamped by the admin user-management routes; system-managed.',
1088+
'When on, the user must change their password before they can continue. ' +
1089+
'Turned on when an administrator creates or imports the account, or resets its password; system-managed.',
10901090
}),
10911091

10921092
// ADR-0069 D3 — when enforced MFA first applied to this user; starts the

‎packages/plugins/plugin-approvals/src/sys-approval-delegation.object.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export const SysApprovalDelegation = ObjectSchema.create({
137137
required: false,
138138
group: 'Delegation',
139139
description:
140-
'Rule is inactive AT and AFTER this instant (half-open [from, until), UTC). Null = never expires.',
140+
'The rule stops applying at this time (UTC). Leave empty for no end date.',
141141
}),
142142

143143
reason: Field.text({

‎packages/plugins/plugin-approvals/src/translations/en.objects.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
366366
},
367367
valid_until: {
368368
label: "Valid Until",
369-
help: "Rule is inactive AT and AFTER this instant (half-open [from, until), UTC). Null = never expires."
369+
help: "The rule stops applying at this time (UTC). Leave empty for no end date."
370370
},
371371
reason: {
372372
label: "Reason",

‎packages/plugins/plugin-approvals/src/translations/zh-CN.objects.generated.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
366366
},
367367
valid_until: {
368368
label: "失效时间",
369-
help: "此刻(含)及之后规则不生效(半开区间 [from, until),UTC)。留空 = 永不过期。"
369+
help: "规则到此时间即失效(按 UTC 计)。留空 = 永不过期。"
370370
},
371371
reason: {
372372
label: "原因",

‎packages/plugins/plugin-email/src/email-plugin.ts‎

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import type {
3535
import { bindEmailTemplateDoor, unbindEmailTemplateDoor } from './email-template-door.js';
3636
import { sweepStrandedOutbox, type OutboxSweepResult } from './outbox-sweep.js';
3737
import { readInternalHeadersJson } from './internal-header-readback.js';
38-
import { describeLiveMailTransport, type LiveMailTransportStatus } from './live-transport-status.js';
38+
import { describeLiveMailTransport, mailDeliveryCopy, type LiveMailTransportStatus } from './live-transport-status.js';
3939
import {
4040
EMAIL_LOG_OBJECTS,
4141
TENANT_EMAIL_OBJECT,
@@ -153,12 +153,17 @@ interface MailSettingsSurface {
153153
payload?: Record<string, unknown>;
154154
/** The caller's settings context; `userId` is the signed-in admin who pressed the button. */
155155
ctx?: { userId?: string };
156+
/** The caller's display language, which the answer is written in. Absent: English. */
157+
locale?: string;
156158
}) => Promise<unknown>,
157159
): void;
158160
/** State the live transport at the top of Setup → Email (service-settings' `registerLiveStatus`). */
159161
registerLiveStatus?(
160162
namespace: string,
161-
provider: () => Promise<LiveMailTransportStatus | undefined>,
163+
provider: (input?: {
164+
/** The caller's display language, which the statement is written in. Absent: English. */
165+
locale?: string;
166+
}) => Promise<LiveMailTransportStatus | undefined>,
162167
): void;
163168
}
164169

@@ -658,9 +663,9 @@ export class EmailServicePlugin implements Plugin {
658663
// each page read, after any re-apply still running, so it follows a
659664
// transport swap made from the same page.
660665
if (typeof settings.registerLiveStatus === 'function') {
661-
settings.registerLiveStatus('mail', async () => {
666+
settings.registerLiveStatus('mail', async (input) => {
662667
await Promise.allSettled([...applying]);
663-
return this.service ? describeLiveMailTransport(this.service) : undefined;
668+
return this.service ? describeLiveMailTransport(this.service, input?.locale) : undefined;
664669
});
665670
}
666671

@@ -681,7 +686,11 @@ export class EmailServicePlugin implements Plugin {
681686
// `from_email`.
682687
if (typeof settings.registerAction === 'function') {
683688
const svc = this.service;
684-
settings.registerAction('mail', 'test', async ({ values, payload, ctx: actionCtx }: any) => {
689+
settings.registerAction('mail', 'test', async ({ values, payload, ctx: actionCtx, locale }: any) => {
690+
// The answer is the toast the administrator reads: written in
691+
// the caller's display language (English when none is given).
692+
const callerLocale = typeof locale === 'string' ? locale : undefined;
693+
const copy = mailDeliveryCopy(callerLocale);
685694
const overrides = extractOverrides(payload);
686695
const merged: Record<string, unknown> = { ...(values ?? {}), ...overrides };
687696
const recipient = await resolveMailTestRecipient(payload, actionCtx?.userId, engine);
@@ -699,7 +708,8 @@ export class EmailServicePlugin implements Plugin {
699708
// (#5087 — this action used to report success for SMTP
700709
// while the live transport was still the LogTransport).
701710
let target: EmailService = svc;
702-
let tempDescription = '';
711+
/** The form's transport the test used, when not the live one — named in the answer. */
712+
let testedVia: string | undefined;
703713
/** One-shot SMTP transport built for this test — closed below. */
704714
let tempSmtp: SmtpTransport | undefined;
705715
const provider = String(merged.provider ?? 'smtp');
@@ -724,7 +734,7 @@ export class EmailServicePlugin implements Plugin {
724734
...(svc.options.persistence ? { persistence: svc.options.persistence } : {}),
725735
logger: ctx.logger,
726736
});
727-
tempDescription = ` via smtp (${smtp.host}:${smtp.port ?? 587})`;
737+
testedVia = `smtp (${smtp.host}:${smtp.port ?? 587})`;
728738
} catch (err: any) {
729739
return { ok: false, severity: 'error', message: `Failed to build SMTP transport: ${err?.message ?? String(err)}` };
730740
}
@@ -782,7 +792,7 @@ export class EmailServicePlugin implements Plugin {
782792
...(svc.options.persistence ? { persistence: svc.options.persistence } : {}),
783793
logger: ctx.logger,
784794
});
785-
tempDescription = ` via ${provider}`;
795+
testedVia = provider;
786796
} catch (err: any) {
787797
return { ok: false, severity: 'error', message: `Failed to build ${provider} transport: ${err?.message ?? String(err)}` };
788798
}
@@ -808,7 +818,7 @@ export class EmailServicePlugin implements Plugin {
808818
return {
809819
ok: false,
810820
severity: 'error',
811-
message: `Test send failed${tempDescription}: ${result.error ?? 'unknown transport error'}`,
821+
message: copy.testFailed(testedVia, result.error ?? 'unknown transport error'),
812822
};
813823
}
814824
// A LogTransport "send" is not a delivery. Say so instead of
@@ -817,8 +827,7 @@ export class EmailServicePlugin implements Plugin {
817827
return {
818828
ok: false,
819829
severity: 'warning',
820-
message: 'No delivery transport is active — the message was only logged and kept in the email log. '
821-
+ 'Configure an SMTP host (or an API provider) and save before testing.',
830+
message: copy.testLoggedLive,
822831
};
823832
}
824833
// The form's unsaved "None (log only)": the same severity and
@@ -828,15 +837,13 @@ export class EmailServicePlugin implements Plugin {
828837
return {
829838
ok: false,
830839
severity: 'warning',
831-
message: 'Nothing was delivered — the provider on the form is "None (log only)", so the test message '
832-
+ 'was only logged and kept in the email log; no mail left this machine. The live transport was '
833-
+ `not used (${describeLiveMailTransport(svc).label}); save the form to stop delivery.`,
840+
message: copy.testLoggedForm(describeLiveMailTransport(svc, callerLocale).label),
834841
};
835842
}
836843
return {
837844
ok: true,
838845
severity: 'info',
839-
message: `Sent test email to ${to}${tempDescription} (id=${result.id}).`,
846+
message: copy.testSent(to, testedVia, String(result.id)),
840847
};
841848
} catch (err: any) {
842849
return { ok: false, severity: 'error', message: err?.message ?? String(err) };

0 commit comments

Comments
 (0)