/** * Compares every locale file against the reference locale (fa). * * The `Dictionary` annotation on each wrapper already fails the build on a * MISSING key. This catches the other two cases it cannot see: keys left behind * after a rename, and arrays whose length drifted apart (a mission list of 14 * items in one language and 13 in another renders fine and is wrong). * * Run after editing fa.json, before touching en/ar: npm run i18n:check */ import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const here = dirname(fileURLToPath(import.meta.url)); const dir = join(here, "..", "src", "i18n", "locales"); const REFERENCE = "fa"; const OTHERS = ["en", "ar"]; const load = (l) => JSON.parse(readFileSync(join(dir, `${l}.json`), "utf8")); /** Flattens to `a.b.c` paths; arrays report as one path plus their length. */ function paths(value, prefix = "", out = new Map()) { if (Array.isArray(value)) { out.set(prefix, `array:${value.length}`); return out; } if (value && typeof value === "object") { for (const [k, v] of Object.entries(value)) { paths(v, prefix ? `${prefix}.${k}` : k, out); } return out; } out.set(prefix, typeof value); return out; } const reference = paths(load(REFERENCE)); let problems = 0; for (const locale of OTHERS) { const current = paths(load(locale)); const missing = [...reference.keys()].filter((k) => !current.has(k)); const stale = [...current.keys()].filter((k) => !reference.has(k)); const lengths = [...reference.entries()].filter( ([k, v]) => current.has(k) && v !== current.get(k) && v.startsWith("array") ); if (!missing.length && !stale.length && !lengths.length) { console.log(` ${locale}: in sync with ${REFERENCE} (${reference.size} keys)`); continue; } problems += missing.length + stale.length + lengths.length; console.log(` ${locale}:`); for (const k of missing) console.log(` missing ${k}`); for (const k of stale) console.log(` stale ${k} (not in ${REFERENCE})`); for (const [k, v] of lengths) console.log(` length ${k} ${REFERENCE}=${v} ${locale}=${current.get(k)}`); } if (problems) { console.error(`\ni18n check failed: ${problems} problem(s).`); process.exit(1); } console.log("\ni18n check passed.");