Frutiger Aero, closer to the mock: - The scene is water now: sun shafts through a bright surface, caustic patches, reeds on the left, a city silhouette on the right, and ten soap bubbles that actually rise (own keyframes, varied sizes/speeds/sway; the spans are display:none outside aero so they cost nothing elsewhere, and they sit still under reduced motion). - Pale blue glass at 0.8: still reads as glass, but dark navy text clears 4.5:1 over the water. The phone tab bar mixes the sheet glass instead of the page — in aero the page *is* the water, which left its labels at 3.9:1. - The aero page-level text (muted/subtle) is much darker for the same reason. - The PWA status bar follows the new water blue (#2fa7e6). The cookie notice is floating glass now (blurred backdrop, rim, shadow): as a plain card it read as part of the page background instead of a notice. Czech: "Tvoje mraznička" -> "Tvůj mrazák" (library title), test updated. Contrast: 0 below-minimum findings and 0 axe violations across every screen, all three themes. Version 0.2.1, SW -> freezmo-v55.
71 lines
3.6 KiB
JavaScript
71 lines
3.6 KiB
JavaScript
/**
|
|
* i18n suite: EN/CS dictionary parity, the locale switch, Czech copy on the
|
|
* desktop shell and the phone tab bar, and Czech plurals.
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
import { BASE, DECK, REPO, launchBrowser, makeChecker, makePage, seed } from "./lib.mjs";
|
|
|
|
const { check, finish } = makeChecker();
|
|
const browser = await launchBrowser();
|
|
|
|
/* 1. dictionary parity ----------------------------------------------- */
|
|
{
|
|
const source = readFileSync(path.join(REPO, "lib/i18n-dict.ts"), "utf8");
|
|
const split = source.indexOf("const cs");
|
|
const pattern = /^\s{2}"([^"]+)":/gm;
|
|
const en = [...source.slice(0, split).matchAll(pattern)].map((match) => match[1]);
|
|
const cs = [...source.slice(split).matchAll(pattern)].map((match) => match[1]);
|
|
const missingInCs = en.filter((key) => !cs.includes(key));
|
|
const missingInEn = cs.filter((key) => !en.includes(key));
|
|
check("key parity: same keys in both dictionaries", missingInCs.length === 0 && missingInEn.length === 0, JSON.stringify({ missingInCs, missingInEn }).slice(0, 200));
|
|
check("dictionary is substantial", en.length > 700, String(en.length));
|
|
}
|
|
|
|
/* 2. Czech desktop shell --------------------------------------------- */
|
|
{
|
|
const { context, page } = await makePage(browser, { width: 1280, height: 900, deviceScaleFactor: 1 });
|
|
await seed(page, { locale: "cs", decks: [DECK] });
|
|
await page.goto(BASE, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(900);
|
|
check("library title is Czech", await page.getByRole("heading", { name: "Tvůj mrazák" }).isVisible());
|
|
check("sidebar says Všechny sady", await page.getByRole("button", { name: /Všechny sady/ }).first().isVisible());
|
|
check("sidebar says Nezařazené", await page.getByRole("button", { name: /Nezařazené/ }).first().isVisible());
|
|
check("deck card CTA is Studovat", (await page.getByRole("link", { name: "Studovat" }).count()) === 1);
|
|
check("Czech plural on the card (2 aktivity)", (await page.getByText("2 aktivity").filter({ visible: true }).count()) >= 1);
|
|
check("review card is Czech", await page.getByText("Chytré opakování").isVisible());
|
|
check("desktop has no tab bar visible", !(await page.locator("nav.tabbar").isVisible()));
|
|
|
|
// Locale switch from the settings dialog (desktop footer).
|
|
await page.locator("aside").getByRole("button", { name: "Nastavení" }).click();
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.waitFor({ timeout: 6000 });
|
|
await page.waitForTimeout(600);
|
|
await dialog.locator(".segmented").first().getByRole("button", { name: /English/ }).click();
|
|
await page.waitForTimeout(500);
|
|
await page.keyboard.press("Escape");
|
|
await page.waitForTimeout(600);
|
|
check("locale switch to English updates the UI", await page.getByRole("heading", { name: "Your icebox" }).isVisible());
|
|
await context.close();
|
|
}
|
|
|
|
/* 3. Czech phone tab bar + review card ------------------------------- */
|
|
{
|
|
const { context, page } = await makePage(browser);
|
|
await seed(page, { locale: "cs", decks: [DECK] });
|
|
await page.goto(BASE, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(900);
|
|
const tabTexts = (await page.locator("nav.tabbar").locator("a, button").allInnerTexts()).map((text) => text.trim());
|
|
check(
|
|
"cs tab labels are the short ones",
|
|
JSON.stringify(tabTexts) === JSON.stringify(["Knihovna", "Pokrok", "Nová sada", "Zkouška", "Nastavení"]),
|
|
JSON.stringify(tabTexts),
|
|
);
|
|
check("cs review card present", await page.getByText("Chytré opakování").isVisible());
|
|
check("cs exam tab label is not the long one", !tabTexts.includes("Zkouškový režim"));
|
|
await context.close();
|
|
}
|
|
|
|
await browser.close();
|
|
finish();
|