diff --git a/command-center-ai/CHANGELOG.md b/command-center-ai/CHANGELOG.md index 413c52fe..da32bbbf 100644 --- a/command-center-ai/CHANGELOG.md +++ b/command-center-ai/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +Compatibility axes: remove the unreleased `ui/design-agent-icons-and-motion` skill and its +local guide and routing from this package. The skill and worked MetaTables example belong to +`ms-tau-sdk`. No npm public API, stylesheet class, browser storage key or value, installer +behavior, SDK peer range, or platform route or payload changes. No backend rollout or storage +migration is needed; existing agent icons and rendering defaults are unchanged. + ## 0.0.17 Compatibility axes: additive npm API (`normalizeCommandCenterAgentIcon`, `primeAgentIcon`, the diff --git a/command-center-ai/tests/cli/agent-skills.node.mjs b/command-center-ai/tests/cli/agent-skills.node.mjs index debe2e7a..e6177458 100644 --- a/command-center-ai/tests/cli/agent-skills.node.mjs +++ b/command-center-ai/tests/cli/agent-skills.node.mjs @@ -283,7 +283,7 @@ test("the command installs into --path, -p, and --path=, previews with --dry-run const args = form[0] === "--path=" ? [`--path=${project}`] : [form[0], project]; const install = spawnSync(process.execPath, [cliPath, "skills", "install", ...args], { encoding: "utf8" }); assert.equal(install.status, 0, install.stderr); - assert.match(install.stdout, /Installed 8 Command Center AI skill\(s\)/u); + assert.ok(install.stdout.includes(`Installed ${expectedSkills.length} Command Center AI skill(s)`)); assert.match(install.stdout, /Provenance: .*PINNED_FROM\.txt/u); } @@ -336,7 +336,7 @@ test("concurrent postinstalls into one repository both succeed and leave one int ); for (const run of runs) { assert.equal(run.status, 0, run.stderr); - assert.match(run.stdout, /Installed 8 agent skill\(s\)/u); + assert.ok(run.stdout.includes(`Installed ${expectedSkills.length} agent skill(s)`)); } assert.deepEqual(await readdir(join(application, ".agents", "skills")), ["command-center-ai"]); diff --git a/command-center-sdk/CHANGELOG.md b/command-center-sdk/CHANGELOG.md index 5afbb5c1..56c844a9 100644 --- a/command-center-sdk/CHANGELOG.md +++ b/command-center-sdk/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.5.19 + +- Add optional validated locale to v1 iframe initialization and live context. Older + hosts default to English; locale updates preserve authorization and child state. +- Add public `/localization` and `/localization/react` message adapters, English + defaults and live localization of resource controls and accessibility messages. +- Keep existing schema IDs, protocol versions and component overrides. External + strict validators receive the legacy payload unless their child advertises + `locale-context-v1`; locale-aware validators adopt the updated schema. No SDK-owned + language persistence or backend routes are added. + ## 0.5.18 Compatibility axes: additive optional `titleLead` prop on `EntitySummaryProps` and the stylesheet diff --git a/command-center-sdk/README.md b/command-center-sdk/README.md index 8db24b1d..630e572c 100644 --- a/command-center-sdk/README.md +++ b/command-center-sdk/README.md @@ -306,3 +306,9 @@ example release workflow, and the installed `general/maintain-command-center-cod - Major modules require a nearest README. - Persisted contract, theme-ID, or protocol changes require an explicit compatibility and backend/storage assessment. + +## Localization + +SDK 0.5.19 adds `/localization` and `/localization/react` for application-owned +translation adapters and English defaults across shared resource controls, plus +initial/live iframe locale. See [the localization guide](./docs/localization.md). diff --git a/command-center-sdk/agent_scaffold/skills/controls/compose-command-center-controls/SKILL.md b/command-center-sdk/agent_scaffold/skills/controls/compose-command-center-controls/SKILL.md index 7f19e614..c6fb1e7e 100644 --- a/command-center-sdk/agent_scaffold/skills/controls/compose-command-center-controls/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/controls/compose-command-center-controls/SKILL.md @@ -139,3 +139,13 @@ disabled field, one dark and one light preset, 375×812 with a coarse pointer, a - Do not attach handlers to a `Badge`. - Do not style an anchor with button classes; links keep native semantics. - Do not override SDK control variables or geometry and still claim conformance. + +## Localize SDK messages (0.5.19+) + +Wrap the application in `CommandCenterLocalizationProvider` from the public +`@dev-mainsequence/command-center-sdk/localization/react` entrypoint. Supply the +effective `locale` and a `TranslateSdkMessage` callback from `/localization`, backed +by the application catalog. Return undefined for missing keys to keep English +defaults; forward interpolation values and numeric `count` for whole-message plurals. +Explicit labels/renderers override the provider and remain application-owned. +The SDK creates no i18next instance or preference storage. See `docs/localization.md`. diff --git a/command-center-sdk/agent_scaffold/skills/embed/integrate-static-site-iframe/SKILL.md b/command-center-sdk/agent_scaffold/skills/embed/integrate-static-site-iframe/SKILL.md index 505d320c..46d3c00f 100644 --- a/command-center-sdk/agent_scaffold/skills/embed/integrate-static-site-iframe/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/embed/integrate-static-site-iframe/SKILL.md @@ -314,3 +314,18 @@ For platform requests, test the host's allow-list (`not_allowed` for other paths JSON and binary responses, cancellation reaching the host's fetch, a user change, an older host's `unsupported`, and that the platform receives only the host's origin and credential while no platform credential reaches the child. + +## Follow locale updates (SDK 0.5.19+) + +Apply every `context.locale` update to the child translation instance. Use +`resolveCommandCenterLocale` from `/localization` for exact/base/English fallback +and update document language/direction. Hosts pass `locale` to `StaticSiteIframe`; +locale-only updates must preserve iframe, launch and authorization state. Use +`CommandCenterLocalizationProvider` from `/localization/react` to translate SDK +controls through the app's catalog; return undefined for missing messages to keep +English defaults. Read `docs/localization.md` for typed keys, interpolation/plurals, +and explicit component overrides. No SDK-owned persistence or i18next singleton. + +Locale is negotiated by the SDK's ready capability `locale-context-v1`. Keep the +SDK-generated readiness message; raw legacy ready messages deliberately receive +legacy context without locale, preserving old strict schemas. diff --git a/command-center-sdk/agent_scaffold/skills/feedback/build-application-loading-flow/SKILL.md b/command-center-sdk/agent_scaffold/skills/feedback/build-application-loading-flow/SKILL.md index c4d99504..7c78e9d2 100644 --- a/command-center-sdk/agent_scaffold/skills/feedback/build-application-loading-flow/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/feedback/build-application-loading-flow/SKILL.md @@ -128,3 +128,13 @@ npx command-center-sdk theme audit --path src The React progress model is not a serialized SDK contract. Do not add backend fields, schemas, or fixtures unless a separate contract-evolution task establishes a language-neutral wire format. + +## Localize SDK messages (0.5.19+) + +Wrap the application in `CommandCenterLocalizationProvider` from the public +`@dev-mainsequence/command-center-sdk/localization/react` entrypoint. Supply the +effective `locale` and a `TranslateSdkMessage` callback from `/localization`, backed +by the application catalog. Return undefined for missing keys to keep English +defaults; forward interpolation values and numeric `count` for whole-message plurals. +Explicit labels/renderers override the provider and remain application-owned. +The SDK creates no i18next instance or preference storage. See `docs/localization.md`. diff --git a/command-center-sdk/agent_scaffold/skills/general/use-command-center-sdk/SKILL.md b/command-center-sdk/agent_scaffold/skills/general/use-command-center-sdk/SKILL.md index cf358ae1..09469f71 100644 --- a/command-center-sdk/agent_scaffold/skills/general/use-command-center-sdk/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/general/use-command-center-sdk/SKILL.md @@ -177,3 +177,13 @@ rerun `command-center-sdk skills sync --path . --json` so authentication, catalo filesystem ownership errors remain explicit. For themed consumers, run `command-center-sdk theme audit` and treat unknown tokens, literal fallbacks, and hardcoded semantic visual values as failed verification. + +## Localize SDK-owned messages + +SDK 0.5.19 adds `/localization` (framework-neutral message keys/defaults and locale +helpers) and `/localization/react` (`CommandCenterLocalizationProvider` and +`useSdkTranslation`). Supply an application-owned translator and effective locale. +Forward numeric `count` for plurals; translate complete messages and accessibility +labels. Explicit component overrides win; absent translations fall back to English. +For embeds, apply every validated `onContext` locale without remounting the iframe +or providers. See `docs/localization.md`; persistence remains host/backend-owned. diff --git a/command-center-sdk/agent_scaffold/skills/navigation/compose-command-center-application-shell/SKILL.md b/command-center-sdk/agent_scaffold/skills/navigation/compose-command-center-application-shell/SKILL.md index aab4a022..aab3177e 100644 --- a/command-center-sdk/agent_scaffold/skills/navigation/compose-command-center-application-shell/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/navigation/compose-command-center-application-shell/SKILL.md @@ -96,3 +96,13 @@ existing implementation. Read [the application-shell standard](references/application-shell-standard.md) when migrating an existing application or resolving ambiguous information architecture. Keep API clients, router, permissions, readiness policy, and persistence in the consuming application. + +## Localize SDK messages (0.5.19+) + +Wrap the application in `CommandCenterLocalizationProvider` from the public +`@dev-mainsequence/command-center-sdk/localization/react` entrypoint. Supply the +effective `locale` and a `TranslateSdkMessage` callback from `/localization`, backed +by the application catalog. Return undefined for missing keys to keep English +defaults; forward interpolation values and numeric `count` for whole-message plurals. +Explicit labels/renderers override the provider and remain application-owned. +The SDK creates no i18next instance or preference storage. See `docs/localization.md`. diff --git a/command-center-sdk/agent_scaffold/skills/theme/theme-command-center-app/SKILL.md b/command-center-sdk/agent_scaffold/skills/theme/theme-command-center-app/SKILL.md index 523805a9..77451085 100644 --- a/command-center-sdk/agent_scaffold/skills/theme/theme-command-center-app/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/theme/theme-command-center-app/SKILL.md @@ -75,3 +75,13 @@ statuses, charts, contrast, and nested-surface hierarchy. Do not treat the theme audit as proof of page spacing or responsive geometry. When the application uses `/layout`, run its public browser verifier through `$compose-command-center-page` as a separate required check. + +## Localize SDK-owned messages + +SDK 0.5.19 adds `/localization` (framework-neutral message keys/defaults and locale +helpers) and `/localization/react` (`CommandCenterLocalizationProvider` and +`useSdkTranslation`). Supply an application-owned translator and effective locale. +Forward numeric `count` for plurals; translate complete messages and accessibility +labels. Explicit component overrides win; absent translations fall back to English. +For embeds, apply every validated `onContext` locale without remounting the iframe +or providers. See `docs/localization.md`; persistence remains host/backend-owned. diff --git a/command-center-sdk/agent_scaffold/skills/views/add-resource-actions/SKILL.md b/command-center-sdk/agent_scaffold/skills/views/add-resource-actions/SKILL.md index f6a24038..143390dd 100644 --- a/command-center-sdk/agent_scaffold/skills/views/add-resource-actions/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/views/add-resource-actions/SKILL.md @@ -65,3 +65,13 @@ outside this frontend view skill. Test list action placement, disabled and pending state, detail action placement, every bulk selection mode, defaults, confirmation text, allowed and blocked preflight, stale-request cancellation, rediscovery, retry, transport failure, successful refresh, and selection cleanup. + +## Localize SDK-owned messages + +SDK 0.5.19 adds `/localization` (framework-neutral message keys/defaults and locale +helpers) and `/localization/react` (`CommandCenterLocalizationProvider` and +`useSdkTranslation`). Supply an application-owned translator and effective locale. +Forward numeric `count` for plurals; translate complete messages and accessibility +labels. Explicit component overrides win; absent translations fall back to English. +For embeds, apply every validated `onContext` locale without remounting the iframe +or providers. See `docs/localization.md`; persistence remains host/backend-owned. diff --git a/command-center-sdk/agent_scaffold/skills/views/build-resource-detail/SKILL.md b/command-center-sdk/agent_scaffold/skills/views/build-resource-detail/SKILL.md index de510567..df2233ff 100644 --- a/command-center-sdk/agent_scaffold/skills/views/build-resource-detail/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/views/build-resource-detail/SKILL.md @@ -140,3 +140,13 @@ Enter selects, that each strip is one Tab stop, that a disabled tab is focusable that a deep link to a hidden tab shows the first enabled tab without changing the URL, and that a tab picked from More receives focus. Find tabs by their tab role or `data-cc-tab-id`. Confirm unknown domain tab content does not take ownership of surrounding SDK chrome. + +## Localize SDK messages (0.5.19+) + +Wrap the application in `CommandCenterLocalizationProvider` from the public +`@dev-mainsequence/command-center-sdk/localization/react` entrypoint. Supply the +effective `locale` and a `TranslateSdkMessage` callback from `/localization`, backed +by the application catalog. Return undefined for missing keys to keep English +defaults; forward interpolation values and numeric `count` for whole-message plurals. +Explicit labels/renderers override the provider and remain application-owned. +The SDK creates no i18next instance or preference storage. See `docs/localization.md`. diff --git a/command-center-sdk/agent_scaffold/skills/views/build-resource-list/SKILL.md b/command-center-sdk/agent_scaffold/skills/views/build-resource-list/SKILL.md index d0e0a127..4d894157 100644 --- a/command-center-sdk/agent_scaffold/skills/views/build-resource-list/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/views/build-resource-list/SKILL.md @@ -80,3 +80,13 @@ refresh. Verify backend column order preserves matching local renderers, generic safe `value_path` plus `data_type`, stale discovery is cancelled, and pagination alone does not refetch discovery. Confirm the screen is a thin resource definition and controller around `ResourceListPage`. + +## Localize SDK messages (0.5.19+) + +Wrap the application in `CommandCenterLocalizationProvider` from the public +`@dev-mainsequence/command-center-sdk/localization/react` entrypoint. Supply the +effective `locale` and a `TranslateSdkMessage` callback from `/localization`, backed +by the application catalog. Return undefined for missing keys to keep English +defaults; forward interpolation values and numeric `count` for whole-message plurals. +Explicit labels/renderers override the provider and remain application-owned. +The SDK creates no i18next instance or preference storage. See `docs/localization.md`. diff --git a/command-center-sdk/agent_scaffold/skills/views/build-resource-picker/SKILL.md b/command-center-sdk/agent_scaffold/skills/views/build-resource-picker/SKILL.md index 64145a43..1805905f 100644 --- a/command-center-sdk/agent_scaffold/skills/views/build-resource-picker/SKILL.md +++ b/command-center-sdk/agent_scaffold/skills/views/build-resource-picker/SKILL.md @@ -94,3 +94,13 @@ Test keyboard navigation, focus return, search, loading, empty options, disabled single/multiple updates, action invocation, portal placement, and narrow container behavior. For a transfer list, test marking and moving by keyboard and pointer, "all shown" with a search, locked choices, the `change` your application saves, and the stacked form at 375×812. + +## Localize SDK messages (0.5.19+) + +Wrap the application in `CommandCenterLocalizationProvider` from the public +`@dev-mainsequence/command-center-sdk/localization/react` entrypoint. Supply the +effective `locale` and a `TranslateSdkMessage` callback from `/localization`, backed +by the application catalog. Return undefined for missing keys to keep English +defaults; forward interpolation values and numeric `count` for whole-message plurals. +Explicit labels/renderers override the provider and remain application-owned. +The SDK creates no i18next instance or preference storage. See `docs/localization.md`. diff --git a/command-center-sdk/contracts/README.md b/command-center-sdk/contracts/README.md index ae113349..994c24a4 100644 --- a/command-center-sdk/contracts/README.md +++ b/command-center-sdk/contracts/README.md @@ -24,3 +24,13 @@ bounds characters, and enforce correlation, cancellation, in-flight limits, and Schema filenames and contract IDs are immutable after release. Additive changes require fixtures; breaking semantics require a new versioned schema, a migration plan, mixed-version tests, and a coordinated frontend/backend rollout and rollback. + +The iframe v1 initialize payload accepts optional `locale` (SDK 0.5.19). The +`initialize-locale` and `invalid-locale` fixtures cover the additive field; omitted +locale retains old-host compatibility and normalizes to English in the client. + +Locale negotiation preserves strict legacy validators: new clients advertise +`ready.payload.capabilities: ["locale-context-v1"]`. The host includes locale only +for that capability. Missing/unknown capabilities retain the frozen legacy payload; +malformed declarations are rejected. The normalized new-client context still +defaults to English when an old host omits locale. See SDK ADR 019. diff --git a/command-center-sdk/contracts/fixtures/invalid/static-site-iframe-v1.invalid-locale.json b/command-center-sdk/contracts/fixtures/invalid/static-site-iframe-v1.invalid-locale.json new file mode 100644 index 00000000..527edbee --- /dev/null +++ b/command-center-sdk/contracts/fixtures/invalid/static-site-iframe-v1.invalid-locale.json @@ -0,0 +1,15 @@ +{ + "channel": "mainsequence.portfolio-dashboard", + "version": 1, + "type": "initialize", + "payload": { + "theme": "dark", + "themeId": "main-sequence-space", + "user": { + "id": "user-public-uid", + "uid": "user-public-uid", + "user_uid": "user-public-uid" + }, + "locale": "de_AT" + } +} diff --git a/command-center-sdk/contracts/fixtures/invalid/static-site-iframe-v1.malformed-capabilities.json b/command-center-sdk/contracts/fixtures/invalid/static-site-iframe-v1.malformed-capabilities.json new file mode 100644 index 00000000..d4ef36ec --- /dev/null +++ b/command-center-sdk/contracts/fixtures/invalid/static-site-iframe-v1.malformed-capabilities.json @@ -0,0 +1,8 @@ +{ + "channel": "mainsequence.portfolio-dashboard", + "version": 1, + "type": "ready", + "payload": { + "capabilities": "locale-context-v1" + } +} diff --git a/command-center-sdk/contracts/fixtures/valid/static-site-iframe-v1.initialize-locale.json b/command-center-sdk/contracts/fixtures/valid/static-site-iframe-v1.initialize-locale.json new file mode 100644 index 00000000..00371624 --- /dev/null +++ b/command-center-sdk/contracts/fixtures/valid/static-site-iframe-v1.initialize-locale.json @@ -0,0 +1,15 @@ +{ + "channel": "mainsequence.portfolio-dashboard", + "version": 1, + "type": "initialize", + "payload": { + "theme": "dark", + "themeId": "main-sequence-space", + "user": { + "id": "user-public-uid", + "uid": "user-public-uid", + "user_uid": "user-public-uid" + }, + "locale": "de-AT" + } +} diff --git a/command-center-sdk/contracts/fixtures/valid/static-site-iframe-v1.ready-locale-capability.json b/command-center-sdk/contracts/fixtures/valid/static-site-iframe-v1.ready-locale-capability.json new file mode 100644 index 00000000..19a4056a --- /dev/null +++ b/command-center-sdk/contracts/fixtures/valid/static-site-iframe-v1.ready-locale-capability.json @@ -0,0 +1,10 @@ +{ + "channel": "mainsequence.portfolio-dashboard", + "version": 1, + "type": "ready", + "payload": { + "capabilities": [ + "locale-context-v1" + ] + } +} diff --git a/command-center-sdk/contracts/manifest.json b/command-center-sdk/contracts/manifest.json index d3232145..d0b9229c 100644 --- a/command-center-sdk/contracts/manifest.json +++ b/command-center-sdk/contracts/manifest.json @@ -40,7 +40,9 @@ "fixtures/valid/static-site-iframe-v1.platform-error-not-allowed.json", "fixtures/valid/static-site-iframe-v1.platform-error-temporarily-unavailable.json", "fixtures/valid/static-site-iframe-v1.platform-error-unsupported.json", - "fixtures/valid/static-site-iframe-v1.platform-cancel.json" + "fixtures/valid/static-site-iframe-v1.platform-cancel.json", + "fixtures/valid/static-site-iframe-v1.initialize-locale.json", + "fixtures/valid/static-site-iframe-v1.ready-locale-capability.json" ], "invalid": [ "fixtures/invalid/static-site-iframe-v1.wrong-version.json", @@ -66,7 +68,9 @@ "fixtures/invalid/static-site-iframe-v1.platform-response-extra-header.json", "fixtures/invalid/static-site-iframe-v1.platform-response-invalid-status.json", "fixtures/invalid/static-site-iframe-v1.platform-response-invalid-base64.json", - "fixtures/invalid/static-site-iframe-v1.platform-error-unknown-code.json" + "fixtures/invalid/static-site-iframe-v1.platform-error-unknown-code.json", + "fixtures/invalid/static-site-iframe-v1.invalid-locale.json", + "fixtures/invalid/static-site-iframe-v1.malformed-capabilities.json" ] } }, diff --git a/command-center-sdk/contracts/schemas/static-site-iframe-v1.schema.json b/command-center-sdk/contracts/schemas/static-site-iframe-v1.schema.json index 778a1f0f..00a0b215 100644 --- a/command-center-sdk/contracts/schemas/static-site-iframe-v1.schema.json +++ b/command-center-sdk/contracts/schemas/static-site-iframe-v1.schema.json @@ -72,7 +72,7 @@ { "properties": { "type": { "const": "ready" }, - "payload": { "type": "object" } + "payload": { "type": "object", "properties": { "capabilities": { "type": "array", "items": { "type": "string", "pattern": "\\S" } } } } } } ], @@ -92,6 +92,7 @@ "properties": { "theme": { "enum": ["dark", "light"] }, "themeId": { "type": "string", "minLength": 1 }, + "locale": { "type": "string", "minLength": 2, "maxLength": 128, "pattern": "^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?$" }, "user": { "oneOf": [ { "type": "null" }, diff --git a/command-center-sdk/docs/README.md b/command-center-sdk/docs/README.md index 58fc8185..70ae4103 100644 --- a/command-center-sdk/docs/README.md +++ b/command-center-sdk/docs/README.md @@ -104,6 +104,7 @@ view. | Resource model | `/resource`, `/resource/react`, `/views` | | Application chrome | `/navigation`, `/navigation/testing`, `/layout`, `/layout/testing`, `/feedback`, `/controls` | | Visual language | `/theme`, `/theme/presets`, `/theme/data-viz`, theme CSS exports | +| Localization | `/localization`, `/localization/react` | | Static-site integration | `/embed`, `/embed/react`, `/vite` | | Language-neutral contracts | `/contracts`, `/contracts/manifest.json`, schemas and fixtures | @@ -152,6 +153,7 @@ specific consuming-application task. | Implement resource collections | [Backend contracts](./backend-contracts.md) | `contracts/implement-resource-collection-contract` | | Implement bulk actions | [Backend contracts](./backend-contracts.md) | `contracts/implement-bulk-actions-contract` | | Theme an application | [Themes](./themes.md) | `theme/theme-command-center-app` | +| Localize SDK controls and follow host language | [Localization](./localization.md) | `embed/integrate-static-site-iframe`, `controls/compose-command-center-controls` | | Integrate a static site locally or as a hosted iframe | [Static-site embeds](./static-site-embeds.md) | `embed/integrate-static-site-iframe` | Refresh packaged guidance after an upgrade or when lifecycle scripts were disabled: @@ -169,3 +171,5 @@ These guides describe exports in the current package unless a section explicitly ADRs explain decisions and may be marked `Proposed`. Do not treat a proposed identifier, message, or code sample as a released capability. For the version actually installed in an application, inspect its `package.json` exports, declarations, README, contract manifest, and changelog. + +- [Localization](./localization.md): translate SDK messages and follow live host locale. diff --git a/command-center-sdk/docs/adr/README.md b/command-center-sdk/docs/adr/README.md index b3589ce8..3097393c 100644 --- a/command-center-sdk/docs/adr/README.md +++ b/command-center-sdk/docs/adr/README.md @@ -27,6 +27,7 @@ owning application repositories; backend implementation decisions are out of sco - [SDK ADR 015: Detail Tabs Own Their Keyboard, Panel, and Overflow](./adr-sdk-015-detail-tabs.md) — Accepted - [SDK ADR 016: A Side-by-Side Transfer List for Choosing Many Items](./adr-sdk-016-transfer-list.md) — Accepted - [SDK ADR 017: One Machine Session for Local Development](./adr-sdk-017-machine-session-for-local-development.md) — Accepted +- [SDK ADR 019: Locale Transport and Localization of SDK Controls](./adr-sdk-019-locale-transport-and-control-localization.md) — Accepted ## Rules diff --git a/command-center-sdk/docs/adr/adr-sdk-019-locale-transport-and-control-localization.md b/command-center-sdk/docs/adr/adr-sdk-019-locale-transport-and-control-localization.md new file mode 100644 index 00000000..27087924 --- /dev/null +++ b/command-center-sdk/docs/adr/adr-sdk-019-locale-transport-and-control-localization.md @@ -0,0 +1,287 @@ +# SDK ADR 019: Locale Transport and Localization of SDK Controls + +- Status: Accepted +- Date: 2026-11-09 +- Accepted: 2026-11-10 +- Implementation: SDK 0.5.19 +- Owners: Command Center SDK maintainers +- Package: `@dev-mainsequence/command-center-sdk` +- Issue: [SDK #25](https://github.com/mainsequence-sdk/command-center-sdk/issues/25) +- Contract: `command-center.static_site_iframe@v1`, with negotiated locale support +- Related: [Static-site embeds](../static-site-embeds.md), + [Backend contracts](../backend-contracts.md), + [Extending and releasing](../extending-and-releasing.md) + +## Context + +An embedded application needs both the host's selected language and a way to translate SDK-owned +controls. Transport alone leaves pagination, selection, actions, empty states, and accessibility +labels in English. Translating controls alone leaves each application guessing the user's choice. + +The released baseline inspected for issue #25 carries theme and public user UID in the iframe +context. Its host already republishes context, and its child invokes `onContext` on later +initializations. Identity changes invalidate authenticated work; presentation changes must not. +The React host already separates context updates from the effect that creates its bridge. + +There is an important compatibility distinction: the baseline JavaScript initialize parser ignores +unknown payload properties, but the published v1 initialize schema has `additionalProperties: +false`. An unconditional new property would fail an older schema validator even when an older +JavaScript client accepts it. Compatibility must cover both implementations. + +The reference host owns an authenticated, persisted language preference and applies it to an +application-owned i18next runtime. Its current choices are `en`, `es`, `de`, `fr`, and `it`. +The SDK receives the host's resolved value; this host-specific set is not the SDK's locale enum. +Settings, preference hydration, profile projection, and database persistence belong to the host +and backend. This decision defines the SDK boundary they consume. + +## Decision + +### 1. Libraries and public ownership + +Keep locale validation and the message contract framework-neutral under the +`@dev-mainsequence/command-center-sdk/localization` export. Put the React provider and hook under +`@dev-mainsequence/command-center-sdk/localization/react`. + +Use **i18next and react-i18next in the reference React integration and consumer example**. The +SDK accepts a typed translation callback, so applications using Lingui or another library can +provide the same behavior. The SDK does not initialize i18next, add it as a runtime dependency, +register application resources, detect a browser language, or persist a preference. + +| Owner | Responsibility | +| --- | --- | +| Host/backend | Save and hydrate the user's preference; supply its current resolved language | +| SDK embed | Validate and carry locale at initialization and on subsequent context updates | +| SDK controls | Stable message keys, parameters, English defaults, and localized accessibility text | +| Application | Translation library/catalogs, supported-language resolution, document metadata, and formatting | + +The reference host should pass its reactive resolved language after backend hydration or a +confirmed preference update. A profile's read-only language projection is another view of that +same preference, not a new SDK persistence source. The SDK never fetches the profile or preferences +and never imports a private host store. Retain the host/backend's existing stored `language` field; +the SDK name `locale` is not a request to rename it. + +### 2. Locale input and normalization + +Add optional `locale` to `StaticSiteIframeContextInput` and React `StaticSiteIframe` props. +Expose a required normalized `locale` on `StaticSiteIframeContext`. Omission resolves to `en`. +Use BCP 47 language tags, including base and regional examples such as `de` and `de-AT`. + +SDK 0.5.19 accepts the BCP 47 core profile: a two- or three-letter language, optionally a +four-letter script, optionally a two-letter or three-digit region (`de`, `de-AT`, `zh-Hant-TW`). +Variants, extensions, private-use tags, underscores, whitespace, and repeated subtags are rejected. +The runtime and JSON Schema use the same grammar and 128-character upper bound. Accepted values +are canonicalized with `Intl.getCanonicalLocales`. A valid locale absent from an application's +catalogs survives transport unchanged; available translations are a separate concern. + +- A malformed explicitly supplied host locale fails before sending; it is not silently persisted + or converted to English. +- A missing wire locale means an older host or an omitted value and normalizes to `en`. +- A malformed wire locale rejects the message through the existing protocol-error path, preserving + the last valid context and transport state. +- Wire/schema/runtime fixtures must agree on the accepted profile, including casing, malformed + separators, repeated subtags, non-string values, and length limits. A permissive regular + expression alone must not be described as full BCP 47 validation. + +Keep locale validation distinct from application fallback. Resolve application catalogs by exact +canonical tag, then its base language, then English. Resolve missing messages to English as well. +The application retains the requested host locale separately from its effective rendered locale. +Its fallback never changes the host's saved preference. + +### 3. Negotiate the optional v1 wire field + +Retain numeric protocol version `1` and existing contract/schema identifiers only with this +compatibility gate. A new child advertises `locale-context-v1` in an optional `capabilities` string +array in its `ready.payload`. The existing ready schema permits additional payload content, so an +older host can receive this message without learning a new envelope or message type. + +Messages available from SDK 0.5.19: + +```json +{ + "channel": "mainsequence.example", + "version": 1, + "type": "ready", + "payload": { "capabilities": ["locale-context-v1"] } +} +``` + +```json +{ + "channel": "mainsequence.example", + "version": 1, + "type": "initialize", + "payload": { + "theme": "light", + "themeId": "quartz-light", + "user": null, + "locale": "de-AT" + } +} +``` + +The host sends `payload.locale` only after that child advertises support. A legacy child's +initialize payload retains exactly the legacy fields, including on subsequent updates. Defaulting +the host's internal context to `en` must not accidentally serialize locale for a legacy child. +Recognize the exact capability identifier, ignore unknown capabilities, and reject a malformed +capabilities declaration. Replacing the child/handshake resets the negotiated state. + +| Host | Child | Result | +| --- | --- | --- | +| Old | Old | Existing v1 behavior | +| Old | New | Host ignores capability; child receives legacy context and uses `en` | +| New | Old, including strict schema consumer | No advertised capability; host sends legacy shape | +| New | New | Host sends current locale initially and on later updates | + +Capability support is not authority: retain origin, source-window, channel, version, payload-size, +and message validation. It must not authorize a platform request or alter any credential binding. +Builders and React bindings must use the same negotiated behavior as the framework-neutral host. +Add fixtures for ready capability, legacy initialize, and locale-bearing initialize, and exercise +the frozen legacy schema/parser against messages actually sent to legacy peers. + +If this gate cannot preserve the released contract, introduce a new versioned contract and explicit +transition instead of silently weakening old validation. Do not overwrite a released schema ID +with incompatible semantics. + +### 4. Live updates are presentation updates + +Reuse the established ready/initialize context lifecycle and the existing `onContext` callback. +The React host's `locale` prop updates context without recreating its bridge, iframe, or listener. +Keep locale out of iframe keys, launch URLs, and authorization/launch effect dependencies. + +In a React child, application state holding the latest SDK context feeds the localization provider +and its translation instance. The SDK currently supplies a framework-neutral child callback and a +React host; this plan does not assume an existing React child-context hook. Show the complete +callback-to-state/provider composition in the consumer example. + +A locale-only update must preserve the current record, unsaved forms, focus, selection, scroll, +credential cache, pending HTTP/platform requests, WebSocket connections/tickets, and application +startup state. Preserve existing invalidation for actual user/target changes. Locale must not +become part of transport identity or request cache keys. + +Apply the latest locale when ready arrives, even if the host changed it before the handshake. +Subsequent context updates remain synchronous notifications; the application owns asynchronous +catalog loading. Document how it prevents an earlier language-load completion from overwriting a +later selection. Mount translation providers without a locale-based React key. + +### 5. Localize complete SDK messages + +The public exports are `SdkMessageKey`, `SdkMessageValues`, `TranslateSdkMessage`, +`sdkEnglishMessages`, `normalizeCommandCenterLocale`, `resolveCommandCenterLocale`, and +`formatSdkEnglishMessage` from `/localization`, plus `CommandCenterLocalizationProvider` and +`useSdkTranslation` from `/localization/react`. The provider and hook share the same message +contract used by SDK-owned controls. + +The provider accepts the effective rendered `locale` and a translation callback. It updates +messages in place when either changes. Use this order for every existing customizable message: + +1. An explicitly supplied component message/label/render override. +2. The provider's translated complete message. +3. The SDK's complete English default. + +The translation callback receives a stable typed key and interpolation values and returns a +rendered string, or `undefined` for a missing translation. Preserve literal overrides, including +an intentional empty string where the component permits one. Do not replace mandatory accessible +names with empty fallback output, expose raw keys, or use HTML injection for interpolation. + +Provide numbers as numbers, with a named `count` for plural messages, plus the values each key +requires. Translate whole sentences, not fragments assembled from English word order. Resource +names, domain labels, and action titles are application inputs; applications can translate the +whole containing message and must not be forced to infer grammar from an English plural noun. +English defaults must handle singular/plural cases where their wording differs. + +The i18next adapter uses one application-owned SDK namespace, complete-message interpolation, +`count`-based plurals, and English fallback. It returns `undefined` when no translation/default is +available rather than returning a raw key. Changing the language must update subscribers even +when the application's `t` function has stable identity. Other libraries implement the same +typed callback without an SDK dependency on them. + +Audit every SDK-owned visible or accessibility message. Required coverage includes: + +- Resource list/table search, sort, pagination, page/result summaries, selection, and action menus. +- Pickers and transfer lists, their search/loading/empty states, keyboard and live announcements. +- Action confirmation/preflight dialogs, validation/help messages, and retry/cancel/working states. +- SDK defaults in resource details, navigation, feedback, and general controls, including icon-only + labels, overflow controls, status announcements, and fallback errors. + +Caller-authored button children, column titles, domain errors, and backend-provided business text +remain caller-owned. Keep protocol error codes and machine identifiers stable. Translating a +diagnostic must not change its code or automatically translate user/business content. + +### 6. Document metadata and formatting + +The SDK does not change the host or child's document as a side effect of receiving a message. +The application applies its effective rendered locale to `document.documentElement.lang`, sets +direction according to its supported catalogs, and uses that locale for `Intl` date/number +formatting. A standalone child without a trusted host uses its documented standalone fallback. + +Language selection does not select timezone, currency, units, or date-storage semantics. Do not +claim RTL layout/catalog support from accepting a language tag; verify it separately when such +catalogs are adopted. Preserve React escaping for caller-provided interpolation values. + +## Implementation and acceptance evidence + +| Step | Work and completion evidence | +| --- | --- | +| 1. Freeze compatibility baseline | Preserve released parser/schema examples and catalogue every SDK default/override; agree the accepted locale profile and capability identifier. | +| 2. Locale transport | Align framework-neutral host/client, ready negotiation, React props, normalized context, schema/manifest, valid/invalid fixtures, and old/new peer tests. | +| 3. Localization APIs | Add framework-neutral messages/locale helpers and React provider/hook with public exports, types, complete English fallback, interpolation/plural tests, and no React import in the neutral module. | +| 4. Controls | Migrate the audited SDK-owned strings and accessibility text while preserving override precedence, keyboard behavior, resource state, and existing English behavior. | +| 5. Consumer adoption guide | Show i18next/react-i18next setup, live callback/provider updates, a consumer with fewer catalogs, document metadata, number/date formatting, and standalone behavior using published imports. | +| 6. Release and host handoff | Pass the release checks below, publish the exact minimum SDK version and negotiated compatibility policy, then let host/application owners adopt the released package. | + +Browser acceptance must start with German, edit a form/select a record, switch to English through +a context update, and retain the same iframe node, form value, focus, selection, and transport. +Count iframe loads/mounts, handshakes, credential resolver calls, and socket closes. Assert no +additional authorization/launch work caused by locale alone, including with a platform request +already pending. Repeat with provisional context corrected after hydration and with multiple +mounted children. + +Cover exact/base/English resolution, absent/invalid locale, all four old/new combinations, +unknown/malformed capability, missing keys, zero/one/many plurals, explicit overrides, translated +accessibility announcements, longer labels, and malformed/wrong-origin/wrong-source/wrong-channel +messages. Keep legacy identity/theme and credential-invalidation coverage. + +Update the SDK README, embed/localization/view module READMEs, static-site/control/resource guides, +backend-contract and compatibility guidance, consumer examples, and the matching packaged embed, +theme, control, and resource skills. Add localization guidance to the public API/skill maps. +These surfaces ship together in SDK 0.5.19. + +Run the repository's package-boundary/type checks, SDK tests, build/size lane, packed-consumer +smoke tests, documentation build, and relevant real-browser tests before release. Inspect the +packed exports, declarations, schemas, fixtures, docs, and skills. Record the first actual package +version with both locale transport and control localization; the inspected baseline is insufficient. + +## Backend/storage, rollout, and rollback + +This SDK change stores no user preference, adds no product endpoint, and requires no database +migration. Host/backend owners must independently prove that a confirmed language choice survives +a fresh database read, profile/bootstrap/preferences response, and new session. An existing saved +base-language field must not silently gain arbitrary regional tags because the SDK can carry them. + +The iframe bytes do change. Update the language-neutral conformance bundle and backend/integrator +handoff with old/new messages, defaults, negotiation, rollout order, and mixed-version evidence. +Backend services that validate these messages need the matching schema; ordinary language +preference storage does not become an iframe contract store. + +Release the SDK first, then adopt it in hosts and children independently. A new child on an old +host uses English; an old child on a new host receives legacy context. Rollback of either side +uses the same compatibility matrix and leaves stored preferences and user content untouched. +Do not declare end-to-end completion solely from a local `changeLanguage` call or source changes +in a checkout; require released-package and host/backend acceptance evidence. + +## Alternatives considered + +- **Always add locale to initialize:** accepted by the baseline JavaScript reader but rejected by + the baseline strict schema. Negotiate support to preserve both. +- **Query parameters, storage, or a second postMessage bridge:** duplicate the context lifecycle + and can couple language changes to navigation or leak another user's cached preference. +- **Bundle or initialize i18next inside the SDK:** couples consumers to one library/global state + and duplicates the application's catalog ownership. Use the injected boundary instead. +- **Only add label props:** leaves nested controls and accessibility announcements inconsistent + and encourages partial word substitution. Retain overrides above a shared whole-message layer. + +## References + +- [BCP 47 language-tag syntax](https://www.rfc-editor.org/rfc/rfc5646) +- [i18next language and message fallback](https://www.i18next.com/principles/fallback) +- [i18next plural messages and the count parameter](https://www.i18next.com/translation-function/plurals) diff --git a/command-center-sdk/docs/application-controls.md b/command-center-sdk/docs/application-controls.md index 5b3821cf..1d0eb290 100644 --- a/command-center-sdk/docs/application-controls.md +++ b/command-center-sdk/docs/application-controls.md @@ -239,3 +239,11 @@ and 1280×800. The control props are React presentation input, not a backend contract. There is no controls contract ID, JSON Schema, fixture bundle, persisted state, or migration. Form payloads, validation responses, and error shapes are application and backend concerns. + +## Follow the application language + +SDK 0.5.19 exposes `/localization` and `/localization/react` for SDK-owned visible +and accessibility messages. Use the [localization guide](./localization.md) to +connect your translation library, plural messages, and effective rendered locale. +Explicit labels/renderers retain priority; application catalogs and language +persistence stay with the consumer. diff --git a/command-center-sdk/docs/backend-contracts.md b/command-center-sdk/docs/backend-contracts.md index 189c8417..f9c51113 100644 --- a/command-center-sdk/docs/backend-contracts.md +++ b/command-center-sdk/docs/backend-contracts.md @@ -221,3 +221,26 @@ with `$comment` where applicable. Adding the SDK schema bundle documents SDK-owned contracts and does not itself migrate stored data. A backend changes only when an endpoint opts into or evolves one of these contracts. + +## Optional iframe locale (SDK 0.5.19) + +`command-center.static_site_iframe@v1` retains its ID, schema ID and numeric v1 +protocol. Initialize payload adds optional `locale`; older messages remain valid. +Use the updated schema/manifest fixtures before validating new-host payloads. +Locale changes are presentation-only, never token/credential or persisted data. +Backend preference/profile persistence remains application-owned; no SDK backend +route or database migration is required. Roll out updated validators/SDK first, +then hosts, then children; rolling back a host leaves new children at English. + +Locale negotiation preserves strict legacy validators: new clients advertise +`ready.payload.capabilities: ["locale-context-v1"]`. The host includes locale only +for that capability. Missing/unknown capabilities retain the frozen legacy payload; +malformed declarations are rejected. The normalized new-client context still +defaults to English when an old host omits locale. See SDK ADR 019. + +SDK 0.5.19 accepts a BCP 47 core profile: two- or three-letter language, optional +four-letter script, optional two-letter or three-digit region (for example `de-AT` +or `zh-Hant-TW`). Variants, extensions, private-use tags, underscores, whitespace, +and repeated subtags are rejected. The runtime and JSON Schema enforce the same +profile; accepted tags are canonicalized. This transport profile does not expand +the backend's stored language choices. diff --git a/command-center-sdk/docs/localization.md b/command-center-sdk/docs/localization.md new file mode 100644 index 00000000..41bc7cfb --- /dev/null +++ b/command-center-sdk/docs/localization.md @@ -0,0 +1,123 @@ +# Localize SDK controls + +Available from SDK 0.5.19. Import the framework-neutral message types and defaults +from `@dev-mainsequence/command-center-sdk/localization`; React composition lives +at `@dev-mainsequence/command-center-sdk/localization/react`. + +```tsx +import { CommandCenterLocalizationProvider } from '@dev-mainsequence/command-center-sdk/localization/react'; +import type { TranslateSdkMessage } from '@dev-mainsequence/command-center-sdk/localization'; +import { useTranslation } from 'react-i18next'; +import { useCallback } from 'react'; + +function LocalizedApplication({ children }) { + const { i18n } = useTranslation('sdk'); + const locale = i18n.resolvedLanguage ?? i18n.language; + const translate = useCallback((key, values) => + i18n.exists(key, { ...values, ns: 'sdk', lng: locale }) + ? String(i18n.t(key, { ...values, ns: 'sdk', lng: locale })) + : undefined, + [i18n, locale]); + return + {children} + ; +} +``` + +The provider updates pagination, search, selection, table accessibility, pickers, +transfer lists, and action/confirmation/preflight messages without replacing their +state. The application owns its i18next instance/catalog and supported locales. +The SDK has no i18next dependency or language persistence. Any translation library +can implement `TranslateSdkMessage`. + +Translate complete messages; `SdkMessageKey` and `sdkEnglishMessages` enumerate the +keys/templates, with `SdkMessageValues` carrying interpolation values. Forward +`count` to your translator for plural rules. For example, an i18next namespace +may define `selectionCount_one` as `{{count}} item selected` and +`selectionCount_other` as `{{count}} items selected`. Do not concatenate words +in your translator. Format application values through Intl for the effective locale. + +An absent provider/translation returns the complete English message. Return +undefined for an unknown key. Explicit component label/render props override the +provider. Application/backend/domain text (including item names, action labels, +descriptions, warnings, error messages, and confirmation words) remains owned by +its source. Confirmation words are never translated automatically. + +Locale itself uses the existing v1 iframe schema's optional `locale` property; +the callbacks/provider are runtime-only, so no separate wire/storage contract is +introduced. Older hosts default to English; invalid incoming locale is rejected. +See [static-site embeds](./static-site-embeds.md#follow-the-host-language). + +## Connect a React child to live host context + +Initialize the application's i18next instance with its catalogs, `lng: 'en'`, and +`fallbackLng: 'en'`. The following composition uses `LocalizedApplication` above; +keep the same React root and provider instances when the language changes. + +```tsx +import { useEffect, type ReactNode } from 'react'; +import type { i18n as I18n } from 'i18next'; +import { I18nextProvider } from 'react-i18next'; +import { createStaticSiteIframeClient } from '@dev-mainsequence/command-center-sdk/embed'; +import { resolveCommandCenterLocale } from '@dev-mainsequence/command-center-sdk/localization'; + +export function EmbeddedApplication({ hostOrigin, i18n, children }: { + hostOrigin: string; + i18n: I18n; + children: ReactNode; +}) { + useEffect(() => { + if (window.parent === window) return; // Standalone uses the application's English default. + let disposed = false; + let languageChanges = Promise.resolve(); + const client = createStaticSiteIframeClient({ + channel: 'mainsequence.example', + hostOrigin, + parentWindow: window.parent, + onContext(context) { + const locale = resolveCommandCenterLocale(context.locale, ['en', 'de']); + // Serialize asynchronous catalog loads so an earlier completion cannot overwrite a later choice. + languageChanges = languageChanges.then(async () => { + if (disposed) return; + await i18n.changeLanguage(locale); + if (disposed) return; + document.documentElement.lang = locale; + document.documentElement.dir = i18n.dir(locale); + }).catch(error => console.error('Unable to load language catalog', error)); + }, + }); + const receive = (event: MessageEvent) => client.handleMessage(event); + window.addEventListener('message', receive); + client.announceReady(); + return () => { + disposed = true; + window.removeEventListener('message', receive); + client.dispose(); + }; + }, [hostOrigin, i18n]); + return + {children} + ; +} +``` + +Keep `hostOrigin` and the i18next instance stable. Reuse this client for delegated +requests in a data-using application; do not create a second bridge just for +language. Use `Intl.DateTimeFormat(locale, { timeZone })` and +`Intl.NumberFormat(locale)` for application values after supported-language +resolution. A child that supports only English resolves every host locale to +`en`, without changing the user's saved preference. Report catalog failures in +the application's own feedback flow if catalogs are loaded over the network. + +Locale negotiation preserves strict legacy validators: new clients advertise +`ready.payload.capabilities: ["locale-context-v1"]`. The host includes locale only +for that capability. Missing/unknown capabilities retain the frozen legacy payload; +malformed declarations are rejected. The normalized new-client context still +defaults to English when an old host omits locale. See SDK ADR 019. + +SDK 0.5.19 accepts a BCP 47 core profile: two- or three-letter language, optional +four-letter script, optional two-letter or three-digit region (for example `de-AT` +or `zh-Hant-TW`). Variants, extensions, private-use tags, underscores, whitespace, +and repeated subtags are rejected. The runtime and JSON Schema enforce the same +profile; accepted tags are canonicalized. This transport profile does not expand +the backend's stored language choices. diff --git a/command-center-sdk/docs/public-api.md b/command-center-sdk/docs/public-api.md index 25727228..d573aa67 100644 --- a/command-center-sdk/docs/public-api.md +++ b/command-center-sdk/docs/public-api.md @@ -46,7 +46,9 @@ framework integrations do not affect the application. | `/theme/presets` | Framework-neutral | Individual built-in preset objects | | `/theme/data-viz` | Framework-neutral | Data-visualization palette types and resolvers | | `/embed` | Browser | Static-site message contracts, host/client lifecycle, delegated HTTP access, native FastAPI WebSockets, and platform requests sent through the host | -| `/embed/react` | React + DOM | Managed `StaticSiteIframe` host component | +| `/embed/react` | React + DOM | Managed `StaticSiteIframe` host component with live locale updates | +| `/localization` | Framework-neutral | Locale normalization/fallback, typed message keys and English defaults | +| `/localization/react` | React | `CommandCenterLocalizationProvider` and `useSdkTranslation` for SDK-owned messages | | `/vite` | Node, in a Vite dev server | `platformRequestProxy()`: a top-level local page's platform requests, sent with the developer's token during local development. `localAgentProxy()`: a chat's routes to an Agent run with `ms-tau` in local mode on this machine | | `/contracts` | Framework-neutral | Ordered migration helper for versioned SDK payloads | | `/contracts/manifest.json` | JSON | Canonical backend contract catalog | @@ -310,3 +312,11 @@ serialized meaning, or renaming a stable ID is not. Contract changes also requir schemas, fixtures, migration or mixed-version behavior, and backend coordination. For package-source work, follow [Extending and releasing](./extending-and-releasing.md). + +## Localize SDK controls + +From SDK 0.5.19, wrap SDK controls in `CommandCenterLocalizationProvider` from +`/localization/react`, supplying your effective locale and a `TranslateSdkMessage` +callback from `/localization`. The framework-neutral module imports no React. +See [Localization](./localization.md) for a complete i18next adapter, interpolation, +plural handling, English fallback, and live host-context composition. diff --git a/command-center-sdk/docs/resources.md b/command-center-sdk/docs/resources.md index ce18f3df..2dac426b 100644 --- a/command-center-sdk/docs/resources.md +++ b/command-center-sdk/docs/resources.md @@ -546,3 +546,11 @@ action is in flight. Do not render a raw `