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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
'@objectstack/spec': minor
'@objectstack/service-settings': minor
---

**BREAKING for per-app translation bundles** — the translation bundle type splits in two: `settings` is a PLATFORM group and a per-app bundle may no longer declare it (#15178)

Clause-②: yes

`TranslationDataSchema` served two different bundles at once — the per-app one an
application authors (`stack.translations`, `defineTranslationBundle`) and the
code-authored bundles the platform packages ship. It now names the **per-app**
bundle entry and declares ten groups; the new `PlatformTranslationDataSchema` /
`PlatformTranslationBundleSchema` (types `PlatformTranslationData` /
`PlatformTranslationBundle`) carry the eleven-group platform face, `settings`
included.

### Migration — FROM → TO

| You wrote | Write instead |
| --- | --- |
| `defineTranslationBundle({ 'zh-CN': { settings: { mail: { title: '邮件投递' } } } })` | delete the `settings` group — there is no per-app replacement key |
| `defineStack({ translations: [{ 'zh-CN': { settings: … } }] })` | delete the `settings` group from the bundle entry |
| `const b: TranslationBundle = { en: { settings: … } }` — a PLATFORM package's own bundle | `const b: PlatformTranslationBundle = { en: { settings: … } }` |
| `const d: TranslationData = { settings: … }` — a PLATFORM package's own locale entry | `const d: PlatformTranslationData = { settings: … }` |

**The one-line fix for an application: delete the `settings` group.** Settings copy
is not application-authorable at all — `settings` is keyed by
`SettingsManifest.namespace` and only platform code declares a manifest, so the
only namespaces a per-app entry could ever address were the platform's own.
`settingsCommon` is **not** affected — the Settings UI shell strings (the source
badges, under `settingsCommon.sourceLabels`) stay on the per-app face; only the
per-namespace manifest copy under `settings` leaves.
Run `os migrate meta --from 17` to list the mechanical edits for existing
sources; apply them by hand.

### What the deletion changes, which is not nothing

⚠️ This is **not** a lossless delete, and the record says so rather than claiming
the house phrase. Both bundles load into ONE served tree — `AppPlugin`'s
`loadTranslations` and every platform plugin's `kernel:ready` contribution both
call `II18nService.loadTranslations`, which deep-merges — and the
`resolveSettings*` family and the console's settings labels read that merged
tree. So an app-authored `settings` branch did resolve.

**It was a gap filler, not an override.** The app's bundles are loaded in
`AppPlugin`'s own `start()` (kernel Phase 2); the platform's settings
translations arrive from `SettingsServicePlugin`'s `kernel:ready` hook (Phase
3); `deepMerge` gives the **later** source the leaf. So the platform won every
key both bundles defined, and a per-app entry rendered **only where the platform
bundle carried no string for that key and locale** — the platform ships `en`,
`zh-CN`, `ja-JP` and `es-ES`.

**What to expect after upgrading.** Where the platform already carried the
string, nothing changes on screen — that value was the one being served all
along. Where your entry was filling a gap, that Settings screen now renders the
**manifest's own literal, which is English** (the `?? fallback` every
`resolveSettings*` helper ends in). Those are the screens to re-read. If a
platform string is wrong or missing for your locale, correct it in the platform
bundle (`@objectstack/service-settings`'s `settingsBuiltinTranslations`) — do
not re-add the app-side copy, which the platform overwrites on every boot
wherever it has its own value.

No deprecation window: the per-app door refuses the key by name from this major,
and the rejection carries the prescription above.

### Unchanged

The registered `translation` metadata type (`TranslationItemSchema`) still
declares `settings` — this ruling covers the file-authored bundle. `GET
/api/v1/i18n/translations/:locale` still declares it on its response, because the
served document is the merged tree; `GetTranslationsResponseSchema` is typed
against the platform face for exactly that reason.

Ruling batch #132 item 2 letter ② (2026-09-13) — 「同意」. The card's original
"removal" disposition is struck: `settings` is a live platform key.

<!-- adr-0087: registered translation-per-app-settings-removed -->
26 changes: 18 additions & 8 deletions content/docs/protocol/kernel/i18n-standard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,18 @@ Three rules the shape enforces, all of them closed since #4001:

- **The top level is the declared group set and nothing else** — `objects`,
`apps`, `messages`, `globalActions`, `dashboards`, `datasets`, `pages`,
`flows`, `settings`, `metadataForms`, `settingsCommon`. A group invented
beside them (`"account"`, `"fields"`, `"list"`, `"industries"`) is rejected
**by name** at both authoring doors, with the group to use instead named in
the rejection.
`flows`, `metadataForms`, `settingsCommon`. A group invented beside them
(`"account"`, `"fields"`, `"list"`, `"industries"`) is rejected **by name**
at both authoring doors, with the group to use instead named in the
rejection.
- **`settings` is a PLATFORM group and an application bundle may not carry
it.** It is keyed by `SettingsManifest.namespace`, and only platform code
declares a manifest — so the only namespaces an application could ever
address are the platform's own. Writing it in `stack.translations` (or in
`defineTranslationBundle`) is refused by name with that prescription; the
platform's own bundles author it against `PlatformTranslationData`, and the
served document (`GET /i18n/translations/:locale`) carries it because it is
the merge of every loaded bundle.
- **Field options are a map keyed by the option's stored `value`** — never an
array of `{ value, label }` pairs, and never the display label. See
[Orphan Keys and Option Keys](#orphan-keys-and-option-keys).
Expand Down Expand Up @@ -1046,10 +1054,12 @@ options: { 'Direct Mail': '直邮' } // ❌ keyed by the label — never resolv
options: { 'direct-mail': '直邮' } // ❌ a variant spelling of the value
```

`messages`, `settings`, `settingsCommon` and
`metadataForms` are **not** checked: their keys are owned by application code,
plugins, and the platform's own metadata-type registry rather than by this
stack's metadata, so there is no set of legal names to resolve against.
`messages`, `settingsCommon` and `metadataForms` are **not** checked: their
keys are owned by application code, plugins, and the platform's own
metadata-type registry rather than by this stack's metadata, so there is no set
of legal names to resolve against. (`settings` is not checked either, and since
it left the per-app bundle it cannot appear in one at all — the parse refuses it
before any lint runs.)

### Translation Service Integration

Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/api/protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1615,9 +1615,9 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
| **datasets** | `Record<string, { label?: string; description?: string; dimensions?: Record<string, object>; measures?: Record<string, object> }>` | optional | Analytics dataset translations keyed by dataset name |
| **pages** | `Record<string, { label?: string; description?: string; title?: string; subtitle?: string; … }>` | optional | Page translations keyed by page name |
| **flows** | `Record<string, { label?: string; screens?: Record<string, object> }>` | optional | Screen-flow translations keyed by flow name |
| **settings** | `Record<string, { title?: string; description?: string; groups?: Record<string, object>; keys?: Record<string, object>; … }>` | optional | Settings manifest translations keyed by namespace |
| **metadataForms** | `Record<string, { label?: string; description?: string; sections?: Record<string, object>; fields?: Record<string, object> }>` | optional | Translations for metadata-type configuration forms keyed by metadata type |
| **settingsCommon** | `{ sourceLabels?: object }` | optional | Cross-namespace Settings UI strings |
| **settings** | `Record<string, { title?: string; description?: string; groups?: Record<string, object>; keys?: Record<string, object>; … }>` | optional | Settings manifest translations keyed by namespace |


---
Expand Down
10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Protocol Reference
description: Every schema published by @objectstack/spec — 1535 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1537 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand Down Expand Up @@ -31,9 +31,9 @@ counts are sums of the rows they head. Regenerate with
| [Security Protocol](/docs/references/security) | 5 | 30 | Permission sets, row-level security, sharing rules, tenancy posture. |
| [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 34 | 273 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [System Protocol](/docs/references/system) | 34 | 275 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 158 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **195** | **1535** | 14 protocol modules |
| **Total** | **195** | **1537** | 14 protocol modules |

---

Expand Down Expand Up @@ -316,7 +316,7 @@ Studio designer metadata — the authoring surfaces for the protocols above.

## System Protocol

**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **34 pages, 273 schemas**
**Source:** `packages/spec/src/system/` · **Import:** `@objectstack/spec/system` · **34 pages, 275 schemas**

The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance.

Expand Down Expand Up @@ -354,7 +354,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
| [`supplier-security.zod.ts`](/docs/references/system/supplier-security) | `SupplierAssessmentStatus`, `SupplierRiskLevel`, `SupplierSecurityAssessment`, `SupplierSecurityPolicy`, `SupplierSecurityRequirement` |
| [`tenant.zod.ts`](/docs/references/system/tenant) | `DatabaseLevelIsolationStrategy`, `DatabaseProvider`, `QuotaEnforcementResult`, `RowLevelIsolationStrategy`, `SchemaLevelIsolationStrategy`, `Tenant`, `TenantConnectionConfig`, `TenantIsolationConfig`, `TenantIsolationLevel`, `TenantQuota`, `TenantSecurityPolicy`, `TenantUsage` |
| [`tracing.zod.ts`](/docs/references/system/tracing) | `OpenTelemetryCompatibility`, `OtelExporterType`, `SamplingDecision`, `SamplingStrategyType`, `Span`, `SpanAttributeValue`, `SpanAttributes`, `SpanEvent`, `SpanKind`, `SpanLink`, `SpanStatus`, `TraceContext`, `TraceContextPropagation`, `TraceFlags`, `TracePropagationFormat`, `TraceSamplingConfig`, `TraceState`, `TracingConfig` |
| [`translation.zod.ts`](/docs/references/system/translation) | `ActionResultDialogTranslation`, `CoverageBreakdownEntry`, `FieldTranslation`, `Locale`, `ObjectTranslationData`, `TranslationBundle`, `TranslationConfig`, `TranslationCoverageResult`, `TranslationData`, `TranslationDiffItem`, `TranslationDiffStatus`, `TranslationItem` |
| [`translation.zod.ts`](/docs/references/system/translation) | `ActionResultDialogTranslation`, `CoverageBreakdownEntry`, `FieldTranslation`, `Locale`, `ObjectTranslationData`, `PlatformTranslationBundle`, `PlatformTranslationData`, `TranslationBundle`, `TranslationConfig`, `TranslationCoverageResult`, `TranslationData`, `TranslationDiffItem`, `TranslationDiffStatus`, `TranslationItem` |
| [`worker.zod.ts`](/docs/references/system/worker) | `BatchProgress`, `QueueConfig`, `Task`, `TaskExecutionResult`, `TaskPriority`, `TaskRetryPolicy`, `TaskStatus`, `WorkerStats` |

---
Expand Down
Loading
Loading