Last active
March 5, 2018 16:02
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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