/** * 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();