Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
Reference in New Issue
Block a user