Files
Recordly/scripts/i18n-check.mjs

98 lines
2.5 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
const root = process.cwd();
const localesDir = path.join(root, "src", "i18n", "locales");
const locales = fs
.readdirSync(localesDir)
.filter((entry) => {
const fullPath = path.join(localesDir, entry);
return fs.statSync(fullPath).isDirectory();
})
.sort((left, right) => left.localeCompare(right));
if (!locales.includes("en")) {
console.error('i18n-check: expected base locale directory "en"');
process.exit(1);
}
function loadJson(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`i18n-check: failed to load ${path.relative(root, filePath)}: ${message}`);
}
}
function collectKeyPaths(obj, prefix = "") {
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
return prefix ? [prefix] : [];
}
const keys = Object.keys(obj);
if (keys.length === 0) {
return prefix ? [prefix] : [];
}
const paths = [];
for (const key of keys) {
const nextPrefix = prefix ? `${prefix}.${key}` : key;
const value = obj[key];
if (value && typeof value === "object" && !Array.isArray(value)) {
paths.push(...collectKeyPaths(value, nextPrefix));
} else {
paths.push(nextPrefix);
}
}
return paths;
}
const baseLocaleDir = path.join(localesDir, "en");
const namespaceFiles = fs
.readdirSync(baseLocaleDir)
.filter((file) => file.endsWith(".json"))
.sort((left, right) => left.localeCompare(right));
let hasErrors = false;
for (const namespaceFile of namespaceFiles) {
const baseData = loadJson(path.join(baseLocaleDir, namespaceFile));
const baseKeys = new Set(collectKeyPaths(baseData));
for (const locale of locales) {
if (locale === "en") continue;
const localeFile = path.join(localesDir, locale, namespaceFile);
if (!fs.existsSync(localeFile)) {
console.error(`i18n-check: missing namespace file ${locale}/${namespaceFile}`);
hasErrors = true;
continue;
}
const localeData = loadJson(localeFile);
const localeKeys = new Set(collectKeyPaths(localeData));
for (const key of baseKeys) {
if (!localeKeys.has(key)) {
console.error(`i18n-check: missing key ${locale}/${namespaceFile}:${key}`);
hasErrors = true;
}
}
for (const key of localeKeys) {
if (!baseKeys.has(key)) {
console.error(`i18n-check: extra key ${locale}/${namespaceFile}:${key}`);
hasErrors = true;
}
}
}
}
if (hasErrors) {
process.exit(1);
}
console.log("i18n-check: locale files are structurally consistent");