rollup-plugin-drupal-interface-translations
Version:
This is a [Rollup](https://rollupjs.org) plugin that helps you to [translation strings in JavaScript](https://www.drupal.org/docs/8/api/translation-api/overview#s-translation-in-javascript-files) that are used in [Drupal](https://www.drupal.org) websites.
162 lines (129 loc) • 3.72 kB
JavaScript
import { relative } from 'node:path';
const TRANSLATION_FUNCTIONS_REG_EXP = (function () {
const translationFunctions = ['Drupal.t', 'Drupal.formatPlural']
.map((keypath) =>
keypath.replace(/\*/g, '\\w+').replace(/\./g, '\\s*\\.\\s*'),
)
.map((keypath) => '(?:\\b\\w+\\.|)' + keypath);
return new RegExp(`^(?:${translationFunctions.join('|')})$`);
})();
function getName(node) {
if (node.type === 'Identifier') {
return node.name;
}
if (node.type === 'ThisExpression') {
return 'this';
}
if (node.type === 'Super') {
return 'super';
}
return null;
}
function addLoc(msg, expression, code) {
const lines = code.substring(0, expression.start);
const splitLines = lines.split('\n');
const line = splitLines.length;
const lastLine = splitLines[line - 1];
const column = lastLine.length;
const loc = {
start: {
...expression.start,
line,
column,
},
end: expression.end,
};
const reference = {
loc,
};
msg.references.push(reference);
}
function flatten(node) {
const parts = [];
while (node.type === 'MemberExpression') {
if (node.computed) {
return null;
}
parts.unshift(node.property.name);
node = node.object;
}
const name = getName(node);
if (!name) {
return null;
}
parts.unshift(name);
return parts.join('.');
}
function generateKey(msgId, msgIdPlural, msgCtxt) {
const messageIdPart = JSON.stringify({ msgId, msgIdPlural });
const messageContextPart = msgCtxt ? JSON.stringify(msgCtxt) : '';
return `msgid<${messageIdPart}>;msgctxt<${messageContextPart}>`;
}
export default class MessagesBuilder {
constructor(cwd) {
this.msgs = {};
this.cwd = cwd;
}
build() {
return Object.values(this.msgs);
}
static isDrupalTranslationFunction(node) {
if (node.type !== 'CallExpression') {
return false;
}
const keypath = flatten(node.callee);
return keypath && TRANSLATION_FUNCTIONS_REG_EXP.test(keypath);
}
extractTranslationCall(id, node, code) {
const keypath = flatten(node.callee);
if (keypath === 'Drupal.formatPlural') {
this.#extractFormatPlural(id, node, code);
} else {
this.#extractT(id, node, code);
}
}
// private
#contextReducer(prev, curr) {
if (curr.key.name === 'context' && curr.value.type === 'Literal') {
return curr.value.value;
}
return prev;
}
#extractT(id, expression, code) {
const msgId = expression.arguments[0]?.value;
const properties = expression.arguments[2]?.properties;
const msgCtxt = properties?.reduce(this.#contextReducer, undefined);
const msg = this.#getOrCreate(msgId, msgCtxt, null);
addLoc(msg, expression, code);
this.#assignReferences(id, msg);
return msg;
}
#extractFormatPlural(id, expression, code) {
const msgId = expression.arguments[1]?.value;
const msgIdPlural = expression.arguments[2]?.value;
const properties = expression.arguments[4]?.properties;
const msgCtxt = properties?.reduce(this.#contextReducer, undefined);
const msg = this.#getOrCreate(msgId, msgCtxt, msgIdPlural);
addLoc(msg, expression, code);
this.#assignReferences(id, msg);
return msg;
}
#getOrCreate(msgId, msgCtxt, msgIdPlural = undefined) {
const key = generateKey(msgId, msgIdPlural, msgCtxt);
if (!Object.hasOwn(this.msgs, key)) {
this.msgs[key] = {
msgId,
msgCtxt,
msgIdPlural,
references: [],
};
}
return this.msgs[key];
}
#assignReferences(id, msg) {
Object.values(msg.references).forEach((ref) => {
ref.path = id;
ref.relativePath = relative(this.cwd, id);
});
}
}