Files
kandev/apps/web/e2e/tests/i18n/language-switch.spec.ts
T
2026-08-24 11:09:03 +00:00

166 lines
6.7 KiB
TypeScript

import { test, expect } from "../../fixtures/test-base";
/**
* i18n language switcher + pseudo-locale.
*
* The pseudo-locale accents/pads every extracted message (`Language` →
* `Ĺàńĝũàĝē`), so it doubles as the completeness oracle for the string-
* externalization sweep: any plain-ASCII user-facing text under `pseudo` is a
* literal that was never routed through `t()`.
*
* See docs/specs/platform/requirements/i18n.md and docs/i18n.md.
*/
const APPEARANCE_URL = "/settings/preferences/appearance";
/** Latin letters carrying diacritics, as produced by scripts/generate-pseudo-locale.mjs. */
const ACCENTED = /[À-ɏ]/;
test.describe("i18n language switcher", () => {
test("defaults to English with lang=en", async ({ testPage, prCapture }) => {
await testPage.goto(APPEARANCE_URL);
await expect(testPage.locator("html")).toHaveAttribute("lang", "en");
await expect(testPage.getByLabel("Display language")).toBeVisible({ timeout: 10_000 });
await prCapture.screenshot("language-switcher-desktop", {
caption: "Settings > General > Appearance: the Display language selector",
});
await testPage.setViewportSize({ width: 390, height: 844 });
await expect(testPage.getByLabel("Display language")).toBeVisible({ timeout: 10_000 });
await prCapture.screenshot("language-switcher-mobile", {
caption: "The same selector at a phone width (390x844)",
});
});
test("switching to Simplified Chinese persists through reload and can restore English", async ({
testPage,
prCapture,
}) => {
await testPage.goto(APPEARANCE_URL);
const select = testPage.getByLabel("Display language");
await expect(select).toBeVisible({ timeout: 10_000 });
await select.click();
await testPage.getByRole("listbox").getByRole("option", { name: "简体中文" }).click();
await expect(testPage.locator("html")).toHaveAttribute("lang", "zh-cn", { timeout: 10_000 });
await expect(testPage.getByLabel("显示语言")).toBeVisible({ timeout: 10_000 });
await expect
.poll(async () => {
const cookies = await testPage.context().cookies();
return cookies.find((cookie) => cookie.name === "kandev_locale")?.value;
})
.toBe("zh-cn");
await prCapture.screenshot("simplified-chinese-locale-desktop", {
caption: "Settings > General > Appearance with Simplified Chinese active",
});
await testPage.reload();
await expect(testPage.locator("html")).toHaveAttribute("lang", "zh-cn", { timeout: 10_000 });
await expect(testPage.getByLabel("显示语言")).toBeVisible({ timeout: 10_000 });
const selectAfter = testPage.getByLabel("显示语言");
await selectAfter.click();
await testPage.getByRole("listbox").getByRole("option", { name: "English" }).click();
await expect(testPage.locator("html")).toHaveAttribute("lang", "en", { timeout: 10_000 });
await expect(testPage.getByLabel("Display language")).toBeVisible({ timeout: 10_000 });
});
for (const locale of [
{
id: "zh-tw",
option: "繁體中文(台灣)",
displayLanguage: "顯示語言",
screenshot: "traditional-chinese-taiwan-locale-desktop",
caption: "Settings > General > Appearance with Traditional Chinese (Taiwan) active",
},
{
id: "zh-hk",
option: "繁體中文(香港)",
displayLanguage: "顯示語言",
screenshot: "traditional-chinese-hong-kong-locale-desktop",
caption: "Settings > General > Appearance with Traditional Chinese (Hong Kong) active",
},
] as const) {
test(`switching to ${locale.option} persists through reload and can restore English`, async ({
testPage,
prCapture,
}) => {
await testPage.goto(APPEARANCE_URL);
const select = testPage.getByLabel("Display language");
await expect(select).toBeVisible({ timeout: 10_000 });
await select.click();
await testPage.getByRole("listbox").getByRole("option", { name: locale.option }).click();
await expect(testPage.locator("html")).toHaveAttribute("lang", locale.id, {
timeout: 10_000,
});
await expect(testPage.getByLabel(locale.displayLanguage)).toBeVisible({ timeout: 10_000 });
await expect
.poll(async () => {
const cookies = await testPage.context().cookies();
return cookies.find((cookie) => cookie.name === "kandev_locale")?.value;
})
.toBe(locale.id);
await prCapture.screenshot(locale.screenshot, {
caption: locale.caption,
});
await testPage.reload();
await expect(testPage.locator("html")).toHaveAttribute("lang", locale.id, {
timeout: 10_000,
});
await expect(testPage.getByLabel(locale.displayLanguage)).toBeVisible({ timeout: 10_000 });
const selectAfter = testPage.getByLabel(locale.displayLanguage);
await selectAfter.click();
await testPage.getByRole("listbox").getByRole("option", { name: "English" }).click();
await expect(testPage.locator("html")).toHaveAttribute("lang", "en", { timeout: 10_000 });
await expect(testPage.getByLabel("Display language")).toBeVisible({ timeout: 10_000 });
});
}
test("switching to pseudo re-renders accented copy and survives reload", async ({
testPage,
prCapture,
}) => {
await testPage.goto(APPEARANCE_URL);
const select = testPage.getByLabel("Display language");
await expect(select).toBeVisible({ timeout: 10_000 });
await select.click();
await testPage
.getByRole("listbox")
.getByRole("option", { name: /Pseudo/ })
.click();
// Locale activates client-side: <html lang> flips and chrome is accented.
await expect(testPage.locator("html")).toHaveAttribute("lang", "pseudo", { timeout: 10_000 });
await expect(testPage.locator("body")).toHaveText(ACCENTED, { timeout: 10_000 });
await prCapture.screenshot("pseudo-locale-desktop", {
caption:
"Pseudo locale active: every catalog message renders accented, so any plain-ASCII text left on screen is a string that was never externalized",
});
// The kandev_locale cookie is the source of truth, so the Go shell serves
// lang="pseudo" on the very next request — no flash of English.
await testPage.reload();
await expect(testPage.locator("html")).toHaveAttribute("lang", "pseudo", { timeout: 10_000 });
// Switch back so the persisted cookie does not leak into later tests.
const selectAfter = testPage.getByLabel(/Display language|Ďĩśƥĺàŷ ĺàńĝũàĝē/);
await selectAfter.click();
await testPage
.getByRole("listbox")
.getByRole("option", { name: /English|Ēńĝĺĩśĥ/ })
.click();
await expect(testPage.locator("html")).toHaveAttribute("lang", "en", { timeout: 10_000 });
});
});