Files
HealthLog/scripts/i18n-key-tool.mjs
Marc-André Bombeck 764794496c feat(i18n): register Brazilian Portuguese as a locale
Add pt-BR to the locale list and everything that hangs off it: the
client loader, Intl and date-fns mapping, the reply-language footers of
the Coach and insights prompts (English body, pt-BR footer, the same
path Korean and Dutch take), the citation-coverage normative words, the
no-key fallback copy, the AUDIT-C standard-drink line, country labels,
period search, CSV yes/no cells and the Telegram bare-word aliases.

The locale enums in the extract, managed-profile and record-settings
schemas now derive from the locale list instead of repeating it, and
the five notification and login-alert routes share one isLocale guard
instead of hand-rolled chains, so a later locale is one edit there.
A language tag is matched exactly and case-insensitively before its
primary subtag, so pt-BR and pt-PT browsers land on the Portuguese
bundle rather than English, and AUDIT-C no longer cuts a region tag to
two letters.

PHQ-9, GAD-7 and WHO-5 are not marked validated for pt-BR: the wording
has not been read against the published Brazilian forms, so readers get
the English items plus the existing note.
2026-10-22 14:50:00 +02:00

84 lines
2.4 KiB
JavaScript

/**
* One-shot helper for a text sweep across the six locale bundles.
*
* node scripts/i18n-key-tool.mjs drop <dotted.key> [...]
* node scripts/i18n-key-tool.mjs set <locale> <dotted.key> <value>
* node scripts/i18n-key-tool.mjs show <dotted.key>
*
* Keys are dotted paths into the bundle; a segment containing a literal dot
* is not supported (no such key exists). `drop` removes the key from every
* locale and prunes an object left empty behind it.
*/
import { readFileSync, writeFileSync } from "node:fs";
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko", "nl", "pt-BR"];
const file = (loc) => `messages/${loc}.json`;
function read(loc) {
return JSON.parse(readFileSync(file(loc), "utf8"));
}
function write(loc, data) {
writeFileSync(file(loc), `${JSON.stringify(data, null, 2)}\n`, "utf8");
}
function drop(bundle, path) {
const parts = path.split(".");
const last = parts.pop();
const chain = [];
let node = bundle;
for (const part of parts) {
if (node == null || typeof node !== "object") return false;
chain.push([node, part]);
node = node[part];
}
if (node == null || !(last in node)) return false;
delete node[last];
for (let i = chain.length - 1; i >= 0; i -= 1) {
const [parent, key] = chain[i];
if (parent[key] && Object.keys(parent[key]).length === 0)
delete parent[key];
else break;
}
return true;
}
function set(bundle, path, value) {
const parts = path.split(".");
const last = parts.pop();
let node = bundle;
for (const part of parts) {
if (typeof node[part] !== "object" || node[part] === null) node[part] = {};
node = node[part];
}
node[last] = value;
}
function get(bundle, path) {
return path.split(".").reduce((n, p) => (n == null ? n : n[p]), bundle);
}
const [cmd, ...rest] = process.argv.slice(2);
if (cmd === "drop") {
for (const loc of LOCALES) {
const bundle = read(loc);
let n = 0;
for (const key of rest) if (drop(bundle, key)) n += 1;
write(loc, bundle);
console.log(`${loc}: dropped ${n}/${rest.length}`);
}
} else if (cmd === "set") {
const [loc, key, ...valueParts] = rest;
const bundle = read(loc);
set(bundle, key, valueParts.join(" "));
write(loc, bundle);
console.log(`${loc}: ${key} set`);
} else if (cmd === "show") {
for (const loc of LOCALES) {
console.log(`${loc}\t${JSON.stringify(get(read(loc), rest[0]))}`);
}
} else {
console.error("usage: drop|set|show");
process.exit(1);
}