/** * Harvest EN→locale phrase map from already-translated message keys, * then merge into phrase-map.json (does not overwrite non-empty existing locales). * * Run: node apps/web/scripts/harvest-phrase-map.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"]; 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 load(code) { return parseMessageDict(fs.readFileSync(path.join(messagesDir, `${code}.ts`), "utf8")); } const en = load("en"); const packs = Object.fromEntries(LOCALES.map((c) => [c, load(c)])); const map = fs.existsSync(mapPath) ? JSON.parse(fs.readFileSync(mapPath, "utf8")) : {}; let harvestedPhrases = 0; let filledSlots = 0; let newPhrases = 0; for (const [key, enVal] of Object.entries(en)) { if (!enVal || typeof enVal !== "string") continue; const byLoc = {}; let good = 0; for (const code of LOCALES) { const v = packs[code][key]; if (typeof v === "string" && v.trim() && v !== enVal) { byLoc[code] = v; good += 1; } } if (good < 4) continue; const existing = map[enVal] ?? {}; let changed = false; const next = { ...existing }; for (const code of LOCALES) { if ((!next[code] || !String(next[code]).trim()) && byLoc[code]) { next[code] = byLoc[code]; filledSlots += 1; changed = true; } } if (changed) { if (!map[enVal]) newPhrases += 1; map[enVal] = next; harvestedPhrases += 1; } } fs.writeFileSync(mapPath, JSON.stringify(map, null, "\t") + "\n", "utf8"); console.log({ phraseCount: Object.keys(map).length, harvestedPhrases, newPhrases, filledSlots, });