Files
wyndham-Condon/tests/i18n.test.mjs
2026-08-03 17:06:58 +08:00

82 lines
3.7 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import vm from "node:vm";
const appSource = await readFile(new URL("../app.js", import.meta.url), "utf8");
const htmlSource = await readFile(new URL("../index.html", import.meta.url), "utf8");
const cssSource = await readFile(new URL("../styles.css", import.meta.url), "utf8");
function loadDictionaries() {
const start = appSource.indexOf("const I18N = ");
const end = appSource.indexOf("\n\nconst $ =", start);
assert.notEqual(start, -1, "I18N block should exist");
assert.notEqual(end, -1, "I18N block should have a stable end marker");
const expression = appSource.slice(start + "const I18N = ".length, end).trim().replace(/;$/, "");
return vm.runInNewContext(`(${expression})`, {
formatNumber: value => String(value)
});
}
const dictionaries = loadDictionaries();
const locales = Object.keys(dictionaries);
function placeholderNames(value) {
if (typeof value === "function") {
const match = value.toString().match(/\(\s*\{([^}]*)\}\s*\)/);
return match
? match[1].split(",").map(name => name.trim().split(/\s*=/)[0]).filter(Boolean).sort()
: [];
}
return [...String(value).matchAll(/\{(\w+)\}/g)].map(match => match[1]).sort();
}
test("all three locale dictionaries have identical keys", () => {
assert.deepEqual(locales, ["en", "zh", "th"]);
const expected = Object.keys(dictionaries.en).sort();
assert.equal(expected.length, 217);
for (const locale of locales) {
assert.deepEqual(Object.keys(dictionaries[locale]).sort(), expected, `${locale} key parity`);
}
});
test("parameter placeholders stay aligned across locales", () => {
for (const key of Object.keys(dictionaries.en)) {
const expected = placeholderNames(dictionaries.en[key]);
for (const locale of locales.slice(1)) {
assert.deepEqual(placeholderNames(dictionaries[locale][key]), expected, `${locale}.${key} placeholders`);
}
}
});
test("all literal translation references resolve in every locale", () => {
const referenced = new Set();
for (const match of appSource.matchAll(/\bt\(\s*["']([^"']+)["']/g)) referenced.add(match[1]);
for (const match of htmlSource.matchAll(/data-i18n(?:-placeholder|-aria-label)?="([^"]+)"/g)) referenced.add(match[1]);
for (const key of referenced) {
for (const locale of locales) assert.ok(key in dictionaries[locale], `${locale}.${key} is defined`);
}
assert.ok(referenced.size >= 100, `expected broad translation coverage, got ${referenced.size}`);
});
test("Thai locale metadata and three-way controls are present", () => {
assert.match(appSource, /th:\s*Object\.freeze\(\{ html: "th", intl: "th-TH-u-ca-gregory-nu-latn"/);
assert.equal((htmlSource.match(/data-login-language=/g) || []).length, 3);
assert.equal((htmlSource.match(/data-language=/g) || []).length, 3);
assert.match(cssSource, /grid-template-rows: repeat\(3, 1fr\)/);
assert.match(cssSource, /grid-template-columns: repeat\(3, 1fr\)/);
assert.match(cssSource, /\[data-locale="th"\][^{]*\{ transform: translateY\(200%\); \}/);
assert.match(cssSource, /\[data-locale="th"\][^{]*\{ transform: translateX\(200%\); \}/);
});
test("Thai messages do not fall back for representative runtime states", () => {
const keys = [
"signIn", "invalidCredentials", "sessionExpired", "runtimeApiLoadingMessage", "accountsShown",
"recordsShown", "checkoutLater", "insufficientPrivileges", "usageSaveNetwork", "usageSavedMessage"
];
for (const key of keys) {
assert.notEqual(dictionaries.th[key], undefined, `Thai key ${key}`);
if (typeof dictionaries.th[key] === "string") assert.notEqual(dictionaries.th[key], dictionaries.en[key], `Thai copy for ${key}`);
}
});