/** * MERGE-preserve recovery: fill locale values that still equal English * from phrase-map.json. Never overwrite a value that already differs from en. * * Run: node apps/web/scripts/merge-preserve-packs.mjs */ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const messagesDir = path.resolve(__dirname, "../src/lib/i18n/messages"); const mapPath = path.join(__dirname, "phrase-map.json"); const LOCALES = ["es", "fr", "de", "it", "pt", "nl", "pl", "ja"]; const comments = { es: "Spanish (es) UI pack — keys must stay in sync with en.ts.", fr: "French (fr) UI pack — keys must stay in sync with en.ts.", de: "German (de) UI pack — keys must stay in sync with en.ts.", it: "Italian (it) UI pack — keys must stay in sync with en.ts.", pt: "Portuguese (pt) UI pack — keys must stay in sync with en.ts.", nl: "Dutch (nl) UI pack — keys must stay in sync with en.ts.", pl: "Polish (pl) UI pack — keys must stay in sync with en.ts.", ja: "Japanese (ja) UI pack — keys must stay in sync with en.ts.", }; function parseMessageDict(source) { const dict = {}; const re = /"([^"\\]+)"\s*:\s*((?:"(?:\\.|[^"\\])*")|(?:`(?:\\.|[^`\\])*`))/gs; let m; while ((m = re.exec(source))) { const key = m[1]; const raw = m[2]; dict[key] = raw.startsWith("`") ? raw.slice(1, -1).replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\n/g, "\n") : JSON.parse(raw); } return dict; } function emitPack(exportName, comment, dict, keyOrder) { const lines = [ `import type { MessageDict } from "./types";`, ``, `/** ${comment} */`, `export const ${exportName}: MessageDict = {`, ]; for (const key of keyOrder) { lines.push(`\t${JSON.stringify(key)}: ${JSON.stringify(dict[key])},`); } lines.push(`};`, ``); return lines.join("\n"); } function load(code) { return parseMessageDict(fs.readFileSync(path.join(messagesDir, `${code}.ts`), "utf8")); } if (!fs.existsSync(mapPath)) { console.error(`missing phrase-map: ${mapPath}`); process.exit(1); } const phraseMap = JSON.parse(fs.readFileSync(mapPath, "utf8")); const en = load("en"); const keyOrder = Object.keys(en); let totalApplied = 0; let totalSkippedGood = 0; let totalNoMap = 0; const byLocale = {}; for (const code of LOCALES) { const pack = load(code); const next = { ...pack }; let applied = 0; let skippedGood = 0; let noMap = 0; let missingKeys = 0; for (const key of keyOrder) { const enVal = en[key]; const cur = next[key]; if (typeof cur === "string" && cur !== enVal) { skippedGood += 1; continue; } // Identical to EN (or missing) — try phrase-map by English string const mapped = phraseMap[enVal]; const tr = mapped && typeof mapped[code] === "string" ? mapped[code].trim() : ""; if (tr && tr !== enVal) { next[key] = mapped[code]; applied += 1; continue; } if (!(key in next)) { next[key] = enVal; missingKeys += 1; } noMap += 1; } fs.writeFileSync( path.join(messagesDir, `${code}.ts`), emitPack(code, comments[code], next, keyOrder), "utf8", ); byLocale[code] = { applied, skippedGood, noMap, missingKeys }; totalApplied += applied; totalSkippedGood += skippedGood; totalNoMap += noMap; console.log( `${code}: applied=${applied} skippedGood=${skippedGood} noMap=${noMap} missingKeys=${missingKeys}`, ); } console.log("---"); console.log( JSON.stringify( { phraseMapEntries: Object.keys(phraseMap).length, enKeys: keyOrder.length, totalApplied, totalSkippedGood, totalNoMap, byLocale, }, null, 2, ), );