Next.js 15 site for گروه بازرگانی بین المللی آریو مجد آسیا, rebuilt from majdtrading.com. Persian, English and Arabic; RTL and LTR. - Pages: home, about, contact, six product categories, blog index and detail - i18n: prose in per-locale JSON, structure in TypeScript, drift checked by `npm run i18n:check`. Blog stays single-language by design - Meshkee backend: blog content, tenant resolution, session via the customer portal, and /meshkee/sitemap-config.json - Live market data from livedata.ir, cached server-side once a minute - Self-hosted fonts: IRANYekan (fa), IBM Plex Sans Arabic (ar), Space Grotesk and BBH Bartle (Latin) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
67 lines
2.3 KiB
JavaScript
67 lines
2.3 KiB
JavaScript
/**
|
|
* 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.");
|