chore(i18n): key-parity + placeholder check script, catches untranslated keys
This commit is contained in:
1 parent
ddc3dd4262
commit
f3197ffc53
1 file changed
+32
@@ -0,0 +1,32 @@
|
||||
// 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");
|
||||
Reference in new issue
Block a user