// Catalog fidelity — the CLJS replacement for the TS-era MsgKey type check.
// Two gates: every EN key exists in ro/hu with nothing stray (a missing
// translation fails the suite), and every message VALUE is byte-equal to the
// TS catalogs' snapshot in vectors/i18n.json (so the port never quietly
// reworded a string).
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";
const ts = JSON.parse(readFileSync(new URL("./vectors/i18n.json", import.meta.url), "utf8"));
const en = [...lib.catalogKeys("en")];
test("EN catalog is non-empty and carries the social.* block", () => {
assert.ok(en.length >= 40);
assert.ok(en.some((k) => k.startsWith("social.")));
});
for (const lang of ["ro", "hu"]) {
test(`${lang} covers every EN key, and nothing else`, () => {
assert.deepEqual([...lib.catalogKeys(lang)], en);
});
}
for (const lang of ["en", "ro", "hu"]) {
test(`${lang} messages are byte-identical to the TS catalog`, () => {
assert.deepEqual([...lib.catalogKeys(lang)], Object.keys(ts[lang]).sort(), `${lang} key set`);
for (const k of Object.keys(ts[lang])) {
const got = lib.catalogMsg(lang, k);
const want = ts[lang][k];
if (typeof want === "string") {
assert.equal(got, want, `${lang} ${k}`);
} else {
// a plural site: compare the variant map, categories included
assert.deepEqual(JSON.parse(JSON.stringify(got)), want, `${lang} ${k}`);
}
}
});
}
test("plural sites: ro one/few/other, hu other-only, en one/other", () => {
const cats = (lang, k) => Object.keys(JSON.parse(JSON.stringify(lib.catalogMsg(lang, k)))).sort();
for (const k of ["rec.bars"]) {
assert.deepEqual(cats("en", k), ["one", "other"], `en ${k}`);
assert.deepEqual(cats("ro", k), ["few", "one", "other"], `ro ${k}`);
assert.deepEqual(cats("hu", k), ["other"], `hu ${k}`);
}
});
test("wire-adjacent strings are never translated (brand + protocol)", () => {
for (const lang of ["en", "ro", "hu"]) {
for (const k of lib.catalogKeys(lang)) {
const m = lib.catalogMsg(lang, k);
if (typeof m === "string") assert.ok(!m.includes("/odeon/"), `${lang} ${k}`);
}
}
});
test("music notation never enters a catalog (pad names, note names)", () => {
// The pad and note names are drawn straight from the code, never through
// t() — so no catalog may carry one as a key or as a whole message.
const notation = new Set([...lib.padNames, ...lib.noteNames]);
for (const lang of ["en", "ro", "hu"]) {
for (const k of lib.catalogKeys(lang)) {
assert.ok(!notation.has(k), `${lang} has a notation key ${k}`);
const m = lib.catalogMsg(lang, k);
if (typeof m === "string") {
assert.ok(!notation.has(m.trim()), `${lang} ${k} translates the notation "${m}"`);
}
}
}
});
test("index.html's baked English matches the catalog for every data-i18n key", () => {
const html = readFileSync(new URL("../public/index.html", import.meta.url), "utf8");
const seen = [];
for (const m of html.matchAll(/data-i18n="([^"]+)"[^>]*>([^<]*)</g)) {
seen.push([m[1], m[2]]);
}
assert.ok(seen.length >= 8, "markup carries data-i18n keys");
for (const [key, text] of seen) {
assert.equal(lib.catalogMsg("en", key), text, `data-i18n=${key}`);
}
// and every data-i18n-attr target resolves too
for (const m of html.matchAll(/data-i18n-attr="([^"]+)"/g)) {
for (const pair of m[1].split(",")) {
const key = pair.split(":")[1];
assert.ok(lib.catalogMsg("en", key) !== null, `data-i18n-attr key ${key}`);
}
}
});