@dvhb/react-intl-messages
Version:
Library for parsing source files and extract react-intl messages
126 lines (125 loc) • 5.26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("@oclif/command");
const core_1 = require("@babel/core");
const path = require("path");
const base_1 = require("../base");
const utils_1 = require("../utils");
class Extract extends base_1.Base {
constructor() {
super(...arguments);
this.messages = {};
this.fileToMessages = {};
this.processFile = async (filename) => {
const compare = (a, b) => (a === b ? 0 : a < b ? -1 : 1);
try {
const code = await utils_1.readFile(filename);
const posixName = utils_1.posixPath(filename);
const result = core_1.transform(code, { filename, plugins: ['react-intl'] });
if (result && result.metadata) {
// @ts-ignore
const metadata = result.metadata['react-intl'];
if (metadata.messages && metadata.messages.length) {
const messages = metadata.messages;
this.fileToMessages[posixName] = messages.sort((a, b) => compare(a.id, b.id));
}
else {
delete this.fileToMessages[posixName];
}
}
}
catch (err) {
utils_1.showError(`extractMessages: In ${filename}:\n${err.codeFrame || err}`);
}
};
}
static async writeMessages(fileName, msgs) {
return utils_1.writeFile(fileName, `${JSON.stringify(msgs, null, 2)}\n`);
}
/**
* Merge messages to source files
* @param locale
*/
async mergeToFile(locale) {
const { flags: { messagesDir }, } = this.parse(Extract);
const fileName = path.join(messagesDir, `${locale}.json`);
const originalMessages = {};
try {
const oldFile = await utils_1.readFile(fileName);
let oldJson;
try {
oldJson = JSON.parse(oldFile);
}
catch (err) {
throw new Error(`Error parsing messages JSON in file ${fileName}`);
}
oldJson.forEach((message) => {
originalMessages[message.id] = message;
delete originalMessages[message.id].files;
});
}
catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
Object.keys(this.messages).forEach(id => {
const newMsg = this.messages[id];
originalMessages[id] = originalMessages[id] || { id };
const msg = originalMessages[id];
msg.description = newMsg.description || msg.description;
msg.defaultMessage = newMsg.defaultMessage || msg.defaultMessage;
msg.message = msg.message || '';
msg.files = newMsg.files;
});
const result = Object.keys(originalMessages)
.map(key => originalMessages[key])
.filter(msg => msg.files || msg.message);
await Extract.writeMessages(fileName, result);
utils_1.showInfo(`Messages updated: ${fileName}`);
}
mergeMessages() {
this.messages = {};
const messages = this.messages;
Object.keys(this.fileToMessages).forEach(fileName => {
this.fileToMessages[fileName].forEach(newMsg => {
const message = messages[newMsg.id] || {};
messages[newMsg.id] = {
description: newMsg.description || message.description,
defaultMessage: newMsg.defaultMessage || message.defaultMessage,
message: newMsg.message || message.message || '',
files: message.files ? [...message.files, fileName].sort() : [fileName],
};
});
});
}
/**
* Extract react-intl messages and write it to <dest>/_default.json
* Also extends known localizations
*/
async run() {
const { flags: { langs, pattern, ignore }, } = this.parse(Extract);
const locales = langs.split(',');
const ignorePattern = ignore && ignore.includes(',') ? ignore.split(',') : ignore;
const files = await utils_1.glob(pattern, ignorePattern);
await Promise.all(files.map(this.processFile));
this.mergeMessages();
await Promise.all(['_default', ...locales].map(locale => this.mergeToFile(locale)));
}
}
exports.default = Extract;
Extract.description = 'Extract translations from source files to json';
Extract.examples = [
`$ messages extract --langs=en,fr,de,ru --pattern="src/**/*.{ts,tsx}"
`,
];
Extract.flags = Object.assign(Object.assign(Object.assign({}, base_1.Base.flags), base_1.Base.langFlags), { pattern: command_1.flags.string({
char: 'p',
description: 'Regex mask for files',
default: () => (base_1.Base.cosmiconfig ? base_1.Base.cosmiconfig.pattern : undefined),
required: true,
}), ignore: command_1.flags.string({
char: 'i',
description: 'Regex mask for ignored files',
default: () => (base_1.Base.cosmiconfig ? base_1.Base.cosmiconfig.ignore : undefined),
}) });