Initial import: Majd Trading website
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>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.");
|
||||
Reference in New Issue
Block a user