Skip to content
Merged
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
2 changes: 2 additions & 0 deletions command-center-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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.
- Preserve selection and open confirmations when the normalized search query has
not changed; a delayed mount-time debounce must not clear them during a locale update.
- 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
Expand Down
4 changes: 2 additions & 2 deletions command-center-sdk/docs/localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ at `@dev-mainsequence/command-center-sdk/localization/react`.
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';
import { useCallback, type ReactNode } from 'react';

function LocalizedApplication({ children }) {
function LocalizedApplication({ children }: { children: ReactNode }) {
const { i18n } = useTranslation('sdk');
const locale = i18n.resolvedLanguage ?? i18n.language;
const translate = useCallback<TranslateSdkMessage>((key, values) =>
Expand Down
5 changes: 5 additions & 0 deletions command-center-sdk/src/views/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,8 @@ title; the host sizes it, and a lead that renders nothing takes no space. No vie
Shared message defaults now come from `/localization/react`; wrap views in
`CommandCenterLocalizationProvider` to inject application catalogs. Explicit label
props retain precedence. See `docs/localization.md` for interpolation/plurals.

`ResourceListPage` resets selection only when the normalized debounced search
query changes. Mounting, locale updates, and whitespace-only edits to the same
query preserve selection and any open confirmation. Keep translation callbacks
out of transport/query dependencies.
7 changes: 5 additions & 2 deletions command-center-sdk/src/views/ResourceListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,16 +394,19 @@ export function ResourceListPage<T, Id extends ResourceId>({
]);

useEffect(() => {
const nextSearch = searchInput.trim();
// Mounting, a locale update, or whitespace around the same query must not clear selection.
if (nextSearch === search) return undefined;
const timeout = window.setTimeout(() => {
setSearch(searchInput.trim());
setSearch(nextSearch);
setPageIndex(0);
setAllMatching(false);
selection.clearSelection();
}, 250);
return () => window.clearTimeout(timeout);
// Selection callbacks intentionally stay outside dependencies: the query value is the reset boundary.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchInput]);
}, [searchInput, search]);

useEffect(() => {
setPageIndex(0);
Expand Down
6 changes: 6 additions & 0 deletions command-center-sdk/tests/browser/localization-controls.pw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ test.beforeAll(async () => {

test("localizes resource selection, long pagination labels and an open confirmation in place", async ({ page }) => {
await page.setContent(`<!doctype html><html><head><style>${styles} body { margin: 0; padding: 16px; } #root { max-width: 1100px; }</style></head><body><div id="root"></div></body></html>`);
await page.clock.install();
const pageErrors: string[] = [];
page.on("pageerror", error => pageErrors.push(error.message));
await page.addScriptTag({ content: bundle });
await expect(page.getByRole("button", { name: "Weiter zur nächsten Ergebnisseite" })).toBeVisible();
const pagination = page.getByRole("navigation", { name: "records pagination" });
Expand All @@ -34,6 +37,9 @@ test("localizes resource selection, long pagination labels and an open confirmat
await dialog.getByLabel("Bestätigungswort").fill("ARC");
// An application-owned update can arrive while focus is inside a modal.
await page.getByRole("button", { name: "Change language" }).evaluate((button: HTMLButtonElement) => button.click());
// A delayed mount-time search reset used to clear selection and destroy the open dialog.
await page.clock.fastForward(1_000);
expect(pageErrors).toEqual([]);
await expect(dialog.getByRole("button", { name: "Cancel", exact: true })).toBeVisible();
await expect(confirmationInput).toHaveAttribute("aria-label", "Confirmation word");
await expect(confirmationInput).toHaveValue("ARC");
Expand Down
Loading