babel-plugin-ltag
Version:
a babel-plugin to transform l-template tags.
154 lines (122 loc) • 4 kB
JavaScript
const fs = require('fs');
const po = require("gettext-parser").po;
export default function ({ types: t }) {
let reHasParams = /%\d+/g;
var pots = {};
function addToPot(file, str, ctx, ref) {
if (!file) return;
if (!pots[file] && fs.existsSync(file))
pots[file] = po.parse(fs.readFileSync(file, 'utf8'));
let pot = pots[file] = pots[file] || {},
tran = pot.translations = pot.translations || {},
ctxs = tran[ctx] = tran[ctx] || {},
item = ctxs[str] = ctxs[str] || {};
if (!pot.charset) {
pot.charset = 'utf-8';
pot.headers = {
'content-type': 'text/plain; charset=utf-8'
};
}
item.msgctxt = ctx;
item.msgid = str;
let coms = item.comments = item.comments || {};
if (reHasParams.test(str))
coms.flag = 'kde-format';
if (coms.reference) {
if (coms.reference.indexOf(ref) < 0)
coms.reference += ' ' + ref;
}
else coms.reference = ref;
}
function savePotFiles() {
for (let file in pots) {
var data = po.compile(pots[file]);
fs.writeFileSync(file, data);
}
}
var poFiles = {};
function readPoFile(file, str, ctx) {
if (!file) return;
if (!poFiles[file]) {
if (!fs.existsSync(file)) return;
poFiles[file] = po.parse(fs.readFileSync(file, 'utf8'));
}
let tran = poFiles[file].translations,
item = (tran[ctx] || {})[str];
if (item) return item.msgstr[0];
}
// -------------------------------------------------------------------
function isString(node) {
return t.isLiteral(node) && typeof node.value === "string";
}
function buildBinary(left, right) {
return t.binaryExpression("+", left, right);
}
function translate(opts, str, ctx, ref) {
// save to pristine po-file
if (!ctx) ctx = '';
addToPot(opts.pot, str, ctx, ref);
return readPoFile(opts.po, str, ctx) || str;
}
function encode(str) {
return str.replace(/%/g, '%%');
}
// escape double quote
const reDeco = /\\[\\n%]/g;
const reHole = /(?:(.*?)%(\d+))|(?:.*$)/g;
return {
post: function (file) {
savePotFiles();
},
visitor: {
TaggedTemplateExpression(path, state) {
const { node } = path;
let ctx;
if (t.isCallExpression(node.tag) && t.isIdentifier(node.tag.callee, {name:'l'})) {
let args = path.get("tag.arguments");
for (let i=0; i<args.length; ++i) {
let e = args[i].evaluate();
if (!e.confident) return;
if (ctx) ctx += '|';
else ctx = '';
ctx += e.value;
}
}
else if (!t.isIdentifier(node.tag, {name:'l'})) return;
const { quasis, expressions: exps } = node.quasi;
const ref = (node.loc.filename || path.hub.file.opts.filename) + ':' + node.loc.start.line;
// #, kde-format
let srcStr = encode(quasis[0].value.cooked);
for (let i=1; i<quasis.length; ++i)
srcStr += '%' + (i-1) + encode(quasis[i].value.cooked);
// TODO: Plural Forms msgid_plural / msgstr[0, 1...]
let trStr = translate(state.opts, srcStr, ctx, ref);
// ----------------------------------------------------------------------------
// fill nodes with new orders
let nodes = [];
trStr
.replace(/%%/g, '\u0000')
.replace(reHole, function (a, b, c) {
let pre = (b===undefined ? a : b).replace(/\u0000/g, '%');
nodes.push(t.stringLiteral(pre));
if (c) nodes.push(exps[parseInt(c)]);
});
// filter out empty elements, insert a empty string at start
nodes = nodes.filter((n) => !t.isLiteral(n, { value: "" }));
if (!isString(nodes[0]) && !isString(nodes[1]))
nodes.unshift(t.stringLiteral(""));
// merge consecutive literals, converting them to strings
let sz = 1;
for (let i=1; i<nodes.length; ++i)
if (t.isLiteral(nodes[sz-1]) && t.isLiteral(nodes[i]))
nodes[sz-1] = t.stringLiteral(nodes[sz-1].value + nodes[i].value);
else nodes[sz++] = nodes[i];
nodes.length = sz;
let root = nodes.shift();
for (const node of nodes)
root = buildBinary(root, node);
path.replaceWith(root);
}
}
}
};