Skip to content

Instantly share code, notes, and snippets.

@leye0
Created December 11, 2018 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leye0/5cb118fa6821ab81122864ac8e57d0e1 to your computer and use it in GitHub Desktop.
Save leye0/5cb118fa6821ab81122864ac8e57d0e1 to your computer and use it in GitHub Desktop.
xlf-translate.js - Support ng-bootstrap i18n entry id format
#!/usr/bin/env node
'use strict';
// Based on
// Fix on https://github.com/tsvetomir/xlf-translate to...
// -> support ID fields directly on a trans-unit 'id' attribute instead of trans-unit.node.meaning (ex: ng-bootstrap i18n)
// -> support description generated from id and source instead of missing trans-unit.node.description
// See: https://github.com/tsvetomir/xlf-translate/issues/9
const jp = require('jsonpath');
const yaml = require('js-yaml');
const fs = require('fs');
const cheerio = require('cheerio');
const indent = node => {
const indent = node.parent().html().match(/^\s+/);
return indent !== null ? indent[0] : '';
};
/**
* Fills in translations in XLIFF files based on 'meaning' metadata as a key.
*
* @param doc an XLIFF document parsed with cheerio
* @param lang a tree of keys and their corresponding translations
* @return statistics the messages will be filled in-place
*/
const translate = (doc, lang, force) => {
const units = doc('trans-unit').toArray();
let stats = { count: 0, skip: 0 };
units
// .filter(unit => unit.find('note').length > 0) // Fix for ng-bootstrap;
.map(unit => {
const source = doc(unit).find('source');
let target = doc(unit).find('target');
if (target.length === 0 && source.length === 1) {
target = doc('<target />');
source.after(indent(source), target);
}
return {
target: target,
id: doc(unit).find('note[from=meaning]').text() || (unit.attribs ? unit.attribs['id'] : null) // Fix for ng-bootstrap;
};
})
.filter(({ id }) => id && isKey(id))
.forEach(({ id, target }) => {
const parts = id.split('.').map(part => `['${part}']`);
const query = '$' + parts.join('');
const match = jp.query(lang, query);
if (match.length === 1) {
if (target.text() === '' || force) {
target.text(match[0]);
stats.count++;
} else if (!force) {
stats.skip++;
}
}
});
return stats;
};
const isKey = val => val.match(/^[a-zA-Z0-9.\-_]*$/); // Fix for ng-bootstrap;
const args = (() => {
const argparse = require('argparse');
const parser = new argparse.ArgumentParser({
addHelp: true,
description: 'Looks-up translations based on unique keys'
});
parser.addArgument('file', {
help: 'XLIFF (.xlf) file to translate'
});
parser.addArgument([ '-l', '--lang-file' ], {
help: 'YAML file containing translated messages',
required: true
});
parser.addArgument([ '-f', '--force' ], {
help: 'Overwrites existing translation targets',
defaultValue: false,
action: 'storeTrue'
});
parser.addArgument([ '-e', '--encoding' ], {
help: 'Specifies the XLIFF file encoding. Default is "utf-8".',
defaultValue: 'utf-8'
});
return parser.parseArgs();
})();
const langData = fs.readFileSync(args.lang_file);
const lang = yaml.safeLoad(langData);
const messageData = fs.readFileSync(args.file, { encoding: args.encoding });
const messages = cheerio.load(messageData, { xmlMode: true, decodeEntities: false });
const stats = translate(messages, lang, args.force);
const out = messages.html();
fs.writeFileSync(args.file, out);
console.log(`Done. Replaced ${stats.count} units, skipped ${stats.skip}.`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment