From e8d9055ec7ca343cb5b308a8019843f5c138f7e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 12:51:38 +0000 Subject: [PATCH 1/2] i18n(platform-objects): ship the platform objects, Setup, Studio and Account in zh-Hant Adds the Traditional Chinese bundles for every sys_* object, the metadata configuration forms, the Setup / Studio / Account navigation, the System Overview dashboard and datasets, and the platform pages, registers them in the served bundles behind the source-hash fallback, and adds zh-Hant to the user and organization pages' inline copy. The extract config documents zh-Hant in --locales, so check:i18n gates the new bundles. Tests that list the shipped locales now include zh-Hant; the page parity shape test accepts a script subtag; the nav pins in plugin-approvals, plugin-security and service-settings hold zh-Hant to the same agreement. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_016WV4cya5ej5GQV86RPbRPK --- .../30512-platform-objects-locales-zh-hant.md | 10 + .../test/platform-page-i18n-parity.test.ts | 9 +- .../scripts/i18n-extract.config.ts | 6 +- .../system-overview-null-label.test.ts | 1 + .../src/apps/dashboards/system.datasets.ts | 2 +- .../src/apps/setup-copy-vocabulary.test.ts | 10 +- .../apps/setup-nav-distinct-labels.test.ts | 3 +- .../apps/setup-nav-per-user-stores.test.ts | 3 +- .../app-nav-translation-parity.test.ts | 3 +- .../bu-rename-consistency.test.ts | 2 + .../confirm-question-carryover.test.ts | 2 + .../dashboard-header-children.test.ts | 2 + .../src/apps/translations/index.ts | 1 + .../metadata-forms-vocabulary.test.ts | 2 + .../repeater-row-properties.test.ts | 2 + .../setup-nav-dead-key-tombstone.test.ts | 3 +- .../apps/translations/setup.translation.ts | 10 +- .../src/apps/translations/source-hash.test.ts | 5 + .../zh-Hant.metadata-forms.generated.ts | 2815 ++++++++++++ .../translations/zh-Hant.objects.generated.ts | 3810 +++++++++++++++++ .../zh-Hant.source-hashes.generated.ts | 61 + .../translations/zh-Hant.source-hashes.ts | 195 + .../src/apps/translations/zh-Hant.ts | 208 + .../identity/invitation-delivery-copy.test.ts | 2 + .../identity/invitation-screens-copy.test.ts | 2 + .../identity/invite-user-action-copy.test.ts | 2 + .../identity/sys-api-key-action-copy.test.ts | 2 + .../transfer-ownership-action-copy.test.ts | 2 + .../src/metadata-translations/index.ts | 7 +- .../src/pages/sys-organization.page.ts | 12 +- .../src/pages/sys-user-details-tab.test.ts | 6 +- .../src/pages/sys-user.page.ts | 58 +- .../src/plugin.translations.test.ts | 4 +- .../approval-status-vocabulary.test.ts | 3 +- ...le-org-assignment-nav-contribution.test.ts | 4 +- .../src/setup-nav-hub-parity.test.ts | 2 +- 36 files changed, 7216 insertions(+), 55 deletions(-) create mode 100644 .changeset/30512-platform-objects-locales-zh-hant.md create mode 100644 packages/platform-objects/src/apps/translations/zh-Hant.metadata-forms.generated.ts create mode 100644 packages/platform-objects/src/apps/translations/zh-Hant.objects.generated.ts create mode 100644 packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.generated.ts create mode 100644 packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts create mode 100644 packages/platform-objects/src/apps/translations/zh-Hant.ts diff --git a/.changeset/30512-platform-objects-locales-zh-hant.md b/.changeset/30512-platform-objects-locales-zh-hant.md new file mode 100644 index 00000000000..0a14e897834 --- /dev/null +++ b/.changeset/30512-platform-objects-locales-zh-hant.md @@ -0,0 +1,10 @@ +--- +'@objectstack/platform-objects': patch +--- + +i18n(platform-objects): the platform objects, Setup, Studio and Account ship in Traditional Chinese + +Clause-②: no +Metadata-protocol: none + +`@objectstack/platform-objects` now carries `zh-Hant` beside `en`, `zh-CN`, `ja-JP` and `es-ES`: every `sys_*` object (labels, fields, help, options, views, actions and their dialogs), the metadata-type configuration forms Studio opens, the Setup, Studio and Account navigation, the System Overview dashboard and its datasets, the platform pages' titles, and the inline copy on the user and organization record pages. An administrator or developer reading the platform in Traditional Chinese no longer sees it in English. The new bundles pass through the same source-hash fallback as the existing ones, and every hand-authored leaf records the digest of the source it was translated from. The extract config documents `zh-Hant` in its `--locales`, so `check:i18n` now gates the new bundles. Package 11 of #30512, first of seven locales; `ar`, `fr`, `pt-BR`, `ru`, `de` and `ko` follow. diff --git a/packages/cli/test/platform-page-i18n-parity.test.ts b/packages/cli/test/platform-page-i18n-parity.test.ts index 2c653ab58f0..dbd46455e49 100644 --- a/packages/cli/test/platform-page-i18n-parity.test.ts +++ b/packages/cli/test/platform-page-i18n-parity.test.ts @@ -738,11 +738,12 @@ const RECORD_PAGES: Array> = Object.values( const SHIPPED_LOCALES = Object.keys(SetupAppTranslations as Record); /** - * A key is a locale code (`en`, `zh-CN`). Deliberately a shape test rather than - * a membership test against `SHIPPED_LOCALES`: a map carrying a locale the - * bundle does not ship is still an inline map, and must still be judged. + * A key is a locale code (`en`, `zh-CN`, and the platform locale table's + * script-subtag tag `zh-Hant`). Deliberately a shape test rather than a + * membership test against `SHIPPED_LOCALES`: a map carrying a locale the bundle + * does not ship is still an inline map, and must still be judged. */ -const LOCALE_KEY = /^[a-z]{2}(-[A-Z]{2})?$/; +const LOCALE_KEY = /^[a-z]{2}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$/; /** An inline `I18nLabel` map: every key a locale code, every value a string. */ const isInlineLocaleMap = (value: unknown): value is Record => { diff --git a/packages/platform-objects/scripts/i18n-extract.config.ts b/packages/platform-objects/scripts/i18n-extract.config.ts index 21092751b77..0e44393d7f3 100644 --- a/packages/platform-objects/scripts/i18n-extract.config.ts +++ b/packages/platform-objects/scripts/i18n-extract.config.ts @@ -8,7 +8,7 @@ * pointed at this file via `bundle-require`: * * os i18n extract packages/platform-objects/scripts/i18n-extract.config.ts \ - * --locales=zh-CN,ja-JP,es-ES \ + * --locales=zh-CN,ja-JP,es-ES,zh-Hant \ * --fill=default \ * --source-hashes \ * --out=packages/platform-objects/src/apps/translations @@ -221,10 +221,12 @@ import { enObjects } from '../src/apps/translations/en.objects.generated.js'; import { zhCNObjects } from '../src/apps/translations/zh-CN.objects.generated.js'; import { jaJPObjects } from '../src/apps/translations/ja-JP.objects.generated.js'; import { esESObjects } from '../src/apps/translations/es-ES.objects.generated.js'; +import { zhHantObjects } from '../src/apps/translations/zh-Hant.objects.generated.js'; import { enMetadataForms } from '../src/apps/translations/en.metadata-forms.generated.js'; import { zhCNMetadataForms } from '../src/apps/translations/zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from '../src/apps/translations/ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from '../src/apps/translations/es-ES.metadata-forms.generated.js'; +import { zhHantMetadataForms } from '../src/apps/translations/zh-Hant.metadata-forms.generated.js'; // ── Existing hand-authored app/dashboard/page translations ──────────────── // The per-locale assemblers, NOT the `SetupAppTranslations` wrapper. Each one @@ -247,6 +249,7 @@ import { en } from '../src/apps/translations/en.js'; import { zhCN } from '../src/apps/translations/zh-CN.js'; import { jaJP } from '../src/apps/translations/ja-JP.js'; import { esES } from '../src/apps/translations/es-ES.js'; +import { zhHant } from '../src/apps/translations/zh-Hant.js'; /** * The annotation is load-bearing, not decoration (#10868). `defineStack` @@ -347,6 +350,7 @@ const config: ObjectStackDefinition = defineStack({ { 'zh-CN': { ...zhCN, objects: zhCNObjects, metadataForms: zhCNMetadataForms } }, { 'ja-JP': { ...jaJP, objects: jaJPObjects, metadataForms: jaJPMetadataForms } }, { 'es-ES': { ...esES, objects: esESObjects, metadataForms: esESMetadataForms } }, + { 'zh-Hant': { ...zhHant, objects: zhHantObjects, metadataForms: zhHantMetadataForms } }, ], }); diff --git a/packages/platform-objects/src/apps/dashboards/system-overview-null-label.test.ts b/packages/platform-objects/src/apps/dashboards/system-overview-null-label.test.ts index c207d3d336c..dc109ed1342 100644 --- a/packages/platform-objects/src/apps/dashboards/system-overview-null-label.test.ts +++ b/packages/platform-objects/src/apps/dashboards/system-overview-null-label.test.ts @@ -30,6 +30,7 @@ describe('System Overview: the null-user bucket reads "System"', () => { 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', + 'zh-Hant': '系統', }); }); diff --git a/packages/platform-objects/src/apps/dashboards/system.datasets.ts b/packages/platform-objects/src/apps/dashboards/system.datasets.ts index eef0bf4ef63..5bb905fa4c8 100644 --- a/packages/platform-objects/src/apps/dashboards/system.datasets.ts +++ b/packages/platform-objects/src/apps/dashboards/system.datasets.ts @@ -57,7 +57,7 @@ export const SysAuditLogDataset = defineDataset({ label: 'User', field: 'user_id', type: 'lookup', - nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema' }, + nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統' }, }, ], measures: [{ name: 'event_count', label: 'Events', aggregate: 'count' }], diff --git a/packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts b/packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts index a436bd79022..5de2d5dd6c2 100644 --- a/packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts +++ b/packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts @@ -45,14 +45,17 @@ import { en } from './translations/en.js'; import { zhCN } from './translations/zh-CN.js'; import { jaJP } from './translations/ja-JP.js'; import { esES } from './translations/es-ES.js'; +import { zhHant } from './translations/zh-Hant.js'; import { enObjects } from './translations/en.objects.generated.js'; import { zhCNObjects } from './translations/zh-CN.objects.generated.js'; import { jaJPObjects } from './translations/ja-JP.objects.generated.js'; import { esESObjects } from './translations/es-ES.objects.generated.js'; +import { zhHantObjects } from './translations/zh-Hant.objects.generated.js'; import { enMetadataForms } from './translations/en.metadata-forms.generated.js'; import { zhCNMetadataForms } from './translations/zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from './translations/ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from './translations/es-ES.metadata-forms.generated.js'; +import { zhHantMetadataForms } from './translations/zh-Hant.metadata-forms.generated.js'; /** The design-record vocabulary an admin-facing string must not carry. */ const DESIGN_RECORD_VOCABULARY: ReadonlyArray = [ @@ -127,10 +130,13 @@ function objectSchemas(...modules: Array>): Map { diff --git a/packages/platform-objects/src/apps/setup-nav-distinct-labels.test.ts b/packages/platform-objects/src/apps/setup-nav-distinct-labels.test.ts index d36c63cd406..2619708b056 100644 --- a/packages/platform-objects/src/apps/setup-nav-distinct-labels.test.ts +++ b/packages/platform-objects/src/apps/setup-nav-distinct-labels.test.ts @@ -15,8 +15,9 @@ import { en } from './translations/en.js'; import { zhCN } from './translations/zh-CN.js'; import { jaJP } from './translations/ja-JP.js'; import { esES } from './translations/es-ES.js'; +import { zhHant } from './translations/zh-Hant.js'; -const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const; +const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const; type NavItem = { id?: string; label?: string }; diff --git a/packages/platform-objects/src/apps/setup-nav-per-user-stores.test.ts b/packages/platform-objects/src/apps/setup-nav-per-user-stores.test.ts index c820560344d..6bf8c5668dd 100644 --- a/packages/platform-objects/src/apps/setup-nav-per-user-stores.test.ts +++ b/packages/platform-objects/src/apps/setup-nav-per-user-stores.test.ts @@ -110,9 +110,10 @@ describe('(2) the User page lists each per-user store, keyed to its record (#238 'zh-CN': '偏好设置', 'ja-JP': '設定', 'es-ES': 'Preferencias', + 'zh-Hant': '偏好設定', }); expect((tabOf('sys_account')?.label as Rec)?.en).toBe('Linked Accounts'); - expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN']); + expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN', 'zh-Hant']); }); }); diff --git a/packages/platform-objects/src/apps/translations/app-nav-translation-parity.test.ts b/packages/platform-objects/src/apps/translations/app-nav-translation-parity.test.ts index 9bfc7a90c4a..35fe3c0c88c 100644 --- a/packages/platform-objects/src/apps/translations/app-nav-translation-parity.test.ts +++ b/packages/platform-objects/src/apps/translations/app-nav-translation-parity.test.ts @@ -58,8 +58,9 @@ import { en } from './en.js'; import { zhCN } from './zh-CN.js'; import { jaJP } from './ja-JP.js'; import { esES } from './es-ES.js'; +import { zhHant } from './zh-Hant.js'; -const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const; +const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const; /** Every statically declared nav item of an app, depth-first, with its label. */ function navItems(app: { navigation?: unknown[] }): Array<{ id: string; label?: string }> { diff --git a/packages/platform-objects/src/apps/translations/bu-rename-consistency.test.ts b/packages/platform-objects/src/apps/translations/bu-rename-consistency.test.ts index dd0cbfe7fbf..639c1d32238 100644 --- a/packages/platform-objects/src/apps/translations/bu-rename-consistency.test.ts +++ b/packages/platform-objects/src/apps/translations/bu-rename-consistency.test.ts @@ -14,12 +14,14 @@ import { enObjects } from './en.objects.generated.js'; import { zhCNObjects } from './zh-CN.objects.generated.js'; import { jaJPObjects } from './ja-JP.objects.generated.js'; import { esESObjects } from './es-ES.objects.generated.js'; +import { zhHantObjects } from './zh-Hant.objects.generated.js'; const LOCALES = [ { name: 'en', objs: enObjects as Record, old: /\bDepartment\b/ }, { name: 'zh-CN', objs: zhCNObjects as Record, old: /部门/ }, { name: 'ja-JP', objs: jaJPObjects as Record, old: /部門/ }, { name: 'es-ES', objs: esESObjects as Record, old: /Departamento/ }, + { name: 'zh-Hant', objs: zhHantObjects as Record, old: /部門/ }, ]; const OBJECTS = ['sys_business_unit', 'sys_business_unit_member']; diff --git a/packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts b/packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts index a1fdba5f27c..cedad533e4d 100644 --- a/packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts +++ b/packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts @@ -25,11 +25,13 @@ import { enObjects } from './en.objects.generated.js'; import { zhCNObjects } from './zh-CN.objects.generated.js'; import { jaJPObjects } from './ja-JP.objects.generated.js'; import { esESObjects } from './es-ES.objects.generated.js'; +import { zhHantObjects } from './zh-Hant.objects.generated.js'; const LOCALES = [ ['zh-CN', zhCNObjects], ['ja-JP', jaJPObjects], ['es-ES', esESObjects], + ['zh-Hant', zhHantObjects], ] as const; /** diff --git a/packages/platform-objects/src/apps/translations/dashboard-header-children.test.ts b/packages/platform-objects/src/apps/translations/dashboard-header-children.test.ts index b3a4ca5bfc7..8726e34b853 100644 --- a/packages/platform-objects/src/apps/translations/dashboard-header-children.test.ts +++ b/packages/platform-objects/src/apps/translations/dashboard-header-children.test.ts @@ -24,12 +24,14 @@ import { enMetadataForms } from './en.metadata-forms.generated.js'; import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from './es-ES.metadata-forms.generated.js'; +import { zhHantMetadataForms } from './zh-Hant.metadata-forms.generated.js'; const LOCALES = [ { name: 'en', forms: enMetadataForms as Record }, { name: 'zh-CN', forms: zhCNMetadataForms as Record }, { name: 'ja-JP', forms: jaJPMetadataForms as Record }, { name: 'es-ES', forms: esESMetadataForms as Record }, + { name: 'zh-Hant', forms: zhHantMetadataForms as Record }, ]; const HEADER_CHILDREN = ['header.showTitle', 'header.showDescription', 'header.actions']; diff --git a/packages/platform-objects/src/apps/translations/index.ts b/packages/platform-objects/src/apps/translations/index.ts index 2633cc7f6df..b3978113156 100644 --- a/packages/platform-objects/src/apps/translations/index.ts +++ b/packages/platform-objects/src/apps/translations/index.ts @@ -9,6 +9,7 @@ export { en } from './en.js'; export { zhCN } from './zh-CN.js'; export { jaJP } from './ja-JP.js'; export { esES } from './es-ES.js'; +export { zhHant } from './zh-Hant.js'; /** * The staleness mechanism (#8765 Option B, extended to the generated bundles by diff --git a/packages/platform-objects/src/apps/translations/metadata-forms-vocabulary.test.ts b/packages/platform-objects/src/apps/translations/metadata-forms-vocabulary.test.ts index 5ab6b928a33..b220a54f1e9 100644 --- a/packages/platform-objects/src/apps/translations/metadata-forms-vocabulary.test.ts +++ b/packages/platform-objects/src/apps/translations/metadata-forms-vocabulary.test.ts @@ -14,12 +14,14 @@ import { enMetadataForms } from './en.metadata-forms.generated.js'; import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from './es-ES.metadata-forms.generated.js'; +import { zhHantMetadataForms } from './zh-Hant.metadata-forms.generated.js'; const LOCALES = [ { name: 'en', forms: enMetadataForms as Record }, { name: 'zh-CN', forms: zhCNMetadataForms as Record }, { name: 'ja-JP', forms: jaJPMetadataForms as Record }, { name: 'es-ES', forms: esESMetadataForms as Record }, + { name: 'zh-Hant', forms: zhHantMetadataForms as Record }, ]; describe('ADR-0090 D2/D3 — metadata-form translations use the v2 vocabulary', () => { diff --git a/packages/platform-objects/src/apps/translations/repeater-row-properties.test.ts b/packages/platform-objects/src/apps/translations/repeater-row-properties.test.ts index c4c07023e45..1870948b809 100644 --- a/packages/platform-objects/src/apps/translations/repeater-row-properties.test.ts +++ b/packages/platform-objects/src/apps/translations/repeater-row-properties.test.ts @@ -53,12 +53,14 @@ import { enMetadataForms } from './en.metadata-forms.generated.js'; import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from './es-ES.metadata-forms.generated.js'; +import { zhHantMetadataForms } from './zh-Hant.metadata-forms.generated.js'; const LOCALES = [ { name: 'en', forms: enMetadataForms as Record }, { name: 'zh-CN', forms: zhCNMetadataForms as Record }, { name: 'ja-JP', forms: jaJPMetadataForms as Record }, { name: 'es-ES', forms: esESMetadataForms as Record }, + { name: 'zh-Hant', forms: zhHantMetadataForms as Record }, ]; /** Every `*.form.ts` the spec package exports, by its export name. */ diff --git a/packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts b/packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts index 4268b8fd726..0cd1f9e15ba 100644 --- a/packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts +++ b/packages/platform-objects/src/apps/translations/setup-nav-dead-key-tombstone.test.ts @@ -106,8 +106,9 @@ import { en } from './en.js'; import { zhCN } from './zh-CN.js'; import { jaJP } from './ja-JP.js'; import { esES } from './es-ES.js'; +import { zhHant } from './zh-Hant.js'; -const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const; +const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const; /** Removed Setup nav ids. Delete a line here only together with its nav item. */ const DEAD_SETUP_NAV_IDS = [ diff --git a/packages/platform-objects/src/apps/translations/setup.translation.ts b/packages/platform-objects/src/apps/translations/setup.translation.ts index 742896293d4..974357305b9 100644 --- a/packages/platform-objects/src/apps/translations/setup.translation.ts +++ b/packages/platform-objects/src/apps/translations/setup.translation.ts @@ -5,30 +5,33 @@ import { en } from './en.js'; import { zhCN } from './zh-CN.js'; import { jaJP } from './ja-JP.js'; import { esES } from './es-ES.js'; +import { zhHant } from './zh-Hant.js'; import { zhCNSourceHashes } from './zh-CN.source-hashes.js'; import { jaJPSourceHashes } from './ja-JP.source-hashes.js'; import { esESSourceHashes } from './es-ES.source-hashes.js'; +import { zhHantSourceHashes } from './zh-Hant.source-hashes.js'; import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js'; import { jaJPGeneratedSourceHashes } from './ja-JP.source-hashes.generated.js'; import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js'; +import { zhHantGeneratedSourceHashes } from './zh-Hant.source-hashes.generated.js'; import { withSourceFallback } from './source-hash.js'; /** * Setup App — Internationalization (i18n) * * Mirrors the CRM example's `per_locale` convention: each language lives - * in its own file (`en.ts`, `zh-CN.ts`, `ja-JP.ts`, `es-ES.ts`) and is + * in its own file (`en.ts`, `zh-CN.ts`, `ja-JP.ts`, `es-ES.ts`, `zh-Hant.ts`) and is * assembled into a single `TranslationBundle` here. * * Loaded into the kernel's i18n service by `plugin-auth` during * `kernel:ready` (auth is the natural registration point for the Setup * App — see `auth-plugin.ts`). * - * Supported locales: en, zh-CN, ja-JP, es-ES. + * Supported locales: en, zh-CN, ja-JP, es-ES, zh-Hant. * * ## This assembly is where staleness is resolved (#8765) * - * The three translated bundles pass through {@link withSourceFallback}, which + * The translated bundles pass through {@link withSourceFallback}, which * replaces any leaf whose RECORDED source hash disagrees with the current * `en` source string by that source string. This is the serving seam for the * package: `plugin.ts` hands exactly this object to the kernel's i18n service @@ -69,4 +72,5 @@ export const SetupAppTranslations: TranslationBundle = { 'zh-CN': withSourceFallback(zhCN, en, zhCNSourceHashes, zhCNGeneratedSourceHashes), 'ja-JP': withSourceFallback(jaJP, en, jaJPSourceHashes, jaJPGeneratedSourceHashes), 'es-ES': withSourceFallback(esES, en, esESSourceHashes, esESGeneratedSourceHashes), + 'zh-Hant': withSourceFallback(zhHant, en, zhHantSourceHashes, zhHantGeneratedSourceHashes), }; diff --git a/packages/platform-objects/src/apps/translations/source-hash.test.ts b/packages/platform-objects/src/apps/translations/source-hash.test.ts index 834daf9fa1c..d34bb1a081d 100644 --- a/packages/platform-objects/src/apps/translations/source-hash.test.ts +++ b/packages/platform-objects/src/apps/translations/source-hash.test.ts @@ -23,9 +23,11 @@ import { en } from './en.js'; import { zhCN } from './zh-CN.js'; import { jaJP } from './ja-JP.js'; import { esES } from './es-ES.js'; +import { zhHant } from './zh-Hant.js'; import { zhCNSourceHashes } from './zh-CN.source-hashes.js'; import { jaJPSourceHashes } from './ja-JP.source-hashes.js'; import { esESSourceHashes } from './es-ES.source-hashes.js'; +import { zhHantSourceHashes } from './zh-Hant.source-hashes.js'; import { SetupAppTranslations } from './setup.translation.js'; import { collectFilledFromHashes, @@ -39,6 +41,7 @@ import { import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js'; import { jaJPGeneratedSourceHashes } from './ja-JP.source-hashes.generated.js'; import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js'; +import { zhHantGeneratedSourceHashes } from './zh-Hant.source-hashes.generated.js'; // A source bundle and a translation of it, at one nav leaf per app so the // per-locale independence claim has something to be independent about. @@ -187,6 +190,7 @@ describe('the shipped bundles', () => { ['zh-CN', zhCN, zhCNSourceHashes], ['ja-JP', jaJP, jaJPSourceHashes], ['es-ES', esES, esESSourceHashes], + ['zh-Hant', zhHant, zhHantSourceHashes], ] as const; // ⭐ The acceptance criterion, applied to the real data: B introduces NO new @@ -384,6 +388,7 @@ describe('the committed provenance companions', () => { 'zh-CN': zhCNGeneratedSourceHashes, 'ja-JP': jaJPGeneratedSourceHashes, 'es-ES': esESGeneratedSourceHashes, + 'zh-Hant': zhHantGeneratedSourceHashes, } as const; // ⚠️ Deliberately NOT asserted here: "no shipped leaf is stale". That claim diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/zh-Hant.metadata-forms.generated.ts new file mode 100644 index 00000000000..40041d25e99 --- /dev/null +++ b/packages/platform-objects/src/apps/translations/zh-Hant.metadata-forms.generated.ts @@ -0,0 +1,2815 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-Hant'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + * Merge only fills gaps: correcting a source label/description does not + * push the correction into a leaf here that already holds a translation — + * a present-but-stale string is not a gap, so it is left as-is. Re-translate + * it by hand when its source changes; nothing here or in `os i18n check` + * tells a leaf a translator updated on purpose from one nobody has looked + * at since the source moved. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const zhHantMetadataForms: NonNullable = { + object: { + label: "物件", + sections: { + basics: { + label: "基礎資訊", + description: "識別資訊、顯示名與分類標籤" + }, + fields: { + label: "欄位", + description: "定義資料模型——每行對應資料庫表中的一欄" + }, + capabilities: { + label: "功能開關", + description: "系統功能與 API 暴露" + }, + advanced: { + label: "進階設定", + description: "狀態機、動作與儲存" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼(建立後不可修改)" + }, + label: { + label: "顯示名稱", + helpText: "單數顯示名(如:「客戶」)" + }, + pluralLabel: { + label: "複數顯示名稱", + helpText: "複數顯示名(如:「客戶清單」)" + }, + icon: { + label: "圖示", + helpText: "Lucide 圖示名稱(如:\"building\"、\"users\")" + }, + description: { + label: "描述", + helpText: "開發文件說明" + }, + nameField: { + label: "記錄標題欄位", + helpText: "用作每筆記錄標題的欄位(例如 \"name\"、\"subject\")。ADR-0079 規定的規範指標——記錄顯示、ObjectQL 搜尋和關聯記錄預覽都讀它。" + }, + imageField: { + label: "記錄圖片欄位", + helpText: "用作每筆記錄圖片的欄位,顯示在記錄頁首的標題旁。必須是本物件中類型為 image 或 avatar 的欄位,其他名稱會在儲存物件時被拒絕。留空則不顯示記錄圖片,也不畫佔位圖。" + }, + isSystem: { + label: "系統內建", + helpText: "系統物件(受保護,不可刪除;共用預設為公開)" + }, + highlightFields: { + label: "重點欄位", + helpText: "本物件的欄位名,依重要性從高到低排列——只能放下一個時取第一項(ADR-0085)。決定預設清單欄、卡片、子記錄預覽和詳細資料頁面的重點資訊條。不是本物件欄位的名稱會在發布時被拒絕。" + }, + relatedLists: { + label: "相關清單順序", + helpText: "本物件記錄頁上相關清單的業務順序,依重要性從高到低排列。每一項是子物件名稱(例如 task),當該子物件與本物件有多個關係時寫 object.field。列出的清單排在前面,其餘依派生順序排在後面。不對應任何指向本物件的關係的名稱不起作用,儲存或驗證物件時會被拒絕。" + }, + searchableFields: { + label: "可搜尋欄位", + helpText: "$search 查詢比對的欄位名(ADR-0061):記錄選擇器、清單快速搜尋和全域搜尋的預設範圍,檢視可以再收窄。未設定時,搜尋使用名稱/標題欄位加上短文字欄位。每一項都必須是本物件已儲存的欄位——未知的名稱或虛擬的 formula 欄位會在發布時被拒絕。" + }, + fieldGroups: { + label: "欄位分組", + helpText: "對本物件的欄位進行分組的有序分割區,用於輸入表單和記錄詳細資料頁面(ADR-0085);陣列順序即顯示順序。欄位在自身的 group 設定中填寫分組的鍵即可加入該分組。未歸入任何分組的欄位排在各分組之後;沒有任何欄位加入的分組不會顯示。" + }, + "fieldGroups.key": { + label: "鍵", + helpText: "snake_case 格式的機器鍵,在本物件內唯一——其他寫法會被 schema 拒絕。欄位透過填寫此鍵加入分組,因此重新命名它會使這些欄位變為未分組。" + }, + "fieldGroups.label": { + label: "顯示名稱", + helpText: "該分組所在分割區的標題文字。" + }, + "fieldGroups.icon": { + label: "圖示", + helpText: "在記錄詳細資料頁面的標題旁顯示的 Lucide 圖示名稱(例如 \"banknote\")。輸入表單不顯示它。" + }, + "fieldGroups.description": { + label: "描述", + helpText: "顯示在標題下方的文字,出現在輸入表單和記錄詳細資料頁面上。" + }, + "fieldGroups.relatedList": { + label: "相關清單", + helpText: "讓該條目代表一個相關清單的表格,而不是一組欄位:填寫子物件(或 object.field),其清單就顯示在記錄詳細資料欄的這個位置。這樣的分組不包含欄位;把它填為自身分組的欄位會被拒絕,不對應任何指向本物件的關係的名稱也會被拒絕。記錄頁目前還不會繪製這種位置。" + }, + "fieldGroups.collapse": { + label: "摺疊", + helpText: "該分割區能否摺疊,作用於輸入表單和記錄詳細資料頁面。未設定:none。" + }, + "fieldGroups.visibleWhen": { + label: "可見條件", + helpText: "基於記錄的 CEL 謂詞(例如 record.type == 'invoice')——僅當其為 TRUE 時,輸入表單才顯示整個分組(包括標題)。" + }, + "fieldGroups.requiresFeature": { + label: "所需功能", + helpText: "該分組需要的部署功能(例如 multiOrgEnabled):功能關閉時,輸入表單和記錄詳細資料頁面隱藏整個分組。與「可見條件」同時設定時,兩者都成立才顯示該分組。分組中的欄位仍可通過 API 讀取。" + }, + fields: { + label: "欄位", + helpText: "新增該物件將儲存的欄" + }, + "fields.label": { + label: "顯示名稱", + helpText: "顯示用標籤" + }, + "fields.type": { + label: "類型", + helpText: "欄位類型" + }, + "fields.description": { + label: "描述", + helpText: "此欄的開發者文件" + }, + "fields.required": { + label: "必填", + helpText: "每筆記錄都必須填寫" + }, + "fields.unique": { + label: "唯一", + helpText: "不允許重複值" + }, + "fields.readonly": { + label: "唯讀", + helpText: "可見,但使用者不可編輯" + }, + "fields.hidden": { + label: "隱藏", + helpText: "在預設介面中隱藏" + }, + "fields.maskingRule": { + label: "遮蔽規則", + helpText: "部分遮蔽:預設('phone'、'id_card'、'bank_account'、'email'、'name')或 {\"keepHead\": n, \"keepTail\": m}。未持有該欄位 requiredPermissions 的呼叫者將看到遮蔽值" + }, + "fields.searchable": { + label: "可搜尋", + helpText: "納入全文搜尋" + }, + "fields.sortable": { + label: "可排序", + helpText: "允許依此欄排序" + }, + "fields.defaultValue": { + label: "預設值", + helpText: "新記錄的預設值(JSON 字面量)" + }, + "fields.placeholder": { + label: "預留位置文字", + helpText: "顯示在空輸入框內的提示文字;輸入內容後消失" + }, + "fields.maxLength": { + label: "最大長度", + helpText: "最大字元數" + }, + "fields.minLength": { + label: "最小長度", + helpText: "最小字元數" + }, + "fields.valueDomain": { + label: "值域", + helpText: "寫入值必須歸屬的標準;寫入非成員值將被拒絕" + }, + "fields.rows": { + label: "列數", + helpText: "內嵌編輯器高度(文字行數)" + }, + "fields.min": { + label: "最小值", + helpText: "最小值" + }, + "fields.max": { + label: "最大值", + helpText: "最大值" + }, + "fields.precision": { + label: "精度", + helpText: "總位數" + }, + "fields.scale": { + label: "小數位", + helpText: "小數位數" + }, + "fields.options": { + label: "選項", + helpText: "可選項" + }, + "fields.options.label": { + label: "顯示名稱" + }, + "fields.options.value": { + label: "值" + }, + "fields.options.color": { + label: "顏色" + }, + "fields.options.description": { + label: "描述" + }, + "fields.options.kind": { + label: "類型", + helpText: "生命週期類別(僅限單值下拉 / 單選):進行中的一步、成功的終點或失敗的終點。階段欄位的每個選項都必須宣告。" + }, + "fields.reference": { + label: "參照物件", + helpText: "目標物件名稱。tree 欄位可省略;若填寫,必須是本物件自身(樹是同一物件內部的層級 — 連結到其他物件請用 lookup)" + }, + "fields.lookupFilters": { + label: "Lookup 篩選條件", + helpText: "套用於選擇器的篩選規則({field, operator, value})" + }, + "fields.deleteBehavior": { + label: "刪除行為", + helpText: "被參照記錄刪除時的處理方式" + }, + "fields.multiple": { + label: "多選", + helpText: "允許選擇多筆記錄" + }, + "fields.expression": { + label: "運算式", + helpText: "計算此欄位值所用的 CEL 運算式。公式欄位:讀取時計算,不儲存欄。其他欄位:由引擎根據每次寫入所儲存的列計算並儲存,因此用戶端提交的值不會生效。" + }, + "fields.returnType": { + label: "傳回類型", + helpText: "公式結果類型" + }, + "fields.summaryOperations": { + label: "彙總操作", + helpText: "彙總:取哪個子物件、哪個欄位、用哪種聚合" + }, + "fields.summaryOperations.object": { + label: "物件", + helpText: "來源子物件名稱" + }, + "fields.summaryOperations.field": { + label: "欄位", + helpText: "子物件上參與聚合的欄位(count 時忽略)" + }, + "fields.summaryOperations.function": { + label: "函式", + helpText: "聚合函式" + }, + "fields.autonumberFormat": { + label: "自動編號格式", + helpText: "例如 \"INV-{0000}\";支援日期預留位置 {YYYY}/{MM}/{DD} 以及 {field_name} 插值" + }, + "fields.language": { + label: "語言", + helpText: "編輯器語言(如 sql、javascript)" + }, + "fields.trackHistory": { + label: "歷史追蹤", + helpText: "在記錄的活動時間軸上概述該欄位的變更" + }, + "fields.visibleWhen": { + label: "可見條件", + helpText: "CEL 判定式——結果為 TRUE 時才顯示該欄位" + }, + "fields.readonlyWhen": { + label: "唯讀條件", + helpText: "CEL 判定式——結果為 TRUE 時該欄位唯讀(由伺服器端強制)" + }, + "fields.requiredWhen": { + label: "必填條件", + helpText: "CEL 判定式——結果為 TRUE 時該欄位必填(由伺服器端強制)" + }, + enable: { + label: "啟用", + helpText: "啟用或停用系統功能" + }, + "enable.trackHistory": { + label: "歷史追蹤" + }, + "enable.searchable": { + label: "可搜尋" + }, + "enable.apiEnabled": { + label: "已啟用 API" + }, + "enable.files": { + label: "檔案" + }, + "enable.feeds": { + label: "動態" + }, + "enable.activities": { + label: "活動" + }, + "enable.clone": { + label: "複製" + }, + "enable.approvalsVisibleToReaders": { + label: "核准對讀者可見", + helpText: "能讀取記錄的使用者可唯讀檢視該記錄的核准請求與歷史,不提供任何核准動作" + }, + validations: { + label: "驗證規則", + helpText: "物件級驗證規則——由規則物件組成的陣列,例如 [{ \"type\": \"script\", \"name\": \"amount_positive\", \"condition\": \"amount > 0\", \"message\": \"Amount must be positive\" }]。狀態機轉移表也在此宣告(ADR-0020)" + }, + activityMilestones: { + label: "活動里程碑", + helpText: "由欄位到達某個值觸發的時間軸條目(ADR-0052 §5b.2):當一次更新把被監視的欄位變為該值時,稽核外掛會把該里程碑的摘要寫入記錄的活動時間軸,代替欄位變更條目。第一個比對的里程碑生效。" + }, + "activityMilestones.field": { + label: "欄位", + helpText: "要監視的本物件欄位名(例如 status)。儲存或發布時不會檢查它:不是本物件欄位的名稱永遠不會觸發。" + }, + "activityMilestones.value": { + label: "值", + helpText: "欄位必須變為的儲存值,依文字精確比較——對於選擇欄位,填寫選項值而不是其標籤(例如 done)。數字或布林欄位上的里程碑永遠不會觸發。" + }, + "activityMilestones.summary": { + label: "摘要", + helpText: "時間軸文字(例如 \"Deal won: {name}\")。{field_name} 標記取更新後記錄中的值;查詢、主從或使用者欄位的標記顯示被參照記錄的標題;不指向任何欄位的標記顯示為空。" + }, + "activityMilestones.type": { + label: "類型", + helpText: "時間軸條目的活動類型:內建類型(如 completed)或你自己的詞,依原樣儲存。未設定:updated。" + }, + datasource: { + label: "資料來源", + helpText: "目標資料來源 ID(預設:\"default\")" + }, + indexes: { + label: "索引", + helpText: "本物件資料表上的資料庫索引。SQL 驅動在同步資料表時建立表中尚不存在的索引;同步從不刪除索引。" + }, + "indexes.name": { + label: "名稱", + helpText: "物理索引名。未設定:根據表名和欄名生成(例如 idx_task_status)。" + }, + "indexes.fields": { + label: "欄位", + helpText: "本物件的欄名,按鍵的順序排列(例如 status、owner)。儲存時不會檢查它們;發布和 os validate 會拒絕不是本物件欄位的名稱。若某個欄位不是已儲存的欄(例如公式欄位),SQL 驅動會跳過整個索引,並在伺服器日誌中記錄一條錯誤。" + }, + "indexes.unique": { + label: "唯一", + helpText: "唯一性範圍(ADR-0120)。未設定:不唯一。已棄用的裸 true(等同於 global)不在選項中;帶有它的索引會保留該值,直到你選擇一個範圍。" + }, + ownership: { + label: "歸屬模型", + helpText: "記錄歸屬模型。留空依 user 處理。" + }, + sharingModel: { + label: "共用模型", + helpText: "面向內部使用者的組織級預設記錄可見性(OWD)。自訂物件若不宣告,執行時依 private 處理(ADR-0090 D1)。" + }, + sharing: { + label: "共用", + helpText: "依物件宣告的共用可選項,用於細化上方的組織級預設值,但絕不取代它。未設定:各可選項依各自的預設值生效。" + }, + "sharing.controlledByParent": { + label: "隨父記錄控制", + helpText: "僅在 sharingModel 為 'controlled by parent' 時讀取;在其他共用模型下會被拒絕。" + }, + "sharing.controlledByParent.ownerMayEdit": { + label: "擁有者可編輯", + helpText: "開啟:子記錄的擁有者(其 owner_id)只要對父記錄有讀權限,就可以依 id 更新該列;其他人,以及所有新增、刪除、多列更新和代辦寫入,仍需要對父記錄的編輯權限。關閉(預設):子記錄的每一次寫入都需要對其父記錄的編輯權限。" + }, + access: { + label: "存取策略", + helpText: "通配授權策略(ADR-0066 D2)。不宣告時依 public 處理。它決定權限集的 '*' 物件授權是否覆蓋本物件;使用者之間的記錄可見性由 sharingModel 決定。" + }, + "access.default": { + label: "預設暴露級別", + helpText: "public:被 '*' 通配授權覆蓋。private:需要顯式的逐物件授權,並且不受通配列級安全策略約束。" + }, + requiredPermissions: { + label: "所需權限", + helpText: "呼叫方存取本物件必須持有的能力(權限集的 systemPermissions),在增刪改查授權之外額外檢查(ADR-0066 D3)。清單限制所有操作;{read, create, update, delete} 對應只限制其中列出的操作。不宣告或為空:不設能力門檻。" + }, + publicSharing: { + label: "公開分享", + helpText: "分享連結策略:本物件的記錄能否通過任何持有者都能開啟的連結發布,以及發布的條件。它不同於 sharingModel——後者與指定的使用者和團隊共用。未設定或關閉:無法建立連結,也沒有連結能開啟。" + }, + "publicSharing.enabled": { + label: "已啟用", + helpText: "允許為本物件的記錄建立分享連結。每次存取連結時都會檢查:關閉後所有已有連結都無法開啟,重新開啟後又會恢復服務。關閉(預設):此處其他設定均不生效。" + }, + "publicSharing.allowedAudiences": { + label: "允許的受眾", + helpText: "新連結可以指定的受眾;其他受眾會被拒絕。未設定:僅限連結。每種受眾都仍需要連結本身:signed in 還需要一位已登入的使用者,email 還需要收件者地址在該連結的名單上。" + }, + "publicSharing.allowedPermissions": { + label: "允許的權限", + helpText: "新連結可以授予的權限級別;其他級別會被拒絕。未設定:僅檢視。" + }, + "publicSharing.maxExpiryDays": { + label: "最長有效天數", + helpText: "新連結可請求的最晚過期時間,依從現在起的天數計;更晚的會被拒絕。未設定:365。它不會強制設定過期時間:建立時未設定過期時間的連結永不過期。" + }, + "publicSharing.redactFields": { + label: "遮蔽欄位", + helpText: "透過連結提供的每筆記錄中都會移除的本物件欄位名,與受眾無關;擁有者自身的存取不受影響。不是本物件欄位的名稱會在發布時被拒絕。" + }, + "publicSharing.eligibility": { + label: "資格條件", + helpText: "基於記錄的 CEL 謂詞(例如 record.status == 'published'):僅當其為 TRUE 時才會建立連結;記錄不再符合條件後,已有連結將無法開啟。無法編譯或求值出錯的謂詞會拒絕該連結。" + }, + managedBy: { + label: "生命週期歸屬", + helpText: "生命週期分類:platform(使用者可增刪改查)、config(管理員編寫)、system-data(平台定義結構、管理員/使用者可寫資料)、engine-owned(引擎獨佔,使用者不可寫)、append-only(稽核)、better-auth(身分)。UI 用戶端據此推導 CRUD 能力,因此它決定使用者在該物件記錄上能做什麼。" + }, + userActions: { + label: "使用者操作", + helpText: "UI 用戶端在本物件的記錄上提供哪些通用入口(新增、匯入、編輯、刪除、匯出),逐項覆寫 managedBy 的預設值。未設定的入口保持該預設值:platform 提供全部五項;config 和 system-data 提供除匯入外的全部;engine-owned、append-only 和 better-auth 只提供匯出。未改動過的開關不寫入任何值,所以即使預設提供該入口,它也顯示為關閉。在 engine-owned 或 append-only 物件上,開啟某個入口還會允許使用者透過資料 API 執行該寫入。使用者仍需要相應的權限。" + }, + "userActions.create": { + label: "新增", + helpText: "「新增」按鈕:開啟則顯示,關閉則隱藏。已儲存的 {enabled, visibleWhen, disabledWhen} 物件會逐鍵編輯;如需依目前範圍內的記錄控制該按鈕,請在原始碼中編寫該物件,它在每個工具列上求值一次(在相關清單上是宿主記錄)。" + }, + "userActions.import": { + label: "匯入", + helpText: "CSV 匯入入口:開啟則顯示,關閉則隱藏。已儲存的 {enabled, visibleWhen, disabledWhen} 物件會逐鍵編輯;如需依目前範圍內的記錄控制該入口,請在原始碼中編寫該物件,它在每個工具列上求值一次。" + }, + "userActions.edit": { + label: "編輯", + helpText: "編輯已有記錄(內嵌和表單中):開啟則提供,關閉則隱藏。已儲存的 {enabled, visibleWhen, disabledWhen} 物件會逐鍵編輯;如需依每一列自身的記錄控制,請在原始碼中編寫該物件。" + }, + "userActions.delete": { + label: "刪除", + helpText: "列刪除和批次刪除:開啟則提供,關閉則隱藏。已儲存的 {enabled, visibleWhen, disabledWhen} 物件會逐鍵編輯;如需依每一列自身的記錄控制,請在原始碼中編寫該物件。" + }, + "userActions.exportCsv": { + label: "匯出 CSV", + helpText: "CSV 匯出入口。未設定:顯示,因為每個 managedBy 類別預設都提供匯出。" + }, + editMode: { + label: "編輯方式", + helpText: "該物件記錄的編輯互動意圖。留空則由呈現端自行選擇預設方式。這是跨呈現端的意圖宣告,不是樣式。" + }, + fileAccessDelegate: { + label: "檔案存取委託服務", + helpText: "授權下載該物件媒體欄位所屬檔案的核心服務,並決定無權讀取 sys_file 的讀者在讀取該物件記錄時能否看到這些檔案的名稱、大小和類型(每次此類讀取,對每條所屬記錄詢問一次);有了它就不再以「呼叫方能否讀取宿主記錄」來判斷。適用於存取權由某個服務裁決的物件。失敗即拒絕。" + }, + lifecycle: { + label: "資料生命週期", + helpText: "資料生命週期契約(ADR-0057):資料保留多久、空間如何回收。留空即永久保留(record 語義)。非 record 類必須宣告至少一個界定策略(保留期、TTL 或輪轉)。" + }, + "lifecycle.class": { + label: "生命週期類別", + helpText: "本物件資料的持久化契約" + }, + "lifecycle.retention": { + label: "保留期", + helpText: "依資料年齡界定的保留視窗" + }, + "lifecycle.retention.maxAge": { + label: "最長保留", + helpText: "早於該視窗(依 created_at)的列將被清理。時長字面量:h/d/w/y,如 \"30d\"" + }, + "lifecycle.ttl": { + label: "TTL 過期", + helpText: "依列的 TTL 自動過期" + }, + "lifecycle.ttl.field": { + label: "時間欄位", + helpText: "TTL 計時起點的時間戳欄位(如 expires_at)" + }, + "lifecycle.ttl.expireAfter": { + label: "過期時長", + helpText: "列在該欄位之後這麼久過期,如 \"1d\"" + }, + "lifecycle.storage": { + label: "儲存策略", + helpText: "高頻遙測的物理輪轉(SQLite:O(1) 分片 DROP)" + }, + "lifecycle.storage.strategy": { + label: "策略", + helpText: "儲存策略" + }, + "lifecycle.storage.shards": { + label: "分片數", + helpText: "保留的分片數量;總視窗 = 分片數 × 單位" + }, + "lifecycle.storage.unit": { + label: "分片單位", + helpText: "單個分片的時間寬度" + }, + "lifecycle.archive": { + label: "封存", + helpText: "冷存交接(audit 類)。封存複製成功前,列絕不會被熱刪除。" + }, + "lifecycle.archive.after": { + label: "封存時點", + helpText: "早於該視窗的列進入封存 — 必須等於 retention.maxAge" + }, + "lifecycle.archive.to": { + label: "封存資料來源", + helpText: "冷儲存的目標資料來源名稱" + }, + "lifecycle.archive.keep": { + label: "封存保留", + helpText: "封存中保留多久(留空 = 永久),如 \"7y\"" + }, + "lifecycle.reclaim": { + label: "空間回收", + helpText: "清理後回收驅動層空間(非 record 類預設開啟)" + } + } + }, + object_extension: { + label: "物件擴充", + description: "一個套件為另一個套件定義的物件新增的欄位和規則" + }, + field: { + label: "欄位", + sections: { + basics: { + label: "基礎資訊", + description: "核心識別資訊與約束" + }, + configuration: { + label: "設定", + description: "類型相關設定(不同欄位類型顯示不同選項)" + }, + formula: { + label: "公式與計算", + description: "計算值與彙總" + }, + advanced: { + label: "進階設定", + description: "資料庫、介面、稽核與安全" + } + }, + fields: { + name: { + label: "名稱", + helpText: "唯一識別碼(snake_case,建立後不可修改)" + }, + label: { + label: "顯示名稱", + helpText: "使用者看到的顯示名稱" + }, + type: { + label: "類型", + helpText: "該欄位的資料類型" + }, + group: { + label: "分組", + helpText: "表單佈局中的分組名稱" + }, + description: { + label: "描述", + helpText: "顯示給使用者的說明文字" + }, + required: { + label: "必填", + helpText: "使用者必須填寫" + }, + unique: { + label: "唯一", + helpText: "任意兩筆記錄的值不能相同" + }, + multiple: { + label: "多選", + helpText: "允許多個值(用於 select / lookup)" + }, + defaultValue: { + label: "預設值", + helpText: "新增記錄時的預設值" + }, + placeholder: { + label: "預留位置文字", + helpText: "顯示在空輸入框內的提示文字(輸入內容後消失);需要常駐幫助請使用 inlineHelpText" + }, + inlineHelpText: { + label: "常駐說明文字", + helpText: "顯示在輸入框下方、始終可見的說明文字;與 `placeholder` 不同,後者一旦填入值就消失。" + }, + minLength: { + label: "最小長度", + helpText: "最少字元數" + }, + maxLength: { + label: "最大長度", + helpText: "最多字元數" + }, + valueDomain: { + label: "值域", + helpText: "寫入值必須歸屬的標準:iana_time_zone、iso_4217_currency 或 iso_3166_alpha2;寫入非成員值將被拒絕" + }, + rows: { + label: "列數", + helpText: "內嵌編輯器高度,以文字行數計" + }, + min: { + label: "最小值", + helpText: "允許的最小數值" + }, + max: { + label: "最大值", + helpText: "允許的最大數值" + }, + precision: { + label: "精度", + helpText: "總位數" + }, + scale: { + label: "小數位", + helpText: "小數部分位數" + }, + useGrouping: { + label: "千分位符號", + helpText: "顯示值時的數字分組(千分位符號)。未設定:由呈現端決定;未改動過的開關不寫入任何值,所以即使呈現端會分組,它也顯示為關閉。關閉:從不分組,例如年份或 ID。開啟:始終分組。" + }, + percentDisplay: { + label: "百分比顯示", + helpText: "唯讀時的顯示方式(清單儲存格與記錄頁)。未設定:百分比旁顯示進度條。條形:始終顯示進度條,用於進度。純數字:只顯示百分比,用於比率或機率。" + }, + currencyConfig: { + label: "貨幣設定", + helpText: "此欄位使用哪種貨幣——貨幣欄位,或傳回類型為「貨幣」的公式欄位。未設定時為 dynamic 模式。任何模式下值都是一個純數字。" + }, + "currencyConfig.currencyMode": { + label: "貨幣模式", + helpText: "dynamic(預設):欄位本身沒有貨幣,金額以租用戶預設貨幣顯示(localization.currency 設定)。fixed:欄位只有一種貨幣,即 defaultCurrency。record:每筆記錄有自己的貨幣,儲存在 currencyField 指向的欄位中。" + }, + "currencyConfig.currencyField": { + label: "幣別欄位", + helpText: "僅用於 record 模式,且在該模式下必填:本物件上儲存每筆記錄 ISO 4217 幣別程式碼的欄位——一個聲明瞭 valueDomain iso_4217_currency 的文字欄位,慣例命名為 currency_code。" + }, + "currencyConfig.defaultCurrency": { + label: "預設貨幣", + helpText: "fixed 模式欄位的唯一貨幣,使用三個字元的 ISO 4217 程式碼(例如 USD、EUR)。fixed 模式下必填,不會自動填入。dynamic 模式下不讀取;record 模式下不允許設定。" + }, + step: { + label: "步進值", + helpText: "滑桿的步進增量(預設 1)。僅呈現端使用:寫入路徑不會拒絕偏離步進網格的值。" + }, + maxSize: { + label: "最大檔案大小", + helpText: "允許的最大檔案大小,單位為位元組(正整數)。寫入時在伺服器端依已記錄的檔案大小驗證——沒有記錄大小的檔案不會被它攔下。" + }, + accept: { + label: "允許的檔案類型", + helpText: "允許上傳的類型,可寫 MIME 類型、type/* 萬用字元或 .ext 字尾(例如 image/*、.pdf)。提供給檔案選擇器,並在寫入時於伺服器端對照已儲存的檔案再次檢查。未設定:任何類型。" + }, + dimensions: { + label: "向量維度", + helpText: "向量維度——1 到 10000 之間的整數(例如 OpenAI 嵌入為 1536)。" + }, + language: { + label: "程式碼語言", + helpText: "編輯器語法醒目提示所用的語言(例如 javascript、python、sql)。" + }, + autonumberFormat: { + label: "自動編號格式", + helpText: "字面文字加 {0000} 計數器、依業務時區取值的 {YYYY}/{MM}/{DD}/{YYYYMMDD} 日期標記,以及 {field_name} 欄位插值。計數器依呈現出的字首分別重設。autonumber 欄位不填時預設為 {0000}。" + }, + referenceVia: { + label: "多型指標伴隨欄位", + helpText: "把該文字欄位宣告為多型指標的 id 半邊:此處填寫同一物件上儲存目標物件名的另一個欄位,目標逐列決定(ADR-0052 §5)。使用 snake_case;僅限文字欄位,且與 `reference` 互斥。" + }, + referenceDeletedWhen: { + label: "多型指標目標已刪除條件", + helpText: "僅與 referenceVia 同用:當同一物件上某個選擇(select)欄位取其某個選項值時,表示該指標指向的記錄已被平台刪除,指標以該選項的標籤顯示為已刪除標記。未設定:不會把任何列標記為已刪除。" + }, + "referenceDeletedWhen.field": { + label: "欄位", + helpText: "本物件上的同級選擇欄位(snake_case),例如 cancel_reason。必須是聲明瞭內嵌選項的單選欄位。" + }, + "referenceDeletedWhen.value": { + label: "取值", + helpText: "該選擇欄位中表示目標已被刪除的選項值,例如 record_deleted。必須是其已宣告的選項值之一。" + }, + mailAssociation: { + label: "比對傳入郵件", + helpText: "把傳入郵件關聯到本物件中該欄位值與郵件寄件者或某位收件者地址相同的記錄(不區分大小寫;不含信箱自身的地址)。僅限信箱欄位。只在設定了信箱服務的環境中生效。" + }, + dueLike: { + label: "截止日期", + helpText: "將此日期標記為截止日期:過了這個日期後,檢視可將其顯示為逾期(相對時間措辭與逾期顏色)。關閉或未設定:不是截止日期,也不會根據欄位名推測。" + }, + settledWhen: { + label: "了結條件", + helpText: "基於記錄的 CEL 謂詞(例如 record.status == 'done')——為 TRUE 時該截止日期視為已了結,該記錄不再顯示逾期措辭或顏色。僅用於顯示。" + }, + options: { + label: "選項", + helpText: "可選項(label/value 對)" + }, + "options.label": { + label: "顯示名稱" + }, + "options.value": { + label: "值" + }, + "options.description": { + label: "描述" + }, + "options.color": { + label: "顏色" + }, + "options.default": { + label: "預設選項" + }, + "options.visibleWhen": { + label: "可見條件" + }, + "options.kind": { + label: "類型" + }, + reference: { + label: "參照物件", + helpText: "被參照的物件名稱" + }, + referenceNameVisible: { + label: "不可讀時顯示名稱", + helpText: "允許此記錄的讀者看到被參照記錄的名稱(純文字,不帶連結),即使其無權開啟該記錄。該記錄的其他內容一律不顯示;名稱欄位上的欄位級規則仍然生效(此時名稱顯示為 Record # 加 id)。未設定:此類參照顯示為「受限記錄」。" + }, + deleteBehavior: { + label: "刪除行為", + helpText: "被參照記錄刪除時的處理方式" + }, + relatedListFilter: { + label: "關聯清單篩選", + helpText: "該關係在父記錄詳細資料頁面上的關聯清單的預設篩選——以 AND 方式與父記錄比對條件組合,分頁徽標統計的也是同一集合" + }, + displayField: { + label: "候選項標題欄位", + helpText: "在選擇器中作為每個候選記錄標題顯示的欄位。留空則使用被參照物件自身的標題欄位。" + }, + descriptionField: { + label: "候選項副標題欄位", + helpText: "在快速選擇浮層中顯示於標題下方的次要欄位。" + }, + allowCreate: { + label: "允許即時新增", + helpText: "選擇器找不到相符項目時,允許使用者用已輸入的文字直接建立記錄。適合只有標題欄位為必填的物件。" + }, + lookupPageSize: { + label: "選擇器每頁行數", + helpText: "記錄選擇對話方塊每頁顯示的列數——正整數,預設 10。" + }, + lookupColumns: { + label: "Lookup 欄", + helpText: "記錄選擇器表格的欄:被參照物件的欄位名,或 {field, label, width, type} 條目(例如 [\"name\", {\"field\": \"status\", \"label\": \"Stage\"}])。未設定:從被參照物件推導。" + }, + lookupFilters: { + label: "Lookup 篩選條件", + helpText: "選擇器候選記錄的基礎篩選,寫作被參照物件上的 {field, operator, value} 規則——operator 取 eq、ne、gt、lt、gte、lte、contains、in、notIn 之一(例如 [{\"field\": \"status\", \"operator\": \"eq\", \"value\": \"active\"}])。作用於每一種選擇器介面,並與 dependsOn 篩選以 AND 組合。" + }, + dependsOn: { + label: "相依欄位", + helpText: "此欄位的可選項所依賴的同一記錄上的欄位:在它們都有值之前,表單會暫緩此欄位,任一變化時重新計算。lookup 用它們篩選候選記錄——寫欄位名時篩選被參照物件上同名的欄位,{field, param} 則指定另一個欄位。選項類欄位在此列出父欄位名;逐項規則寫在各選項的 visibleWhen 中。" + }, + relatedListTitle: { + label: "關聯清單標題", + helpText: "該關係在父記錄詳細資料頁面上關聯清單的標題。" + }, + relatedListColumns: { + label: "相關清單欄", + helpText: "父記錄詳細資料頁面上此關係的相關清單的欄,寫作本物件(即子物件)的欄位名,例如 name、status。未設定:從子物件推導。只寫名稱——標籤、儲存格類型和格式都來自子物件的欄位定義。" + }, + inlineTitle: { + label: "內嵌表格標題", + helpText: "父記錄上內嵌主從表格的標題。" + }, + inlineColumns: { + label: "內嵌表格欄", + helpText: "父記錄表單上內嵌表格的欄,依顯示順序排列;僅當本欄位設定了 inlineEdit(在原始碼中編寫)時才使用。未設定:從本物件的可編輯欄位推導,超過六欄時其餘欄起初收在表格的欄選擇器中。只寫欄位名的條目會從該欄位取得類型、選項和規則;其他欄設定(首先是 type)在原始碼中編寫。" + }, + "inlineColumns.name": { + label: "名稱", + helpText: "該欄顯示和編輯的本(子)物件欄位(例如 quantity)。儲存或發布時不會檢查它:不是本物件欄位的名稱會顯示為普通文字欄。" + }, + "inlineColumns.label": { + label: "標籤", + helpText: "欄標題。未設定:使用該欄位自身的標籤。" + }, + "inlineColumns.width": { + label: "寬度", + helpText: "固定欄寬,單位為像素。未設定:依儲存格類型確定寬度,文字欄自適應伸展,數字、日期和選擇欄保持較窄。" + }, + "inlineColumns.defaultHidden": { + label: "預設隱藏", + helpText: "讓該欄起初收在表格的欄選擇器中,而不是顯示在螢幕上;使用者可以將其顯示出來。欄位為必填的欄始終顯示。" + }, + inlineAmountField: { + label: "內嵌合計欄位", + helpText: "用於內嵌表格合計行求和的子物件數值欄位。" + }, + expression: { + label: "運算式", + helpText: "計算此欄位值所用的 CEL 運算式。公式欄位:讀取時計算,不儲存欄。其他欄位:由引擎根據每次寫入所儲存的列計算並儲存,因此用戶端提交的值不會生效。" + }, + returnType: { + label: "公式傳回類型", + helpText: "公式宣告的值類型,依推斷出的 CEL 類型寫入。消費方直接讀它,無需重新解析運算式。貨幣:一筆金額,其貨幣由此欄位的貨幣設定宣告(未設定時為 dynamic)。" + }, + summaryOperations: { + label: "彙總操作", + helpText: "父子關係下的彙總聚合設定" + }, + "summaryOperations.object": { + label: "物件", + helpText: "參與聚合的子物件" + }, + "summaryOperations.function": { + label: "函式", + helpText: "聚合函式" + }, + "summaryOperations.field": { + label: "欄位", + helpText: "參與聚合的子物件欄位(count 時忽略)" + }, + "summaryOperations.relationshipField": { + label: "關係欄位", + helpText: "子物件上指回該父物件的外鍵(省略時自動探測)" + }, + "summaryOperations.filter": { + label: "篩選", + helpText: "僅聚合比對該判定式的子記錄(例如 status == received)" + }, + storage: { + label: "儲存約束", + helpText: "物理儲存約束(ADR-0113):寫入契約刻意不隱含的 DDL。未設定:不要求任何約束。" + }, + "storage.notNull": { + label: "非空(NOT NULL)", + helpText: "在該欄上生成資料庫 NOT NULL 約束。未設定時,即使 required 為真該欄仍可為空——必填由引擎在寫入時保證。對已存在空值的列宣告它是一次破壞性遷移,受 schema drift 流程把關(先回填)。與 requiredWhen 同時宣告會被拒絕。" + }, + readonly: { + label: "唯讀", + helpText: "在表單中唯讀" + }, + hidden: { + label: "隱藏", + helpText: "在預設介面檢視中隱藏" + }, + searchable: { + label: "可搜尋", + helpText: "納入全域搜尋結果" + }, + sortable: { + label: "可排序", + helpText: "允許依此欄位排序" + }, + visibleWhen: { + label: "可見條件", + helpText: "基於記錄的 CEL 謂詞(例如 record.type == 'invoice')——僅當其為 TRUE 時表單才顯示此欄位。" + }, + readonlyWhen: { + label: "唯讀條件", + helpText: "基於記錄的 CEL 謂詞(例如 record.status == 'paid')——為 TRUE 時欄位唯讀,並在伺服器端強制執行:更新中對已鎖定欄位的修改會被丟棄,保留已儲存的值。唯讀取記錄自身的欄;objectstack validate 會拒絕經由參照欄位的讀取。" + }, + requiredWhen: { + label: "必填條件", + helpText: "基於記錄的 CEL 謂詞——為 TRUE 時欄位必填,並在伺服器端作為狀態轉換門檻執行:若寫入前記錄合規,而寫入後缺少該值,則拒絕寫入;因此原本就缺少該值的列,其無關編輯仍可通過。若需要每次寫入都必須滿足的規則,請使用 validations 中的 script 規則。與 storage.notNull 同時宣告會被拒絕。" + }, + maskingRule: { + label: "遮蔽規則", + helpText: "部分遮蔽:預設('phone'、'id_card'、'bank_account'、'email'、'name')或 {\"keepHead\": n, \"keepTail\": m}。未持有該欄位 requiredPermissions 的呼叫者將看到遮蔽值" + }, + requiredPermissions: { + label: "所需權限", + helpText: "呼叫方讀取或編輯此欄位必須持有的能力(權限集的 systemPermissions),列出的每一項都必須持有(ADR-0066 D3)。不持有時,讀取到的值會被掩碼(設定了 maskingRule 時為部分掩碼),編輯會被拒絕。為空或未設定:不設能力門檻。" + }, + internal: { + label: "不對外傳回", + helpText: "該欄位的值永遠不經通用資料通道傳回:引擎會從 find/findOne 結果以及建立和更新的回應體中刪除這個鍵——預設投影如此,用戶端在 ?select= 中點名它時同樣如此。儲存、過濾和索引都不受影響。" + }, + trackHistory: { + label: "記錄變更歷史", + helpText: "把該欄位的值變更呈現為記錄活動時間軸上的條目(ADR-0052 §5b)。依欄位逐個開啟。" + }, + widget: { + label: "控制項覆寫", + helpText: "表單控制項覆寫——填寫已註冊的欄位元件名,查詢時會拼成 `field:` 加該名稱,用它代替依 type 推斷出的預設控制項。名稱未註冊時退回 type 對應的控制項。" + }, + ackPlaintextMasking: { + label: "確認明文儲存", + helpText: "確認該通用 password 欄位「落盤為明文、讀取時打碼」的契約確實是有意為之,從而不再提示編寫期警告(ADR-0100)。對其他欄位類型無效。" + } + } + }, + hook: { + label: "掛鉤", + sections: { + identity: { + label: "基礎資訊", + description: "名稱、物件與觸發事件" + }, + body: { + label: "處理體", + description: "觸發時執行的內嵌運算式或沙箱 JavaScript" + }, + legacy_handler: { + label: "舊版處理器", + description: "函式名參照——已廢棄,請改用 body" + }, + execution: { + label: "執行" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 識別碼(建立後不可修改)" + }, + label: { + label: "顯示名稱" + }, + description: { + label: "描述" + }, + object: { + label: "物件", + helpText: "目標物件名(或 \"*\" 表示全域)" + }, + events: { + label: "事件", + helpText: "生命週期事件(如 beforeInsert、afterUpdate)" + }, + priority: { + label: "優先順序", + helpText: "數字越小越先執行" + }, + body: { + label: "內文", + helpText: "L1 運算式或 L2 沙箱 JS 體" + }, + "body.language": { + label: "語言", + helpText: "expression = 純公式;js = 沙箱 JavaScript" + }, + "body.source": { + label: "原始碼", + helpText: "函式體原始碼——禁止頂層 import" + }, + "body.capabilities": { + label: "能力", + helpText: "可用的 ctx API(api.read、api.write、crypto.uuid、log 等)" + }, + "body.timeoutMs": { + label: "逾時(毫秒)", + helpText: "單次呼叫逾時時間(毫秒)" + }, + "body.memoryMb": { + label: "記憶體(MB)", + helpText: "單次呼叫記憶體上限(MB,最大 256)" + }, + refusals: { + label: "拒絕目錄", + helpText: "拒絕目錄,以內文丟擲的 SCREAMING_SNAKE 錯誤碼為鍵(例如 CLEAN_VERSION_REQUIRED)。呼叫方讀到宣告的提示語(依其語言翻譯)和宣告的狀態碼。僅適用於 JavaScript(L2)內文" + }, + "refusals.message": { + label: "提示語", + helpText: "內文丟擲此錯誤碼時使用者讀到的句子——純文字,不含預留位置" + }, + "refusals.status": { + label: "狀態碼", + helpText: "拒絕的 HTTP 狀態碼(400–499)。不填:400" + }, + handler: { + label: "處理器", + helpText: "處理器函式名(已廢棄——建議使用 body)" + }, + async: { + label: "非同步", + helpText: "在背景執行,不阻塞目前交易" + }, + onError: { + label: "錯誤處理" + }, + timeoutMs: { + label: "逾時(毫秒)", + helpText: "超過 N 毫秒後中止該掛鉤" + }, + runAs: { + label: "執行身分", + helpText: "ctx.api 資料操作的身分:inherit 繼承觸發寫入的上下文(預設)、system 提權、user 釘到觸發使用者" + }, + condition: { + label: "條件", + helpText: "可選公式——求值為 false 時跳過該掛鉤" + }, + retryPolicy: { + label: "重試策略", + helpText: "失敗時重試——對非同步掛鉤最有用" + }, + "retryPolicy.maxRetries": { + label: "最大重試次數", + helpText: "最多可重試的次數" + }, + "retryPolicy.backoffMs": { + label: "退避(毫秒)", + helpText: "兩次重試之間的延遲(毫秒)" + } + } + }, + seed: { + label: "種子資料", + description: "發布時套用的預置/初始化資料" + }, + picklist: { + label: "共用選項集", + description: "選擇欄位依名稱參照的共用選項清單" + }, + mapping: { + label: "匯入對應", + description: "可複用的匯入/匯出欄位對應(重新命名 + 轉換),在匯入時依名稱參照" + }, + view: { + label: "檢視", + sections: { + basics: { + label: "基礎資訊", + description: "識別資訊與主顯示形態" + }, + columns_filters: { + label: "欄與篩選", + description: "顯示哪些列以及使用者如何篩選" + }, + table_options: { + label: "表格選項", + description: "僅 Grid 表格的顯示選項" + }, + pagination: { + label: "分頁", + description: "每頁筆數與每頁筆數選項——所有檢視類型都接受,不只是 Grid 表格" + }, + kanban: { + label: "看板設定", + description: "看板專屬設定" + }, + calendar: { + label: "日曆設定", + description: "日曆專屬設定" + }, + gantt: { + label: "甘特圖設定", + description: "甘特圖專屬設定" + }, + gallery: { + label: "畫廊設定", + description: "畫廊專屬設定" + }, + timeline: { + label: "時間軸設定", + description: "時間軸專屬設定" + }, + chart: { + label: "圖表設定", + description: "圖表專屬設定" + }, + end_user_controls: { + label: "終端使用者控制", + description: "終端使用者在此檢視上可執行的操作——快速篩選、篩選分頁、視覺化切換。" + }, + navigation_sharing: { + label: "導覽與共用", + description: "檢視出現在哪裡以及誰可以檢視" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case,環境內唯一" + }, + label: { + label: "顯示名稱" + }, + description: { + label: "描述" + }, + type: { + label: "類型", + helpText: "主要的檢視形態" + }, + data: { + label: "資料", + helpText: "資料來源——如:{\"provider\":\"object\",\"object\":\"task\"}" + }, + columns: { + label: "欄", + helpText: "要顯示的欄(來自所選物件的欄位名)" + }, + "columns.field": { + label: "欄位" + }, + "columns.label": { + label: "顯示名稱" + }, + "columns.width": { + label: "寬度(像素)" + }, + "columns.align": { + label: "對齊方式" + }, + "columns.hidden": { + label: "隱藏" + }, + "columns.sortable": { + label: "可排序" + }, + "columns.resizable": { + label: "可調整大小" + }, + "columns.wrap": { + label: "自動換行" + }, + "columns.type": { + label: "呈現器類型" + }, + "columns.pinned": { + label: "固定" + }, + "columns.summary": { + label: "彙總" + }, + "columns.prefix": { + label: "字首" + }, + "columns.link": { + label: "主連結" + }, + "columns.action": { + label: "點選操作" + }, + filter: { + label: "篩選", + helpText: "篩選條件——與清單工具列相同的視覺化構建器,運算子與取值輸入隨欄位類型變化" + }, + sort: { + label: "排序", + helpText: "預設排序方式" + }, + "sort.field": { + label: "欄位" + }, + "sort.order": { + label: "排序方向" + }, + searchableFields: { + label: "可搜尋欄位", + helpText: "可用於快速搜尋的欄位名" + }, + resizable: { + label: "可調整大小", + helpText: "允許調整欄寬" + }, + compactToolbar: { + label: "緊湊工具列", + helpText: "將分組 / 顏色 / 密度 / 隱藏欄位收進同一個「檢視設定」彈出框" + }, + rowHeight: { + label: "列高", + helpText: "列高 / 密度設定" + }, + selection: { + label: "選擇", + helpText: "列選擇設定" + }, + "selection.type": { + label: "類型", + helpText: "選擇模式" + }, + pagination: { + label: "分頁", + helpText: "分頁設定" + }, + "pagination.pageSize": { + label: "每頁筆數", + helpText: "每頁顯示的記錄數。在沒有分頁器的檢視(看板、畫廊、時間軸)上,它是取數上限,呈現器須做到兩點:依此數限定取數;當篩選結果多於此數時,顯示明顯的截斷提示,說明螢幕上並非全部記錄" + }, + "pagination.pageSizeOptions": { + label: "每頁筆數選項", + helpText: "可選的每頁筆數" + }, + kanban: { + label: "看板" + }, + calendar: { + label: "日曆" + }, + gantt: { + label: "甘特圖" + }, + gallery: { + label: "畫廊" + }, + timeline: { + label: "時間軸" + }, + chart: { + label: "圖表" + }, + userFilters: { + label: "使用者篩選器", + helpText: "快速篩選欄:控制項樣式(dropdown / tabs / toggle)+ 暴露的欄位或分頁預設" + }, + "userFilters.element": { + label: "控制項樣式", + helpText: "物件檢視上的篩選控制項樣式:\"dropdown「(依欄位取值的篩選標籤)。」toggle「 已棄用。」tabs\" 僅用於頁面——命名預設請使用 `listViews`。" + }, + "userFilters.fields": { + label: "篩選欄位", + helpText: "作為快速篩選暴露的欄位(dropdown / toggle 控制項)" + }, + "userFilters.fields.field": { + label: "欄位" + }, + "userFilters.fields.label": { + label: "顯示名稱" + }, + "userFilters.fields.type": { + label: "類型" + }, + "userFilters.fields.options": { + label: "選項" + }, + "userFilters.fields.showCount": { + label: "顯示計數" + }, + "userFilters.fields.defaultValues": { + label: "預設值" + }, + appearance: { + label: "外觀", + helpText: "allowedVisualizations:允許使用者切換的呈現器" + }, + "appearance.showDescription": { + label: "顯示描述", + helpText: "顯示檢視的描述文字" + }, + "appearance.allowedVisualizations": { + label: "允許的視覺化", + helpText: "使用者可在其間切換的視覺化類型白名單(如 [\"grid\", \"gallery\", \"kanban\"])" + }, + userActions: { + label: "使用者操作", + helpText: "工具列開關:排序 / 搜尋 / 篩選 / 列高" + }, + "userActions.sort": { + label: "排序", + helpText: "允許使用者對記錄排序" + }, + "userActions.search": { + label: "搜尋", + helpText: "允許使用者搜尋記錄" + }, + "userActions.filter": { + label: "篩選", + helpText: "允許使用者篩選記錄" + }, + "userActions.refresh": { + label: "重新整理", + helpText: "允許使用者從後端重新載入檢視資料,無需整頁重新整理" + }, + "userActions.rowHeight": { + label: "列高", + helpText: "允許使用者切換列高 / 密度" + }, + "userActions.group": { + label: "分組", + helpText: "允許使用者在工具列中更改記錄分組。僅為開關——分組本身在檢視級的 `grouping` 設定中設定。" + }, + "userActions.addRecordForm": { + label: "透過表單新增記錄", + helpText: "透過表單而非內嵌方式新增記錄" + }, + "userActions.editInline": { + label: "內嵌編輯", + helpText: "允許使用者在內嵌編輯記錄。預設開啟:有權更新該物件的使用者可直接在儲存格內編輯,使用與該欄位類型相符的控制項(與表單所用相同),並受決定該物件是否可編輯的同一權限門控約束。宣告 `editInline: false` 可讓清單在原位唯讀。" + }, + "userActions.hideFields": { + label: "隱藏欄位", + helpText: "允許使用者在工具列中隱藏 / 顯示欄位(即檢視級 `hiddenFields` 清單背後的入口)。布林開關——不同於記錄詳細資料元件的 `hideFields`,後者是要省略的欄位名陣列。預設開啟:讀者可自行隱藏和顯示欄;宣告 `hideFields: false` 可收起該控制項。" + }, + "userActions.rowColor": { + label: "列顏色", + helpText: "允許使用者在工具列中設定列著色。布林開關——著色規則本身在檢視級的 `rowColor` 設定中。預設關閉:列著色需顯式開啟。" + }, + "userActions.buttons": { + label: "按鈕", + helpText: "要在工具列中顯示的自訂操作按鈕 ID" + }, + addRecord: { + label: "新增記錄", + helpText: "新增記錄入口設定" + }, + "addRecord.enabled": { + label: "已啟用", + helpText: "顯示新增記錄入口——必填:`true` 顯示「新增」按鈕,`false` 宣告關閉" + }, + "addRecord.position": { + label: "位置", + helpText: "新增記錄按鈕的位置:`top`(工具列,預設)、`bottom`(清單下方)或 `both`" + }, + showRecordCount: { + label: "顯示記錄數", + helpText: "在清單底部顯示記錄數" + }, + navigation: { + label: "導覽", + helpText: "點選條目時的導覽設定(預設為抽屜,也可為 page、new_window 或 none)" + }, + "navigation.mode": { + label: "模式" + }, + "navigation.preventNavigation": { + label: "禁止導覽", + helpText: "完全停用標準導覽" + }, + "navigation.openNewTab": { + label: "在新分頁開啟", + helpText: "強制在新分頁開啟(適用於 page 模式)" + }, + "navigation.size": { + label: "尺寸", + helpText: "抽屜詳細資料的浮層尺寸檔位:'auto'(預設——呈現器依欄位數與視口推導,AI 無需填寫),或粗粒度覆寫 sm/md/lg/xl/full。優先使用它而非像素 `width`;page 模式會忽略它。" + }, + "navigation.width": { + label: "寬度", + helpText: "[已棄用 → size] 抽屜的像素 / 百分比寬度(如 \"600px\")。編寫時無法預知用戶端視口,因此選不出合適的像素寬度——請使用 `size` 檔位。" + }, + sharing: { + label: "共用", + helpText: "檢視共用設定:誰鎖定了檢視設定" + }, + "sharing.lockedBy": { + label: "鎖定者", + helpText: "鎖定該檢視設定的使用者" + } + } + }, + page: { + label: "頁面", + sections: { + basics: { + label: "基礎資訊", + description: "頁面識別資訊與範本" + }, + data_context: { + label: "資料上下文", + description: "關聯物件與變數" + }, + layout: { + label: "佈局", + description: "頁面區塊與元件" + }, + interface: { + label: "Interface(清單頁)", + description: "Interface 模式(對標 Airtable):頁面直接定義自己的資料展現面——欄、篩選、視覺化與工具列——不再繼承獨立檢視。" + }, + advanced: { + label: "進階設定", + description: "啟用狀態、適用人群與無障礙" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼" + }, + label: { + label: "顯示名稱", + helpText: "顯示給使用者的頁面標題" + }, + icon: { + label: "圖示", + helpText: "導覽選單中顯示的圖示" + }, + type: { + label: "類型", + helpText: "頁面種類。\"List / Interface\" 把來源檢視繫結為經過編排的介面——它的展現形式(grid / kanban / calendar / …)是在 Interface 中設定的視覺化,不是頁面類型。" + }, + template: { + label: "範本", + helpText: "佈局範本(如 \"header-sidebar-main\")" + }, + description: { + label: "描述", + helpText: "用於導覽的頁面描述" + }, + source: { + label: "頁面原始碼", + helpText: "頁面原始碼文字。kind 為 'html' 時,它是受約束的 JSX,在儲存時被編譯成元件樹——只解析,絕不執行。kind 為 'react' 時,它是真正的 React,由受信任執行時在呈現時執行。" + }, + object: { + label: "物件", + helpText: "繫結的物件(用於記錄頁)" + }, + variables: { + label: "變數", + helpText: "頁面本地狀態變數" + }, + "variables.name": { + label: "名稱", + helpText: "變數名——在運算式中以 `page.` 存取" + }, + "variables.type": { + label: "類型", + helpText: "值類型" + }, + "variables.defaultValue": { + label: "預設值", + helpText: "初始值(預設為對應類型的空值)" + }, + "variables.source": { + label: "寫入元件", + helpText: "寫入該變數的元件(依 id 指定),如 element:record_picker" + }, + regions: { + label: "區域", + helpText: "佈局區域(header、main、sidebar、footer)及其元件" + }, + "regions.name": { + label: "區域" + }, + "regions.width": { + label: "寬度" + }, + "regions.components": { + label: "元件" + }, + interfaceConfig: { + label: "Interface 設定", + helpText: "頁面本身就是檢視:source 選擇物件,columns/filterBy 直接在這裡定義;appearance.allowedVisualizations 限定可用的呈現器(只有一項即鎖定);userActions 控制工具列開關。" + }, + "interfaceConfig.source": { + label: "資料來源", + helpText: "此頁面讀取資料的物件" + }, + "interfaceConfig.columns": { + label: "欄", + helpText: "要顯示的欄——直接在頁面上定義(留空表示物件的全部欄位)" + }, + "interfaceConfig.filterBy": { + label: "篩選條件", + helpText: "頁面始終生效的基礎篩選——與清單工具列相同的視覺化構建器。" + }, + "interfaceConfig.sort": { + label: "排序", + helpText: "頁面的預設排序方式,直接在頁面上定義。" + }, + "interfaceConfig.sort.field": { + label: "欄位" + }, + "interfaceConfig.sort.order": { + label: "排序方向" + }, + "interfaceConfig.levels": { + label: "層級", + helpText: "要顯示的層級(樹形資料來源)" + }, + "interfaceConfig.appearance": { + label: "外觀", + helpText: "允許的視覺化(Grid / Kanban / Calendar / …)與描述是否顯示" + }, + "interfaceConfig.userFilters": { + label: "使用者篩選器", + helpText: "終端使用者篩選欄:None(不顯示篩選欄)/ Tabs(命名預設)/ Dropdown(依欄位)。None 會移除該設定。" + }, + "interfaceConfig.userActions": { + label: "使用者操作", + helpText: "工具列開關(搜尋、排序、篩選、列高)" + }, + "interfaceConfig.addRecord": { + label: "新增記錄", + helpText: "新增記錄的入口" + }, + "interfaceConfig.addRecord.enabled": { + label: "已啟用" + }, + "interfaceConfig.addRecord.position": { + label: "位置" + }, + "interfaceConfig.emptyState": { + label: "空狀態", + helpText: "清單沒有記錄時頁面顯示的內容:標題、說明、圖示和一個連結。在此設定後,它將替換源檢視的空狀態。" + }, + "interfaceConfig.buttons": { + label: "按鈕", + helpText: "工具列按鈕——從該物件的操作中選擇" + }, + "interfaceConfig.recordAction": { + label: "記錄操作", + helpText: "點選記錄時如何開啟其詳細資料" + }, + "interfaceConfig.showRecordCount": { + label: "顯示記錄數", + helpText: "顯示記錄數欄" + }, + "interfaceConfig.allowPrinting": { + label: "允許列印", + helpText: "允許使用者列印此頁面" + }, + isDefault: { + label: "預設", + helpText: "開啟該物件的記錄時使用此記錄頁。每個物件只能有一個預設記錄頁:第二個會被拒絕。" + }, + kind: { + label: "模式", + helpText: "頁面覆寫模式:完整(full)或插槽式(slotted),僅用於記錄頁。" + }, + aria: { + label: "無障礙", + helpText: "無障礙標籤與角色" + } + } + }, + dashboard: { + label: "儀表板", + sections: { + basics: { + label: "基礎資訊", + description: "儀表板識別資訊與描述" + }, + layout: { + label: "佈局", + description: "格線尺寸與重新整理間隔" + }, + widgets: { + label: "元件", + description: "放置在格線上的卡片與圖表" + }, + filters: { + label: "篩選", + description: "套用到所有元件的預設篩選與全域篩選" + }, + advanced: { + label: "進階設定", + description: "效能與無障礙" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼" + }, + label: { + label: "顯示名稱", + helpText: "顯示名" + }, + description: { + label: "描述" + }, + columns: { + label: "欄", + helpText: "格線欄數(預設 12)" + }, + gap: { + label: "間距", + helpText: "元件之間的間距,每級 0.25rem(4 = 1rem)" + }, + refreshIntervalSeconds: { + label: "重新整理間隔", + helpText: "自動重新整理間隔(秒)" + }, + header: { + label: "頁首", + helpText: "儀表板頁首設定(title、subtitle、actions)" + }, + "header.showTitle": { + label: "顯示標題", + helpText: "在頁首中顯示儀表板標題" + }, + "header.showDescription": { + label: "顯示描述", + helpText: "在頁首中顯示儀表板描述" + }, + "header.actions": { + label: "操作按鈕", + helpText: "頁首中的操作按鈕" + }, + "header.actions.label": { + label: "標籤" + }, + "header.actions.actionUrl": { + label: "操作地址" + }, + "header.actions.actionType": { + label: "操作類型" + }, + "header.actions.icon": { + label: "圖示" + }, + widgets: { + label: "元件", + helpText: "包含位置和尺寸的儀表板元件" + }, + "widgets.id": { + label: "元件 ID" + }, + "widgets.title": { + label: "標題" + }, + "widgets.description": { + label: "描述" + }, + "widgets.type": { + label: "視覺化類型" + }, + "widgets.chartConfig": { + label: "圖表設定" + }, + "widgets.colorVariant": { + label: "配色" + }, + "widgets.requiresObject": { + label: "所需物件" + }, + "widgets.requiresService": { + label: "所需服務" + }, + "widgets.filter": { + label: "篩選" + }, + "widgets.compareTo": { + label: "對比區間" + }, + "widgets.dataset": { + label: "資料集" + }, + "widgets.dimensions": { + label: "維度" + }, + "widgets.values": { + label: "度量" + }, + "widgets.layout": { + label: "佈局" + }, + "widgets.options": { + label: "選項" + }, + "widgets.filterBindings": { + label: "篩選繫結" + }, + "widgets.suppressWarnings": { + label: "抑制的警示" + }, + dateRange: { + label: "日期範圍", + helpText: "預設日期範圍選擇器" + }, + globalFilters: { + label: "全域篩選", + helpText: "套用到所有元件的篩選條件" + }, + "globalFilters.name": { + label: "名稱" + }, + "globalFilters.field": { + label: "欄位" + }, + "globalFilters.object": { + label: "物件" + }, + "globalFilters.label": { + label: "顯示名稱" + }, + "globalFilters.type": { + label: "輸入類型" + }, + "globalFilters.options": { + label: "選項" + }, + "globalFilters.optionsFrom": { + label: "選項來源" + }, + "globalFilters.defaultValue": { + label: "預設值" + }, + "globalFilters.scope": { + label: "範圍" + }, + "globalFilters.targetWidgets": { + label: "目標元件" + } + } + }, + app: { + label: "應用程式", + sections: { + basics: { + label: "基礎資訊", + description: "應用程式識別資訊與啟用狀態" + }, + navigation: { + label: "導覽", + description: "側邊欄選單項目與分割區分組" + }, + content: { + label: "內容", + description: "環境助手繫結。物件與介面在 stack 上宣告,不在 app 上" + }, + branding: { + label: "品牌", + description: "主題色與 Logo" + }, + access_and_sharing: { + label: "存取與共用", + description: "誰可以存取此應用程式。公開存取與嵌入依表單檢視授予,不是應用程式級開關" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼" + }, + label: { + label: "顯示名稱" + }, + description: { + label: "描述" + }, + icon: { + label: "圖示", + helpText: "Lucide 圖示名(如 users、briefcase)" + }, + active: { + label: "啟用" + }, + isDefault: { + label: "預設", + helpText: "設為新使用者的預設應用程式" + }, + hidden: { + label: "從應用程式切換器隱藏", + helpText: "把該應用程式排除在應用程式切換器之外——外殼改為從頭像選單顯示它。這隻影響導覽:隱藏的應用程式依然可路由,且照常做權限驗證。" + }, + navigation: { + label: "導覽", + helpText: "遞迴的導覽結構" + }, + areas: { + label: "區域", + helpText: "將選單項目組織為可摺疊分組" + }, + "areas.id": { + label: "區域 ID" + }, + "areas.label": { + label: "顯示名稱" + }, + "areas.icon": { + label: "圖示" + }, + "areas.description": { + label: "描述" + }, + "areas.navigation": { + label: "導覽" + }, + defaultAgent: { + label: "預設 AI 代理", + helpText: "環境助手使用的平台 AI 代理(預設 'ask';創作類介面用 'build')" + }, + branding: { + label: "品牌", + helpText: "主色、輔色、Logo 與主題" + }, + requiredPermissions: { + label: "所需權限", + helpText: "存取該應用程式需要的權限" + } + } + }, + action: { + label: "操作", + sections: { + basics: { + label: "基礎資訊", + description: "識別資訊、物件與圖示" + }, + behavior: { + label: "行為", + description: "點選後執行的操作" + }, + placement: { + label: "位置", + description: "在介面上的呈現與可見性" + }, + advanced: { + label: "進階設定", + description: "AI 暴露與 API 請求體結構" + } + }, + fields: { + name: { + label: "名稱", + helpText: "唯一識別碼(snake_case)" + }, + label: { + label: "顯示名稱", + helpText: "顯示給使用者的按鈕文字" + }, + objectName: { + label: "物件名稱", + helpText: "所屬物件(可選)" + }, + icon: { + label: "圖示", + helpText: "Lucide 圖示名(如 \"check\"、\"x-circle\")" + }, + type: { + label: "類型", + helpText: "點選後發生什麼" + }, + variant: { + label: "按鈕樣式", + helpText: "按鈕樣式(primary=藍色,danger=紅色,ghost=透明)" + }, + mode: { + label: "語義模式", + helpText: "該動作的語義模式。目前只有 AI 人工確認(HITL)啟發式會讀它——沒有任何呈現端依它分支。" + }, + order: { + label: "排序權重", + helpText: "同一放置分組內的排序值——數值越小越靠前,記錄頭部會取排在最前的那個作為主按鈕。不填則保持註冊順序。" + }, + target: { + label: "目標", + helpText: "呼叫的 URL、流程名或 API 端點" + }, + method: { + label: "方法", + helpText: "HTTP 方法(GET、POST、PUT、DELETE)" + }, + body: { + label: "內文", + helpText: "L1 運算式或 L2 沙箱 JS 體" + }, + "body.language": { + label: "語言", + helpText: "expression = 純公式;js = 沙箱 JavaScript" + }, + "body.source": { + label: "原始碼", + helpText: "函式體原始碼——禁止頂層 import" + }, + "body.capabilities": { + label: "能力", + helpText: "可用的 ctx API(api.read、api.write、crypto.uuid、log 等)" + }, + "body.timeoutMs": { + label: "逾時(毫秒)", + helpText: "單次呼叫逾時時間(毫秒)" + }, + "body.memoryMb": { + label: "記憶體(MB)", + helpText: "單次呼叫記憶體上限(MB,最大 256)" + }, + params: { + label: "參數", + helpText: "執行前向使用者收集的輸入參數" + }, + "params.name": { + label: "名稱" + }, + "params.field": { + label: "欄位" + }, + "params.objectOverride": { + label: "物件覆寫" + }, + "params.label": { + label: "顯示名稱" + }, + "params.type": { + label: "類型" + }, + "params.widget": { + label: "控制項覆寫" + }, + "params.required": { + label: "必填" + }, + "params.options": { + label: "選項" + }, + "params.placeholder": { + label: "預留位置文字" + }, + "params.helpText": { + label: "說明文字" + }, + "params.defaultValue": { + label: "預設值" + }, + "params.multiple": { + label: "多選" + }, + "params.accept": { + label: "允許的類型" + }, + "params.maxSize": { + label: "最大大小(位元組)" + }, + "params.reference": { + label: "參照物件" + }, + "params.defaultFromRow": { + label: "預設取自目前列" + }, + "params.carryOver": { + label: "沿用行值" + }, + "params.visible": { + label: "可見條件" + }, + "params.requiresFeature": { + label: "所需功能" + }, + operation: { + label: "宣告式寫入", + helpText: "對單筆記錄的宣告式欄位寫入:'update' 會把 `patch`(合併在已收集的 `params` 之下)套用到目前記錄,並且以呼叫方的身分執行——絕不提權,因此呼叫方的權限、物件的掛鉤與驗證都會像使用者手動編輯那樣觸發。" + }, + patch: { + label: "靜態欄位值(patch)", + helpText: "更新寫入目前記錄的靜態欄位值,例如 {\"status\": \"done\"}——合併在 `params` 收集的值之下,同名參數優先。以呼叫方身分寫入:物件的權限、掛鉤和驗證規則都會像使用者編輯一樣生效。" + }, + undoable: { + label: "可撤銷", + helpText: "更新成功後提供「撤銷」入口。撤銷捕獲的是該動作寫入的每個欄位的原值——也就是合併後的寫入集合,即 `params` 之下的 `patch`。沒有宣告 `operation` 的動作沒有寫入集合,因此也沒有可捕獲的內容。" + }, + execution: { + label: "批次分發契約", + helpText: "該動作主體所依據的批次分發契約:'perRecord' 為每條選中的記錄各發一次呼叫,並帶上該列的 recordId;'aggregate' 對整批選中只發一次呼叫,所有 id 都在 params._selectedIds 裡。不填則依逐筆記錄分發。" + }, + description: { + label: "描述", + helpText: "顯示在此動作參數對話方塊標題下方的說明文字。對於會收集參數的動作,確認問題應寫在這裡而不是 confirmText 中——一個對話方塊,而不是兩個。它不是 ai.description,後者是 AI 代理讀取的文字。" + }, + submitText: { + label: "提交按鈕文字", + helpText: "此動作參數對話方塊中提交按鈕的文字,即按鈕執行的動作(例如「傳送」「記錄通話」)。僅用於需要收集參數的動作。留空則顯示「確認」。" + }, + cancelText: { + label: "取消按鈕文字", + helpText: "此動作執行前彈出的對話方塊(確認框或參數對話方塊)中取消按鈕的文字。動作本身就是「取消……」時,寫明取消後保留的是什麼(例如「保留邀請」)。僅用於需要確認或收集參數的動作。留空則顯示「取消」。" + }, + confirmText: { + label: "確認文字", + helpText: "執行前的確認提示(如「確定要執行嗎?」)" + }, + successMessage: { + label: "成功訊息", + helpText: "執行成功後的提示資訊" + }, + errorMessage: { + label: "錯誤訊息", + helpText: "動作失敗時顯示的錯誤訊息,替代原始錯誤資訊。" + }, + outcomeMessages: { + label: "依結果的成功訊息", + helpText: "依處理程序結果顯示的成功訊息,以 JSON 填寫:每個鍵是處理程序在成功傳回中給出的 snake_case `outcome` 值,每個值是該結果顯示的文字,例如 {\"archived\": \"已封存\", \"already_archived\": \"此前已封存\"}。文字中可插入 {{ result.* }}。沒有對應條目的結果顯示成功訊息;處理程序從不傳回的鍵永遠不會顯示。不能與結果對話方塊或 `operation: 'update'` 同時使用。" + }, + onSuccess: { + label: "成功後導向", + helpText: "動作成功後的導向:要開啟的路由,以及在哪裡開啟。" + }, + "onSuccess.navigate": { + label: "導向目標", + helpText: "動作成功後開啟的路由或 URL 範本。可插入 ${param.*}(參數對話方塊收集的值)、${ctx.*}(origin、apiBase、user、org、recordId、selection)和 ${result.*}(伺服器回應,例如 ${result.id})。相對值是應用程式內路由。" + }, + "onSuccess.openIn": { + label: "導向開啟位置", + helpText: "導向在哪裡開啟——'self'(預設)在目前頁導向,'newTab' 開啟新的瀏覽器分頁。" + }, + refreshAfter: { + label: "完成後重新整理", + helpText: "執行完成後重新整理目前清單/頁面" + }, + openIn: { + label: "連結開啟位置", + helpText: "靜態 `target` URL 的開啟位置——'self' 在目前頁導向,'new-tab' 開啟新的瀏覽器分頁。不填時,絕對或外部 URL 開啟新分頁,相對 URL 在目前頁導向。" + }, + opensInNewTab: { + label: "結果在新分頁開啟", + helpText: "在新分頁開啟動作的執行結果:呈現端在點選時同步預開一個分頁(不會被彈出式視窗攔截),隨後把它導覽到處理程序傳回的 redirectUrl。與 `openIn` 不同,後者決定靜態 URL 目標的去向。" + }, + newTabUrl: { + label: "新分頁路徑", + helpText: "本後端上的路徑,必須以 / 開頭(如 /sso-open/{recordId}),可用 {recordId} 預留位置。與 `opensInNewTab` 同時設定時,呈現端會立即把預開的分頁導覽到「後端地址 + 該路徑」,不再發起動作請求——因此該端點必須自行驗證驗證。絕對 URL 會被拒絕;要開啟外部頁面,請用 url 類型的動作,並把「連結開啟位置」設為 new-tab。" + }, + locations: { + label: "位置", + helpText: "出現在工具列、列選單等位置" + }, + component: { + label: "元件", + helpText: "以按鈕、圖示或選單項目的形式呈現" + }, + visible: { + label: "可見", + helpText: "CEL 運算式:滿足條件時顯示" + }, + disabled: { + label: "停用", + helpText: "CEL 運算式:滿足條件時停用" + }, + disabledReason: { + label: "停用原因", + helpText: "動作置灰的原因,在「停用」為 true 時懸停顯示,並由螢幕閱讀器讀出(例如「需要一條通過的法務審查後才能送審。」)。僅用於顯示:伺服器端仍依自身規則拒絕呼叫。留空則顯示「對此記錄不可用」。" + }, + requiresFeature: { + label: "所需驗證特性", + helpText: "用於控制該動作是否出現的公共驗證特性開關。它在解析時被降解進 `visible` 斷言並從輸出中移除,因此下游消費方永遠看不到這個鍵。" + }, + requiresMembershipReach: { + label: "所需成員等級可達端點", + helpText: "該動作呼叫的組織端點:只有成員等級可達該端點的成員才會看到它。它在解析時被降解進 `visible` 斷言並從輸出中移除,因此下游消費方永遠看不到這個鍵。" + }, + requiredPermissions: { + label: "所需權限", + helpText: "呼叫此動作必須持有的能力(權限集的 systemPermissions),列出的每一項都必須持有(ADR-0066 D4)。平台動作門對它分派的每一次呼叫都以 403 拒絕其他呼叫方(包括 MCP/AI 路徑),並對他們隱藏按鈕。type 為 api 的動作,其端點仍可被直接呼叫,因此該端點仍須自行檢查這些能力;target 不同源的 api 動作,平台在伺服器端不做任何檢查。" + }, + ai: { + label: "AI 暴露", + helpText: "AI 暴露(需顯式開啟):設定 ai.exposed=true 並填寫 ai.description(≥40 個字元),此動作即可被代理呼叫。" + }, + recordIdParam: { + label: "記錄 ID 參數", + helpText: "API 請求體中記錄 ID 的參數名" + }, + recordIdField: { + label: "記錄 ID 欄位", + helpText: "作為記錄 ID 的欄位(預設 \"id\")" + }, + bodyShape: { + label: "請求體結構", + helpText: "請求體的組織形式(扁平或巢狀)" + }, + bodyExtra: { + label: "附加請求體(bodyExtra)", + helpText: "此 api 動作的靜態請求體欄位,最後合併,因此會覆寫收集到的參數(例如 {\"resend\": true})。頁面變數標記(寫在雙花括號中的 page.NAME)由執行時解析。請求承載寫在這裡,不要寫在 params 中。" + } + } + }, + report: { + label: "報表", + sections: { + basics: { + label: "基礎資訊", + description: "識別資訊與報表類型" + }, + dataset_binding: { + label: "資料集繫結", + description: "本報表呈現的語義層資料集。度量與列分別來自該資料集的度量與維度" + }, + joined_blocks: { + label: "joined 報表分塊", + description: "疊加進同一張報表的其他資料集分塊(僅 joined 報表)" + }, + filter_and_chart: { + label: "篩選與圖表", + description: "條件與圖表顯示" + }, + period_and_export: { + label: "時間段與匯出", + description: "報表的預設時間段,以及其表格可下載的格式" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼" + }, + label: { + label: "顯示名稱" + }, + description: { + label: "描述" + }, + type: { + label: "類型", + helpText: "報表類型:tabular / summary / matrix / joined" + }, + dataset: { + label: "資料集", + helpText: "要繫結的資料集(度量/維度來自其語義層)" + }, + values: { + label: "度量", + helpText: "要顯示的度量名(來自該資料集)" + }, + rows: { + label: "列", + helpText: "用於分組列的維度名(來自該資料集)" + }, + columns: { + label: "欄", + helpText: "橫向展開的維度名(僅 matrix 報表)" + }, + order: { + label: "排序", + helpText: "排序鍵,最重要的在前(rows/columns 維度或 values 度量)。時間維度預設依時間順序排列。" + }, + "order.by": { + label: "排序依據" + }, + "order.direction": { + label: "排序方向" + }, + drilldown: { + label: "下鑽", + helpText: "點選聚合後的列/儲存格,開啟其底層記錄" + }, + blocks: { + label: "分塊", + helpText: "繫結資料集的子報表(僅 joined 報表)" + }, + "blocks.name": { + label: "名稱" + }, + "blocks.label": { + label: "顯示名稱" + }, + "blocks.description": { + label: "描述" + }, + "blocks.type": { + label: "分塊類型" + }, + "blocks.dataset": { + label: "資料集" + }, + "blocks.rows": { + label: "列" + }, + "blocks.columns": { + label: "欄" + }, + "blocks.values": { + label: "度量" + }, + "blocks.runtimeFilter": { + label: "執行時篩選" + }, + "blocks.order": { + label: "排序" + }, + runtimeFilter: { + label: "執行時篩選", + helpText: "呈現時的範圍篩選,在查詢時以 AND 方式併入" + }, + chart: { + label: "圖表", + helpText: "圖表類型與設定" + }, + dateRange: { + label: "日期範圍", + helpText: "預設時間段與自訂範圍開關。維度必須是資料集的日期維度;資料集只有一個日期維度時可留空" + }, + exportOptions: { + label: "匯出選項", + helpText: "下載格式(csv、xlsx)與檔案名稱。誰可以下載由資料集物件上的匯出權限決定" + } + } + }, + dataset: { + label: "資料集", + description: "分析語義層 — 維度與度量", + sections: { + basics: { + label: "基礎資訊", + description: "資料集識別資訊" + }, + source: { + label: "資料來源", + description: "基礎物件、要關聯的關係,以及資料集的固有範圍。關聯由物件關係圖推導 — 選擇關係(lookup / master_detail)欄位名,不要手寫 ON 子句" + }, + dimensions: { + label: "維度", + description: "可分組的軸。使用基礎物件的欄位,或對上方已包含的關係使用 `relationship.field`(如 account.region)" + }, + measures: { + label: "度量", + description: "定義一次即可依名稱參照的可聚合值。度量是某個欄位的 sum/avg/count/…;派生度量由其他度量組合而成(ratio/sum/difference/product)。度量級篩選與派生運算在資料集設計器中逐列編輯" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼" + }, + label: { + label: "顯示名稱", + helpText: "顯示名" + }, + description: { + label: "描述", + helpText: "此資料集衡量什麼" + }, + object: { + label: "物件", + helpText: "基礎物件 — 即 FROM" + }, + include: { + label: "包含關聯", + helpText: "要關聯的關係(lookup / master_detail)欄位名 — 用於啟用 `relationship.field` 維度/度量(例如 include \"account\" → 依 account.region 分組)" + }, + filter: { + label: "篩選", + helpText: "固有範圍篩選(例如排除軟刪除記錄),以 AND 方式併入每個查詢" + }, + dimensions: { + label: "維度", + helpText: "每個維度:名稱(供顯示層參照)、欄位、類型,以及日期類型的預設分桶粒度" + }, + "dimensions.name": { + label: "名稱" + }, + "dimensions.label": { + label: "顯示名稱" + }, + "dimensions.field": { + label: "欄位" + }, + "dimensions.type": { + label: "類型" + }, + "dimensions.dateGranularity": { + label: "日期粒度" + }, + "dimensions.nullLabel": { + label: "空值標籤" + }, + measures: { + label: "度量", + helpText: "每個度量:名稱、聚合函式、欄位(count 時可省略)、顯示格式/貨幣" + }, + "measures.name": { + label: "名稱" + }, + "measures.label": { + label: "顯示名稱" + }, + "measures.aggregate": { + label: "聚合函式" + }, + "measures.field": { + label: "欄位" + }, + "measures.filter": { + label: "篩選" + }, + "measures.format": { + label: "格式" + }, + "measures.currency": { + label: "貨幣" + }, + "measures.polarity": { + label: "極性" + }, + "measures.derived": { + label: "派生自" + }, + "measures.duration": { + label: "時長" + } + } + }, + flow: { + label: "流程", + sections: { + basics: { + label: "基礎資訊", + description: "流程識別資訊與啟動方式" + }, + canvas: { + label: "畫布", + description: "節點、連線與流程變數——複雜流程建議用視覺化設計器" + }, + execution: { + label: "執行設定", + description: "部署狀態、身分與異常處理" + } + }, + fields: { + name: { + label: "名稱", + helpText: "唯一識別碼(snake_case)" + }, + label: { + label: "顯示名稱", + helpText: "使用者看到的顯示名稱" + }, + type: { + label: "類型", + helpText: "流程如何啟動(autolaunched / record_change / schedule / screen / api)" + }, + description: { + label: "描述", + helpText: "此流程做什麼" + }, + nodes: { + label: "節點", + helpText: "⚠️ 建議使用流程設計器,而非手寫 JSON" + }, + "nodes.id": { + label: "節點 ID" + }, + "nodes.type": { + label: "節點類型" + }, + "nodes.label": { + label: "顯示名稱" + }, + "nodes.config": { + label: "設定" + }, + "nodes.connectorConfig": { + label: "連接器操作" + }, + "nodes.position": { + label: "畫布位置" + }, + "nodes.timeoutMs": { + label: "逾時(毫秒)" + }, + "nodes.inputSchema": { + label: "輸入 Schema" + }, + "nodes.waitEventConfig": { + label: "等待事件" + }, + "nodes.boundaryConfig": { + label: "邊界事件" + }, + edges: { + label: "連線", + helpText: "節點間的連線——建議用流程設計器編輯" + }, + "edges.id": { + label: "連線 ID" + }, + "edges.source": { + label: "起始節點" + }, + "edges.target": { + label: "目標節點" + }, + "edges.condition": { + label: "條件" + }, + "edges.type": { + label: "連線類型" + }, + "edges.label": { + label: "顯示名稱" + }, + "edges.isDefault": { + label: "預設路徑" + }, + variables: { + label: "變數", + helpText: "流程變數(輸入/輸出)" + }, + "variables.name": { + label: "名稱" + }, + "variables.type": { + label: "類型" + }, + "variables.isInput": { + label: "輸入" + }, + "variables.isOutput": { + label: "輸出" + }, + "variables.defaultValue": { + label: "預設值" + }, + "variables.objectName": { + label: "記錄物件" + }, + status: { + label: "狀態", + helpText: "部署狀態:draft → active → obsolete" + }, + version: { + label: "版本", + helpText: "版本號(自動遞增)" + }, + runAs: { + label: "執行身分", + helpText: "以系統(管理員)或目前使用者權限執行" + }, + requiredPermissions: { + label: "所需權限", + helpText: "呼叫方啟動本流程必須持有的能力(權限集的 systemPermissions),列出的每一項都要具備。每個啟動入口(觸發路由、經 REST 與 MCP 的流程動作、流程端點)對其他呼叫方一律傳回 403;啟動本流程的動作繼承這份清單。父流程的子流程步驟不再重複檢查。" + }, + errorHandling: { + label: "錯誤處理", + helpText: "節點失敗時的處理方式(fail / retry / continue)" + }, + successMessage: { + label: "成功提示訊息", + helpText: "流程成功結束時隨執行結果傳回的訊息;螢幕流介面會用它代替通用的「Done」彈出提示。" + }, + errorMessage: { + label: "失敗提示訊息", + helpText: "流程失敗時隨執行結果傳回的訊息;螢幕流介面會用它代替原始錯誤彈出提示。" + } + } + }, + job: { + label: "背景工作" + }, + datasource: { + label: "資料來源" + }, + external_catalog: { + label: "外部目錄" + }, + api: { + label: "API 端點", + description: "其他系統呼叫的穩定 HTTP URL,用於執行流程或操作應用程式中的記錄" + }, + translation: { + label: "翻譯" + }, + email_template: { + label: "郵件範本", + sections: { + identity: { + label: "基礎資訊", + description: "傳送方依名稱參照此範本;語言區域決定傳送哪個語言版本。" + }, + subject: { + label: "主旨", + description: "主旨行。支援 {{var.path}} 插值。" + }, + html_body: { + label: "HTML 內文", + description: "富 HTML 內文。大多數用戶端會移除 ,因此請使用內嵌樣式。" + }, + plain_text_body: { + label: "純文字內文", + description: "可選的純文字替代內文。省略時,服務會從 HTML 內文去除標籤生成;顯式提供可改善垃圾郵件評分。" + }, + variables: { + label: "變數", + description: "宣告的變數。Studio 會將其呈現為提示,並在必填時由 sendTemplate() 驗證。" + }, + delivery_overrides: { + label: "傳送覆寫項目", + description: "可選的範本級 From / Reply-To 覆寫設定。" + }, + status: { + label: "狀態" + } + }, + fields: { + name: { + label: "名稱", + helpText: "點分隔的 snake_case(如 auth.password_reset、crm.welcome)" + }, + label: { + label: "顯示名稱" + }, + locale: { + label: "地區語言", + helpText: "BCP-47 標籤,例如 en-US、zh-CN" + }, + description: { + label: "描述" + }, + subject: { + label: "主旨" + }, + bodyHtml: { + label: "HTML 內文" + }, + bodyText: { + label: "純文字內文" + }, + variables: { + label: "變數", + helpText: "示例:[{ \"name\": \"user.name\", \"type\": \"string\", \"required\": true, \"description\": \"...\" }]" + }, + fromOverride: { + label: "寄件者覆寫", + helpText: "示例:{ \"name\": \"Acme Sales\", \"address\": \"sales@acme.com\" }" + }, + replyTo: { + label: "回覆地址", + helpText: "Reply-To 電子郵件地址" + }, + active: { + label: "啟用", + helpText: "未勾選時,sendTemplate() 傳回 TEMPLATE_INACTIVE。" + } + } + }, + doc: { + label: "文件", + description: "以 Markdown 編寫的說明頁面,可由套件隨附,也可自行編寫" + }, + book: { + label: "文件手冊", + description: "將說明頁面依順序分節組織的目錄" + }, + permission: { + label: "權限集", + sections: { + identity: { + label: "身分", + description: "權限集是唯一的能力容器:使用者獲得其持有的所有權限集的並集,因此權限集只會增加存取權限。職位負責把權限集指派給人。" + }, + system_permissions: { + label: "系統權限", + description: "不針對特定物件的高層能力——如 manage_users、view_audit_logs" + }, + object_and_field_permissions: { + label: "物件與欄位權限", + description: "物件級增刪改查 + 欄位級安全(FLS)。可用矩陣編輯器編輯,或在此貼上 JSON。" + }, + tab_and_row_level_security: { + label: "分頁與列級安全", + description: "分頁可見性與列級安全策略" + } + }, + fields: { + label: { + label: "顯示名稱", + helpText: "面向管理員的顯示標籤" + }, + name: { + label: "名稱", + helpText: "建立權限集時由顯示名稱中的拉丁字母和數字自動填寫;顯示名稱不含這些字元時(如全為中文)請自行輸入。snake_case" + }, + description: { + label: "描述", + helpText: "在 Setup 中顯示的可讀描述。" + }, + isDefault: { + label: "預設權限集", + helpText: "透過內建的 everyone 職位,把這個權限集授予每一位登入使用者。應用程式自身宣告的權限集在啟動時授予;已安裝套件中的權限集則成為一條建議,由管理員在 Setup 中確認。含高權限的權限集不會以這種方式授予。預設 false。" + }, + packageId: { + label: "所屬套件 ID", + helpText: "分發這個權限集的套件;填寫其他套件的 id 會被拒絕。在本系統中建立的權限集請留空。" + }, + systemPermissions: { + label: "系統權限", + helpText: "系統能力鍵清單" + }, + adminScope: { + label: "委派管理範圍", + helpText: "委派管理:讓持有本權限集的人管理一個業務單位子樹。businessUnit 是子樹的根(sys_business_unit.name),必填且不能為空白;includeSubtree 預設為 true;manageAssignments 預設為 false,開啟後持有人可在該子樹內把人員指派到職位、並直接授予權限集;被委派者只能指派 assignablePermissionSets 中列出的權限集。不委派任何管理權的權限集請留空。" + }, + objects: { + label: "物件權限", + helpText: "依物件設定增刪改查" + }, + fields: { + label: "欄位", + helpText: "依欄位設定可讀 / 可寫" + }, + tabPermissions: { + label: "分頁權限", + helpText: "導覽中分頁的可見性" + }, + rowLevelSecurity: { + label: "列級安全", + helpText: "基於記錄條件的存取規則" + } + } + }, + position: { + label: "職位", + sections: { + position: { + label: "職位", + description: "職位是扁平的、可指派的權限集組合(如 sales_rep、sales_manager)。能力在權限集上;可見範圍深度在業務單位樹上。" + } + }, + fields: { + name: { + label: "名稱", + helpText: "snake_case 唯一識別碼" + }, + label: { + label: "顯示名稱" + }, + description: { + label: "描述" + }, + permissionSets: { + label: "權限集", + helpText: "該職位指派的權限集,依名稱填寫(snake_case)。" + }, + delegatable: { + label: "可自助委派", + helpText: "持有者可自行委派該職位,需限定時段並填寫理由。預設 false——委派僅限管理員。" + } + } + }, + capability: { + label: "能力", + description: "套件定義的一項權限;透過權限集授予" + }, + sharing_rule: { + label: "共用規則", + description: "套件宣告的記錄共用規則;組織在設定中編寫自己的規則" + }, + queue: { + label: "佇列", + description: "一組具名的人員,記錄可停留其中,直到其中一人認領;組織在設定中設定自己的佇列" + }, + assignment_rule: { + label: "指派規則", + description: "套件宣告的規則,把新記錄指派到某個佇列或某個人" + }, + agent: { + label: "AI 代理", + sections: { + identity: { + label: "身分資訊", + description: "使用者如何識別與呼叫該代理" + }, + ai_configuration: { + label: "AI 設定", + description: "模型選擇、指令、規劃與記憶" + }, + capabilities: { + label: "能力設定", + description: "代理可使用的技能與知識來源" + }, + access: { + label: "存取與安全", + description: "誰能使用此代理以及防護措施" + } + }, + fields: { + name: { + label: "名稱", + helpText: "唯一識別碼(snake_case)" + }, + label: { + label: "顯示名稱", + helpText: "顯示名稱(如:「銷售助手」)" + }, + role: { + label: "人設角色", + helpText: "代理人設(如:「客戶支援專家」)" + }, + avatar: { + label: "頭像", + helpText: "頭像圖片 URL" + }, + active: { + label: "啟用", + helpText: "啟用或停用此代理" + }, + surface: { + label: "繫結產品介面", + helpText: "該 AI 代理繫結的產品介面(ADR-0063 §1)。只有自身介面與之相符、或宣告為 'both' 的技能才會掛載,而該 AI 代理的工具集就是這些技能工具的並集。預設 'ask'。" + }, + instructions: { + label: "指令", + helpText: "系統提示詞——告訴代理如何行動與可以做什麼" + }, + model: { + label: "模型", + helpText: "AI 模型設定(提供方、模型名、溫度等)" + }, + planning: { + label: "規劃", + helpText: "自主推理設定:代理停止前的最大推理迭代次數(1–100,預設 10)。" + }, + memory: { + label: "記憶", + helpText: "長期記憶:依使用者儲存的提煉筆記,在每次工作階段前召回,並每隔 reflectionInterval 次已送達的互動由一次反思寫入。啟用長期記憶時,maxEntries 與 reflectionInterval 為必填。由雲端 AI 執行時強制執行。" + }, + structuredOutput: { + label: "結構化輸出", + helpText: "代理最終回答的輸出契約:JSON 格式、用於驗證回答的 JSON Schema、重試、回退格式與轉換步驟。由雲端 AI 執行時強制執行。" + }, + skills: { + label: "技能", + helpText: "技能名稱(Agent→Skill→Tool 架構)" + }, + access: { + label: "存取", + helpText: "可以與此代理對話的使用者 ID 或角色名" + }, + permissions: { + label: "權限", + helpText: "使用此代理所需的權限" + }, + guardrails: { + label: "護欄", + helpText: "安全規則與內容策略" + } + } + }, + tool: { + label: "工具", + sections: { + basics: { + label: "基礎資訊", + description: "工具身分與面向 AI 的說明" + }, + schemas: { + label: "參數 / 傳回結構", + description: "工具接收的輸入與輸出形態" + } + }, + fields: { + name: { + label: "名稱", + helpText: "唯一識別碼(snake_case)" + }, + label: { + label: "顯示名稱", + helpText: "Studio 中顯示的名稱" + }, + description: { + label: "描述", + helpText: "告訴 AI 何時使用此工具——請儘量具體!" + }, + objectName: { + label: "物件名稱", + helpText: "相關物件(若工具針對特定物件)" + }, + parameters: { + label: "參數", + helpText: "輸入參數——定義形如:{name: {type: \"string\", description: \"...\"}}" + }, + outputSchema: { + label: "輸出 Schema", + helpText: "用於驗證的輸出結構(可選)" + } + } + }, + skill: { + label: "技能", + sections: { + basics: { + label: "基礎資訊", + description: "名稱與描述" + }, + ai_instructions: { + label: "AI 指令", + description: "AI 何時與如何使用該技能" + }, + triggers: { + label: "觸發條件", + description: "此技能應在何時啟用" + } + }, + fields: { + name: { + label: "名稱", + helpText: "唯一識別碼(snake_case)" + }, + label: { + label: "顯示名稱", + helpText: "顯示名(如:「案件管理」)" + }, + description: { + label: "描述", + helpText: "此技能的作用" + }, + active: { + label: "啟用", + helpText: "啟用或停用此技能" + }, + surface: { + label: "繫結 AI 代理介面", + helpText: "該技能繫結的 AI 代理介面(ADR-0063 §3)。把它掛到介面不相符的 AI 代理上會在解析時直接回報錯誤,而不是悄悄跳過。預設 'ask'。" + }, + instructions: { + label: "指令", + helpText: "AI 呼叫此技能時遵循的詳細指令" + }, + tools: { + label: "工具", + helpText: "工具名(支援萬用字元:action_*)" + }, + triggerConditions: { + label: "觸發條件", + helpText: "程式化條件(如 objectName == \"case\")" + }, + "triggerConditions.field": { + label: "上下文欄位" + }, + "triggerConditions.operator": { + label: "運算子" + }, + "triggerConditions.value": { + label: "值" + } + } + } +}; diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.objects.generated.ts b/packages/platform-objects/src/apps/translations/zh-Hant.objects.generated.ts new file mode 100644 index 00000000000..5f560637aaf --- /dev/null +++ b/packages/platform-objects/src/apps/translations/zh-Hant.objects.generated.ts @@ -0,0 +1,3810 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-Hant'. + * Edit translations in place; re-run extract (with --merge) to fill new gaps. + * Do not hand-edit the structure — only the leaf string values. + * Merge only fills gaps: correcting a source label/description does not + * push the correction into a leaf here that already holds a translation — + * a present-but-stale string is not a gap, so it is left as-is. Re-translate + * it by hand when its source changes; nothing here or in `os i18n check` + * tells a leaf a translator updated on purpose from one nobody has looked + * at since the source moved. + */ + +import type { TranslationData } from '@objectstack/spec/system'; + +export const zhHantObjects: NonNullable = { + sys_user: { + label: "使用者", + pluralLabel: "使用者", + description: "用於身分驗證的使用者帳號", + fields: { + name: { + label: "名稱" + }, + email: { + label: "電子郵件" + }, + email_verified: { + label: "電子郵件已驗證" + }, + two_factor_enabled: { + label: "已啟用雙因素驗證", + help: "該使用者是否已啟用雙因素驗證。由 better-auth 的 `twoFactor` 外掛維護。" + }, + role: { + label: "平台角色", + help: "遺留的 better-auth 角色標量(admin、user 等)。ObjectStack 不再寫入該欄位。要授予平台管理員身分,請將該使用者已驗證的電子郵件加入 `OS_PLATFORM_OWNER_EMAIL`;在 `single` 租用戶態勢下,`sys_user_permission_set` 中一條未限定範圍的 `admin_full_access` 授權同樣可以賦予該身分。" + }, + banned: { + label: "已停權", + help: "開啟後,使用者無法登入。可透過「停權使用者 / 解除停權」操作切換。" + }, + ban_reason: { + label: "停權原因" + }, + ban_expires: { + label: "停權到期時間", + help: "設定後,到達該時間會自動解除停權。" + }, + failed_login_count: { + label: "連續登入失敗次數", + help: "連續登入失敗的次數;登入成功後重設為 0。由驗證管理器維護。" + }, + locked_until: { + label: "鎖定至", + help: "設定且時間在未來時,登入會被拒絕(防暴力破解鎖定)。超過該時間自動解除;管理員也可透過「解鎖帳號」提前解除。" + }, + password_changed_at: { + label: "密碼修改時間", + help: "最近一次修改密碼的時間。支撐 password_expiry_days(密碼有效期);系統維護。" + }, + phone_number: { + label: "手機號碼", + help: "用於登入的手機號(建議 E.164 格式)。每個使用者唯一;啟用 phoneNumber 外掛時由 better-auth 管理。" + }, + phone_number_verified: { + label: "手機已驗證", + help: "手機號是否已驗證(OTP 驗證依賴簡訊基礎設施,在其上線前保持 false)。系統維護。" + }, + must_change_password: { + label: "須修改密碼", + help: "開啟後,使用者必須先修改密碼才能繼續使用。管理員建立帳號、匯入使用者或重設密碼時會自動開啟;系統維護。" + }, + mfa_required_at: { + label: "MFA 強制生效時間", + help: "強制 MFA 首次對該使用者生效的時間(寬限期計時起點)。支撐 mfa_required;系統維護。" + }, + last_login_at: { + label: "最近登入時間", + help: "最近一次成功登入的時間。由驗證管理器寫入;系統維護。" + }, + last_login_ip: { + label: "最近登入 IP", + help: "最近一次成功登入的用戶端 IP(取自可信代理轉發頭)。系統維護。" + }, + ai_access: { + label: "AI 存取權限", + help: "該使用者是否佔用一個 AI 席位。席位授予 `ai_seat` 能力,可使用應用程式內的 AI 助手(build 和 ask)。可佔用席位的使用者數受許可證所含席位數限制。" + }, + image: { + label: "頭像" + }, + locale: { + label: "語言", + help: "該使用者介面顯示和接收通知所用的語言,填寫語言標籤,如 zh-CN 或 ja-JP。未設定時採用部署的預設語言。" + }, + manager_id: { + label: "經理", + help: "該使用者的直屬上級。構成 `own_and_reports` 層級範圍所遍歷的彙報鏈。" + }, + primary_business_unit_id: { + label: "主屬業務單位", + help: "該使用者的主屬業務單位——sys_business_unit_member.is_primary 的去規範化投影,由 plugin-sharing 維護。使使用者查詢欄位無需遍歷成員關係關聯表即可依業務單位篩選候選人。請勿直接編輯;請透過業務單位成員關係設定。" + }, + source: { + label: "身分來源", + help: "該身分的建立方式——idp_provisioned(聯合 SSO 即時開通)或 env_native(本地註冊 / 應用程式終端使用者)。系統維護,請勿編輯。", + options: { + idp_provisioned: "IdP 開通", + env_native: "本地帳號" + } + }, + id: { + label: "使用者 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all_users: { + label: "全部使用者" + }, + me: { + label: "我的資料" + }, + unverified: { + label: "未驗證" + }, + banned: { + label: "已停權" + } + }, + _actions: { + invite_user: { + label: "邀請使用者", + description: "依電子郵件地址邀請他人。對方接受邀請後,即以所選角色加入此組織。", + successMessage: "已向 {{ result.email }} 傳送邀請", + outcomeMessages: { + sent: "已向 {{ result.email }} 傳送邀請", + link_only: "已為 {{ result.email }} 建立邀請。此伺服器未設定郵件,因此沒有發出任何郵件。請在該邀請上使用「複製邀請連結」分享給對方。" + }, + params: { + role: { + helpText: "擁有者:完全控制組織,包括刪除組織。\n管理員:管理成員、邀請、團隊和組織設定。\n受託管理員:可以邀請人員,並且只能將其安排到管理員已委託的範圍內。\n成員:使用組織的應用程式,存取權限由其權限集決定。\n外部成員:來自組織外部的人員(如供應商或客戶),只能看到與其共用的內容。", + options: { + owner: "擁有者", + admin: "管理員", + delegated_admin: "受託管理員", + member: "成員", + external: "外部成員" + } + } + } + }, + ban_user: { + label: "停權使用者", + description: "要停權該使用者嗎?停權後會立即登出,在解除停權前無法再次登入,其 API 金鑰也會被拒絕,OAuth 存取權杖與權杖續期同樣被拒絕。解除停權後這些金鑰恢復可用。", + successMessage: "使用者已停權", + params: { + banReason: { + label: "停權原因" + } + } + }, + unban_user: { + label: "解除停權", + successMessage: "使用者已解除停權" + }, + unlock_user: { + label: "解鎖帳號", + successMessage: "帳號已解鎖" + }, + create_user: { + label: "建立使用者", + successMessage: "使用者已建立", + params: { + phoneNumber: { + label: "手機號", + helpText: "登入手機號(E.164 格式,如 +8613800000000)。未填寫電子郵件時必填。" + }, + password: { + label: "密碼(留空則自動生成)" + }, + mustChangePassword: { + label: "首次登入時必須修改密碼" + }, + permissionSet: { + label: "權限集", + helpText: "可選。將該權限集授予新使用者,使其首次登入即可開啟自己的應用程式。" + } + }, + resultDialog: { + title: "使用者已建立", + acknowledge: "完成", + fields: { + "user.email": "電子郵件", + "user.phoneNumber": "手機號", + permissionSet: "權限集", + temporaryPassword: "臨時密碼:請立即複製,它只顯示一次,不會被儲存" + } + } + }, + set_user_password: { + label: "設定密碼", + successMessage: "密碼已更新", + params: { + newPassword: { + label: "新密碼(留空則自動生成)" + }, + mustChangePassword: { + label: "下次登入時必須修改密碼" + }, + revokeSessionsAndKeys: { + label: "在所有裝置上登出並撤銷 API 金鑰", + helpText: "結束該使用者的所有工作階段,並撤銷其名下的所有 API 金鑰。懷疑帳號可能被盜用時請保持開啟;常規重設可關閉。被撤銷的金鑰在修改密碼後仍保持撤銷。" + } + }, + resultDialog: { + title: "密碼已更新", + acknowledge: "完成", + fields: { + temporaryPassword: "臨時密碼:請立即複製,它只顯示一次,不會被儲存" + } + } + }, + impersonate_user: { + label: "模擬使用者", + confirmText: "要為該使用者啟動模擬工作階段嗎?僅限合法支援場景使用——所有操作都會被記錄。", + successMessage: "已開始模擬該使用者" + }, + set_user_manager: { + label: "設定經理", + description: "設定該使用者的經理。彙報鏈決定核准路由與 own_and_reports 記錄範圍。", + successMessage: "已更新經理", + params: { + managerId: { + label: "經理", + helpText: "該使用者的直接彙報物件。伺服器端會拒絕不在同一組織的經理、把使用者指派給自己,以及會形成環路或超出彙報鏈深度上限的關係。" + } + } + }, + list_platform_admins: { + label: "平台管理員", + resultDialog: { + title: "平台管理員", + description: "目前持有平台管理員資格的所有人:持有預設組織所有、仍在有效期內的 admin_full_access 授權,或其已驗證信箱在部署的擁有者名單上。", + acknowledge: "關閉", + fields: { + entries: "平台管理員" + } + } + }, + update_my_profile: { + label: "更新資料", + successMessage: "已更新資料" + }, + change_my_password: { + label: "修改密碼", + successMessage: "已修改密碼", + params: { + currentPassword: { + label: "目前密碼" + }, + newPassword: { + label: "新密碼" + }, + revokeOtherSessions: { + label: "登出其他裝置" + } + } + }, + change_my_email: { + label: "修改電子郵件", + successMessage: "已傳送驗證郵件,請前往新電子郵件確認。", + params: { + newEmail: { + label: "新電子郵件" + } + } + }, + resend_verification_email: { + label: "重發驗證郵件", + successMessage: "驗證郵件已傳送,請查收。" + }, + delete_my_account: { + label: "刪除我的帳號", + description: "確定要永久刪除您的帳戶嗎?此動作無法復原——您的所有工作階段都將被終止,並將依照設定的保留策略移除您擁有的所有資料。", + successMessage: "已刪除帳號", + params: { + password: { + label: "目前密碼" + } + } + }, + enable_two_factor: { + label: "啟用雙因素驗證", + params: { + password: { + label: "目前密碼" + } + }, + resultDialog: { + title: "雙因素驗證已啟用", + description: "請用身分驗證器應用程式掃描 QR 碼,並將備用碼妥善儲存。備用碼只顯示一次。", + acknowledge: "我已儲存備用碼", + fields: { + totpURI: "身分驗證器 URI", + backupCodes: "備用碼" + } + } + }, + disable_two_factor: { + label: "停用雙因素驗證", + description: "要關閉雙因素驗證嗎?您的帳戶安全性將降低。", + successMessage: "雙因素驗證已停用。", + params: { + password: { + label: "目前密碼" + } + } + }, + generate_backup_codes: { + label: "重新生成備用碼", + description: "要生成一組新的備用碼嗎?之前生成的備用碼將全部失效。", + params: { + password: { + label: "目前密碼" + } + }, + resultDialog: { + title: "已生成新的備用碼", + description: "之前的備用碼已全部失效。請妥善儲存這些新備用碼——它們只顯示一次。", + acknowledge: "我已儲存新備用碼", + fields: { + backupCodes: "備用碼" + } + } + } + }, + _validations: { + locale_bcp47_shape: { + message: "語言必須是 BCP-47 語言標籤,如 zh-CN 或 ja-JP。" + } + } + }, + sys_session: { + label: "工作階段", + pluralLabel: "工作階段", + description: "活躍的使用者工作階段", + fields: { + user_id: { + label: "使用者" + }, + expires_at: { + label: "到期時間" + }, + last_activity_at: { + label: "最後活動時間", + help: "該工作階段上最後一次請求的時間戳;用於判定空閒逾時。由系統維護。" + }, + revoked_at: { + label: "撤銷時間", + help: "設定後表示該工作階段已被撤銷(空閒逾時 / 絕對時長上限 / 併發數上限 / 管理員 / 組織成員關係終止)。由系統維護。" + }, + revoke_reason: { + label: "撤銷原因", + help: "工作階段被撤銷的原因(idle_timeout、absolute_max、concurrent_cap、user_revoked、admin、organization_membership_ended、admin_password_reset、password_reset 等)。" + }, + active_organization_id: { + label: "目前組織" + }, + active_team_id: { + label: "目前團隊" + }, + ip_address: { + label: "IP 位址" + }, + user_agent: { + label: "使用者代理程式" + }, + impersonated_by: { + label: "模擬發起人", + help: "如存在,該欄位表示發起本次模擬工作階段的管理員使用者 ID。" + }, + token: { + label: "工作階段權杖", + help: "不透明工作階段權杖——絕不會在 UI 中暴露。" + }, + id: { + label: "工作階段 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all_sessions: { + label: "全部" + }, + mine: { + label: "我的工作階段" + }, + revoked: { + label: "已撤銷" + } + }, + _actions: { + revoke_my_other_sessions: { + label: "登出其他裝置", + confirmText: "要登出你目前已登入的其他所有裝置嗎?目前工作階段將保持活躍。", + successMessage: "其他所有工作階段已撤銷" + }, + revoke_session: { + label: "撤銷工作階段", + confirmText: "要撤銷此工作階段嗎?系統將使該裝置上的使用者登出。", + successMessage: "工作階段已撤銷" + } + } + }, + sys_account: { + label: "身分連結", + pluralLabel: "身分連結", + description: "OAuth 與驗證提供方帳號", + fields: { + id: { + label: "身分連結 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:提供方及其簽發的帳號 ID(讀取時計算)" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + provider_id: { + label: "提供方 ID", + help: "OAuth 提供方識別資訊(google、github 等)" + }, + account_id: { + label: "提供方帳號 ID", + help: "使用者在該提供方系統中的 ID" + }, + user_id: { + label: "使用者", + help: "關聯到使用者表" + }, + access_token: { + label: "存取權杖", + help: "由提供方簽發的有效 OAuth 存取權杖——資料 API 絕不傳回該值;better-auth 透過引擎的特權內部欄位存取器讀回它" + }, + refresh_token: { + label: "重新整理權杖", + help: "有效的 OAuth 重新整理權杖——長期有效,撤銷 ObjectStack 工作階段不會使其失效;資料 API 絕不傳回該值" + }, + id_token: { + label: "ID 權杖", + help: "由提供方簽發的 OIDC ID 權杖——資料 API 絕不傳回該值" + }, + access_token_expires_at: { + label: "Access Token 過期時間" + }, + refresh_token_expires_at: { + label: "Refresh Token 過期時間" + }, + scope: { + label: "OAuth 範圍" + }, + password: { + label: "密碼雜湊", + help: "電子郵件/密碼提供方使用的密碼雜湊" + }, + previous_password_hashes: { + label: "歷史密碼雜湊", + help: "以往密碼雜湊的 JSON 陣列(數量上限為 password_history_count);僅用於防止重複使用。由系統維護。" + } + }, + _views: { + all_links: { + label: "全部" + }, + mine: { + label: "我的連結" + }, + by_provider: { + label: "依提供方" + } + }, + _actions: { + unlink_account: { + label: "解除關聯", + description: "確定要解除此身分關聯嗎?在使用者重新關聯之前,將無法再使用此提供方登入。", + successMessage: "已解除身分關聯" + } + } + }, + sys_verification: { + label: "驗證記錄", + pluralLabel: "驗證記錄", + description: "電子郵件和手機號驗證權杖", + fields: { + id: { + label: "驗證記錄 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:正在驗證的識別碼(讀取時計算)" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + value: { + label: "驗證權杖", + help: "用於驗證的權杖或驗證碼" + }, + expires_at: { + label: "到期時間" + }, + identifier: { + label: "識別碼", + help: "電子郵件地址或手機號碼" + } + } + }, + sys_organization: { + label: "組織", + pluralLabel: "組織", + description: "用於多租用戶分組的組織", + fields: { + name: { + label: "名稱", + help: "組織自身的名稱。可在此記錄頂部用「重新命名組織」修改。控制台顯示的產品名稱是另一項設定,在 設定 → 品牌 中設定。" + }, + slug: { + label: "識別碼", + help: "適合 URL 使用的識別碼" + }, + logo: { + label: "徽標" + }, + metadata: { + label: "中繼資料", + help: "JSON 序列化的組織中繼資料" + }, + require_mfa: { + label: "強制多因素驗證", + help: "開啟後,該組織的每位成員都必須先繫結身分驗證器 App,才能存取資料。需要本部署開啟雙重驗證(OS_AUTH_TWO_FACTOR=true);雙重驗證關閉時,儲存為開啟會被拒絕。" + }, + timezone: { + label: "時區", + help: "IANA 時區(如 UTC、Asia/Shanghai),作為未自行設定時區的業務單位的根預設值儲存。它目前不會改變任何日期計算:公式、報表和顯示的日期使用 設定 → 在地化 → 預設時區。" + }, + parent_organization_id: { + label: "上級組織", + help: "用於彙報 / 分組的上級。不授予任何權限——跨組織的可見性來自成員關係,絕不來自此參照。僅在部署執行多個組織時有意義。" + }, + sort_order: { + label: "排序", + help: "在同級組織中的顯示順序。僅用於顯示。僅在部署執行多個組織時有意義。" + }, + id: { + label: "組織 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all_orgs: { + label: "全部" + } + }, + _actions: { + invite_user: { + label: "邀請使用者", + description: "依電子郵件地址邀請他人。對方接受邀請後,即以所選角色加入此組織。", + successMessage: "已向 {{ result.email }} 傳送邀請", + outcomeMessages: { + sent: "已向 {{ result.email }} 傳送邀請", + link_only: "已為 {{ result.email }} 建立邀請。此伺服器未設定郵件,因此沒有發出任何郵件。請在該邀請上使用「複製邀請連結」分享給對方。" + }, + params: { + role: { + helpText: "擁有者:完全控制組織,包括刪除組織。\n管理員:管理成員、邀請、團隊和組織設定。\n受託管理員:可以邀請人員,並且只能將其安排到管理員已委託的範圍內。\n成員:使用組織的應用程式,存取權限由其權限集決定。\n外部成員:來自組織外部的人員(如供應商或客戶),只能看到與其共用的內容。", + options: { + owner: "擁有者", + admin: "管理員", + delegated_admin: "受託管理員", + member: "成員", + external: "外部成員" + } + } + } + }, + create_organization: { + label: "建立組織", + successMessage: "組織已建立" + }, + update_organization: { + label: "編輯組織", + successMessage: "組織已更新" + }, + delete_organization: { + label: "刪除組織", + confirmText: "要刪除該組織嗎?所有成員將立即失去存取權限。此動作無法復原。", + successMessage: "組織已刪除" + }, + set_active_organization: { + label: "設為目前", + successMessage: "目前組織已切換" + }, + rename_organization: { + label: "重新命名組織", + successMessage: "組織已重新命名" + }, + leave_organization: { + label: "退出組織", + confirmText: "要退出該組織嗎?退出後你將失去對其環境和成員的存取權限。", + successMessage: "你已退出該組織" + } + }, + _sections: { + identity: { + label: "基本資訊" + }, + branding: { + label: "品牌" + }, + configuration: { + label: "設定" + }, + group_structure: { + label: "集團結構" + } + } + }, + sys_member: { + label: "成員", + pluralLabel: "成員", + description: "組織成員關係記錄", + fields: { + id: { + label: "成員 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:成員,以及已記錄時的角色(讀取時計算)" + }, + created_at: { + label: "建立時間" + }, + organization_id: { + label: "組織" + }, + user_id: { + label: "使用者" + }, + role: { + label: "角色", + help: "成員在組織內的角色", + options: { + owner: "擁有者", + admin: "管理員", + delegated_admin: "受託管理員", + member: "成員", + external: "外部成員" + } + } + }, + _views: { + mine: { + label: "我的組織", + emptyState: { + title: "尚無組織", + message: "你尚未加入任何組織。" + } + } + }, + _actions: { + invite_user: { + label: "邀請使用者", + description: "依電子郵件地址邀請他人。對方接受邀請後,即以所選角色加入此組織。", + successMessage: "已向 {{ result.email }} 傳送邀請", + outcomeMessages: { + sent: "已向 {{ result.email }} 傳送邀請", + link_only: "已為 {{ result.email }} 建立邀請。此伺服器未設定郵件,因此沒有發出任何郵件。請在該邀請上使用「複製邀請連結」分享給對方。" + }, + params: { + role: { + helpText: "擁有者:完全控制組織,包括刪除組織。\n管理員:管理成員、邀請、團隊和組織設定。\n受託管理員:可以邀請人員,並且只能將其安排到管理員已委託的範圍內。\n成員:使用組織的應用程式,存取權限由其權限集決定。\n外部成員:來自組織外部的人員(如供應商或客戶),只能看到與其共用的內容。", + options: { + owner: "擁有者", + admin: "管理員", + delegated_admin: "受託管理員", + member: "成員", + external: "外部成員" + } + } + } + }, + add_member: { + label: "新增成員", + successMessage: "成員已新增", + params: { + role: { + helpText: "擁有者:完全控制組織,包括刪除組織。\n管理員:管理成員、邀請、團隊和組織設定。\n受託管理員:可以邀請人員,並且只能將其安排到管理員已委託的範圍內。\n成員:使用組織的應用程式,存取權限由其權限集決定。\n外部成員:來自組織外部的人員(如供應商或客戶),只能看到與其共用的內容。" + } + } + }, + update_member_role: { + label: "更改角色", + description: "為 {{ record.user_id.name | default:'該成員' }} 選擇新的組織角色。", + successMessage: "成員角色已更新", + outcomeMessages: { + permission_set_granted: "成員角色已更新。新角色還授予了權限集 {{ result.grantedPermissionSet.label }}。" + }, + params: { + role: { + helpText: "擁有者:完全控制組織,包括刪除組織。\n管理員:管理成員、邀請、團隊和組織設定。\n受託管理員:可以邀請人員,並且只能將其安排到管理員已委託的範圍內。\n成員:使用組織的應用程式,存取權限由其權限集決定。\n外部成員:來自組織外部的人員(如供應商或客戶),只能看到與其共用的內容。" + } + } + }, + remove_member: { + label: "移除成員", + confirmText: "要將該成員移出組織嗎?移除後其將失去對該組織所有資源的存取權限。", + successMessage: "成員已移除" + }, + transfer_ownership: { + label: "設為擁有者", + confirmText: "確定要將所選成員設為該組織的擁有者嗎?對方將獲得擁有者的全部權限,包括刪除該組織。您仍是擁有者;如需卸任,請在您自己的成員記錄上使用「更改角色」。", + successMessage: "該成員現已成為擁有者", + outcomeMessages: { + permission_set_granted: "該成員現已成為擁有者。新角色還授予了權限集 {{ result.grantedPermissionSet.label }}。" + } + } + } + }, + sys_invitation: { + label: "邀請", + pluralLabel: "邀請", + description: "用於使用者加入組織的邀請記錄", + fields: { + id: { + label: "邀請 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:受邀的電子郵件地址(讀取時計算)" + }, + created_at: { + label: "建立時間" + }, + organization_id: { + label: "組織" + }, + email: { + label: "電子郵件", + help: "被邀請使用者的電子郵件地址" + }, + role: { + label: "角色", + help: "接受邀請後指派的角色", + options: { + owner: "擁有者", + admin: "管理員", + delegated_admin: "受託管理員", + member: "成員", + external: "外部成員" + } + }, + status: { + label: "狀態", + options: { + pending: "待處理", + accepted: "已接受", + rejected: "已拒絕", + expired: "已過期", + canceled: "已取消" + } + }, + inviter_id: { + label: "邀請人", + help: "傳送邀請的使用者" + }, + expires_at: { + label: "到期時間" + }, + team_id: { + label: "團隊", + help: "接受邀請後可選指派的團隊" + }, + business_unit_id: { + label: "指派業務單位", + help: "受邀者接受邀請後被指派到的業務單位。必須位於邀請發起人的委派子樹內。" + }, + positions: { + label: "指派職位", + help: "接受邀請時指派的職位名稱。每個職位的權限集都必須在邀請發起人的 adminScope 允許清單內。" + } + }, + _views: { + pending: { + label: "待處理", + emptyState: { + title: "沒有待處理的邀請", + message: "邀請會一直顯示在這裡,直到被接受、拒絕或取消,或者過期。" + } + }, + accepted: { + label: "已接受", + emptyState: { + title: "沒有已接受的邀請", + message: "對方接受邀請並加入組織後,邀請會移到這裡。" + } + }, + expired: { + label: "已過期 / 已取消", + emptyState: { + title: "沒有已過期或已取消的邀請", + message: "已過期、被拒絕或已取消的邀請保留在這裡。" + } + }, + all_invitations: { + label: "全部", + emptyState: { + title: "尚無邀請", + message: "加入組織的每一份邀請都列在這裡,無論處於什麼狀態。" + } + } + }, + _actions: { + invite_user: { + label: "邀請使用者", + description: "依電子郵件地址邀請他人。對方接受邀請後,即以所選角色加入此組織。", + successMessage: "已向 {{ result.email }} 傳送邀請", + outcomeMessages: { + sent: "已向 {{ result.email }} 傳送邀請", + link_only: "已為 {{ result.email }} 建立邀請。此伺服器未設定郵件,因此沒有發出任何郵件。請在該邀請上使用「複製邀請連結」分享給對方。" + }, + params: { + role: { + helpText: "擁有者:完全控制組織,包括刪除組織。\n管理員:管理成員、邀請、團隊和組織設定。\n受託管理員:可以邀請人員,並且只能將其安排到管理員已委託的範圍內。\n成員:使用組織的應用程式,存取權限由其權限集決定。\n外部成員:來自組織外部的人員(如供應商或客戶),只能看到與其共用的內容。", + options: { + owner: "擁有者", + admin: "管理員", + delegated_admin: "受託管理員", + member: "成員", + external: "外部成員" + } + } + } + }, + cancel_invitation: { + label: "取消邀請", + confirmText: "要取消該邀請嗎?接收人將無法再接受該邀請。", + successMessage: "邀請已取消", + cancelText: "保留邀請" + }, + resend_invitation: { + label: "重新發送邀請", + description: "要重新發送邀請給 {{ record.email }} 嗎?有效期將重新計算;已過期的邀請會被一份新邀請取代。", + successMessage: "邀請已重新發送", + outcomeMessages: { + sent: "已向 {{ result.email }} 重新發送邀請", + link_only: "已為 {{ result.email }} 續期邀請。此伺服器未設定郵件,因此沒有發出任何郵件。請在該邀請上使用「複製邀請連結」分享給對方。" + } + }, + copy_invitation_link: { + label: "複製邀請連結", + resultDialog: { + title: "邀請連結", + description: "任何開啟此連結的人都可以接受該邀請,請只分享給您邀請的人。邀請被接受、拒絕、取消或過期後,連結即失效。", + acknowledge: "完成", + fields: { + acceptUrl: "邀請連結" + } + } + }, + accept_invitation: { + label: "接受邀請", + successMessage: "已接受邀請" + }, + reject_invitation: { + label: "拒絕邀請", + confirmText: "確定要拒絕此邀請嗎?邀請人將收到通知,您需要新的邀請才能加入。", + successMessage: "已拒絕邀請" + } + } + }, + sys_team: { + label: "團隊", + pluralLabel: "團隊", + description: "組織內用於精細化分組的團隊", + fields: { + name: { + label: "名稱" + }, + organization_id: { + label: "組織", + help: "該團隊所屬的上級組織" + }, + member_count: { + label: "成員數", + help: "由 better-auth 維護的席位計數;請勿直接寫入。" + }, + id: { + label: "團隊 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + by_org: { + label: "依組織" + }, + all_teams: { + label: "全部" + } + }, + _actions: { + create_team: { + label: "建立團隊", + successMessage: "團隊已建立" + }, + update_team: { + label: "編輯團隊", + successMessage: "團隊已更新" + }, + add_team_member: { + label: "新增成員", + successMessage: "團隊成員已新增" + }, + remove_team: { + label: "刪除團隊", + confirmText: "要刪除該團隊嗎?成員將失去所有團隊範圍內的存取權限。此動作無法復原。", + successMessage: "團隊已刪除" + } + } + }, + sys_team_member: { + label: "團隊成員", + pluralLabel: "團隊成員", + description: "將使用者關聯到團隊的團隊成員記錄", + fields: { + id: { + label: "團隊成員 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:使用者及其所在團隊(讀取時計算)" + }, + created_at: { + label: "建立時間" + }, + team_id: { + label: "團隊" + }, + user_id: { + label: "使用者" + }, + membership_key: { + label: "成員關係鍵", + help: "由 better-auth 維護的派生成員關係摘要;請勿直接寫入。" + } + }, + _actions: { + add_team_member: { + label: "新增成員", + successMessage: "團隊成員已新增" + }, + remove_team_member: { + label: "移出團隊", + description: "要將該使用者移出團隊嗎?移除後其將失去所有團隊範圍內的存取權限。", + successMessage: "團隊成員已移除" + } + } + }, + sys_business_unit: { + label: "業務單位", + pluralLabel: "業務單位", + description: "公司的組織結構——事業部、部門、區域和辦公地點。共用規則可以把記錄共用給某個業務單位,或某個業務單位及其下級單位:成員獲得這些存取權限,離開後即失去。讓權限集依業務單位讀取全部記錄(讀取範圍「unit」)需要企業版。", + fields: { + name: { + label: "名稱" + }, + code: { + label: "編碼", + help: "簡短且穩定的編碼(例如 EMEA-SALES),在租用戶內唯一。" + }, + kind: { + label: "類型", + help: "僅用於分類和報表,不會改變該單位在組織中的位置和覆蓋範圍。", + options: { + company: "公司", + division: "事業部", + department: "部門", + office: "辦公地點", + cost_center: "成本中心" + } + }, + parent_business_unit_id: { + label: "上級業務單位", + help: "該單位的上級單位。留空表示頂級單位。仍有下級單位的業務單位無法刪除,須先將下級單位改掛到其他上級或刪除。" + }, + timezone: { + label: "時區", + help: "為該業務單位儲存的 IANA 時區(如 Asia/Shanghai);留空則取上級業務單位的,再取組織的。它目前不會改變任何日期計算:公式、報表和顯示的日期使用 設定 → 在地化 → 預設時區。" + }, + organization_id: { + label: "組織", + help: "租用戶範圍。" + }, + manager_user_id: { + label: "業務單位負責人", + help: "負責該組織單位的使用者(業務單位負責人 / lead)。" + }, + active: { + label: "啟用", + help: "關閉後即停用該單位:授予該單位或其上級單位的共用等存取權限,將不再覆蓋其成員及下級單位。" + }, + effective_from: { + label: "生效時間", + help: "該業務單位成立的時間,通常由 HR 系統同步填寫。" + }, + effective_to: { + label: "失效時間", + help: "該業務單位撤銷的時間,通常由 HR 系統同步填寫。" + }, + external_ref: { + label: "外部參照", + help: "該單位在 HR 系統(如 Workday、SAP HR、北森)中的編號,每次同步時用它對應到本單位。" + }, + id: { + label: "業務單位 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + org_chart: { + label: "組織架構" + }, + active: { + label: "啟用" + }, + inactive: { + label: "停用" + }, + by_kind: { + label: "依類型" + }, + all_departments: { + label: "全部" + } + } + }, + sys_business_unit_member: { + label: "業務單位成員", + pluralLabel: "業務單位成員", + description: "使用者到業務單位的任職關係(適配矩陣組織並支援生效時間)。", + fields: { + id: { + label: "成員 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:使用者及其被指派到的業務單位(讀取時計算)" + }, + business_unit_id: { + label: "業務單位" + }, + user_id: { + label: "使用者" + }, + function_in_business_unit: { + label: "業務單位內職能", + help: "`lead` 表示日常負責人;`deputy` 可在核准路由中代替負責人。", + options: { + member: "成員", + lead: "負責人", + deputy: "副負責人" + } + }, + is_primary: { + label: "主任職", + help: "當使用者屬於多個業務單位時,用於標記報表上的主業務單位。" + }, + effective_from: { + label: "生效時間" + }, + effective_to: { + label: "失效時間" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_api_key: { + label: "API 金鑰", + pluralLabel: "API 金鑰", + description: "用於程式化存取的 API 金鑰", + fields: { + name: { + label: "名稱", + help: "API 金鑰的人類可讀標籤" + }, + prefix: { + label: "字首", + help: "用於識別金鑰的可見字首(例如 `osk_`)" + }, + user_id: { + label: "擁有者", + help: "擁有該 API 金鑰的使用者" + }, + active_organization_id: { + label: "目前組織", + help: "該金鑰驗證進入的組織——建立時繼承自簽發者,並被設為請求的目前組織" + }, + scopes: { + label: "範圍", + help: "權限範圍的 JSON 陣列" + }, + expires_at: { + label: "到期時間" + }, + last_used_at: { + label: "最近使用時間", + help: "金鑰被使用時記錄,每小時至多一次。在不活動期限內(預設 90 天)沒有使用記錄的金鑰會被拒絕,需更換新金鑰" + }, + revoked: { + label: "已撤銷" + }, + revoked_reason: { + label: "撤銷原因", + help: "管理員強制重設密碼時撤銷了該金鑰,則記錄此原因。此類金鑰只能由平台管理員恢復,其擁有者應建立新的金鑰。金鑰恢復後清除。從 API 金鑰清單撤銷的金鑰此項為空。", + options: { + admin_password_reset: "管理員強制重設密碼" + } + }, + key: { + label: "雜湊金鑰", + help: "API 金鑰值的雜湊——絕不會向用戶端暴露" + }, + id: { + label: "API 金鑰 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all_keys: { + label: "全部", + emptyState: { + title: "尚無 API 金鑰", + message: "使用「建立金鑰」為指令碼和整合建立金鑰;金鑰只在建立時顯示一次。" + } + }, + mine: { + label: "我的金鑰", + emptyState: { + title: "你還沒有 API 金鑰", + message: "使用「建立金鑰」為你的指令碼和整合建立金鑰;金鑰只在建立時顯示一次。" + } + }, + active: { + label: "啟用", + emptyState: { + title: "沒有啟用的 API 金鑰", + message: "所有未被撤銷的金鑰都列在這裡。" + } + }, + revoked: { + label: "已撤銷", + emptyState: { + title: "沒有已撤銷的金鑰", + message: "金鑰被撤銷後會移到這裡;使用它的用戶端會失去存取權限,直到它被恢復。" + } + } + }, + _actions: { + create_api_key: { + label: "建立金鑰", + description: "為指令碼或整合建立金鑰。該金鑰以你的身分、依你的存取權限,在你目前所在的組織中生效。", + params: { + expires_at: { + helpText: "留空則使用「設定 → 身分驗證 → API 金鑰」中的預設金鑰有效期(未修改時為 90 天)。晚於該處金鑰最長有效期的日期會被拒絕。" + } + }, + resultDialog: { + title: "API 金鑰已建立", + description: "請立即複製金鑰——它只顯示這一次,之後無法找回。如果洩露,請撤銷它並建立新的金鑰。", + acknowledge: "我已儲存金鑰", + fields: { + key: "API 金鑰", + expires_at: "到期時間" + } + } + }, + revoke_api_key: { + label: "撤銷 API 金鑰", + confirmText: "要撤銷該 API 金鑰嗎?所有使用該金鑰的用戶端都將立即失去存取權限。", + successMessage: "API 金鑰「{{ record.name }}」已撤銷" + }, + restore_api_key: { + label: "恢復 API 金鑰", + confirmText: "要恢復已撤銷的 API 金鑰嗎?持有該金鑰的現有用戶端將重新獲得存取權限。", + successMessage: "API 金鑰「{{ record.name }}」已恢復" + } + } + }, + sys_two_factor: { + label: "雙因素驗證", + pluralLabel: "雙因素驗證憑證", + description: "雙因素驗證憑證", + fields: { + id: { + label: "雙因素驗證 ID" + }, + display_title: { + label: "標題", + help: "記錄標題:該憑證所屬的使用者(讀取時計算)" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + user_id: { + label: "使用者" + }, + secret: { + label: "金鑰", + help: "TOTP 金鑰" + }, + backup_codes: { + label: "備用恢復碼", + help: "備用恢復碼,靜態儲存時加密(單個不透明密文,而非可讀的 JSON)" + }, + verified: { + label: "已驗證", + help: "該啟用記錄是否已通過有效的 TOTP 驗證碼確認(由 better-auth 管理)" + }, + failed_verification_count: { + label: "驗證失敗次數", + help: "連續 2FA 驗證失敗的次數;驗證成功後重設。由 better-auth 維護。" + }, + locked_until: { + label: "鎖定至", + help: "當 2FA 驗證失敗次數超過鎖定閾值時設定。由 better-auth 維護。" + } + }, + _views: { + mine: { + label: "我的啟用記錄" + }, + all_enrollments: { + label: "全部" + } + }, + _actions: { + enable_two_factor: { + label: "啟用 2FA", + params: { + password: { + label: "目前密碼" + } + }, + resultDialog: { + title: "雙因素驗證已啟用", + description: "請用身分驗證器應用程式掃描 QR 碼,並將備用碼妥善儲存。備用碼只顯示一次。", + acknowledge: "我已儲存備用碼", + fields: { + totpURI: "身分驗證器 URI", + backupCodes: "備用碼" + } + } + }, + disable_two_factor: { + label: "停用 2FA", + description: "要停用你帳號上的雙因素驗證嗎?", + successMessage: "2FA 已停用", + params: { + password: { + label: "目前密碼" + } + } + }, + regenerate_backup_codes: { + label: "重新生成備用碼", + description: "確定要重新生成備份碼嗎?此前的所有備份碼將立即失效。", + params: { + password: { + label: "目前密碼" + } + }, + resultDialog: { + title: "已生成新的備用碼", + description: "之前的備用碼已全部失效。請妥善儲存這些新備用碼——它們只顯示一次。", + acknowledge: "我已儲存新備用碼", + fields: { + backupCodes: "備用碼" + } + } + } + } + }, + sys_device_code: { + label: "裝置代碼", + pluralLabel: "裝置代碼", + description: "OAuth 2.0 裝置授權流程(RFC 8628)的待處理請求", + fields: { + id: { + label: "裝置代碼 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + device_code: { + label: "裝置代碼", + help: "傳回給輪詢裝置的高熵權杖" + }, + user_code: { + label: "使用者代碼", + help: "面向使用者的短程式碼(例如 ABCD-EFGH)" + }, + user_id: { + label: "使用者", + help: "批准該裝置授權的使用者" + }, + expires_at: { + label: "到期時間", + help: "裝置碼與使用者碼失效的時間" + }, + status: { + label: "狀態", + help: "目前狀態:`pending` | `approved` | `denied`" + }, + last_polled_at: { + label: "最近輪詢時間", + help: "最近一次 `/device/token` 輪詢的時間戳" + }, + polling_interval: { + label: "輪詢間隔(毫秒)", + help: "伺服器建議的最小輪詢間隔(毫秒)" + }, + client_id: { + label: "用戶端 ID", + help: "發起請求的裝置對應的 OAuth 用戶端識別碼" + }, + scope: { + label: "範圍", + help: "裝置請求的、以空格分隔的 OAuth scopes" + } + } + }, + sys_user_preference: { + label: "使用者偏好", + pluralLabel: "使用者偏好", + description: "依使用者儲存的鍵值偏好設定(主題、語言等)", + fields: { + id: { + label: "偏好設定 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + user_id: { + label: "使用者", + help: "該偏好設定所屬的使用者" + }, + key: { + label: "鍵", + help: "偏好設定鍵(例如 theme、locale、plugin.ai.auto_save)" + }, + value: { + label: "值", + help: "偏好設定值(任意可 JSON 序列化類型)" + } + }, + _views: { + all_preferences: { + label: "全部" + }, + mine: { + label: "我的偏好" + }, + by_user: { + label: "依使用者" + } + } + }, + sys_personal_view: { + label: "個人檢視", + pluralLabel: "個人檢視", + description: "讀者自己命名的清單檢視(我的檢視),僅本人可見。", + fields: { + id: { + label: "個人檢視 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + user_id: { + label: "擁有者", + help: "此檢視所屬的使用者。只有該使用者可以讀取或修改它。" + }, + object_name: { + label: "物件", + help: "此檢視列出的物件(其機器名)。" + }, + label: { + label: "名稱", + help: "擁有者為此檢視起的名字。" + }, + config: { + label: "檢視設定", + help: "清單設定,採用 spec 的 ListView 結構。每次儲存都會驗證。" + } + } + }, + sys_oauth_application: { + label: "OAuth 應用程式", + pluralLabel: "OAuth 應用程式", + description: "已註冊的 OAuth/OIDC 用戶端應用程式", + fields: { + id: { + label: "ID" + }, + name: { + label: "名稱" + }, + icon: { + label: "圖示", + help: "顯示在同意授權頁上的 Logo URL" + }, + uri: { + label: "主頁 URI", + help: "註冊用戶端的公開主頁" + }, + contacts: { + label: "聯絡人", + help: "聯絡人電子郵件地址的 JSON 序列化清單" + }, + tos: { + label: "服務條款" + }, + policy: { + label: "隱私政策" + }, + metadata: { + label: "中繼資料", + help: "應用程式中繼資料的 JSON 序列化內容" + }, + client_id: { + label: "用戶端 ID", + help: "公開的 OAuth 用戶端識別碼" + }, + client_discovery_id: { + label: "用戶端發現 ID", + help: "提供方在發現路徑上查詢該用戶端所用的不透明識別碼,與公開的 `client_id` 相互獨立" + }, + client_secret: { + label: "用戶端金鑰", + help: "OAuth 用戶端金鑰(靜態儲存時會雜湊/加密)" + }, + redirect_uris: { + label: "重新導向 URI", + help: "允許的重新導向 URI 的 JSON 序列化清單" + }, + post_logout_redirect_uris: { + label: "登出後重新導向 URI", + help: "允許的登出後重新導向 URI 的 JSON 序列化清單" + }, + type: { + label: "用戶端類型", + options: { + web: "Web 應用程式", + native: "原生應用程式" + } + }, + public: { + label: "公開用戶端", + help: "將用戶端標記為公開(非機密)OAuth 用戶端" + }, + require_pkce: { + label: "要求 PKCE" + }, + token_endpoint_auth_method: { + label: "權杖端點驗證方式", + help: "例如 client_secret_basic、client_secret_post、none" + }, + grant_types: { + label: "授權類型", + help: "允許的授權類型 JSON 序列化清單" + }, + response_types: { + label: "回應類型", + help: "允許的回應類型 JSON 序列化清單" + }, + scopes: { + label: "允許的範圍", + help: "用戶端可申請的 scopes 的 JSON 序列化清單" + }, + client_credentials_scopes: { + label: "用戶端憑證範圍", + help: "用戶端在 client_credentials 授權中可申請的 scopes 的 JSON 序列化清單(該授權下沒有進行同意的使用者)——與管理使用者委託授權的 `scopes` 相互獨立" + }, + subject_type: { + label: "主體類型", + help: "OIDC 主體類型(例如 public、pairwise)" + }, + jwks: { + label: "JWKS", + help: "用戶端的 JSON Web Key Set(用於 private_key_jwt / 簽章請求驗證)" + }, + jwks_uri: { + label: "JWKS URI", + help: "用戶端 JSON Web Key Set 的 URL" + }, + dpop_bound_access_tokens: { + label: "DPoP 繫結存取權杖", + help: "要求籤發給該用戶端的存取權杖必須繫結 DPoP(RFC 9449)" + }, + disabled: { + label: "已停用" + }, + skip_consent: { + label: "跳過同意", + help: "視為受信任用戶端並跳過授權同意頁" + }, + enable_end_session: { + label: "啟用 End Session", + help: "允許用戶端呼叫 OIDC end-session 端點" + }, + backchannel_logout_uri: { + label: "後端通道登出 URI", + help: "用戶端的 OIDC 後端通道登出端點" + }, + backchannel_logout_session_required: { + label: "後端通道登出需要工作階段", + help: "後端通道登出權杖是否必須包含 sid claim" + }, + software_id: { + label: "軟體 ID" + }, + software_version: { + label: "軟體版本" + }, + software_statement: { + label: "軟體宣告", + help: "用於宣告用戶端中繼資料的簽章 JWT(RFC 7591 §2.3)" + }, + user_id: { + label: "擁有者使用者", + help: "註冊該應用程式的使用者" + }, + reference_id: { + label: "參照 ID", + help: "呼叫方提供的關聯識別碼" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all_apps: { + label: "全部", + emptyState: { + title: "尚無 OAuth 應用程式", + message: "註冊應用程式後,其用戶端即可透過本系統讓使用者登入。" + } + }, + mine: { + label: "我的應用程式", + emptyState: { + title: "你還沒有 OAuth 應用程式", + message: "你註冊的應用程式會列在這裡。" + } + }, + active: { + label: "啟用", + emptyState: { + title: "沒有啟用的 OAuth 應用程式", + message: "應用程式在被停用之前一直處於啟用狀態;已停用的應用程式列在「已停用」下。" + } + }, + disabled_apps: { + label: "已停用", + emptyState: { + title: "沒有已停用的 OAuth 應用程式", + message: "應用程式被停用後會移到這裡;在重新啟用之前,其用戶端無法登入。" + } + } + }, + _actions: { + disable_oauth_application: { + label: "停用 OAuth 應用程式", + description: "確定要停用此 OAuth 應用程式嗎?已為其簽發的有效存取權杖/重新整理權杖將繼續在 token、authorize 和 introspect 端點被拒絕。現有整合將立即停止工作。", + successMessage: "OAuth 應用程式已停用" + }, + enable_oauth_application: { + label: "啟用 OAuth 應用程式", + description: "確定要重新啟用此 OAuth 應用程式嗎?權杖簽發、授權和內省將立即恢復。", + successMessage: "OAuth 應用程式已啟用" + }, + create_oauth_application: { + label: "註冊 OAuth 應用程式", + params: { + name: { + label: "應用程式名稱" + }, + redirectURLs: { + label: "回呼 URL", + helpText: "每行一個 URL。Web 應用程式必須使用 https://,且主機不能是 localhost 或其他回送位址。原生應用程式還可以使用 http://,但主機必須恰好是 localhost、127.0.0.1 或 [::1],埠不限。" + }, + type: { + label: "應用程式類型", + helpText: "桌面或行動應用程式,以及在本地開發中回呼到 http://localhost 的應用程式,請選擇「原生應用程式」。", + options: { + web: "Web 應用程式", + native: "原生應用程式" + } + } + }, + resultDialog: { + title: "OAuth 應用程式已註冊", + description: "請立即儲存 client_secret——它只顯示一次,無法找回。如發生洩露可稍後輪換。", + acknowledge: "我已儲存 Client Secret", + fields: { + "client.client_id": "Client ID", + "client.client_secret": "Client Secret" + } + } + }, + rotate_client_secret: { + label: "輪換 Client Secret", + description: "確定要輪換此 OAuth 用戶端的金鑰嗎?舊金鑰將立即失效,任何使用它的整合都將中斷,直到更新為新金鑰為止。新金鑰僅顯示一次。", + resultDialog: { + title: "Client Secret 已輪換", + description: "請立即儲存新金鑰——它只顯示一次。舊金鑰已經失效:請用新金鑰更新所有整合。", + acknowledge: "我已更新所有整合", + fields: { + client_secret: "新的 Client Secret" + } + } + }, + delete_oauth_application: { + label: "刪除 OAuth 應用程式", + description: "確定要永久刪除此 OAuth 應用程式嗎?所有已簽發的權杖和授權同意都將失效,使用此 client_id 的整合將立即停止工作。此動作無法復原。", + successMessage: "OAuth 應用程式已刪除" + } + } + }, + sys_oauth_access_token: { + label: "OAuth 存取權杖", + pluralLabel: "OAuth 存取權杖", + description: "簽發給用戶端應用程式的 OAuth 存取權杖", + fields: { + id: { + label: "ID" + }, + token: { + label: "權杖", + help: "不透明的存取權杖值" + }, + client_id: { + label: "用戶端 ID", + help: "此權杖所簽發給的 OAuth 應用程式的用戶端 ID。" + }, + session_id: { + label: "工作階段", + help: "簽發此權杖時所在的登入工作階段。" + }, + user_id: { + label: "使用者", + help: "此權杖代表其執行操作的使用者。" + }, + refresh_id: { + label: "重新整理權杖", + help: "簽發此存取權杖所依據的重新整理權杖。" + }, + reference_id: { + label: "參照 ID", + help: "簽發該權杖所依據的同意所繫結的組織;未繫結組織時為空" + }, + authorization_code_id: { + label: "授權碼 ID", + help: "該權杖來源的授權碼授權的 ID" + }, + resources: { + label: "資源", + help: "繫結到該權杖的 RFC 8707 資源指示符的 JSON 序列化清單" + }, + requested_user_info_claims: { + label: "請求的 UserInfo claim", + help: "為 userinfo 端點請求的 OIDC claim 的 JSON 序列化清單" + }, + scopes: { + label: "範圍", + help: "授予該權杖的 scopes 的 JSON 序列化清單" + }, + expires_at: { + label: "到期時間" + }, + created_at: { + label: "建立時間" + }, + revoked: { + label: "撤銷時間", + help: "該存取權杖被撤銷的時間戳" + }, + confirmation: { + label: "確認資訊", + help: "將該權杖繫結到某個金鑰的 RFC 7800 cnf claim(JSON,如 DPoP 金鑰指紋)" + } + } + }, + sys_oauth_refresh_token: { + label: "OAuth 重新整理權杖", + pluralLabel: "OAuth 重新整理權杖", + description: "OAuth 重新整理權杖(關聯到工作階段)", + fields: { + id: { + label: "ID" + }, + token: { + label: "權杖", + help: "不透明的重新整理權杖值" + }, + client_id: { + label: "用戶端 ID", + help: "此權杖所簽發給的 OAuth 應用程式的用戶端 ID。" + }, + session_id: { + label: "工作階段", + help: "簽發此權杖時所在的登入工作階段。" + }, + user_id: { + label: "使用者", + help: "此權杖代表其執行操作的使用者。" + }, + reference_id: { + label: "參照 ID", + help: "開啟該重新整理權杖族的同意所繫結的組織;未繫結組織時為空" + }, + authorization_code_id: { + label: "授權碼 ID", + help: "該權杖鏈來源的授權碼授權的 ID" + }, + resources: { + label: "資源", + help: "繫結到該權杖的 RFC 8707 資源指示符的 JSON 序列化清單" + }, + requested_user_info_claims: { + label: "請求的 UserInfo claim", + help: "為 userinfo 端點請求的 OIDC claim 的 JSON 序列化清單" + }, + scopes: { + label: "範圍", + help: "授予該權杖的 scopes 的 JSON 序列化清單" + }, + expires_at: { + label: "到期時間" + }, + created_at: { + label: "建立時間" + }, + revoked: { + label: "撤銷時間", + help: "該重新整理權杖被撤銷的時間戳" + }, + rotated_at: { + label: "輪換時間", + help: "該權杖被輪換(被新記錄取代)的時間戳" + }, + rotation_replay_response: { + label: "輪換重放回應", + help: "舊權杖在複用間隔內被再次出示時重放的快取權杖回應" + }, + rotation_replay_expires_at: { + label: "輪換重放過期時間", + help: "輪換後複用間隔的結束時間,在此期間會傳回重放回應" + }, + auth_time: { + label: "驗證時間", + help: "該權杖鏈中使用者最初完成驗證的時間" + }, + confirmation: { + label: "確認資訊", + help: "將該權杖繫結到某個金鑰的 RFC 7800 cnf claim(JSON,如 DPoP 金鑰指紋)" + } + } + }, + sys_oauth_consent: { + label: "OAuth 同意記錄", + pluralLabel: "OAuth 同意記錄", + description: "使用者對 OAuth 用戶端應用程式的授權同意記錄", + fields: { + id: { + label: "ID" + }, + client_id: { + label: "用戶端 ID", + help: "使用者已授予同意的 OAuth 應用程式的用戶端 ID。" + }, + user_id: { + label: "使用者", + help: "授予此同意的使用者。" + }, + reference_id: { + label: "參照 ID", + help: "該同意繫結的組織:同意時使用者的目前活動組織;同意時無活動組織則為空" + }, + resources: { + label: "資源", + help: "該授權同意涵蓋的 RFC 8707 資源指示符的 JSON 序列化清單" + }, + requested_user_info_claims: { + label: "請求的 UserInfo claim", + help: "使用者同意公開的 OIDC claim 的 JSON 序列化清單" + }, + scopes: { + label: "範圍", + help: "使用者同意的 scopes 的 JSON 序列化清單" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_oauth_resource: { + label: "OAuth 資源", + pluralLabel: "OAuth 資源", + description: "已註冊的 OAuth 受保護資源(RFC 8707 資源指示符)", + fields: { + id: { + label: "ID" + }, + identifier: { + label: "識別碼", + help: "在 RFC 8707 resource 參數中出示的資源指示符 URI" + }, + name: { + label: "名稱" + }, + access_token_ttl: { + label: "存取權杖 TTL", + help: "該資源的存取權杖有效期(秒),覆寫伺服器預設值" + }, + refresh_token_ttl: { + label: "重新整理權杖 TTL", + help: "該資源的重新整理權杖有效期(秒),覆寫伺服器預設值" + }, + signing_algorithm: { + label: "簽章演算法", + help: "為該資源簽章存取權杖所用的 JWS 演算法" + }, + signing_key_id: { + label: "簽章金鑰 ID", + help: "為該資源簽章存取權杖所用的金鑰 ID(kid)" + }, + allowed_scopes: { + label: "允許的範圍", + help: "用戶端可為該資源申請的 scopes 的 JSON 序列化清單" + }, + custom_claims: { + label: "自訂 claim", + help: "附加到該資源存取權杖上的額外 claim 的 JSON 物件" + }, + dpop_bound_access_tokens_required: { + label: "要求 DPoP", + help: "要求該資源的存取權杖必須繫結 DPoP(RFC 9449)" + }, + disabled: { + label: "已停用" + }, + policy_version: { + label: "策略版本", + help: "資源權杖策略的單調遞增版本號" + }, + metadata: { + label: "中繼資料", + help: "附加資源中繼資料的 JSON 物件" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_oauth_client_resource: { + label: "OAuth 用戶端資源", + pluralLabel: "OAuth 用戶端資源", + description: "允許 OAuth 用戶端為受保護資源申請權杖的授權", + fields: { + id: { + label: "ID" + }, + client_id: { + label: "用戶端 ID", + help: "此授權所針對的 OAuth 應用程式的用戶端 ID。" + }, + resource_id: { + label: "資源 ID", + help: "該應用程式可為其申請權杖的受保護資源的識別碼。" + }, + metadata: { + label: "中繼資料", + help: "附加授權中繼資料的 JSON 物件" + }, + created_at: { + label: "建立時間" + } + } + }, + sys_oauth_client_assertion: { + label: "OAuth 用戶端斷言", + pluralLabel: "OAuth 用戶端斷言", + description: "已使用的 OAuth 用戶端斷言 JTI(RFC 7523 防重放)", + fields: { + id: { + label: "ID" + }, + expires_at: { + label: "到期時間", + help: "斷言過期時間——超過該時刻的記錄可安全清理" + } + } + }, + sys_jwks: { + label: "簽章金鑰(JWKS)", + pluralLabel: "簽章金鑰(JWKS)", + description: "用於簽發和驗證 JWT 的非對稱金鑰對", + fields: { + id: { + label: "金鑰 ID", + help: "JWK `kid` 值" + }, + public_key: { + label: "公鑰", + help: "JWK 公鑰的 JSON 序列化內容" + }, + private_key: { + label: "私鑰", + help: "JWK 私鑰的 JSON 序列化內容(靜態儲存時加密)" + }, + alg: { + label: "演算法", + help: "JWK 簽章演算法,如 `EdDSA`(better-auth 1.7+)" + }, + crv: { + label: "曲線", + help: "EdDSA/EC 金鑰使用的 JWK 曲線,如 `Ed25519`(better-auth 1.7+)" + }, + created_at: { + label: "建立時間" + }, + expires_at: { + label: "到期時間", + help: "該金鑰不再用於驗證權杖的時間" + } + } + }, + sys_sso_provider: { + label: "SSO 提供方", + pluralLabel: "SSO 提供方", + description: "本系統用於聯合登入的外部 SSO 身分提供方(OIDC / SAML)", + fields: { + id: { + label: "ID" + }, + provider_id: { + label: "提供方 ID", + help: "穩定的提供方識別碼(在本環境內唯一)" + }, + issuer: { + label: "簽發者", + help: "IdP 簽發者 URL" + }, + domain: { + label: "電子郵件網域", + help: "路由到該 IdP 的電子郵件網域(如 acme.com)" + }, + domain_verified: { + label: "網域已驗證", + help: "是否已證明對該電子郵件網域的 DNS 所有權。在 DNS TXT 記錄解析後由「驗證網域」設定。由 better-auth 管理——不可直接編輯。僅在本環境啟用網域驗證時強制執行。" + }, + oidc_config: { + label: "OIDC 設定", + help: "JSON:clientId、clientSecret、端點、scopes、對應、pkce(由 better-auth 管理)" + }, + saml_config: { + label: "SAML 設定", + help: "JSON:entryPoint、cert、identifierFormat、對應(由 better-auth 管理)" + }, + user_id: { + label: "註冊者", + help: "註冊該提供方的使用者" + }, + organization_id: { + label: "組織", + help: "組織範圍(使用組織級 SSO 時)" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all: { + label: "全部", + emptyState: { + title: "尚無 SSO 提供方", + message: "註冊貴組織的外部 IdP——使用「註冊 SSO 提供方」接入 OIDC(Okta、Entra、Auth0 等),或使用「註冊 SAML 提供方」接入 SAML 2.0。電子郵件網域比對的成員即可透過它登入。" + } + } + }, + _actions: { + register_sso_provider: { + label: "註冊 SSO 提供方", + params: { + providerId: { + label: "提供方 ID", + helpText: "穩定識別碼,如「okta」或「acme-entra」。" + }, + issuer: { + label: "簽發者 URL", + helpText: "IdP 簽發者,如 https://acme.okta.com。除非在下方提供顯式 URL,否則從此處取得發現文件。" + }, + domain: { + label: "電子郵件網域", + helpText: "使用該電子郵件網域的使用者將被路由到此 IdP,如 acme.com。" + }, + clientId: { + label: "用戶端 ID", + helpText: "IdP 為本系統簽發的 OAuth 用戶端 ID。" + }, + clientSecret: { + label: "用戶端金鑰", + helpText: "OAuth 用戶端金鑰(由 better-auth 加密儲存)。" + }, + discoveryEndpoint: { + label: "發現文件 URL", + helpText: "可選。OIDC 發現文件 URL。留空則推導為 `/.well-known/openid-configuration`。" + }, + scopes: { + label: "授權範圍", + helpText: "可選。以空格或逗號分隔的 OAuth 授權範圍(scopes)。預設為「openid email profile」。", + placeholder: "openid email profile" + }, + mapEmail: { + label: "對應:電子郵件 claim", + helpText: "可選。對應到電子郵件的 claim。預設為「email」。", + placeholder: "email" + }, + mapName: { + label: "對應:姓名 claim", + helpText: "可選。對應到顯示名稱的 claim。預設為「name」。", + placeholder: "name" + } + } + }, + register_saml_provider: { + label: "註冊 SAML 提供方", + params: { + providerId: { + label: "提供方 ID", + helpText: "穩定識別碼,如「acme-saml」。" + }, + issuer: { + label: "IdP 實體 ID", + helpText: "IdP 的 SAML EntityID(簽發者),如 https://saml.acme.com/entityid。" + }, + domain: { + label: "電子郵件網域", + helpText: "使用該電子郵件網域的使用者將被路由到此 IdP,如 acme.com。" + }, + entryPoint: { + label: "IdP SSO URL", + helpText: "接收 SAMLRequest 的 IdP SAML 單點登入(重新導向)端點。" + }, + cert: { + label: "IdP 簽章憑證", + helpText: "IdP 的 X.509 簽章憑證(PEM 主體)。用於驗證斷言簽章。" + }, + identifierFormat: { + label: "NameID 格式", + helpText: "可選。請求的 SAML NameID 格式。預設為 IdP 設定的格式。", + placeholder: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + } + } + }, + request_domain_verification: { + label: "申請網域驗證", + resultDialog: { + title: "驗證您的網域", + description: "在您網域的 DNS 提供商處新增下方 DNS TXT 記錄,然後執行「驗證網域」。該權杖僅顯示一次。", + acknowledge: "完成", + fields: { + dnsRecordType: "記錄類型", + dnsRecordName: "名稱 / 主機", + dnsRecordValue: "值" + } + } + }, + verify_domain: { + label: "驗證網域", + successMessage: "網域所有權已驗證" + }, + delete_sso_provider: { + label: "刪除 SSO 提供方", + description: "刪除該 SSO 提供方嗎?其網域下的使用者將無法再透過它登入。", + successMessage: "SSO 提供方已刪除" + } + } + }, + sys_scim_connection_binding: { + label: "SCIM 連線繫結", + pluralLabel: "SCIM 連線繫結", + description: "SCIM 佈建連線的生命週期狀態,包括其下線狀態機", + fields: { + id: { + label: "ID" + }, + connection_id: { + label: "連線 ID", + help: "不可變的 SCIM 連線識別碼,限定每個佈建資源的範圍" + }, + connection_key: { + label: "連線鍵", + help: "由 @better-auth/scim 維護的派生連線唯一鍵;請勿直接寫入。" + }, + provisioning_domain_id: { + label: "佈建網域", + help: "接收佈建資源的應用程式自有邊界(預設為連線 ID)" + }, + decommission_status: { + label: "下線狀態", + help: "連線生命週期狀態:active | reconciling | complete" + }, + decommissioned_at: { + label: "下線時間" + }, + decommission_cursor_user_id: { + label: "下線游標使用者", + help: "由該庫維護的可續傳協調游標" + }, + decommission_reconciled_user_count: { + label: "已協調使用者數" + }, + decommission_batch_count: { + label: "下線批次數" + }, + decommission_revision: { + label: "下線修訂版本", + help: "下線狀態機的樂觀併發修訂版本" + }, + decommission_completed_at: { + label: "下線完成時間" + }, + decommission_lease_id: { + label: "下線租約", + help: "由該庫維護的單工作程序協調租約" + }, + decommission_lease_expires_at: { + label: "下線租約過期時間" + }, + created_at: { + label: "建立時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_connection_credential: { + label: "SCIM 連線憑證", + pluralLabel: "SCIM 連線憑證", + description: "用於驗證 SCIM 佈建連線的 Bearer 憑證(單向摘要)", + fields: { + id: { + label: "ID" + }, + connection_id: { + label: "連線 ID", + help: "該憑證驗證的 SCIM 連線(例如「okta-prod」);限定 IdP 使用它佈建的每個資源的範圍" + }, + provisioning_domain_id: { + label: "佈建網域", + help: "接收佈建資源的應用程式自有邊界;預設時預設為連線 ID" + }, + organization_id: { + label: "組織", + help: "該連線的組織範圍(佈建為組織級時)" + }, + label: { + label: "標籤", + help: "面向維運人員的憑證名稱(例如「rotation 2026-Q3」)" + }, + token_digest: { + label: "權杖摘要", + help: "Bearer 權杖的 HMAC-SHA-256(base64url),以部署的驗證金鑰為鍵——單向;明文僅在簽發時顯示一次,且絕不儲存。" + }, + active: { + label: "啟用", + help: "撤銷開關——未啟用的憑證在驗證時會被拒絕" + }, + expires_at: { + label: "到期時間", + help: "用於分階段憑證輪換的可選硬性過期時間;已過期的憑證會被拒絕" + }, + user_id: { + label: "簽發者", + help: "簽發該憑證的使用者" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_group: { + label: "SCIM 群組", + pluralLabel: "SCIM 群組", + description: "由 IdP 推送的 SCIM 2.0 群組,依佈建連線劃分範圍", + fields: { + id: { + label: "ID" + }, + connection_id: { + label: "連線 ID" + }, + provisioning_domain_id: { + label: "佈建網域" + }, + revision: { + label: "修訂版本", + help: "由 @better-auth/scim 維護的樂觀併發修訂版本" + }, + display_name: { + label: "顯示名稱", + help: "IdP 傳送的 SCIM displayName" + }, + display_name_key: { + label: "顯示名稱鍵", + help: "由 @better-auth/scim 維護的派生 displayName 唯一鍵(已做大小寫摺疊);請勿直接寫入。" + }, + external_id: { + label: "外部 ID", + help: "IdP 指派的 externalId(當 IdP 傳送時)" + }, + external_id_key: { + label: "外部 ID 鍵", + help: "由 @better-auth/scim 維護的派生 externalId 唯一鍵;請勿直接寫入。" + }, + order_key: { + label: "排序鍵", + help: "由 @better-auth/scim 維護的派生穩定分頁鍵;請勿直接寫入。" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_group_member: { + label: "SCIM 群組成員", + pluralLabel: "SCIM 群組成員", + description: "由 IdP 推送的 SCIM 群組成員關係記錄(群組 ↔ 佈建的使用者)", + fields: { + id: { + label: "ID" + }, + display_title: { + label: "標題", + help: "記錄標題:佈建的使用者及其所在組(讀取時計算)" + }, + connection_id: { + label: "連線 ID" + }, + group_id: { + label: "群組" + }, + scim_user_id: { + label: "SCIM 使用者" + }, + membership_key: { + label: "成員關係鍵", + help: "由 @better-auth/scim 維護的派生成員關係唯一鍵;請勿直接寫入。" + }, + created_at: { + label: "建立時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_identity_tombstone: { + label: "SCIM 身分墓碑", + pluralLabel: "SCIM 身分墓碑", + description: "IdP 已刪除的 SCIM 身分的墓碑記錄,保留下來以便識別重新佈建", + fields: { + id: { + label: "ID" + }, + connection_id: { + label: "連線 ID" + }, + provisioning_domain_id: { + label: "佈建網域" + }, + external_id: { + label: "外部 ID", + help: "已刪除身分的 IdP 指派 externalId" + }, + external_id_key: { + label: "外部 ID 鍵", + help: "由 @better-auth/scim 維護的派生 externalId 唯一鍵;請勿直接寫入。" + }, + user_id: { + label: "使用者", + help: "已刪除身分所關聯的平台使用者" + }, + profile: { + label: "最終資料", + help: "刪除時的最終 SCIM 資料(已序列化),由 @better-auth/scim 維護" + }, + deleted_at: { + label: "刪除時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_projection_grant: { + label: "SCIM 投影授權", + pluralLabel: "SCIM 投影授權", + description: "由 SCIM 佈建投影到平台使用者上的角色 / 權益授權", + fields: { + id: { + label: "ID" + }, + display_title: { + label: "標題", + help: "記錄標題:投射的角色及被授予該角色的使用者(讀取時計算)" + }, + connection_id: { + label: "連線 ID" + }, + provisioning_domain_id: { + label: "佈建網域" + }, + scim_user_id: { + label: "SCIM 使用者", + help: "該授權所屬的依連線劃分的使用者投影" + }, + user_id: { + label: "使用者", + help: "該授權投影到的平台使用者" + }, + source_kind: { + label: "來源類型", + help: "投影出該授權的來源(例如 group、entitlement、attribute)" + }, + source_id: { + label: "來源 ID", + help: "投影來源的識別碼(例如 SCIM 群組 ID)" + }, + source_value: { + label: "來源值", + help: "來源屬性值(當來源類型攜帶該值時)" + }, + role: { + label: "角色", + help: "投影到該使用者上的角色 / 權益" + }, + grant_key: { + label: "授權鍵", + help: "由 @better-auth/scim 維護的派生授權唯一鍵;請勿直接寫入。" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_subject: { + label: "SCIM 主體", + pluralLabel: "SCIM 主體", + description: "依使用者劃分的 SCIM 佈建關聯——任一 SCIM 連線佈建的每個使用者對應一列", + fields: { + id: { + label: "ID" + }, + user_id: { + label: "使用者", + help: "該主體記錄追蹤的平台使用者(唯一——每個使用者一列)" + }, + profile_source_id: { + label: "資料來源", + help: "目前為該使用者提供資料的 sys_scim_user 投影" + }, + revision: { + label: "修訂版本", + help: "由 @better-auth/scim 維護的樂觀併發修訂版本" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_scim_user: { + label: "SCIM 使用者", + pluralLabel: "SCIM 使用者", + description: "IdP 佈建使用者的依連線投影(SCIM 2.0 /Users)", + fields: { + id: { + label: "ID" + }, + connection_id: { + label: "連線 ID", + help: "佈建該使用者的 SCIM 連線" + }, + provisioning_domain_id: { + label: "佈建網域" + }, + user_id: { + label: "使用者", + help: "該投影所落地成的平台使用者" + }, + connection_user_key: { + label: "連線使用者鍵", + help: "由 @better-auth/scim 維護的派生(連線,使用者)唯一鍵;請勿直接寫入。" + }, + user_name: { + label: "使用者名稱", + help: "IdP 傳送的 SCIM userName" + }, + user_name_key: { + label: "使用者名稱鍵", + help: "由 @better-auth/scim 維護的派生 userName 唯一鍵(已做大小寫摺疊);請勿直接寫入。" + }, + primary_email: { + label: "主電子郵件" + }, + work_email_value_index: { + label: "工作電子郵件索引", + help: "由 @better-auth/scim 維護的派生查詢索引;請勿直接寫入。" + }, + email_value_index: { + label: "電子郵件索引", + help: "由 @better-auth/scim 維護的派生查詢索引;請勿直接寫入。" + }, + display_name: { + label: "顯示名稱" + }, + formatted_name: { + label: "格式化姓名" + }, + given_name: { + label: "名" + }, + family_name: { + label: "姓" + }, + serialized_emails: { + label: "電子郵件(序列化)", + help: "由 @better-auth/scim 維護的規範化序列化 SCIM 電子郵件清單;請勿直接寫入。" + }, + serialized_attributes: { + label: "屬性(序列化)", + help: "由 @better-auth/scim 維護的規範化序列化 SCIM 屬性;請勿直接寫入。" + }, + external_id: { + label: "外部 ID", + help: "IdP 指派的 externalId(當 IdP 傳送時)" + }, + external_id_key: { + label: "外部 ID 鍵", + help: "由 @better-auth/scim 維護的派生 externalId 唯一鍵;請勿直接寫入。" + }, + active: { + label: "啟用", + help: "SCIM active 標誌——為 false 表示 IdP 已停用該使用者" + }, + order_key: { + label: "排序鍵", + help: "由 @better-auth/scim 維護的派生穩定分頁鍵;請勿直接寫入。" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + all: { + label: "全部" + } + } + }, + sys_notification: { + label: "通知", + pluralLabel: "通知", + description: "平台發出的每一條通知,每個事件一列。", + fields: { + id: { + label: "通知 ID" + }, + topic: { + label: "主題", + help: "通知主題,例如 task.assigned、collab.mention" + }, + payload: { + label: "承載", + help: "傳遞給各通道的範本輸入(title/body/url/actor/source/…)" + }, + severity: { + label: "嚴重程度", + help: "用於呈現 / 篩選的嚴重程度提示", + options: { + info: "資訊", + warning: "警告", + critical: "嚴重" + } + }, + suppressed_channels: { + label: "已抑制的通道", + help: "因對該租用戶不可用而在扇出時被跳過的通道,格式為 [{channel, reason}];reason 為封閉集合:transport_not_configured" + }, + dedup_key: { + label: "去重鍵", + help: "主題視窗內的冪等鍵;重複發出不產生任何效果" + }, + source_object: { + label: "來源物件", + help: "關聯記錄的物件名稱(例如 lead、opportunity)" + }, + source_id: { + label: "來源記錄", + help: "source_object 中的記錄 ID" + }, + actor_id: { + label: "執行人", + help: "觸發該通知的使用者(提及人、指派人)" + }, + created_at: { + label: "建立時間" + } + }, + _views: { + recent: { + label: "最近", + emptyState: { + title: "尚無事件", + message: "尚未發出任何通知事件。" + } + }, + by_topic: { + label: "依主題" + } + } + }, + sys_attachment: { + label: "附件", + pluralLabel: "附件", + description: "sys_file 與任意其他記錄之間的多型關聯", + fields: { + id: { + label: "附件 ID" + }, + parent_object: { + label: "父物件", + help: "被附加記錄的短物件名(例如 account、lead)" + }, + parent_id: { + label: "父記錄", + help: "被附加記錄的主鍵" + }, + file_id: { + label: "檔案", + help: "被附加的 sys_file 儲存條目" + }, + file_name: { + label: "檔案名稱", + help: "sys_file.name 的去規範化副本,用於快速清單呈現" + }, + mime_type: { + label: "MIME 類型" + }, + size: { + label: "大小(位元組)" + }, + uploaded_by: { + label: "上傳者" + }, + description: { + label: "描述" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_email: { + label: "郵件", + pluralLabel: "郵件", + description: "外發郵件傳送日誌", + fields: { + id: { + label: "郵件 ID" + }, + message_id: { + label: "Message-ID", + help: "傳輸層指派的 RFC-5322 Message-ID" + }, + from_address: { + label: "寄件者" + }, + to_addresses: { + label: "收件者", + help: "以逗號分隔的收件者地址" + }, + cc_addresses: { + label: "副本" + }, + bcc_addresses: { + label: "密件副本" + }, + reply_to: { + label: "回覆地址" + }, + subject: { + label: "主旨" + }, + body_text: { + label: "內文(文字)" + }, + body_html: { + label: "內文(HTML)" + }, + headers_json: { + label: "郵件標頭(JSON)", + help: "傳送郵件時隨附的自訂郵件標頭,以 name → value 的 JSON 物件儲存。兩種傳送模式下都會寫入(它既是傳送輸入,也是稽核證據)。在該列出現之前寫入的列沒有此值,讀回時依「無自訂郵件標頭」處理。" + }, + attachments_json: { + label: "附件(JSON)", + help: "附件的 JSON 陣列,元素形狀為 { filename, contentType?, size, hash, cid?, contentForm, inline?, storageKey?, contentReclaimedAt? }。在 plugin-email 預算(SYS_EMAIL_ATTACHMENT_LIMIT_BYTES,合計原始大小 256 KiB —— 最壞約 350 KB 的 base64)以內的內容以 base64 存放在 `inline`;超出的內容存入 file-storage 能力,元素改為攜帶 `storageKey`,因此兩種情況下列體積都有界。filename/contentType/size/hash 是**永久**稽核證據;列外內容屬於傳送工件,在列到達終態並經過寬限窗後被刪除,屆時 `storageKey` 由 `contentReclaimedAt` 取代。" + }, + status: { + label: "狀態", + help: "生命週期狀態——郵件提交發送時即進入排隊,早於首次傳送嘗試", + options: { + queued: "排隊中", + sent: "已傳送", + failed: "失敗" + } + }, + error: { + label: "錯誤", + help: "status=failed 時的傳輸錯誤資訊" + }, + attempt_count: { + label: "嘗試次數", + help: "服務執行的傳送嘗試次數" + }, + sent_at: { + label: "寄送時間", + help: "當狀態切換為 `sent` 時設定" + }, + related_object: { + label: "關聯物件", + help: "關聯記錄的物件名稱(例如 lead、opportunity)" + }, + related_id: { + label: "關聯記錄", + help: "related_object 中的記錄 ID" + }, + sent_by: { + label: "傳送者" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_email_template: { + label: "郵件範本", + pluralLabel: "郵件範本", + description: "依名稱 + 語言解析的外發郵件範本(主旨、內文和變數)", + fields: { + id: { + label: "ID" + }, + name: { + label: "名稱", + help: "點分隔的 snake_case 識別資訊(例如 auth.password_reset)" + }, + label: { + label: "標籤", + help: "在 Studio 中顯示的人類可讀名稱" + }, + category: { + label: "分類", + options: { + auth: "驗證", + notification: "通知", + workflow: "工作流", + marketing: "行銷", + custom: "自訂" + } + }, + locale: { + label: "地區語言", + help: "BCP-47 語言區域標籤(en-US、zh-CN 等)" + }, + description: { + label: "描述" + }, + subject: { + label: "主旨", + help: "主旨範本;支援 {{var.path}} 預留位置" + }, + body_html: { + label: "HTML 內文", + help: "HTML 內文範本;支援 {{var.path}} 預留位置" + }, + body_text: { + label: "純文字內文", + help: "可選的純文字替代內容(為空時從 HTML 自動生成)" + }, + from_name: { + label: "寄件者名稱" + }, + from_address: { + label: "寄件地址" + }, + reply_to: { + label: "回覆地址" + }, + active: { + label: "啟用" + }, + is_system: { + label: "系統範本", + help: "由外掛 / 平台提供" + }, + variables_json: { + label: "變數(JSON)", + help: "形如 {name,type,required,description} 的 JSON 陣列" + }, + managed_by: { + label: "管理方", + help: "記錄來源:platform = 框架內建 / package = 應用程式或套件宣告(啟動時根據宣告的 email_template 中繼資料寫入)/ admin = 在 Studio 中建立。", + options: { + platform: "平台", + package: "套件", + admin: "管理員" + } + }, + customized: { + label: "已自訂", + help: "標記在組織級範本編輯關閉之前被管理員編輯過的套件宣告範本;啟動種子從不覆寫此類記錄。此後不再有任何操作設定該標記。對 admin 記錄無意義。" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_job: { + label: "背景工作", + pluralLabel: "背景工作", + description: "已註冊背景工作目錄", + fields: { + id: { + label: "任務 ID" + }, + name: { + label: "任務名稱", + help: "唯一任務識別資訊(snake_case),在整個安裝範圍內唯一——背景工作目錄屬於該部署本身,而非某個組織" + }, + schedule_type: { + label: "排程類型", + options: { + cron: "Cron", + interval: "間隔", + once: "一次" + } + }, + schedule_expression: { + label: "運算式", + help: "Cron 運算式 / 間隔毫秒 / ISO 日期時間" + }, + timezone: { + label: "時區" + }, + active: { + label: "啟用", + help: "排程器目前是否執行該任務" + }, + last_run_at: { + label: "最近執行時間" + }, + last_status: { + label: "最近狀態", + options: { + success: "成功", + failed: "失敗", + timeout: "逾時", + running: "執行中", + degraded: "降級" + } + }, + last_error: { + label: "最近錯誤" + }, + run_count: { + label: "執行次數" + }, + failure_count: { + label: "失敗次數" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_job_run: { + label: "任務執行", + pluralLabel: "任務執行", + description: "背景工作執行歷史", + fields: { + id: { + label: "執行 ID" + }, + job_name: { + label: "任務" + }, + status: { + label: "狀態", + options: { + running: "執行中", + success: "成功", + failed: "失敗", + timeout: "逾時", + degraded: "降級" + } + }, + started_at: { + label: "開始時間" + }, + completed_at: { + label: "完成時間" + }, + duration_ms: { + label: "耗時(毫秒)" + }, + attempt: { + label: "嘗試", + help: "首次執行為 1,重試/重放則 >1" + }, + trigger: { + label: "觸發方式", + options: { + schedule: "排程", + manual: "手動", + replay: "重放" + } + }, + error: { + label: "錯誤" + }, + created_at: { + label: "建立時間" + } + } + }, + sys_job_queue: { + label: "任務佇列訊息", + pluralLabel: "任務佇列訊息", + description: "持久化任務/訊息佇列,包含死信訊息", + fields: { + id: { + label: "訊息 ID" + }, + queue: { + label: "佇列", + help: "邏輯佇列名稱(snake_case)" + }, + idempotency_key: { + label: "冪等鍵", + help: "在(queue, window) 範圍內去重的鍵" + }, + payload_json: { + label: "承載(JSON)", + help: "序列化的訊息內文" + }, + metadata_json: { + label: "中繼資料(JSON)", + help: "序列化的中繼資料包(tenant_id、source_record 等)" + }, + status: { + label: "狀態", + help: "生命週期狀態", + options: { + pending: "待處理", + running: "執行中", + completed: "已完成", + failed: "失敗", + dlq: "死信佇列" + } + }, + priority: { + label: "優先順序", + help: "數值越低優先順序越高" + }, + attempts: { + label: "嘗試次數" + }, + max_attempts: { + label: "最大嘗試次數" + }, + backoff_type: { + label: "退避策略", + options: { + fixed: "固定", + exponential: "指數" + } + }, + backoff_delay_ms: { + label: "退避基數(毫秒)" + }, + backoff_max_delay_ms: { + label: "退避上限(毫秒)" + }, + scheduled_for: { + label: "計劃執行時間", + help: "工作程序可領取該訊息的最早時間" + }, + locked_by: { + label: "鎖定者", + help: "持有租約的工作程序 ID" + }, + locked_until: { + label: "鎖定到", + help: "租約到期時間;超過後其他工作程序可重新領取" + }, + last_error: { + label: "最近錯誤" + }, + completed_at: { + label: "完成時間" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_import_job: { + label: "匯入任務", + pluralLabel: "匯入任務", + description: "非同步批次匯入任務的狀態、進度與歷史", + fields: { + id: { + label: "任務 ID" + }, + object_name: { + label: "物件", + help: "匯入目標物件的 API 名稱" + }, + status: { + label: "狀態", + options: { + pending: "待處理", + running: "執行中", + succeeded: "成功", + failed: "失敗", + cancelled: "已取消" + } + }, + total_rows: { + label: "總列數" + }, + processed_rows: { + label: "已處理列數" + }, + created_count: { + label: "已建立" + }, + updated_count: { + label: "已更新" + }, + skipped_count: { + label: "已跳過" + }, + error_count: { + label: "錯誤數" + }, + write_mode: { + label: "寫入模式", + options: { + insert: "插入", + update: "更新", + upsert: "插入或更新" + } + }, + dry_run: { + label: "試執行" + }, + run_automations: { + label: "執行自動化" + }, + treat_as_historical: { + label: "視為歷史資料" + }, + error: { + label: "失敗原因" + }, + results: { + label: "列結果(樣本)", + help: "供 UI 使用的依列結果樣本(失敗項目優先,數量有上限)" + }, + undo_log: { + label: "撤銷日誌", + help: "為小型非試執行任務記錄的復原指令({created:[ids], updated:[{id,before}]}),以便撤銷匯入" + }, + reverted_at: { + label: "撤銷時間", + help: "在匯入被撤銷時設定(已建立記錄被刪除,已更新記錄被還原)" + }, + started_at: { + label: "開始時間" + }, + completed_at: { + label: "完成時間" + }, + created_by: { + label: "建立者" + }, + created_at: { + label: "建立時間" + } + } + }, + sys_metadata: { + label: "系統中繼資料", + pluralLabel: "系統中繼資料", + description: "儲存平台級和使用者級中繼資料記錄(物件、檢視、流程等)", + fields: { + id: { + label: "ID" + }, + name: { + label: "名稱" + }, + type: { + label: "中繼資料類型" + }, + namespace: { + label: "命名空間" + }, + package_id: { + label: "套件 ID", + help: "遺留套件清單 ID 字串。新記錄請使用 package_version_id。" + }, + package_version_id: { + label: "套件版本", + help: "此中繼資料安裝自的套件版本。平台內建的中繼資料以及在本環境中所做的更改此項為空。" + }, + managed_by: { + label: "管理方", + options: { + package: "套件", + platform: "平台", + user: "使用者" + } + }, + scope: { + label: "範圍", + options: { + system: "系統", + platform: "平台", + user: "使用者" + } + }, + bundle_member: { + label: "Bundle Member", + help: "Bundle member (e.g. the locale of an email template) this overlay row customizes. Null = the canonical member." + }, + metadata: { + label: "中繼資料", + help: "中繼資料承載的 JSON 序列化內容" + }, + draft_changes: { + label: "草稿變更", + help: "待發布草稿的逐鍵來源(JSON 序列化):每個鍵路徑最後由哪個來源(copilot、studio、api)、哪位操作者、在何時修改。已發布行為空。" + }, + extends: { + label: "繼承自" + }, + strategy: { + label: "策略", + options: { + merge: "合併", + replace: "替換" + } + }, + owner: { + label: "擁有者" + }, + state: { + label: "狀態", + options: { + draft: "草稿", + active: "啟用", + archived: "已封存", + deprecated: "已廢棄" + } + }, + environment_id: { + label: "環境(已廢棄)", + help: "已廢棄。覆疊層是環境級的列,沒有租用戶欄。" + }, + version: { + label: "版本" + }, + checksum: { + label: "總和檢查碼" + }, + source: { + label: "來源", + options: { + filesystem: "檔案系統", + database: "資料庫", + api: "API", + migration: "遷移" + } + }, + tags: { + label: "標籤", + help: "分類標籤的 JSON 序列化陣列" + }, + created_by: { + label: "建立者" + }, + created_at: { + label: "建立時間" + }, + updated_by: { + label: "更新者" + }, + updated_at: { + label: "更新時間" + } + }, + _views: { + only_objects: { + label: "物件" + }, + only_fields: { + label: "欄位" + }, + all_metadata: { + label: "全部" + } + } + }, + sys_metadata_history: { + label: "中繼資料歷史", + pluralLabel: "中繼資料歷史", + description: "中繼資料變更的版本歷史與稽核記錄", + fields: { + id: { + label: "ID" + }, + event_seq: { + label: "事件序號", + help: "依組織的單調遞增事件日誌游標。" + }, + name: { + label: "名稱" + }, + type: { + label: "中繼資料類型" + }, + version: { + label: "版本" + }, + operation_type: { + label: "操作類型", + options: { + create: "建立", + update: "更新", + publish: "發布", + revert: "復原", + delete: "刪除" + } + }, + metadata: { + label: "中繼資料", + help: "該版本對應的中繼資料快照(JSON 序列化)" + }, + checksum: { + label: "總和檢查碼" + }, + previous_checksum: { + label: "上一個總和檢查碼" + }, + change_note: { + label: "變更說明", + help: "對該版本變更內容的說明" + }, + source: { + label: "來源" + }, + recorded_by: { + label: "記錄者", + help: "做出此變更的使用者。NULL 表示由系統發起(啟動同步、遷移、定時任務)——絕不是哨兵字串。" + }, + recorded_at: { + label: "記錄時間" + } + } + }, + sys_metadata_audit: { + label: "中繼資料稽核", + pluralLabel: "中繼資料稽核", + description: "中繼資料寫入決策的僅追加稽核記錄。", + fields: { + id: { + label: "ID" + }, + occurred_at: { + label: "發生時間" + }, + actor: { + label: "執行者", + help: "操作主體——使用者 ID、系統 ID 或 \"system\"。" + }, + source: { + label: "來源" + }, + type: { + label: "中繼資料類型" + }, + name: { + label: "名稱" + }, + operation: { + label: "操作", + options: { + save: "儲存", + publish: "發布", + rollback: "復原", + delete: "刪除", + reset: "重設" + } + }, + outcome: { + label: "結果", + options: { + allowed: "允許", + denied: "拒絕", + forced: "強制執行" + } + }, + code: { + label: "程式碼" + }, + lock_state: { + label: "鎖定狀態" + }, + lock_overridden: { + label: "鎖定已被覆寫" + }, + request_id: { + label: "請求 ID" + }, + note: { + label: "備註" + } + } + }, + sys_setting: { + label: "設定", + pluralLabel: "設定", + description: "為 SettingsManifest 合約提供支援的通用鍵值儲存。", + fields: { + id: { + label: "設定 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + namespace: { + label: "命名空間", + help: "Manifest 命名空間(例如 mail、branding、storage)。" + }, + key: { + label: "鍵", + help: "命名空間內的識別資訊鍵(snake_case)。" + }, + scope: { + label: "範圍", + help: "該記錄所屬的設定解析層級。", + options: { + tenant: "租用戶", + user: "使用者" + } + }, + user_id: { + label: "使用者", + help: "當 scope=user 時對應所屬使用者;否則為 null。" + }, + value: { + label: "值", + help: "JSON 編碼的值。encrypted=true 時為 Null(見 value_enc)。" + }, + encrypted: { + label: "已加密", + help: "為 true 時,值會以靜態加密方式儲存在 value_enc 中;value 列為 null。" + }, + locked: { + label: "已鎖定", + help: "為 true 時,較低範圍的記錄無法覆寫該值;對較低範圍的寫入會傳回 409。租用戶列藉此為該租用戶的使用者固定其值(Phase 2 級聯)。" + }, + locked_reason: { + label: "鎖定原因", + help: "當 locked=true 時,在 UI 提示中顯示的人類可讀說明。" + }, + value_enc: { + label: "加密值", + help: "密文承載(KMS 包裹)。僅在 encrypted=true 時設定。" + }, + updated_by: { + label: "更新者", + help: "最近通過 SettingsService.set() 寫入該記錄的執行人。" + } + }, + _views: { + by_namespace: { + label: "依命名空間" + }, + tenant_only: { + label: "租用戶" + }, + user_only: { + label: "使用者" + }, + all_settings: { + label: "全部" + } + } + }, + sys_platform_setting: { + label: "平台設定", + pluralLabel: "平台設定", + description: "整個部署共用的設定值:設定級聯中的全域層。", + fields: { + id: { + label: "設定 ID" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + }, + namespace: { + label: "命名空間", + help: "Manifest 命名空間(例如 mail、storage、ai)。" + }, + key: { + label: "鍵", + help: "命名空間內的識別資訊鍵(snake_case)。" + }, + value: { + label: "值", + help: "JSON 編碼的值。加密儲存時為空(見「加密值」)。" + }, + encrypted: { + label: "已加密", + help: "開啟後,值加密存放在「加密值」中,「值」一列為空。" + }, + locked: { + label: "已鎖定", + help: "開啟後,組織和使用者的設定不能覆寫該值;對這兩層的寫入會被拒絕(409)。" + }, + locked_reason: { + label: "鎖定原因", + help: "鎖定時在介面提示中顯示的說明。" + }, + value_enc: { + label: "加密值", + help: "儲存密文的 sys_secret 記錄的控制代碼。僅在加密儲存時有值。" + }, + updated_by: { + label: "更新者", + help: "最近一次透過設定服務寫入該記錄的人。" + } + } + }, + sys_secret: { + label: "金鑰", + pluralLabel: "金鑰", + description: "由三個特權寫入方寫入的密文儲存(見 managedBy);每個寫入方各自在自己的欄中儲存控制代碼。絕不儲存明文。", + fields: { + id: { + label: "ID", + help: "不透明控制代碼。參照儲存在寫入方各自的持有欄中 —— `sys_setting.value_enc`、業務記錄上的 `secret:` 參照,或資料來源的 `credentialsRef`;因此某列未被 `sys_setting` 參照,並不等於它未被參照。" + }, + created_at: { + label: "建立時間", + help: "首次寫入該密文的時間。" + }, + rotated_at: { + label: "輪換時間", + help: "最近一次使用新 KMS 金鑰重新包裹該密文的時間。" + }, + namespace: { + label: "命名空間", + help: "依寫入方限定的標籤,並非普遍意義上的設定命名空間:`SettingsService` 寫入設定命名空間,引擎的 `secret` 欄位加密寫入物件名,資料來源繫結器寫入呼叫方提供的範圍(預設 `datasource`)。見 managedBy。" + }, + key: { + label: "鍵", + help: "與 `namespace` 配對的、依寫入方限定的標籤:設定的識別資訊鍵、被加密的 `secret` 欄位名,或資料來源名。該配對記錄的是寫入方如何定址該值;它並不標明是哪個寫入方寫入了該列。" + }, + kms_key_id: { + label: "KMS 金鑰 ID", + help: "外部 KMS 控制代碼(ARN、GCP resource id 或 `local`)。" + }, + alg: { + label: "演算法", + help: "密文 / AEAD 演算法標記。" + }, + version: { + label: "版本", + help: "每次 rotateKey() 重新包裹該記錄時遞增。" + }, + ciphertext: { + label: "密文", + help: "提供方編碼後的密文資料(base64 / JSON)。實現定義;僅比對的 ICryptoProvider 可讀取。" + } + }, + _views: { + all: { + label: "全部金鑰" + } + } + }, + sys_setting_audit: { + label: "設定稽核記錄", + pluralLabel: "設定稽核", + description: "SettingsService 變更的追加寫入稽核記錄。", + fields: { + id: { + label: "ID" + }, + created_at: { + label: "建立時間", + help: "記錄該變更的時間。" + }, + namespace: { + label: "命名空間" + }, + key: { + label: "鍵" + }, + scope: { + label: "範圍", + help: "該記錄寫入的級聯層。", + options: { + tenant: "租用戶", + user: "使用者" + } + }, + action: { + label: "操作", + help: "變更類型。", + options: { + set: "設定", + reset: "重設", + lock: "鎖定", + unlock: "解鎖", + rotate: "輪換" + } + }, + actor_id: { + label: "執行人", + help: "執行該變更的使用者;系統任務時為 null。" + }, + source: { + label: "來源", + help: "變更入口。", + options: { + ui: "UI", + api: "API", + migration: "遷移", + import: "匯入", + system: "系統" + } + }, + reason: { + label: "原因", + help: "執行人提供的自由文字說明(可選)。" + }, + old_hash: { + label: "舊雜湊", + help: "舊值的摘要,絕不是明文:普通值為 sha256: 十六進位摘要,機密值為帶金鑰的 hmac-sha256: 摘要。此前未設定時為 Null,機密值沒有可用的帶金鑰摘要時也為 Null。" + }, + new_hash: { + label: "新雜湊", + help: "新值的摘要,絕不是明文:普通值為 sha256: 十六進位摘要,機密值為帶金鑰的 hmac-sha256: 摘要。重設時為 Null,機密值沒有可用的帶金鑰摘要時也為 Null。" + }, + encrypted: { + label: "已加密", + help: "當欄位包含金鑰材料時為 true(輪換更值得關注)。" + }, + request_id: { + label: "請求 ID", + help: "與 sys_audit_log / tracing 關聯。" + } + }, + _views: { + recent: { + label: "最近" + } + } + }, + sys_platform_setting_audit: { + label: "平台設定稽核記錄", + pluralLabel: "平台設定稽核", + description: "設定服務對全域設定的每次變更的追加寫入稽核記錄。", + fields: { + id: { + label: "ID" + }, + created_at: { + label: "建立時間", + help: "記錄該變更的時間。" + }, + namespace: { + label: "命名空間" + }, + key: { + label: "鍵" + }, + scope: { + label: "範圍", + help: "該記錄寫入的級聯層;這裡始終是全域層。", + options: { + global: "全域" + } + }, + action: { + label: "操作", + help: "變更類型。", + options: { + set: "設定", + reset: "重設", + lock: "鎖定", + unlock: "解鎖", + rotate: "輪換" + } + }, + actor_id: { + label: "執行人", + help: "執行該變更的使用者;系統任務時為空。" + }, + source: { + label: "來源", + help: "變更入口。", + options: { + ui: "UI", + api: "API", + migration: "遷移", + import: "匯入", + system: "系統" + } + }, + reason: { + label: "原因", + help: "執行人提供的說明(可選)。" + }, + old_hash: { + label: "舊雜湊", + help: "舊值的摘要,絕不是明文:普通值為 sha256: 十六進位摘要,機密值為帶金鑰的 hmac-sha256: 摘要。此前未設定時為空,機密值沒有可用的帶金鑰摘要時也為空。" + }, + new_hash: { + label: "新雜湊", + help: "新值的摘要,絕不是明文:普通值為 sha256: 十六進位摘要,機密值為帶金鑰的 hmac-sha256: 摘要。重設時為空,機密值沒有可用的帶金鑰摘要時也為空。" + }, + encrypted: { + label: "已加密", + help: "該設定儲存的是金鑰材料時為是(輪換更值得關注)。" + }, + request_id: { + label: "請求 ID", + help: "用於與稽核日誌和鏈路追蹤關聯。" + } + }, + _views: { + recent: { + label: "最近" + } + } + }, + sys_migration: { + label: "資料遷移", + pluralLabel: "資料遷移", + description: "部署級資料遷移標記:本部署跑過哪些帶門禁的資料遷移,以及各自的自檢是否通過。", + fields: { + id: { + label: "遷移 ID", + help: "約定的遷移識別資訊(如 adr-0104-file-references)。每個遷移一列。" + }, + last_run_at: { + label: "最近執行時間", + help: "本部署最近一次完成帶門禁執行(apply 模式)的時間。" + }, + verified_at: { + label: "通過自檢時間", + help: "自檢最近一次通過的時間。未通過前為空;之後若有執行未通過會被清空——資料回退即自行關閉門禁。消費方要求本欄位有值且阻斷項目為 0。" + }, + applied_at: { + label: "寫入執行時間", + help: "回填最近一次以 apply 模式(允許寫入)執行的時間。" + }, + blocking: { + label: "阻斷項目", + help: "最近一次自檢報告的阻斷項目數量。門禁要求為 0。" + }, + advisory: { + label: "提示項目", + help: "最近一次執行的提示項目(外部 URL、失效擁有者等)——只涉及儲存成本或需要建模決定,絕不阻斷門禁。" + }, + details: { + label: "詳細資料(JSON)", + help: "最近一次執行的計數,JSON 編碼,供診斷用。" + }, + deviation_observed_at: { + label: "偏差發現時間", + help: "本部署最近一次經由 OS_ALLOW_LAX_* 逃生開關「放行」一個其自身已驗證契約會拒絕的值的時間。刻意「不」清空 verified_at:它只撤回該憑證所授權內容中不可逆的那一半——位元組刪除停止,驗證與墓碑化照常進行。下一次 apply 模式執行時清空。" + }, + deviation_detail: { + label: "偏差詳細資料(JSON)", + help: "deviation_observed_at 背後的第一個反例,JSON 編碼(object、field、type、parse issue),便於維運人員找到關閉了不可逆門禁的那個值。" + }, + columns_moved_at: { + label: "欄遷移時間", + help: "本部署最近一次完成該遷移「欄遷移」的時間——即把已遷移的欄改為新類型、並將其中的值重寫為新編碼的那一步。它是獨立於 applied_at 和 verified_at 的證據,後兩者只證明回填及其自檢:一個部署可以兩者都有,卻仍以舊編碼儲存。為空恰恰表示這一點,是預期的穩態而非錯誤——這也是無法讀取本欄位的消費方必須作出的假設。" + }, + created_at: { + label: "建立時間" + }, + updated_at: { + label: "更新時間" + } + } + }, + sys_exchange_rate: { + label: "匯率", + pluralLabel: "匯率", + description: "從組織本位幣到外幣的匯率,依生效日期記錄——外幣金額折算為本位幣時所用的匯率。", + fields: { + id: { + label: "ID" + }, + currency: { + label: "幣別", + help: "外幣的 ISO 4217 程式碼,大寫(EUR、JPY、CHF)。" + }, + rate: { + label: "匯率", + help: "1 單位組織本位幣(在地化 → 預設幣別)= 匯率 單位的該幣別。示例:本位幣 USD、幣別 EUR、匯率 0.92,表示 1 USD = 0.92 EUR。" + }, + effective_date: { + label: "生效日期", + help: "此匯率開始適用的日期。在同一幣別的更晚一列接替之前一直有效。" + }, + source: { + label: "來源", + help: "該列的來源:在 Setup 中輸入(手動)、從檔案匯入(匯入),或由應用程式流程寫入(應用程式)。", + options: { + manual: "手動", + import: "匯入", + app: "應用程式" + } + } + }, + _views: { + all_rates: { + label: "全部", + description: "1 單位組織本位幣(在地化 → 預設幣別)= 匯率 單位的該幣別。示例:本位幣 USD、幣別 EUR、匯率 0.92,表示 1 USD = 0.92 EUR。", + emptyState: { + title: "還沒有匯率", + message: "每個幣別在匯率變動的每個日期各錄一列,例如每月初為每個幣別錄一列。" + } + } + } + } +}; diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.generated.ts b/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.generated.ts new file mode 100644 index 00000000000..bb52532bed3 --- /dev/null +++ b/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.generated.ts @@ -0,0 +1,61 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Auto-generated by 'os i18n extract' for locale 'zh-Hant'. Do not hand-edit. + * + * Each entry is the digest of the SOURCE REVISION that this locale's leaf at + * that path is still a byte copy of — provenance for the generated half of the + * bundles (commit 09b4f4e4e): a leaf whose digest no longer matches its source is stale and serves the source text instead. + * + * An entry exists only while the leaf IS such a copy. Re-translate the leaf in + * `.objects.generated.ts` and the next extract drops its entry by + * itself — the table makes no claim about text a translator wrote. A path with + * no entry is LEGACY-TRUSTED and never reported stale. + * + * ⚠️ Do not "fix" a staleness report by editing this file. Refreshing a digest + * records that the current text was copied from the current source, which is + * the false claim the mechanism exists to detect. Fix the TRANSLATION. + */ + +export const zhHantGeneratedSourceHashes: Readonly> = { + "objects.sys_email.fields.message_id.label": "14cd089a4062f4b1", + "objects.sys_email_template.fields.id.label": "00b0385c9c152888", + "objects.sys_exchange_rate.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata.fields.bundle_member.help": "6912852b7523f266", + "objects.sys_metadata.fields.bundle_member.label": "49b49e6b77397e92", + "objects.sys_metadata.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_metadata_history.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_access_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_application._actions.create_oauth_application.resultDialog.fields.client.client_id": "09cb452151e7b1b6", + "objects.sys_oauth_application._actions.create_oauth_application.resultDialog.fields.client.client_secret": "8c703fe05bf2ed5d", + "objects.sys_oauth_application.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_application.fields.jwks.label": "b2cf79f9d66c0dbc", + "objects.sys_oauth_application.fields.jwks_uri.label": "2a4339df734ab269", + "objects.sys_oauth_client_assertion.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_client_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_consent.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_refresh_token.fields.id.label": "00b0385c9c152888", + "objects.sys_oauth_resource.fields.id.label": "00b0385c9c152888", + "objects.sys_platform_setting_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_platform_setting_audit.fields.source.options.api": "4baeeff968e192d1", + "objects.sys_platform_setting_audit.fields.source.options.ui": "95caad102a5c97ba", + "objects.sys_scim_connection_binding.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_connection_credential.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_group.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_group_member.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_identity_tombstone.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_projection_grant.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_subject.fields.id.label": "00b0385c9c152888", + "objects.sys_scim_user.fields.id.label": "00b0385c9c152888", + "objects.sys_secret.fields.id.label": "00b0385c9c152888", + "objects.sys_setting_audit.fields.id.label": "00b0385c9c152888", + "objects.sys_setting_audit.fields.source.options.api": "4baeeff968e192d1", + "objects.sys_setting_audit.fields.source.options.ui": "95caad102a5c97ba", + "objects.sys_sso_provider._actions.register_saml_provider.params.entryPoint.label": "65c2606e2e467cf7", + "objects.sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder": "5945ff9a6b644855", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapEmail.placeholder": "66309c7adf5c9436", + "objects.sys_sso_provider._actions.register_sso_provider.params.mapName.placeholder": "a484c34aaf624bd6", + "objects.sys_sso_provider._actions.register_sso_provider.params.scopes.placeholder": "58ac90cde28c764d", + "objects.sys_sso_provider.fields.id.label": "00b0385c9c152888", +}; diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts b/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts new file mode 100644 index 00000000000..4e56f7ff3c5 --- /dev/null +++ b/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts @@ -0,0 +1,195 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Recorded source hashes for `zh-Hant` (#8765, Option B). + * + * Each entry is the digest of the `en.ts` SOURCE STRING that the `zh-Hant` + * translation at that path was made from. `setup.translation.ts` compares them + * against the current source when it assembles the served bundle: a mismatch + * marks that leaf stale and serves the source string in its place + * (`withSourceFallback`), which is the same degradation an untranslated key + * already produces. + * + * ## Maintaining this file + * + * **Re-translated a string?** Update the value in `zh-Hant.ts` AND the digest + * here — that pair is what "record the source hash at translation time" means, + * and it is what makes this locale recover on its own while the others keep + * falling back. The new digest is + * `collectSourceHashes(en)['']` (exported from `source-hash.ts`). + * + * **Added a brand-new translation?** No entry is needed. A path with no + * recorded hash is LEGACY-TRUSTED and served verbatim — the ruling's explicit + * rule, so that translations predating this mechanism did not all degrade the + * day it landed. An entry recorded now is strictly better (it will catch the + * NEXT source edit), but its absence never marks anything stale. + * + * ⚠️ Do not "fix" a mismatch by refreshing the digest alone. That records that + * the current translation was made from the current source, which is exactly + * the false claim this mechanism exists to detect — the served text would go + * back to being silently wrong. + * + * ## Which table wins for a path that appears in BOTH + * + * **This one.** A `--no-objects-only` extract records provenance for whatever + * sections it generated, so since #16872 a path under `apps` / `dashboards` / + * `pages` can carry an entry in `.source-hashes.generated.ts` as well + * as here. The two are read by two predicates over two tables and neither + * consults the other, so "wins" is a question about what a HUMAN maintains: + * maintain the digest HERE, exactly as this header already says, and ignore the + * generated one — it is regenerated from the tree on every extract and any + * edit to it is overwritten. + * + * The overlap cannot serve wrong text. The generated table only ever records a + * leaf whose bytes are still a copy of a source revision, so the only paths + * that can appear in both are the ones a translator deliberately left equal to + * the English source; for those, both predicates substitute the SAME source + * string and the served bytes are identical either way. Re-translate such a + * leaf and the next extract drops its generated entry by itself, leaving this + * table the only claim about it. + * + * Recorded when this locale was translated (#30512): every path the `zh-Hant` + * bundle authors carries the digest of the source string it was translated + * from, so the first source edit under any of them is caught here rather than + * served as a superseded translation. + */ +export const zhHantSourceHashes: Readonly> = { + 'apps.account.label': 'ae88af048bfd5d64', + 'apps.account.description': 'd5fa9893662325fd', + 'apps.account.navigation.grp_account_inbox.label': '34dc7a0b58ada93b', + 'apps.account.navigation.grp_account_security.label': '0c463f40cccfdf86', + 'apps.account.navigation.grp_account_developer.label': '59668ec5af5afb55', + 'apps.account.navigation.nav_account_profile.label': '2c92f019ece4d044', + 'apps.account.navigation.nav_account_notifications.label': 'e0fdb90b1d31078d', + 'apps.account.navigation.nav_account_approvals.label': '318a820df22a684b', + 'apps.account.navigation.nav_account_memberships.label': '86164fcd38e0378d', + 'apps.account.navigation.nav_account_linked.label': '58ca623b89e9ff5c', + 'apps.account.navigation.nav_account_sessions.label': '05a759e3a230615e', + 'apps.account.navigation.nav_account_api_keys.label': '8cda9851248b0490', + 'apps.account.navigation.nav_account_oauth_apps.label': '1b26cce61a4c10eb', + 'apps.account.navigation.nav_connect_agent.label': 'eeb174613510e87d', + 'apps.setup.label': 'e4bb0d8bcd47c273', + 'apps.setup.description': 'deacdfc679232a4a', + 'apps.setup.navigation.group_overview.label': 'eebdbe1cc7b59cdf', + 'apps.setup.navigation.group_apps.label': '699b367291abcbe6', + 'apps.setup.navigation.nav_packaged_actions.label': '0d86b11df972dff3', + 'apps.setup.navigation.nav_marketplace_browse.label': '2afbe021f5419fdd', + 'apps.setup.navigation.nav_marketplace_installed.label': '93bd5d458e05db60', + 'apps.setup.navigation.nav_cloud_connection.label': '3a7caad0cb98e71d', + 'apps.setup.navigation.group_people_org.label': 'd313f656b6f0ab65', + 'apps.setup.navigation.group_access_control.label': 'a1a4dd47bcbfaf67', + 'apps.setup.navigation.group_approvals.label': '318a820df22a684b', + 'apps.setup.navigation.group_configuration.label': '5e77c0af10f4252c', + 'apps.setup.navigation.group_integrations.label': '1c83392f57463173', + 'apps.setup.navigation.group_diagnostics.label': '62d648cd434b395c', + 'apps.setup.navigation.group_advanced.label': 'bc9e9a0da37ea229', + 'apps.setup.navigation.nav_system_overview.label': '574b2a4b7fb7f979', + 'apps.setup.navigation.nav_users.label': 'eee81f7307a66c40', + 'apps.setup.navigation.nav_organization.label': '3e55836156e1c1de', + 'apps.setup.navigation.nav_business_units.label': '46e0cd2344b40e77', + 'apps.setup.navigation.nav_teams.label': '3fe8519872f65108', + 'apps.setup.navigation.nav_organizations.label': '07488357631b973d', + 'apps.setup.navigation.nav_invitations.label': '4ebb9fd13de7a546', + 'apps.setup.navigation.nav_position_holders.label': '627cacb50fd1b0d8', + 'apps.setup.navigation.nav_permission_set_assignment.label': 'fcf7d1b69ee9c298', + 'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad', + 'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a', + 'apps.setup.navigation.nav_queues.label': '226d23b8bd681e98', + 'apps.setup.navigation.nav_queue_members.label': '46715e86f5231603', + 'apps.setup.navigation.nav_assignment_rules.label': '1779fe384b23954f', + 'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4', + 'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490', + 'apps.setup.navigation.nav_connect_agent.label': 'eeb174613510e87d', + 'apps.setup.navigation.nav_approvals_inbox.label': '0a9269b20d0266c9', + 'apps.setup.navigation.nav_approval_requests.label': 'aafd0f7c6a376f55', + 'apps.setup.navigation.nav_approval_actions.label': 'd2ebad7acfd50978', + 'apps.setup.navigation.nav_approval_delegations.label': 'fc0d41741659c462', + 'apps.setup.navigation.nav_settings_hub.label': 'a3ac65c7c7f461bf', + 'apps.setup.navigation.nav_settings_localization.label': 'beedfaf00452c829', + 'apps.setup.navigation.nav_exchange_rates.label': '6d043e1f08434720', + 'apps.setup.navigation.nav_settings_company.label': '9d7f685e11eaade9', + 'apps.setup.navigation.nav_settings_mail.label': '5d0599600587427d', + 'apps.setup.navigation.nav_settings_sms.label': '77a3056d33027ca0', + 'apps.setup.navigation.nav_settings_branding.label': 'ec6e391065514b64', + 'apps.setup.navigation.nav_settings_auth.label': '5c6d7dbd850ca37c', + 'apps.setup.navigation.nav_settings_storage.label': 'a2fb526b03c9a9e5', + 'apps.setup.navigation.nav_settings_ai.label': 'a6b41a554173f7ab', + 'apps.setup.navigation.nav_settings_knowledge.label': '140348539a50d9d5', + 'apps.setup.navigation.nav_settings_lifecycle.label': 'cd9fe9c930afed06', + 'apps.setup.navigation.nav_notification_preferences.label': '8869f324c1bd545a', + 'apps.setup.navigation.nav_notification_templates.label': '9e270c87ef2578df', + 'apps.setup.navigation.nav_sessions.label': '673bde6e0c8a3ef9', + 'apps.setup.navigation.nav_audit_logs.label': 'b2fad40733012b18', + 'apps.setup.navigation.nav_notifications.label': 'e0fdb90b1d31078d', + 'apps.setup.navigation.nav_webhooks.label': '8fdd81bc76459a6a', + 'apps.setup.navigation.nav_http_deliveries.label': '340f81dc6ea64987', + 'apps.setup.navigation.nav_datasources.label': '2f0648ce23158e6f', + 'apps.setup.navigation.nav_connector_credentials.label': '0504775224e05a81', + 'apps.setup.navigation.nav_connector_connections.label': '949e9a8fd25c3d57', + 'apps.setup.navigation.nav_oauth_apps.label': '1b26cce61a4c10eb', + 'apps.studio.label': '5aaaaea2294ee893', + 'apps.studio.description': 'c026a5795933995d', + 'apps.studio.navigation.group_overview.label': 'eebdbe1cc7b59cdf', + 'apps.studio.navigation.nav_app_builder.label': '128446e7650210e7', + 'apps.studio.navigation.nav_metadata_directory.label': '87f99e3de29ec25f', + 'apps.studio.navigation.nav_packages.label': '8cd8f29658025f73', + 'apps.studio.navigation.group_data_model.label': 'f7b1fa6f26b344e5', + 'apps.studio.navigation.nav_objects.label': '361008570f571b11', + 'apps.studio.navigation.group_ux.label': '32d21c81ff9312e2', + 'apps.studio.navigation.nav_apps.label': '699b367291abcbe6', + 'apps.studio.navigation.nav_views.label': 'e9c808e26b6a8d11', + 'apps.studio.navigation.nav_pages.label': 'cda91afda2acc4ab', + 'apps.studio.navigation.nav_dashboards.label': '6102d5e8e4bb4b7a', + 'apps.studio.navigation.nav_reports.label': 'f981f2f6d59fce84', + 'apps.studio.navigation.nav_datasets.label': '8491e1ea908b9b0d', + 'apps.studio.navigation.group_logic.label': '415c519f3ad330d7', + 'apps.studio.navigation.nav_actions.label': '7527654bedc9fdfc', + 'apps.studio.navigation.nav_hooks.label': '6fb7e3b7bc144f67', + 'apps.studio.navigation.group_automation.label': '1d5659130670f5f3', + 'apps.studio.navigation.nav_flows.label': '26f7b054faf636a2', + 'apps.studio.navigation.group_ai.label': '0227934c75e56686', + 'apps.studio.navigation.nav_agents.label': '5300d1df6736e399', + 'apps.studio.navigation.nav_tools.label': 'db86c97f27cd31ce', + 'apps.studio.navigation.nav_skills.label': 'eb2af90cd11bbd34', + 'apps.studio.navigation.group_developer.label': '59668ec5af5afb55', + 'apps.studio.navigation.nav_api_console.label': 'd90e508f1921e8d3', + 'apps.studio.navigation.nav_flow_runs.label': '0bdee1504c505862', + 'apps.studio.navigation.nav_public_forms.label': '4592e97959994e86', + 'apps.studio.navigation.nav_integrations.label': 'edbd97d7f401a5b8', + 'apps.studio.navigation.group_integration.label': 'a1f2ddfb5c00c83a', + 'apps.studio.navigation.nav_email_templates.label': '3463bce831c833d5', + 'dashboards.system_overview.label': '574b2a4b7fb7f979', + 'dashboards.system_overview.description': '56dbd944c4b834da', + 'dashboards.system_overview.widgets.widget_total_users.title': 'cee2b388d39f4b4d', + 'dashboards.system_overview.widgets.widget_total_users.description': '54777631b0239fa1', + 'dashboards.system_overview.widgets.widget_organizations.title': '07488357631b973d', + 'dashboards.system_overview.widgets.widget_organizations.description': '4c8ffa2d407f4410', + 'dashboards.system_overview.widgets.widget_active_sessions.title': '05a759e3a230615e', + 'dashboards.system_overview.widgets.widget_active_sessions.description': '72335b16a1f8624e', + 'dashboards.system_overview.widgets.widget_packages_installed.title': 'ca0ce2bb712d83b3', + 'dashboards.system_overview.widgets.widget_packages_installed.description': '67c5558bf10fcb4d', + 'dashboards.system_overview.widgets.widget_login_events.title': 'c6475b3ee5d1efc1', + 'dashboards.system_overview.widgets.widget_login_events.description': '3c41545dbbd34f3c', + 'dashboards.system_overview.widgets.widget_config_changes.title': '4ca2f591d9440211', + 'dashboards.system_overview.widgets.widget_config_changes.description': '6be1d6643282e176', + 'dashboards.system_overview.widgets.widget_events_by_type.title': 'accefd73f7c4187d', + 'dashboards.system_overview.widgets.widget_events_by_type.description': '013308726e1bd2d3', + 'dashboards.system_overview.widgets.widget_events_by_user.title': '04d5cab03abee166', + 'dashboards.system_overview.widgets.widget_events_by_user.description': 'ee9e0d412f66fb4d', + 'dashboards.system_overview.widgets.widget_recent_events.title': '5fb9db734193fe37', + 'dashboards.system_overview.widgets.widget_recent_events.description': '5b9ce7479f6c178c', + 'dashboards.system_overview.globalFilters.created_at.label': 'dcba3619736cb533', + 'datasets.sys_user_metrics.measures.user_count.label': 'eee81f7307a66c40', + 'datasets.sys_organization_metrics.measures.org_count.label': '07488357631b973d', + 'datasets.sys_session_metrics.measures.session_count.label': '673bde6e0c8a3ef9', + 'datasets.sys_package_installation_metrics.measures.package_count.label': 'bd7a67c9d5b3ac44', + 'datasets.sys_audit_log_metrics.dimensions.action.label': 'a36bba313a7aa9ab', + 'datasets.sys_audit_log_metrics.dimensions.user_id.label': '6c0d22a931e235bd', + 'datasets.sys_audit_log_metrics.measures.event_count.label': '41e7c95aaa871569', + 'pages.cloud_connection_settings.label': '3a7caad0cb98e71d', + 'pages.cloud_connection_settings.subtitle': 'f5cdd253e8ffef9f', + 'pages.connect_agent.label': 'eeb174613510e87d', + 'pages.connect_agent.subtitle': 'b0d7c6d174511bcb', + 'pages.sys_user_detail.label': '6c0d22a931e235bd', + 'pages.sys_organization_detail.label': '3e55836156e1c1de', +}; diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.ts b/packages/platform-objects/src/apps/translations/zh-Hant.ts new file mode 100644 index 00000000000..d17d15afc7b --- /dev/null +++ b/packages/platform-objects/src/apps/translations/zh-Hant.ts @@ -0,0 +1,208 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import type { TranslationData } from '@objectstack/spec/system'; +import { zhHantObjects } from './zh-Hant.objects.generated.js'; + +/** + * 繁體中文 (zh-Hant) — Setup App Translations + */ +export const zhHant: TranslationData = { + objects: zhHantObjects, + apps: { + account: { + label: '帳戶', + description: '個人安全與身分設定', + navigation: { + grp_account_inbox: { label: '收件匣' }, + grp_account_security: { label: '安全' }, + grp_account_developer: { label: '開發者' }, + nav_account_profile: { label: '個人資料' }, + nav_account_notifications: { label: '通知' }, + nav_account_approvals: { label: '待我核准' }, + nav_account_memberships: { label: '我的組織' }, + nav_account_linked: { label: '已關聯帳戶' }, + nav_account_sessions: { label: '使用中的工作階段' }, + nav_account_api_keys: { label: 'API 金鑰' }, + nav_account_oauth_apps: { label: 'OAuth 應用程式' }, + // Contributed at runtime by `@objectstack/mcp` into + // `grp_account_developer`; the Setup twin's key does not answer for the + // Account app (one `apps..navigation` namespace per app). + nav_connect_agent: { label: '連接 AI 代理' }, + }, + }, + setup: { + label: '系統設定', + description: '平台設定與管理', + navigation: { + group_overview: { label: '總覽' }, + group_apps: { label: '應用程式' }, + nav_packaged_actions: { label: '打包動作' }, + nav_marketplace_browse: { label: '瀏覽應用程式市場' }, + nav_marketplace_installed: { label: '已安裝應用程式' }, + nav_cloud_connection: { label: '雲端連線' }, + group_people_org: { label: '人員與組織' }, + group_access_control: { label: '存取控制' }, + group_approvals: { label: '核准' }, + group_configuration: { label: '設定' }, + group_integrations: { label: '整合' }, + group_diagnostics: { label: '診斷' }, + group_advanced: { label: '進階' }, + + nav_system_overview: { label: '系統概覽' }, + + nav_users: { label: '使用者' }, + nav_organization: { label: '目前組織' }, + nav_business_units: { label: '業務單位' }, + nav_teams: { label: '團隊' }, + nav_organizations: { label: '所有組織' }, + nav_invitations: { label: '邀請' }, + nav_position_holders: { label: '職位持有人' }, + nav_permission_set_assignment: { label: '權限集指派' }, + + nav_sharing_rules: { label: '共用規則' }, + nav_record_shares: { label: '記錄共用' }, + nav_queues: { label: '佇列' }, + nav_queue_members: { label: '佇列成員' }, + nav_assignment_rules: { label: '指派規則' }, + // Conditionally contributed by `@objectstack/plugin-auth` (only when an + // external IdP is wired), so `check:app-nav-i18n` cannot see it — see + // the rationale in `en.ts` (#6659). Wording matches this locale's + // `sys_sso_provider.pluralLabel`. + nav_sso_providers: { label: 'SSO 提供方' }, + nav_api_keys: { label: 'API 金鑰' }, + nav_connect_agent: { label: '連接 AI 代理' }, + + // 核准中心 = the Approvals Inbox component (#7234) — the surface with + // decision actions. The three below are the engine's raw tables; the + // first and last read as plugin-approvals' zh-Hant object labels. + nav_approvals_inbox: { label: '核准中心' }, + nav_approval_requests: { label: '核准請求' }, + nav_approval_actions: { label: '核准歷史' }, + // `核准委派` matches sys_approval_delegation's object label; `(外出)` + // renders the entry's own "(OOO)" qualifier. + nav_approval_delegations: { label: '核准委派(外出)' }, + + // Each nav_settings_* entry reads as its hub card's title in + // service-settings' zh-Hant bundle (`setup-nav-hub-parity.test.ts`), so + // the two delivery pages say 投遞 here while the HTTP log below says 傳送, + // as sys_http_delivery's zh-Hant object label does. + nav_settings_hub: { label: '全部設定' }, + nav_settings_localization: { label: '在地化' }, + nav_exchange_rates: { label: '匯率' }, + nav_settings_company: { label: '公司' }, + nav_settings_mail: { label: '郵件投遞' }, + nav_settings_sms: { label: '簡訊投遞' }, + nav_settings_branding: { label: '品牌' }, + nav_settings_auth: { label: '驗證' }, + nav_settings_storage: { label: '檔案儲存' }, + nav_settings_ai: { label: 'AI 與 Embedder' }, + nav_settings_knowledge: { label: '知識庫' }, + nav_settings_lifecycle: { label: '資料生命週期' }, + nav_notification_preferences: { label: '通知偏好設定' }, + nav_notification_templates: { label: '通知範本' }, + + nav_sessions: { label: '工作階段' }, + nav_audit_logs: { label: '稽核日誌' }, + nav_notifications: { label: '通知' }, + + // Integrations — contributed at RUNTIME by the owning capability + // plugins (ADR-0029 K2), so no static walk of SETUP_APP can see them. + // `Webhooks` stays the loanword deliberately, as in zh-CN: sys_webhook's + // own zh-Hant object label is `Webhook` too, and this bundle is where + // that choice is RECORDED rather than reached by falling back (#5750). + nav_webhooks: { label: 'Webhooks' }, + nav_http_deliveries: { label: 'HTTP 傳送' }, + nav_datasources: { label: '資料來源' }, + nav_connector_credentials: { label: '連接器憑證' }, + nav_connector_connections: { label: '連接器連線' }, + + nav_oauth_apps: { label: 'OAuth 應用程式' }, + }, + }, + studio: { + label: 'Studio', + description: '供開發者、分析師與導入人員使用的中繼資料工作台', + navigation: { + group_overview: { label: '總覽' }, + nav_app_builder: { label: '應用程式建構器' }, + nav_metadata_directory: { label: '全部中繼資料類型' }, + nav_packages: { label: '套件' }, + group_data_model: { label: '資料模型' }, + nav_objects: { label: '物件' }, + group_ux: { label: '使用者體驗' }, + nav_apps: { label: '應用程式' }, + nav_views: { label: '檢視' }, + nav_pages: { label: '頁面' }, + nav_dashboards: { label: '儀表板' }, + nav_reports: { label: '報表' }, + nav_datasets: { label: '資料集' }, + group_logic: { label: '邏輯' }, + nav_actions: { label: '動作' }, + nav_hooks: { label: '掛鉤' }, + group_automation: { label: '自動化' }, + nav_flows: { label: '流程' }, + group_ai: { label: 'AI' }, + nav_agents: { label: 'AI 代理' }, + nav_tools: { label: '工具' }, + nav_skills: { label: '技能' }, + group_developer: { label: '開發者' }, + nav_api_console: { label: 'API 控制台' }, + nav_flow_runs: { label: '流程執行記錄' }, + nav_public_forms: { label: '公開表單' }, + nav_integrations: { label: '整合與 API' }, + group_integration: { label: '整合' }, + nav_email_templates: { label: '郵件範本' }, + }, + }, + }, + + dashboards: { + system_overview: { + label: '系統概覽', + description: '平台健康狀況、安全活動與最近的稽核事件', + widgets: { + widget_total_users: { title: '使用者總數', description: '系統中已註冊的使用者總數' }, + widget_organizations: { title: '組織數', description: '平台上的組織總數' }, + widget_active_sessions: { title: '活躍工作階段', description: '目前活躍使用者工作階段數量' }, + widget_packages_installed: { title: '已安裝套件', description: '專案中已啟用的安裝套件數' }, + widget_login_events: { title: '登入事件', description: '稽核日誌中記錄的驗證事件' }, + widget_config_changes: { title: '設定變更', description: '系統設定修改' }, + widget_events_by_type: { title: '依操作分佈的稽核事件', description: '稽核事件依操作類型分佈' }, + widget_events_by_user: { title: '依使用者分佈的事件', description: '使用者活動分佈' }, + widget_recent_events: { title: '依操作統計的事件量', description: '依操作分組的事件量(登入、登出、設定等)' }, + }, + // The date-range bar above the widgets — addressable since + // `dashboards..globalFilters` (#16772), keyed by the filter's + // `field` because it authors no `name`. + globalFilters: { + created_at: { label: '日期範圍' }, + }, + }, + }, + + datasets: { + sys_user_metrics: { measures: { user_count: { label: '使用者' } } }, + sys_organization_metrics: { measures: { org_count: { label: '組織' } } }, + sys_session_metrics: { measures: { session_count: { label: '工作階段' } } }, + sys_package_installation_metrics: { measures: { package_count: { label: '安裝' } } }, + sys_audit_log_metrics: { + dimensions: { action: { label: '操作' }, user_id: { label: '使用者' } }, + measures: { event_count: { label: '事件' } }, + }, + }, + + pages: { + cloud_connection_settings: { + label: '雲端連線', + subtitle: '將此執行階段連接到 ObjectStack 控制平面,即可瀏覽並安裝組織的私有套件。', + }, + connect_agent: { + label: '連接 AI 代理', + subtitle: '讓任何支援 MCP 的 AI 用戶端以受控方式存取本系統——每次呼叫都在呼叫者自身的權限與列級安全範圍內執行。', + }, + + // Platform record pages (#15743) -- see `en.ts` for the key set. + sys_user_detail: { label: '使用者' }, + sys_organization_detail: { label: '組織' }, + }, +}; diff --git a/packages/platform-objects/src/identity/invitation-delivery-copy.test.ts b/packages/platform-objects/src/identity/invitation-delivery-copy.test.ts index d7a834620c6..6939fb02bc7 100644 --- a/packages/platform-objects/src/identity/invitation-delivery-copy.test.ts +++ b/packages/platform-objects/src/identity/invitation-delivery-copy.test.ts @@ -29,6 +29,7 @@ import { enObjects } from '../apps/translations/en.objects.generated.js'; import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js'; import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js'; import { esESObjects } from '../apps/translations/es-ES.objects.generated.js'; +import { zhHantObjects } from '../apps/translations/zh-Hant.objects.generated.js'; import { INVITE_USER_OUTCOME_MESSAGES, RESEND_INVITATION_OUTCOME_MESSAGES } from './invite-outcome-messages.js'; import { SysInvitation } from './sys-invitation.object.js'; import { SysMember } from './sys-member.object.js'; @@ -77,6 +78,7 @@ const TRANSLATED = [ ['zh-CN', zhCNObjects], ['ja-JP', jaJPObjects], ['es-ES', esESObjects], + ['zh-Hant', zhHantObjects], ] as const; /** Any spelling of "sent" a `link_only` copy must not carry. */ diff --git a/packages/platform-objects/src/identity/invitation-screens-copy.test.ts b/packages/platform-objects/src/identity/invitation-screens-copy.test.ts index 3fa455dd5a0..b593ab3c68e 100644 --- a/packages/platform-objects/src/identity/invitation-screens-copy.test.ts +++ b/packages/platform-objects/src/identity/invitation-screens-copy.test.ts @@ -19,6 +19,7 @@ import { enObjects } from '../apps/translations/en.objects.generated.js'; import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js'; import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js'; import { esESObjects } from '../apps/translations/es-ES.objects.generated.js'; +import { zhHantObjects } from '../apps/translations/zh-Hant.objects.generated.js'; import { MEMBERSHIP_ROLE_PARAM_HELP } from './membership-role-help.js'; import { SysInvitation } from './sys-invitation.object.js'; import { SysMember } from './sys-member.object.js'; @@ -77,6 +78,7 @@ const TRANSLATED = [ ['zh-CN', zhCNObjects], ['ja-JP', jaJPObjects], ['es-ES', esESObjects], + ['zh-Hant', zhHantObjects], ] as const; const bundleObject = (bundle: unknown, object: string): Record | undefined => diff --git a/packages/platform-objects/src/identity/invite-user-action-copy.test.ts b/packages/platform-objects/src/identity/invite-user-action-copy.test.ts index dae7f00ae8b..e4da48d0583 100644 --- a/packages/platform-objects/src/identity/invite-user-action-copy.test.ts +++ b/packages/platform-objects/src/identity/invite-user-action-copy.test.ts @@ -29,6 +29,7 @@ import { enObjects } from '../apps/translations/en.objects.generated.js'; import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js'; import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js'; import { esESObjects } from '../apps/translations/es-ES.objects.generated.js'; +import { zhHantObjects } from '../apps/translations/zh-Hant.objects.generated.js'; import { SysInvitation } from './sys-invitation.object.js'; import { SysMember } from './sys-member.object.js'; import { SysOrganization } from './sys-organization.object.js'; @@ -76,6 +77,7 @@ const TRANSLATED = [ ['zh-CN', zhCNObjects], ['ja-JP', jaJPObjects], ['es-ES', esESObjects], + ['zh-Hant', zhHantObjects], ] as const; // [#24814] `successMessage` is a `template` slot: the parsed diff --git a/packages/platform-objects/src/identity/sys-api-key-action-copy.test.ts b/packages/platform-objects/src/identity/sys-api-key-action-copy.test.ts index e64efbec1de..f2340874470 100644 --- a/packages/platform-objects/src/identity/sys-api-key-action-copy.test.ts +++ b/packages/platform-objects/src/identity/sys-api-key-action-copy.test.ts @@ -22,6 +22,7 @@ import { enObjects } from '../apps/translations/en.objects.generated.js'; import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js'; import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js'; import { esESObjects } from '../apps/translations/es-ES.objects.generated.js'; +import { zhHantObjects } from '../apps/translations/zh-Hant.objects.generated.js'; import { SysApiKey } from './sys-api-key.object.js'; interface CopyAction { @@ -38,6 +39,7 @@ const TRANSLATED = [ ['zh-CN', zhCNObjects], ['ja-JP', jaJPObjects], ['es-ES', esESObjects], + ['zh-Hant', zhHantObjects], ] as const; // `successMessage` is a `template` slot: the parsed object carries the diff --git a/packages/platform-objects/src/identity/transfer-ownership-action-copy.test.ts b/packages/platform-objects/src/identity/transfer-ownership-action-copy.test.ts index 8c3fca0f4e0..2dea550d5ee 100644 --- a/packages/platform-objects/src/identity/transfer-ownership-action-copy.test.ts +++ b/packages/platform-objects/src/identity/transfer-ownership-action-copy.test.ts @@ -21,6 +21,7 @@ import { enObjects } from '../apps/translations/en.objects.generated.js'; import { zhCNObjects } from '../apps/translations/zh-CN.objects.generated.js'; import { jaJPObjects } from '../apps/translations/ja-JP.objects.generated.js'; import { esESObjects } from '../apps/translations/es-ES.objects.generated.js'; +import { zhHantObjects } from '../apps/translations/zh-Hant.objects.generated.js'; import { SysMember } from './sys-member.object.js'; // [#27948] An action's `description`, each param's `helpText` and each @@ -79,6 +80,7 @@ const LOCALES = [ ['zh-CN', zhCNObjects], ['ja-JP', jaJPObjects], ['es-ES', esESObjects], + ['zh-Hant', zhHantObjects], ] as const; describe('sys_member.transfer_ownership — declared copy', () => { diff --git a/packages/platform-objects/src/metadata-translations/index.ts b/packages/platform-objects/src/metadata-translations/index.ts index 791340ff829..0b37355a9cc 100644 --- a/packages/platform-objects/src/metadata-translations/index.ts +++ b/packages/platform-objects/src/metadata-translations/index.ts @@ -5,12 +5,14 @@ import { enMetadataForms } from '../apps/translations/en.metadata-forms.generate import { zhCNMetadataForms } from '../apps/translations/zh-CN.metadata-forms.generated.js'; import { jaJPMetadataForms } from '../apps/translations/ja-JP.metadata-forms.generated.js'; import { esESMetadataForms } from '../apps/translations/es-ES.metadata-forms.generated.js'; +import { zhHantMetadataForms } from '../apps/translations/zh-Hant.metadata-forms.generated.js'; import { zhCNGeneratedSourceHashes } from '../apps/translations/zh-CN.source-hashes.generated.js'; import { jaJPGeneratedSourceHashes } from '../apps/translations/ja-JP.source-hashes.generated.js'; import { esESGeneratedSourceHashes } from '../apps/translations/es-ES.source-hashes.generated.js'; +import { zhHantGeneratedSourceHashes } from '../apps/translations/zh-Hant.source-hashes.generated.js'; import { withSourceFallback } from '../apps/translations/source-hash.js'; -/** The source bundle these three are judged against — `en` is a copy of it. */ +/** The source bundle the translated locales are judged against — `en` is a copy of it. */ const enSource = { metadataForms: enMetadataForms }; /** @@ -33,7 +35,7 @@ const enSource = { metadataForms: enMetadataForms }; * "Only fills newly added keys" is exactly the sticky drift the source-hash * mechanism exists for: a leaf filled from the source and then left behind when * the source was revised keeps serving a superseded draft, present and in sync - * by key, forever. The three translated locales therefore pass through + * by key, forever. The translated locales therefore pass through * `withSourceFallback`, judged by the generated provenance tables. The THIRD * argument is `undefined` on purpose — the hand-authored table judges * `apps`/`dashboards`/`pages`, and this bundle carries none of those. @@ -43,4 +45,5 @@ export const MetadataFormsTranslations: TranslationBundle = { 'zh-CN': withSourceFallback({ metadataForms: zhCNMetadataForms }, enSource, undefined, zhCNGeneratedSourceHashes), 'ja-JP': withSourceFallback({ metadataForms: jaJPMetadataForms }, enSource, undefined, jaJPGeneratedSourceHashes), 'es-ES': withSourceFallback({ metadataForms: esESMetadataForms }, enSource, undefined, esESGeneratedSourceHashes), + 'zh-Hant': withSourceFallback({ metadataForms: zhHantMetadataForms }, enSource, undefined, zhHantGeneratedSourceHashes), }; diff --git a/packages/platform-objects/src/pages/sys-organization.page.ts b/packages/platform-objects/src/pages/sys-organization.page.ts index 3acf7c0a1f1..2a448edea13 100644 --- a/packages/platform-objects/src/pages/sys-organization.page.ts +++ b/packages/platform-objects/src/pages/sys-organization.page.ts @@ -80,7 +80,7 @@ export const SysOrganizationDetailPage: Page = { position: 'top', items: [ { - label: { en: 'Members', 'zh-CN': '成员', 'ja-JP': 'メンバー', 'es-ES': 'Miembros' }, + label: { en: 'Members', 'zh-CN': '成员', 'ja-JP': 'メンバー', 'es-ES': 'Miembros', 'zh-Hant': '成員' }, icon: 'users', children: [ { @@ -92,13 +92,13 @@ export const SysOrganizationDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Members', 'zh-CN': '成员', 'ja-JP': 'メンバー', 'es-ES': 'Miembros' }, + title: { en: 'Members', 'zh-CN': '成员', 'ja-JP': 'メンバー', 'es-ES': 'Miembros', 'zh-Hant': '成員' }, }, }, ], }, { - label: { en: 'Invitations', 'zh-CN': '邀请', 'ja-JP': '招待', 'es-ES': 'Invitaciones' }, + label: { en: 'Invitations', 'zh-CN': '邀请', 'ja-JP': '招待', 'es-ES': 'Invitaciones', 'zh-Hant': '邀請' }, icon: 'mail', children: [ { @@ -110,13 +110,13 @@ export const SysOrganizationDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Invitations', 'zh-CN': '邀请', 'ja-JP': '招待', 'es-ES': 'Invitaciones' }, + title: { en: 'Invitations', 'zh-CN': '邀请', 'ja-JP': '招待', 'es-ES': 'Invitaciones', 'zh-Hant': '邀請' }, }, }, ], }, { - label: { en: 'Teams', 'zh-CN': '团队', 'ja-JP': 'チーム', 'es-ES': 'Equipos' }, + label: { en: 'Teams', 'zh-CN': '团队', 'ja-JP': 'チーム', 'es-ES': 'Equipos', 'zh-Hant': '團隊' }, icon: 'users-round', children: [ { @@ -128,7 +128,7 @@ export const SysOrganizationDetailPage: Page = { sort: [{ field: 'name', order: 'asc' }], limit: 25, showViewAll: true, - title: { en: 'Teams', 'zh-CN': '团队', 'ja-JP': 'チーム', 'es-ES': 'Equipos' }, + title: { en: 'Teams', 'zh-CN': '团队', 'ja-JP': 'チーム', 'es-ES': 'Equipos', 'zh-Hant': '團隊' }, }, }, ], diff --git a/packages/platform-objects/src/pages/sys-user-details-tab.test.ts b/packages/platform-objects/src/pages/sys-user-details-tab.test.ts index 06ac47d093e..22e9a28c8e5 100644 --- a/packages/platform-objects/src/pages/sys-user-details-tab.test.ts +++ b/packages/platform-objects/src/pages/sys-user-details-tab.test.ts @@ -34,17 +34,17 @@ const HIDE_FIELDS = [ ]; const SECTIONS = [ { - label: { en: 'Identity', 'zh-CN': '身份', 'ja-JP': 'アイデンティティ', 'es-ES': 'Identidad' }, + label: { en: 'Identity', 'zh-CN': '身份', 'ja-JP': 'アイデンティティ', 'es-ES': 'Identidad', 'zh-Hant': '身分' }, // #27859: a saved Locale is shown; the renderer draws only authored sections. fields: ['name', 'image', 'locale'], }, { // #27859: a banned user reads as banned, with the reason, not as active. - label: { en: 'Account Status', 'zh-CN': '账号状态', 'ja-JP': 'アカウントの状態', 'es-ES': 'Estado de la cuenta' }, + label: { en: 'Account Status', 'zh-CN': '账号状态', 'ja-JP': 'アカウントの状態', 'es-ES': 'Estado de la cuenta', 'zh-Hant': '帳號狀態' }, fields: ['banned', 'ban_reason', 'ban_expires'], }, { - label: { en: 'Audit', 'zh-CN': '审计', 'ja-JP': '監査', 'es-ES': 'Auditoría' }, + label: { en: 'Audit', 'zh-CN': '审计', 'ja-JP': '監査', 'es-ES': 'Auditoría', 'zh-Hant': '稽核' }, fields: ['created_at', 'updated_at'], }, ]; diff --git a/packages/platform-objects/src/pages/sys-user.page.ts b/packages/platform-objects/src/pages/sys-user.page.ts index 8a6ce551693..7f6da617737 100644 --- a/packages/platform-objects/src/pages/sys-user.page.ts +++ b/packages/platform-objects/src/pages/sys-user.page.ts @@ -140,12 +140,14 @@ export const SysUserDetailPage: Page = { 'zh-CN': '邮箱未验证', 'ja-JP': 'メールが未認証です', 'es-ES': 'Correo no verificado', + 'zh-Hant': '電子郵件尚未驗證', }, body: { en: 'Verify your email to receive password resets and important system notifications.', 'zh-CN': '验证你的邮箱以接收密码重置和重要的系统通知。', 'ja-JP': 'パスワードリセットや重要なシステム通知を受け取るには、メールアドレスを認証してください。', 'es-ES': 'Verifica tu correo para recibir restablecimientos de contraseña y notificaciones importantes del sistema.', + 'zh-Hant': '驗證你的電子郵件,以接收密碼重設和重要的系統通知。', }, dismissible: false, action: { @@ -155,6 +157,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '重新发送验证邮件', 'ja-JP': '認証メールを再送信', 'es-ES': 'Reenviar correo de verificación', + 'zh-Hant': '重新寄送驗證郵件', }, }, }, @@ -197,7 +200,7 @@ export const SysUserDetailPage: Page = { // authored sections, so a field none of them names is not on // the page: `locale` rides Identity, and the ban state has its // own section, so a banned user does not read as active. - label: { en: 'Details', 'zh-CN': '详情', 'ja-JP': '詳細', 'es-ES': 'Detalles' }, + label: { en: 'Details', 'zh-CN': '详情', 'ja-JP': '詳細', 'es-ES': 'Detalles', 'zh-Hant': '詳細資料' }, icon: 'file-text', children: [ { @@ -214,15 +217,15 @@ export const SysUserDetailPage: Page = { ], sections: [ { - label: { en: 'Identity', 'zh-CN': '身份', 'ja-JP': 'アイデンティティ', 'es-ES': 'Identidad' }, + label: { en: 'Identity', 'zh-CN': '身份', 'ja-JP': 'アイデンティティ', 'es-ES': 'Identidad', 'zh-Hant': '身分' }, fields: ['name', 'image', 'locale'], }, { - label: { en: 'Account Status', 'zh-CN': '账号状态', 'ja-JP': 'アカウントの状態', 'es-ES': 'Estado de la cuenta' }, + label: { en: 'Account Status', 'zh-CN': '账号状态', 'ja-JP': 'アカウントの状態', 'es-ES': 'Estado de la cuenta', 'zh-Hant': '帳號狀態' }, fields: ['banned', 'ban_reason', 'ban_expires'], }, { - label: { en: 'Audit', 'zh-CN': '审计', 'ja-JP': '監査', 'es-ES': 'Auditoría' }, + label: { en: 'Audit', 'zh-CN': '审计', 'ja-JP': '監査', 'es-ES': 'Auditoría', 'zh-Hant': '稽核' }, fields: ['created_at', 'updated_at'], }, ], @@ -231,7 +234,7 @@ export const SysUserDetailPage: Page = { ], }, { - label: { en: 'Positions', 'zh-CN': '岗位', 'ja-JP': 'ポジション', 'es-ES': 'Puestos' }, + label: { en: 'Positions', 'zh-CN': '岗位', 'ja-JP': 'ポジション', 'es-ES': 'Puestos', 'zh-Hant': '職位' }, icon: 'shield-check', children: [ { @@ -251,7 +254,7 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Positions', 'zh-CN': '岗位', 'ja-JP': 'ポジション', 'es-ES': 'Puestos' }, + title: { en: 'Positions', 'zh-CN': '岗位', 'ja-JP': 'ポジション', 'es-ES': 'Puestos', 'zh-Hant': '職位' }, }, }, ], @@ -274,7 +277,7 @@ export const SysUserDetailPage: Page = { // a row showing only the set would read as a live grant when it is // not. The same form sets the window, and the object refuses a // window whose end is not after its start. - label: { en: 'Permission Sets', 'zh-CN': '权限集', 'ja-JP': '権限セット', 'es-ES': 'Conjuntos de permisos' }, + label: { en: 'Permission Sets', 'zh-CN': '权限集', 'ja-JP': '権限セット', 'es-ES': 'Conjuntos de permisos', 'zh-Hant': '權限集' }, icon: 'lock', children: [ { @@ -286,7 +289,7 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Permission Sets', 'zh-CN': '权限集', 'ja-JP': '権限セット', 'es-ES': 'Conjuntos de permisos' }, + title: { en: 'Permission Sets', 'zh-CN': '权限集', 'ja-JP': '権限セット', 'es-ES': 'Conjuntos de permisos', 'zh-Hant': '權限集' }, }, }, ], @@ -299,7 +302,7 @@ export const SysUserDetailPage: Page = { // The Add picker binds a BU by its id (linkField: 'business_unit_id'); // sys_business_unit has no `label` field so the picker labels rows by // its display field `name`. - label: { en: 'Business Units', 'zh-CN': '业务单元', 'ja-JP': 'ビジネスユニット', 'es-ES': 'Unidades de negocio' }, + label: { en: 'Business Units', 'zh-CN': '业务单元', 'ja-JP': 'ビジネスユニット', 'es-ES': 'Unidades de negocio', 'zh-Hant': '業務單位' }, icon: 'network', children: [ { @@ -311,7 +314,7 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Business Units', 'zh-CN': '业务单元', 'ja-JP': 'ビジネスユニット', 'es-ES': 'Unidades de negocio' }, + title: { en: 'Business Units', 'zh-CN': '业务单元', 'ja-JP': 'ビジネスユニット', 'es-ES': 'Unidades de negocio', 'zh-Hant': '業務單位' }, add: { picker: { object: 'sys_business_unit', @@ -323,6 +326,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '加入业务单元', 'ja-JP': 'ビジネスユニットに追加', 'es-ES': 'Añadir a unidad de negocio', + 'zh-Hant': '加入業務單位', }, }, }, @@ -330,7 +334,7 @@ export const SysUserDetailPage: Page = { ], }, { - label: { en: 'Sessions', 'zh-CN': '会话', 'ja-JP': 'セッション', 'es-ES': 'Sesiones' }, + label: { en: 'Sessions', 'zh-CN': '会话', 'ja-JP': 'セッション', 'es-ES': 'Sesiones', 'zh-Hant': '工作階段' }, icon: 'monitor', children: [ { @@ -342,13 +346,13 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Sessions', 'zh-CN': '会话', 'ja-JP': 'セッション', 'es-ES': 'Sesiones' }, + title: { en: 'Sessions', 'zh-CN': '会话', 'ja-JP': 'セッション', 'es-ES': 'Sesiones', 'zh-Hant': '工作階段' }, }, }, ], }, { - label: { en: 'Linked Accounts', 'zh-CN': '关联账号', 'ja-JP': '連携アカウント', 'es-ES': 'Cuentas vinculadas' }, + label: { en: 'Linked Accounts', 'zh-CN': '关联账号', 'ja-JP': '連携アカウント', 'es-ES': 'Cuentas vinculadas', 'zh-Hant': '已關聯帳戶' }, icon: 'link', children: [ { @@ -360,13 +364,13 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Linked Accounts', 'zh-CN': '关联账号', 'ja-JP': '連携アカウント', 'es-ES': 'Cuentas vinculadas' }, + title: { en: 'Linked Accounts', 'zh-CN': '关联账号', 'ja-JP': '連携アカウント', 'es-ES': 'Cuentas vinculadas', 'zh-Hant': '已關聯帳戶' }, }, }, ], }, { - label: { en: 'Organizations', 'zh-CN': '组织', 'ja-JP': '組織', 'es-ES': 'Organizaciones' }, + label: { en: 'Organizations', 'zh-CN': '组织', 'ja-JP': '組織', 'es-ES': 'Organizaciones', 'zh-Hant': '組織' }, icon: 'building-2', children: [ { @@ -378,13 +382,13 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Organizations', 'zh-CN': '组织', 'ja-JP': '組織', 'es-ES': 'Organizaciones' }, + title: { en: 'Organizations', 'zh-CN': '组织', 'ja-JP': '組織', 'es-ES': 'Organizaciones', 'zh-Hant': '組織' }, }, }, ], }, { - label: { en: 'OAuth Apps', 'zh-CN': 'OAuth 应用', 'ja-JP': 'OAuth アプリ', 'es-ES': 'Aplicaciones OAuth' }, + label: { en: 'OAuth Apps', 'zh-CN': 'OAuth 应用', 'ja-JP': 'OAuth アプリ', 'es-ES': 'Aplicaciones OAuth', 'zh-Hant': 'OAuth 應用程式' }, icon: 'key-square', children: [ { @@ -396,7 +400,7 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'OAuth Apps', 'zh-CN': 'OAuth 应用', 'ja-JP': 'OAuth アプリ', 'es-ES': 'Aplicaciones OAuth' }, + title: { en: 'OAuth Apps', 'zh-CN': 'OAuth 应用', 'ja-JP': 'OAuth アプリ', 'es-ES': 'Aplicaciones OAuth', 'zh-Hant': 'OAuth 應用程式' }, }, }, ], @@ -421,7 +425,7 @@ export const SysUserDetailPage: Page = { // reasons: this raw `Page` literal reaches the wire verbatim, and // only the `{ dialect: 'cel' }` envelope routes to CEL. { - label: { en: 'Security', 'zh-CN': '安全', 'ja-JP': 'セキュリティ', 'es-ES': 'Seguridad' }, + label: { en: 'Security', 'zh-CN': '安全', 'ja-JP': 'セキュリティ', 'es-ES': 'Seguridad', 'zh-Hant': '安全' }, icon: 'shield', visibleWhen: P`has(record.id) && record.id == ctx.user.id`, children: [ @@ -434,6 +438,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '密码与登录', 'ja-JP': 'パスワードとサインイン', 'es-ES': 'Contraseña e inicio de sesión', + 'zh-Hant': '密碼與登入', }, }, }, @@ -446,6 +451,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '修改密码或此账号绑定的邮箱地址。', 'ja-JP': 'パスワード、またはこのアカウントに関連付けられたメールアドレスを変更します。', 'es-ES': 'Cambia tu contraseña o la dirección de correo asociada a esta cuenta.', + 'zh-Hant': '變更密碼或此帳號繫結的電子郵件地址。', }, }, }, @@ -473,6 +479,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '两步验证', 'ja-JP': '二要素認証', 'es-ES': 'Autenticación de dos factores', + 'zh-Hant': '兩步驟驗證', }, }, }, @@ -486,6 +493,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '使用 TOTP 验证器应用添加第二层安全防护。备用验证码可在设备丢失时用于登录。', 'ja-JP': 'TOTP 認証アプリでセキュリティをもう一段強化します。バックアップコードがあれば、デバイスを紛失してもサインインできます。', 'es-ES': 'Añade una segunda capa de seguridad con una app de autenticación TOTP. Los códigos de respaldo te permiten iniciar sesión si pierdes tu dispositivo.', + 'zh-Hant': '使用 TOTP 驗證器應用程式新增第二層安全防護。裝置遺失時,可使用備用驗證碼登入。', }, }, }, @@ -517,6 +525,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '邮箱验证', 'ja-JP': 'メール認証', 'es-ES': 'Verificación de correo', + 'zh-Hant': '電子郵件驗證', }, }, }, @@ -529,6 +538,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '验证你的邮箱,以便接收密码重置和系统通知。按钮仅在邮箱待验证时显示。', 'ja-JP': 'パスワードリセットや通知を受け取れるよう、メールアドレスを認証してください。ボタンは未認証の間のみ表示されます。', 'es-ES': 'Verifica tu correo para recibir restablecimientos de contraseña y notificaciones. El botón solo aparece mientras la verificación está pendiente.', + 'zh-Hant': '驗證你的電子郵件,以便接收密碼重設和系統通知。此按鈕僅在電子郵件待驗證時顯示。', }, }, }, @@ -550,6 +560,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '危险操作', 'ja-JP': '危険な操作', 'es-ES': 'Zona de peligro', + 'zh-Hant': '危險操作', }, }, className: 'text-destructive', @@ -563,6 +574,7 @@ export const SysUserDetailPage: Page = { 'zh-CN': '此操作不可逆,账号一经删除将无法恢复。', 'ja-JP': 'この操作は取り消せません。削除されたアカウントは復元できません。', 'es-ES': 'Permanente. Una vez eliminada, tu cuenta no se puede recuperar.', + 'zh-Hant': '此動作無法復原,帳號一經刪除即無法恢復。', }, }, }, @@ -581,7 +593,7 @@ export const SysUserDetailPage: Page = { // user_id FK; the sys_api_key object's own list-item actions // (revoke / restore) handle row operations. { - label: { en: 'API Keys', 'zh-CN': 'API 密钥', 'ja-JP': 'API キー', 'es-ES': 'Claves de API' }, + label: { en: 'API Keys', 'zh-CN': 'API 密钥', 'ja-JP': 'API キー', 'es-ES': 'Claves de API', 'zh-Hant': 'API 金鑰' }, icon: 'key-round', children: [ { @@ -593,7 +605,7 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'created_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'API Keys', 'zh-CN': 'API 密钥', 'ja-JP': 'API キー', 'es-ES': 'Claves de API' }, + title: { en: 'API Keys', 'zh-CN': 'API 密钥', 'ja-JP': 'API キー', 'es-ES': 'Claves de API', 'zh-Hant': 'API 金鑰' }, }, }, ], @@ -612,7 +624,7 @@ export const SysUserDetailPage: Page = { // object's `managedBy: 'system-data'` and the caller's grant, the // same verdict the grid's buttons read. { - label: { en: 'Preferences', 'zh-CN': '偏好设置', 'ja-JP': '設定', 'es-ES': 'Preferencias' }, + label: { en: 'Preferences', 'zh-CN': '偏好设置', 'ja-JP': '設定', 'es-ES': 'Preferencias', 'zh-Hant': '偏好設定' }, icon: 'sliders', children: [ { @@ -624,7 +636,7 @@ export const SysUserDetailPage: Page = { sort: [{ field: 'updated_at', order: 'desc' }], limit: 25, showViewAll: true, - title: { en: 'Preferences', 'zh-CN': '偏好设置', 'ja-JP': '設定', 'es-ES': 'Preferencias' }, + title: { en: 'Preferences', 'zh-CN': '偏好设置', 'ja-JP': '設定', 'es-ES': 'Preferencias', 'zh-Hant': '偏好設定' }, }, }, ], diff --git a/packages/platform-objects/src/plugin.translations.test.ts b/packages/platform-objects/src/plugin.translations.test.ts index df08f165bed..96a23daea80 100644 --- a/packages/platform-objects/src/plugin.translations.test.ts +++ b/packages/platform-objects/src/plugin.translations.test.ts @@ -191,8 +191,8 @@ describe('PlatformObjectsPlugin: a host translation bundle wins over the platfor await boot(ctx); - // Two bundles (Setup, MetadataForms) times the four locales each ships. - expect(loads.sort()).toEqual(['en', 'en', 'es-ES', 'es-ES', 'ja-JP', 'ja-JP', 'zh-CN', 'zh-CN']); + // Two bundles (Setup, MetadataForms) times the five locales each ships. + expect(loads.sort()).toEqual(['en', 'en', 'es-ES', 'es-ES', 'ja-JP', 'ja-JP', 'zh-CN', 'zh-CN', 'zh-Hant', 'zh-Hant']); }); it('fail-safe: a provider with no getTranslations still gets the platform bundles, and says so', async () => { diff --git a/packages/plugins/plugin-approvals/src/translations/approval-status-vocabulary.test.ts b/packages/plugins/plugin-approvals/src/translations/approval-status-vocabulary.test.ts index e7f2fc62a37..f9860bf3f3e 100644 --- a/packages/plugins/plugin-approvals/src/translations/approval-status-vocabulary.test.ts +++ b/packages/plugins/plugin-approvals/src/translations/approval-status-vocabulary.test.ts @@ -41,7 +41,7 @@ import { describe, it, expect } from 'vitest'; import { APPROVAL_STATUSES, APPROVAL_STATUS_LABELS } from '@objectstack/spec/contracts'; import { translateObject, resolveViewLabel } from '@objectstack/spec/system'; import type { TranslationData } from '@objectstack/spec/system'; -import { zhCN, jaJP, esES } from '@objectstack/platform-objects/apps'; +import { zhCN, jaJP, esES, zhHant } from '@objectstack/platform-objects/apps'; import { ApprovalsTranslations } from './index.js'; import { SysApprovalRequest } from '../sys-approval-request.object.js'; @@ -140,6 +140,7 @@ describe('approval status vocabulary (#7232)', () => { ['zh-CN', zhCN], ['ja-JP', jaJP], ['es-ES', esES], + ['zh-Hant', zhHant], ] as const) { const navLabel = navApprovalsLabel(nav); expect(navLabel, `platform-objects lost the ${locale} nav_account_approvals label`).toBeTruthy(); diff --git a/packages/plugins/plugin-security/src/people-org-assignment-nav-contribution.test.ts b/packages/plugins/plugin-security/src/people-org-assignment-nav-contribution.test.ts index 5c010488167..495230e8018 100644 --- a/packages/plugins/plugin-security/src/people-org-assignment-nav-contribution.test.ts +++ b/packages/plugins/plugin-security/src/people-org-assignment-nav-contribution.test.ts @@ -179,8 +179,8 @@ describe('SecurityPlugin — People & Organization assignment entries are labell const navOf = (locale: string) => ((SetupAppTranslations as any)[locale]?.apps?.setup?.navigation ?? {}) as Record; const locales = Object.keys(SetupAppTranslations).filter((l) => navOf(l).group_people_org?.label); - // Control: the four Setup locales carry the group, so the loop below judges something. - expect(locales.sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN']); + // Control: every Setup locale carries the group, so the loop below judges something. + expect(locales.sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN', 'zh-Hant']); const en = navOf('en'); expect(en[POSITION_HOLDERS.id]?.label).toBe(POSITION_HOLDERS.label); diff --git a/packages/services/service-settings/src/setup-nav-hub-parity.test.ts b/packages/services/service-settings/src/setup-nav-hub-parity.test.ts index d93ab3d8376..1331014d50c 100644 --- a/packages/services/service-settings/src/setup-nav-hub-parity.test.ts +++ b/packages/services/service-settings/src/setup-nav-hub-parity.test.ts @@ -46,7 +46,7 @@ import { settingsBuiltinTranslations } from './translations/index.js'; /** Where the hub opens a namespace; the sidebar entry for it points here. */ const SETTINGS_PAGE = /^\/apps\/setup\/system\/settings\/([a-z_]+)$/; -const TRANSLATED_LOCALES = ['zh-CN', 'ja-JP', 'es-ES'] as const; +const TRANSLATED_LOCALES = ['zh-CN', 'ja-JP', 'es-ES', 'zh-Hant'] as const; interface NavItem { id?: string; type?: string; label?: string; url?: string; children?: NavItem[] } From d104e2b4247de8c39d84c79ab7bcccd7abd220a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 14:59:10 +0000 Subject: [PATCH 2/2] i18n(platform-objects): zh-Hant carries the AI Assistant group #30935 added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main's #30935 gave the Account app a `grp_account_ai_assistant` group, renamed the Connect page and both of its nav entries to "Connect an AI Assistant", and moved the Setup entry behind System Overview. The zh-Hant bundle was cut before it, so the app-nav parity test found the new group unlabelled in zh-Hant. zh-Hant now labels the group 「AI 助理」 (the wording plugin-security's zh-Hant bundle already uses for the MCP assistant), reads the page and both entries as 「連接 AI 助理」, and records the new source digests, so none of the four leaves falls back to English. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_016WV4cya5ej5GQV86RPbRPK --- .../src/apps/translations/zh-Hant.source-hashes.ts | 7 ++++--- .../platform-objects/src/apps/translations/zh-Hant.ts | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts b/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts index 4e56f7ff3c5..01bccf83d68 100644 --- a/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts +++ b/packages/platform-objects/src/apps/translations/zh-Hant.source-hashes.ts @@ -59,6 +59,7 @@ export const zhHantSourceHashes: Readonly> = { 'apps.account.navigation.grp_account_inbox.label': '34dc7a0b58ada93b', 'apps.account.navigation.grp_account_security.label': '0c463f40cccfdf86', 'apps.account.navigation.grp_account_developer.label': '59668ec5af5afb55', + 'apps.account.navigation.grp_account_ai_assistant.label': 'cff764b13822a259', 'apps.account.navigation.nav_account_profile.label': '2c92f019ece4d044', 'apps.account.navigation.nav_account_notifications.label': 'e0fdb90b1d31078d', 'apps.account.navigation.nav_account_approvals.label': '318a820df22a684b', @@ -67,7 +68,7 @@ export const zhHantSourceHashes: Readonly> = { 'apps.account.navigation.nav_account_sessions.label': '05a759e3a230615e', 'apps.account.navigation.nav_account_api_keys.label': '8cda9851248b0490', 'apps.account.navigation.nav_account_oauth_apps.label': '1b26cce61a4c10eb', - 'apps.account.navigation.nav_connect_agent.label': 'eeb174613510e87d', + 'apps.account.navigation.nav_connect_agent.label': 'a4542422fb5e8a68', 'apps.setup.label': 'e4bb0d8bcd47c273', 'apps.setup.description': 'deacdfc679232a4a', 'apps.setup.navigation.group_overview.label': 'eebdbe1cc7b59cdf', @@ -99,7 +100,7 @@ export const zhHantSourceHashes: Readonly> = { 'apps.setup.navigation.nav_assignment_rules.label': '1779fe384b23954f', 'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4', 'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490', - 'apps.setup.navigation.nav_connect_agent.label': 'eeb174613510e87d', + 'apps.setup.navigation.nav_connect_agent.label': 'a4542422fb5e8a68', 'apps.setup.navigation.nav_approvals_inbox.label': '0a9269b20d0266c9', 'apps.setup.navigation.nav_approval_requests.label': 'aafd0f7c6a376f55', 'apps.setup.navigation.nav_approval_actions.label': 'd2ebad7acfd50978', @@ -188,7 +189,7 @@ export const zhHantSourceHashes: Readonly> = { 'datasets.sys_audit_log_metrics.measures.event_count.label': '41e7c95aaa871569', 'pages.cloud_connection_settings.label': '3a7caad0cb98e71d', 'pages.cloud_connection_settings.subtitle': 'f5cdd253e8ffef9f', - 'pages.connect_agent.label': 'eeb174613510e87d', + 'pages.connect_agent.label': 'a4542422fb5e8a68', 'pages.connect_agent.subtitle': 'b0d7c6d174511bcb', 'pages.sys_user_detail.label': '6c0d22a931e235bd', 'pages.sys_organization_detail.label': '3e55836156e1c1de', diff --git a/packages/platform-objects/src/apps/translations/zh-Hant.ts b/packages/platform-objects/src/apps/translations/zh-Hant.ts index d17d15afc7b..3ae3e80b04b 100644 --- a/packages/platform-objects/src/apps/translations/zh-Hant.ts +++ b/packages/platform-objects/src/apps/translations/zh-Hant.ts @@ -16,6 +16,7 @@ export const zhHant: TranslationData = { grp_account_inbox: { label: '收件匣' }, grp_account_security: { label: '安全' }, grp_account_developer: { label: '開發者' }, + grp_account_ai_assistant: { label: 'AI 助理' }, nav_account_profile: { label: '個人資料' }, nav_account_notifications: { label: '通知' }, nav_account_approvals: { label: '待我核准' }, @@ -25,9 +26,9 @@ export const zhHant: TranslationData = { nav_account_api_keys: { label: 'API 金鑰' }, nav_account_oauth_apps: { label: 'OAuth 應用程式' }, // Contributed at runtime by `@objectstack/mcp` into - // `grp_account_developer`; the Setup twin's key does not answer for the + // `grp_account_ai_assistant`; the Setup twin's key does not answer for the // Account app (one `apps..navigation` namespace per app). - nav_connect_agent: { label: '連接 AI 代理' }, + nav_connect_agent: { label: '連接 AI 助理' }, }, }, setup: { @@ -49,6 +50,7 @@ export const zhHant: TranslationData = { group_advanced: { label: '進階' }, nav_system_overview: { label: '系統概覽' }, + nav_connect_agent: { label: '連接 AI 助理' }, nav_users: { label: '使用者' }, nav_organization: { label: '目前組織' }, @@ -70,7 +72,6 @@ export const zhHant: TranslationData = { // `sys_sso_provider.pluralLabel`. nav_sso_providers: { label: 'SSO 提供方' }, nav_api_keys: { label: 'API 金鑰' }, - nav_connect_agent: { label: '連接 AI 代理' }, // 核准中心 = the Approvals Inbox component (#7234) — the surface with // decision actions. The three below are the engine's raw tables; the @@ -197,7 +198,7 @@ export const zhHant: TranslationData = { subtitle: '將此執行階段連接到 ObjectStack 控制平面,即可瀏覽並安裝組織的私有套件。', }, connect_agent: { - label: '連接 AI 代理', + label: '連接 AI 助理', subtitle: '讓任何支援 MCP 的 AI 用戶端以受控方式存取本系統——每次呼叫都在呼叫者自身的權限與列級安全範圍內執行。', },