Skip to content

Instantly share code, notes, and snippets.

@studentIvan
Last active March 5, 2018 16:02
Show Gist options
  • Save studentIvan/c7566dcf03b363a33f10e5390bfc85e9 to your computer and use it in GitHub Desktop.
Save studentIvan/c7566dcf03b363a33f10e5390bfc85e9 to your computer and use it in GitHub Desktop.
Translation react script for plugin babel-plugin-jsx-i18n-tag-translate-replacer and transifex
{
"scripts": {
"script:translations:pull": "BABEL_ENV='node' babel-node util/translations/translations.script.js pull",
"script:translations:generate": "BABEL_ENV='node' babel-node util/translations/translations.script.js generate"
},
"dependencies": {
"babel-plugin-jsx-i18n-tag-translate-replacer": "github:studentIvan/babel-plugin-jsx-i18n-tag-translate-replacer",
"transifex-api-es6": "github:studentIvan/transifex-api-es6#fix-deps"
}
}
/* eslint camelcase: "off" */
/* eslint import/extensions: "off" */
/* eslint no-console: "off" */
/* eslint no-param-reassign: "off" */
/* eslint no-cond-assign: "off" */
/* eslint no-restricted-syntax: "off" */
import TransifexAPI from 'transifex-api-es6/src/TransifexApi';
import mkdirp from 'mkdirp';
import path from 'path';
import fs from 'fs';
import PO from 'pofile';
import { readFile, writeFile } from '../common/node-files';
import config from '../../config';
const translationsDir = path.join(__dirname, '../../../translations');
const webTranslateDir = path.join(__dirname, '../../src/lib/services/language/');
const srcDir = path.join(__dirname, '../../../web/src/');
const api = new TransifexAPI(config.transifex);
const mode = process.argv.pop();
(async () => {
if (mode !== 'pull') return;
/** get list of available languages */
const languages = (await api.getProjectLanguages())
.map(({ language_code }) => language_code);
await languages.forEach(async (language) => {
/** setup language directory */
const langDir = path.join(translationsDir, language);
mkdirp.sync(langDir);
/** download translation and save it */
const translationPO = await api.getResourceTranslation(language);
const translationJSON = await api.getTranslationStrings(language);
await writeFile(path.join(langDir, 'translation.po'), translationPO);
await writeFile(path.join(langDir, 'translation.json'), JSON.stringify(translationJSON, null, 2));
/** minify translation file for web using */
const minifiedTranslateJS = {};
const setTranslation = (source, translation) => {
if (!/[<>]/g.test(source)) {
minifiedTranslateJS[source] = translation;
}
else {
console.log('source_string', JSON.stringify(source), 'ignored, because html');
}
};
translationJSON.forEach((translateObj) => {
const { pluralized, source_string, translation } = translateObj;
if (!pluralized) {
setTranslation(source_string, translation);
}
else {
Object.keys(source_string).forEach((key) => {
setTranslation(source_string[key], translation[key]);
});
}
});
await writeFile(path.join(webTranslateDir, `translation.${ language }.json`), JSON.stringify(minifiedTranslateJS, null, 2));
});
})();
/** generate pot file */
(async () => {
if (mode !== 'generate') return;
/** step 1 - collect strings */
const xFiles = [];
const searchFilesInDirectory = (startPath, filter) => {
if (!fs.existsSync(startPath)) {
console.log('no directory found', startPath);
}
const files = fs.readdirSync(startPath);
for (let i = 0; i < files.length; i += 1) {
const filename = path.join(startPath, files[i]);
const stat = fs.lstatSync(filename);
if (stat.isDirectory()) {
searchFilesInDirectory(filename, filter);
}
else if (filename.indexOf(filter) >= 0) {
xFiles.push(filename);
}
}
};
/** fill xFiles array */
searchFilesInDirectory(srcDir, '.js');
const translateTag = /<translate>([^<]+)<\/translate>/g; // $1 str
const translateAttribute = /<[^></]+translate[^>]*?>([^<]+)/g; // $1 str
const translatePluralNode = /<translate[^>]*?plural[^>]*?\/>/g; // $0 node
const translatePluralNodeAttr = /(\w+)=['"]([^'"]+)['"]/g; // $1 attr $2 value
const potStrings = new Map();
const potStringsPlural = new Map();
const potStringCollected = new Promise((resolve, reject) => {
xFiles.forEach(async (srcFile, index) => {
const src = await readFile(srcFile);
src.split(/[\r\n]/).forEach((line, i) => {
let matches;
const fileLineNumber = `${ path.relative(path.join(srcDir, '../../'), srcFile) }:${ i + 1 }`;
while ((matches = translateTag.exec(line)) !== null) {
const sourceString = matches[1];
if (potStrings.has(sourceString)) {
potStrings.set(sourceString, (potStrings.get(sourceString)).concat([fileLineNumber]));
}
potStrings.set(sourceString, [fileLineNumber]);
}
while ((matches = translateAttribute.exec(line)) !== null) {
const sourceString = matches[1];
if (potStrings.has(sourceString)) {
potStrings.set(sourceString, (potStrings.get(sourceString)).concat([fileLineNumber]));
}
else {
potStrings.set(sourceString, [fileLineNumber]);
}
}
while ((matches = translatePluralNode.exec(line)) !== null) {
const nodeString = matches[0];
const pluralNode = { fileLine: [], key: '', keys: {} };
while ((matches = translatePluralNodeAttr.exec(nodeString)) !== null) {
const [key, value] = [matches[1], matches[2]];
pluralNode.keys[key] = value;
}
pluralNode.key = Object.values(pluralNode.keys).join(':');
if (potStringsPlural.has(pluralNode.key)) {
pluralNode.fileLine = (potStringsPlural.get(pluralNode.key))
.fileLine.concat([fileLineNumber]);
}
else {
pluralNode.fileLine = [fileLineNumber];
}
potStringsPlural.set(pluralNode.key, pluralNode);
}
});
if (index + 1 === xFiles.length) {
resolve(true);
}
});
});
await potStringCollected;
const potFile = path.join(translationsDir, 'translation.pot');
const potFileExists = fs.existsSync(potFile);
const potObject = potFileExists ? await new Promise((resolve, reject) => {
PO.load(potFile, (err, _po) => {
if (err) { reject(err); } else { resolve(_po); }
});
}) : new PO();
potObject.items = [];
const createPotItem = (options) => {
const item = new PO.Item();
return Object.assign(item, options);
};
for (const [msgid, lines] of potStrings.entries()) {
const msgstr = [''];
const references = lines;
potObject.items.push(createPotItem({ msgid, msgstr, references }));
}
for (const pluralNode of potStringsPlural.values()) {
const msgid = pluralNode.keys.form1;
const msgid_plural = pluralNode.keys.form5;
const msgstr = ['', ''];
const references = pluralNode.fileLine;
potObject.items.push(createPotItem({ msgid, msgid_plural, msgstr, references }));
}
potObject.headers['Content-Transfer-Encoding'] = '8bit';
potObject.headers['Content-Type'] = 'text/plain; charset=UTF-8';
potObject.headers['Plural-Forms'] = 'nplurals=2; plural=(n != 1);';
await new Promise((resolve, reject) => potObject.save(potFile, resolve));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment