diff --git a/apps/web/src/auth-gate.tsx b/apps/web/src/auth-gate.tsx index 69f41c0..88ad066 100644 --- a/apps/web/src/auth-gate.tsx +++ b/apps/web/src/auth-gate.tsx @@ -393,7 +393,7 @@ function RecoveryForm({ export function AuthGate({ renderApp }: { renderApp: (locale: Locale) => ReactNode }) { const [locale, setLocale] = useState("en"); const [phase, setPhase] = useState("loading"); - const [, setCatalogRevision] = useState(0); + const [, setCatalogInstallations] = useState(0); const t: Translator = (key) => translate(locale, key); const load = useCallback(async (signal?: AbortSignal) => { @@ -432,7 +432,9 @@ export function AuthGate({ renderApp }: { renderApp: (locale: Locale) => ReactNo const controller = new AbortController(); void loadTranslationCatalog(locale, controller.signal) .then((catalog) => { - if (installTranslationCatalog(catalog)) setCatalogRevision(catalog.revision); + if (installTranslationCatalog(catalog)) { + setCatalogInstallations((installations) => installations + 1); + } }) .catch(() => undefined); return () => controller.abort(); diff --git a/apps/web/tests/bootstrap.spec.ts b/apps/web/tests/bootstrap.spec.ts index 8027926..ba5cb39 100644 --- a/apps/web/tests/bootstrap.spec.ts +++ b/apps/web/tests/bootstrap.spec.ts @@ -1,6 +1,53 @@ import AxeBuilder from "@axe-core/playwright"; import { expect, test } from "@playwright/test"; +test("rerenders authentication when an equal-revision locale catalog arrives", async ({ + page, +}) => { + await page.route("**/api/auth/setup/status", (route) => + route.fulfill({ json: { configured: true } }), + ); + await page.route("**/api/auth/get-session", (route) => route.fulfill({ json: null })); + await page.route("**/api/v1/translations/en", (route) => + route.fulfill({ + json: { + locale: "en", + defaultLocale: "en", + revision: 7, + messages: { + signInTitle: "Sign in to Mailflow", + signInDescription: "Use the owner account for this installation.", + }, + missingKeys: [], + }, + }), + ); + await page.route("**/api/v1/translations/es", async (route) => { + await new Promise((resolve) => setTimeout(resolve, 100)); + await route.fulfill({ + json: { + locale: "es", + defaultLocale: "en", + revision: 7, + messages: { + signInTitle: "Inicia sesión en Mailflow", + signInDescription: "Usa la cuenta propietaria de esta instalación.", + }, + missingKeys: [], + }, + }); + }); + + const englishCatalog = page.waitForResponse("**/api/v1/translations/en"); + await page.goto("/"); + await englishCatalog; + await page.getByRole("button", { name: "Language" }).click(); + await page.getByRole("menuitemradio", { name: "ES Spanish" }).click(); + + await expect(page.getByRole("heading", { name: "Inicia sesión en Mailflow" })).toBeVisible(); + await expect(page.getByText("Usa la cuenta propietaria de esta instalación.")).toBeVisible(); +}); + test("creates the only owner in Spanish without persisting secrets", async ({ page }) => { const bootstrapToken = "browser-only-bootstrap-token"; const password = "browser-test-password";