// The three catalogs, held to each other and to the money-word contract —
// plus portofel's own rule: this is a WALLET, and no catalog may talk like a
// chat room.
import test from "node:test";
import assert from "node:assert/strict";
import * as L from "../test-dist/testlib.js";
const LANGS = ["en", "ro", "hu"];
test("the three catalogs carry exactly the same keys", () => {
const en = [...L.catalogKeys("en")];
for (const lang of ["ro", "hu"]) {
assert.deepEqual([...L.catalogKeys(lang)], en, `en vs ${lang}`);
}
});
test("no catalog says the word the protocol forbids a screen to say", () => {
// banca's rule, inherited whole: the fold's own word for not-yet-pinned
// money is `settled`, and in every other financial interface that word
// means "done". It reaches no screen. English word-boundary checks; ro/hu
// are held to the key parity above and to review.
for (const k of [...L.catalogKeys("en")]) {
const v = L.catalogValue("en", k);
const text = typeof v === "string" ? v : Object.values(v).join(" ");
assert.ok(!/\bsettled\b/i.test(text), `${k} says "settled"`);
}
});
test("a wallet does not talk like a chat room", () => {
// The screen-shape rule made checkable: no EN value (nor key) reaches for
// the rooms-line vocabulary. "bank" is the noun here; a "room" is an
// implementation detail no user of a wallet should meet.
for (const k of [...L.catalogKeys("en")]) {
const v = L.catalogValue("en", k);
const text = typeof v === "string" ? v : Object.values(v).join(" ");
for (const word of [/\broom(s)?\b/i, /\bchat\b/i, /\bmessage(s)?\b/i]) {
assert.ok(!word.test(text), `${k} says ${word}: "${text}"`);
assert.ok(!word.test(k), `key ${k} is room/chat-shaped`);
}
}
});
test("every outstanding-money word ships with its sentence", () => {
// The three totals are three different claims, and each word must carry its
// meaning beside it — a chip reading "provisional" with the reason parked in
// a tooltip is the same lie with an extra step.
for (const lang of LANGS) {
const keys = new Set(L.catalogKeys(lang));
for (const word of ["provisional", "unpinned", "locked"]) {
assert.ok(keys.has(`wallet.${word}`), `${lang}: wallet.${word}`);
assert.ok(keys.has(`wallet.${word}.warn`), `${lang}: wallet.${word}.warn`);
}
}
});
test("the six state words exist in every language", () => {
for (const lang of LANGS) {
const keys = new Set(L.catalogKeys(lang));
for (const s of ["final", "provisional", "unpinned", "failed", "locked", "returned"]) {
assert.ok(keys.has(`pay.state.${s}`), `${lang}: pay.state.${s}`);
}
}
});
test("every placeholder an English string uses exists in the translations", () => {
// A translation may REORDER placeholders, never lose or invent one.
const ph = (s) => [...String(s).matchAll(/\{(\w+)\}/g)].map((m) => m[1]).sort();
for (const k of [...L.catalogKeys("en")]) {
const en = L.catalogValue("en", k);
for (const lang of ["ro", "hu"]) {
const tr = L.catalogValue(lang, k);
if (typeof en === "string" && typeof tr === "string") {
assert.deepEqual(ph(tr), ph(en), `${lang}:${k}`);
} else if (typeof en === "object" && typeof tr === "object") {
// plural maps: every variant must use the union of the en variants'
// placeholders — {n} may legitimately vanish in a "one" form
const enAll = [...new Set(Object.values(en).flatMap((s) => ph(s)))].sort();
for (const [variant, s] of Object.entries(tr)) {
for (const p of ph(s)) {
assert.ok(enAll.includes(p), `${lang}:${k}.${variant} invents {${p}}`);
}
}
}
}
}
});