| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- /*
- * Synchronize keys of settings.json across locales using zh-CN as canonical.
- * - Ensures every locale has exactly the same set of nested keys
- * - Keeps existing translations where keys exist
- * - Fills missing keys with zh-CN text as placeholder
- * - Drops extra keys not present in zh-CN (notably for en, but applies consistently)
- */
- const fs = require("fs");
- const path = require("path");
- const ROOT = process.cwd();
- const MESSAGES_DIR = path.join(ROOT, "messages");
- const LOCALES = ["en", "ja", "ru", "zh-TW"];
- const CANONICAL = "zh-CN";
- function isObject(v) {
- return v && typeof v === "object" && !Array.isArray(v);
- }
- function flatten(obj, prefix = "") {
- const out = {};
- for (const [k, v] of Object.entries(obj || {})) {
- const key = prefix ? `${prefix}.${k}` : k;
- if (isObject(v)) Object.assign(out, flatten(v, key));
- else out[key] = v;
- }
- return out;
- }
- function mergeWithCanonical(cn, target) {
- const result = Array.isArray(cn) ? [] : {};
- for (const [k, v] of Object.entries(cn)) {
- const tVal = target?.[k];
- if (isObject(v)) {
- // Canonical expects an object; only descend if target also has an object, else ignore target
- const tchild = isObject(tVal) ? tVal : {};
- result[k] = mergeWithCanonical(v, tchild);
- } else {
- // Canonical expects a leaf (string/number/bool/array/null). If target is an object, ignore it.
- if (Object.prototype.hasOwnProperty.call(target || {}, k) && !isObject(tVal)) {
- result[k] = tVal;
- } else {
- result[k] = v;
- }
- }
- }
- return result;
- }
- function sortKeysDeep(obj) {
- if (!isObject(obj)) return obj;
- const out = {};
- for (const key of Object.keys(obj).sort()) {
- out[key] = sortKeysDeep(obj[key]);
- }
- return out;
- }
- function loadJSON(p) {
- return JSON.parse(fs.readFileSync(p, "utf8"));
- }
- function saveJSON(p, data) {
- fs.writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf8");
- }
- function ensureSettings(locale) {
- const cnPath = path.join(MESSAGES_DIR, CANONICAL, "settings.json");
- const targetPath = path.join(MESSAGES_DIR, locale, "settings.json");
- const cn = loadJSON(cnPath);
- const t = loadJSON(targetPath);
- const merged = mergeWithCanonical(cn, t);
- // Drop extras implicitly by not copying unknown keys; merged contains only canonical keys
- const sorted = sortKeysDeep(merged);
- // Stats
- const cnKeys = Object.keys(flatten(cn));
- const tKeys = Object.keys(flatten(t));
- const mergedKeys = Object.keys(flatten(sorted));
- const missingBefore = cnKeys.filter((k) => !tKeys.includes(k));
- const extraBefore = tKeys.filter((k) => !cnKeys.includes(k));
- const missingAfter = cnKeys.filter((k) => !mergedKeys.includes(k));
- const extraAfter = mergedKeys.filter((k) => !cnKeys.includes(k));
- saveJSON(targetPath, sorted);
- return {
- locale,
- targetPath,
- cnCount: cnKeys.length,
- before: { count: tKeys.length, missing: missingBefore.length, extra: extraBefore.length },
- after: { count: mergedKeys.length, missing: missingAfter.length, extra: extraAfter.length },
- };
- }
- function main() {
- const reports = [];
- for (const loc of LOCALES) {
- const p = path.join(MESSAGES_DIR, loc, "settings.json");
- if (!fs.existsSync(p)) {
- console.error(`[skip] ${loc} has no settings.json`);
- continue;
- }
- reports.push(ensureSettings(loc));
- }
- // Print summary
- for (const r of reports) {
- console.log(
- `${r.locale}: cn=${r.cnCount}, before=${r.before.count} (-${r.before.missing} missing, +${r.before.extra} extra), after=${r.after.count} (-${r.after.missing} missing, +${r.after.extra} extra)`
- );
- }
- }
- if (require.main === module) {
- main();
- }
|