|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +'@objectstack/service-settings': minor |
| 4 | +--- |
| 5 | + |
| 6 | +**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) |
| 7 | + |
| 8 | +Clause-②: yes |
| 9 | + |
| 10 | +`TranslationDataSchema` served two different bundles at once — the per-app one an |
| 11 | +application authors (`stack.translations`, `defineTranslationBundle`) and the |
| 12 | +code-authored bundles the platform packages ship. It now names the **per-app** |
| 13 | +bundle entry and declares ten groups; the new `PlatformTranslationDataSchema` / |
| 14 | +`PlatformTranslationBundleSchema` (types `PlatformTranslationData` / |
| 15 | +`PlatformTranslationBundle`) carry the eleven-group platform face, `settings` |
| 16 | +included. |
| 17 | + |
| 18 | +### Migration — FROM → TO |
| 19 | + |
| 20 | +| You wrote | Write instead | |
| 21 | +| --- | --- | |
| 22 | +| `defineTranslationBundle({ 'zh-CN': { settings: { mail: { title: '邮件投递' } } } })` | delete the `settings` group — there is no per-app replacement key | |
| 23 | +| `defineStack({ translations: [{ 'zh-CN': { settings: … } }] })` | delete the `settings` group from the bundle entry | |
| 24 | +| `const b: TranslationBundle = { en: { settings: … } }` — a PLATFORM package's own bundle | `const b: PlatformTranslationBundle = { en: { settings: … } }` | |
| 25 | +| `const d: TranslationData = { settings: … }` — a PLATFORM package's own locale entry | `const d: PlatformTranslationData = { settings: … }` | |
| 26 | + |
| 27 | +**The one-line fix for an application: delete the `settings` group.** Settings copy |
| 28 | +is not application-authorable at all — `settings` is keyed by |
| 29 | +`SettingsManifest.namespace` and only platform code declares a manifest, so the |
| 30 | +only namespaces a per-app entry could ever address were the platform's own. |
| 31 | +`settingsCommon` is **not** affected — the Settings UI shell strings (the source |
| 32 | +badges, under `settingsCommon.sourceLabels`) stay on the per-app face; only the |
| 33 | +per-namespace manifest copy under `settings` leaves. |
| 34 | +Run `os migrate meta --from 17` to list the mechanical edits for existing |
| 35 | +sources; apply them by hand. |
| 36 | + |
| 37 | +### What the deletion changes, which is not nothing |
| 38 | + |
| 39 | +⚠️ This is **not** a lossless delete, and the record says so rather than claiming |
| 40 | +the house phrase. Both bundles load into ONE served tree — `AppPlugin`'s |
| 41 | +`loadTranslations` and every platform plugin's `kernel:ready` contribution both |
| 42 | +call `II18nService.loadTranslations`, which deep-merges — and the |
| 43 | +`resolveSettings*` family and the console's settings labels read that merged |
| 44 | +tree. So an app-authored `settings` branch did resolve. |
| 45 | + |
| 46 | +**It was a gap filler, not an override.** The app's bundles are loaded in |
| 47 | +`AppPlugin`'s own `start()` (kernel Phase 2); the platform's settings |
| 48 | +translations arrive from `SettingsServicePlugin`'s `kernel:ready` hook (Phase |
| 49 | +3); `deepMerge` gives the **later** source the leaf. So the platform won every |
| 50 | +key both bundles defined, and a per-app entry rendered **only where the platform |
| 51 | +bundle carried no string for that key and locale** — the platform ships `en`, |
| 52 | +`zh-CN`, `ja-JP` and `es-ES`. |
| 53 | + |
| 54 | +**What to expect after upgrading.** Where the platform already carried the |
| 55 | +string, nothing changes on screen — that value was the one being served all |
| 56 | +along. Where your entry was filling a gap, that Settings screen now renders the |
| 57 | +**manifest's own literal, which is English** (the `?? fallback` every |
| 58 | +`resolveSettings*` helper ends in). Those are the screens to re-read. If a |
| 59 | +platform string is wrong or missing for your locale, correct it in the platform |
| 60 | +bundle (`@objectstack/service-settings`'s `settingsBuiltinTranslations`) — do |
| 61 | +not re-add the app-side copy, which the platform overwrites on every boot |
| 62 | +wherever it has its own value. |
| 63 | + |
| 64 | +No deprecation window: the per-app door refuses the key by name from this major, |
| 65 | +and the rejection carries the prescription above. |
| 66 | + |
| 67 | +### Unchanged |
| 68 | + |
| 69 | +The registered `translation` metadata type (`TranslationItemSchema`) still |
| 70 | +declares `settings` — this ruling covers the file-authored bundle. `GET |
| 71 | +/api/v1/i18n/translations/:locale` still declares it on its response, because the |
| 72 | +served document is the merged tree; `GetTranslationsResponseSchema` is typed |
| 73 | +against the platform face for exactly that reason. |
| 74 | + |
| 75 | +Ruling batch #132 item 2 letter ② (2026-09-13) — 「同意」. The card's original |
| 76 | +"removal" disposition is struck: `settings` is a live platform key. |
| 77 | + |
| 78 | +<!-- adr-0087: registered translation-per-app-settings-removed --> |
0 commit comments