47 lines
2 KiB
JavaScript
47 lines
2 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
// Converts locales/en-US/main.ftl into web/locales/en-US.json: a flat
|
||
|
|
// id -> string dictionary the browser can fetch with no parser of its own.
|
||
|
|
//
|
||
|
|
// Deliberately hand-rolled rather than a dependency on @fluent/syntax: the
|
||
|
|
// source file only ever uses Fluent's simplest shape (comments, flat
|
||
|
|
// `id = value` messages, `{ $var }` references, and `{"literal"}` string
|
||
|
|
// inserts for embedded newlines) — confirmed by grep before writing this,
|
||
|
|
// not assumed. If the file ever gains a select expression, a term, or a
|
||
|
|
// multiline value, this script will need to grow with it; until then a
|
||
|
|
// real parser is more dependency than the input justifies.
|
||
|
|
|
||
|
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||
|
|
import { dirname, join } from "node:path";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
|
||
|
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
||
|
|
const srcPath = join(root, "locales/en-US/main.ftl");
|
||
|
|
const outPath = join(root, "web/locales/en-US.json");
|
||
|
|
|
||
|
|
const MESSAGE = /^([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*(.*)$/;
|
||
|
|
// A Fluent string literal, e.g. {"\u000A"} — only used in this file to
|
||
|
|
// embed a literal newline inside an otherwise single-line value.
|
||
|
|
const LITERAL = /\{"((?:[^"\\]|\\.)*)"\}/g;
|
||
|
|
|
||
|
|
function unescapeLiteral(text) {
|
||
|
|
return text.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
||
|
|
.replace(/\\(.)/g, "$1");
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseFtl(text) {
|
||
|
|
const messages = {};
|
||
|
|
for (const line of text.split("\n")) {
|
||
|
|
if (!line || line.startsWith("#")) continue; // blank line or comment
|
||
|
|
const match = MESSAGE.exec(line);
|
||
|
|
if (!match) continue;
|
||
|
|
const [, id, rawValue] = match;
|
||
|
|
messages[id] = rawValue.replace(LITERAL, (_, inner) => unescapeLiteral(inner));
|
||
|
|
}
|
||
|
|
return messages;
|
||
|
|
}
|
||
|
|
|
||
|
|
const messages = parseFtl(readFileSync(srcPath, "utf8"));
|
||
|
|
mkdirSync(dirname(outPath), { recursive: true });
|
||
|
|
writeFileSync(outPath, JSON.stringify(messages, null, 2) + "\n");
|
||
|
|
console.log(`wrote ${Object.keys(messages).length} messages to ${outPath}`);
|