33 lines
1.5 KiB
JavaScript
33 lines
1.5 KiB
JavaScript
// i18n key-parity check: every locale file must have exactly en.json's keys,
|
|
// and every translation must use the same {placeholders} as its English
|
|
// source. Runs as part of `npm run lint` — English fallback otherwise hides
|
|
// missing translations silently (they shipped untranslated for months once).
|
|
import { readFileSync, readdirSync } from "node:fs";
|
|
|
|
const DIR = new URL("../src/lib/i18n/", import.meta.url);
|
|
const en = JSON.parse(readFileSync(new URL("en.json", DIR), "utf8"));
|
|
const placeholders = (s) =>
|
|
[...s.matchAll(/\{(\w+)\}/g)]
|
|
.map((m) => m[1])
|
|
.sort()
|
|
.join(",");
|
|
|
|
let failed = false;
|
|
for (const file of readdirSync(DIR).filter((f) => f.endsWith(".json") && f !== "en.json")) {
|
|
const dict = JSON.parse(readFileSync(new URL(file, DIR), "utf8"));
|
|
const missing = Object.keys(en).filter((k) => !(k in dict));
|
|
const extra = Object.keys(dict).filter((k) => !(k in en));
|
|
const badVars = Object.keys(dict).filter(
|
|
(k) => k in en && placeholders(dict[k]) !== placeholders(en[k]),
|
|
);
|
|
for (const k of missing) console.error(`${file}: missing "${k}"`);
|
|
for (const k of extra) console.error(`${file}: extra "${k}" (not in en.json)`);
|
|
for (const k of badVars) console.error(`${file}: "${k}" placeholders differ from en.json`);
|
|
if (missing.length || extra.length || badVars.length) failed = true;
|
|
}
|
|
if (failed) {
|
|
console.error("\ni18n parity check FAILED — keep every locale in lockstep with en.json.");
|
|
process.exit(1);
|
|
}
|
|
console.log("i18n parity OK");
|