bin/dos-translate
#!/usr/bin/env node
var path = require('path')
var fs = require('fs')
var arrDiff = require('arr-diff')
var jsonFile = require('json-file-plus')
var Batch = require('batch')
var nomnom = require('nomnom')
var config = require('../lib/config')
// Load the list of available languages.
var LANGS_FOLDER = path.resolve(__dirname, '../lib/translations/lib')
var languages = fs.readdirSync(LANGS_FOLDER).map(function (v) {
return v.replace('.json', '')
})
/*
* Extend proto of JSONFile object
*/
;(function () {
function sort (old) {
if (typeof old !== 'object' && old !== null) {
return old
}
var copy = Array.isArray(old) ? [] : {}
var keys = Object.keys(old).sort()
keys.forEach(function (key) {
copy[key] = sort(old[key])
})
return copy
}
jsonFile.JSONFile.prototype.sort = function () {
this.data = sort(this.data)
}
})()
/*
* Utility Functions
*/
function getLangPath (lang) {
return path.join(LANGS_FOLDER, lang + '.json')
}
function getLang (lang) {
return require(getLangPath(lang))
}
function getLangKeys (lang) {
return Object.keys(getLang(lang))
}
function forEachLanguageFile (langs, cb) {
return langs.forEach(function (lang) {
jsonFile(getLangPath(lang), function (err, file) {
if (err) throw err
cb.call(file, lang, file)
})
})
}
/*
* Commands
*/
function commandListMissing (opts) {
if (!opts.on || !opts.on.length) {
opts.on = languages.filter(function (v) { return v !== opts.base })
}
var baseKeys = getLangKeys(opts.base)
opts.on.forEach(function (lang) {
var onKeys = getLangKeys(lang)
var missing = arrDiff(baseKeys, onKeys)
if (!missing.length) return
console.log('Missing ' + missing.length + ' keys on `' + lang + '`:')
missing.forEach(function (k) {
console.log(' - ' + k)
})
})
}
function commandTranslateMissing (opts) {
if (!opts.on || !opts.on.length) {
opts.on = languages.filter(function (v) { return v !== opts.base })
}
var googleTranslate = require('google-translate')(config.googleAPIKey)
var baseLang = getLang(opts.base)
var baseKeys = getLangKeys(opts.base)
var missings = {}
var langs = opts.on.filter(function (lang) {
var onKeys = getLangKeys(lang)
var missing = arrDiff(baseKeys, onKeys)
if (missing.length) missings[lang] = missing
return !!missing.length
})
forEachLanguageFile(langs, function (lang, file) {
console.log('Translating ' + missings[lang].length + ' missing keys on `' + lang + '`:')
var formatRegex = /%[a-z]|{[a-z-]+}/
var batch = new Batch()
batch.concurrency(3)
missings[lang].forEach(function (k) {
if (formatRegex.test(baseLang[k])) {
console.warn(' Couldnt translate `' + k + '`, it has a variable defined with {var} format.')
return
}
batch.push(function (done) {
googleTranslate.translate(baseLang[k], opts.base, lang, function (err, translation) {
if (err) {
console.error(err)
return done()
}
var t = {}
t[k] = translation.translatedText
file.set(t)
console.log(' "' + k + '": "' + translation.translatedText + '"')
done()
})
})
})
batch.end(function (err) {
if (err) throw err
if (opts.test) {
console.log('Done!')
} else {
file.sort()
file.save().then(function () {
console.log('Done!')
}).catch(function (err) {
throw err
})
}
})
})
}
function commandDeleteKey (opts) {
var langs = opts.on || languages
forEachLanguageFile(langs, function (lang, file) {
console.log('Deleting keys from `' + lang + '`:')
opts.key.forEach(function (k) {
if (!file.data[k]) return
file.data[k] = undefined
console.log(' "' + k + '"')
})
file.save().then(function () {
console.log('Done!')
}).catch(function (err) {
throw err
})
})
}
function commandRenameKey (opts) {
var langs = opts.on || languages
forEachLanguageFile(langs, function (lang, file) {
console.log('Renaming keys from `' + lang + '`:')
if (opts.key.length !== opts.name.length) {
throw new Error('Have to provide the same amount of keys and names.')
}
var edited = false
opts.key.forEach(function (k, i) {
if (!file.data[k]) return
var name = opts.name[i]
var value = file.data[k]
if (k === name) return
file.data[k] = undefined
file.data[name] = value
edited = true
console.log(' "' + k + '" => "' + name + '"')
})
if (edited) {
file.sort()
file.save().then(function () {
console.log('Done!')
}).catch(function (err) {
throw err
})
}
})
}
function commandListExtra (opts) {
var on = opts.on || languages
var baseKeys = getLangKeys(opts.base)
on.forEach(function (lang) {
var keys = getLangKeys(lang)
var extra = arrDiff(keys, baseKeys)
if (!extra.length) return
console.log('Extra ' + extra.length + ' keys on `' + lang + '`:')
extra.forEach(function (k) {
console.log(' - ' + k)
})
})
}
function commandDeleteExtra (opts) {
var on = opts.on || languages
var baseKeys = getLangKeys(opts.base)
on.forEach(function (lang) {
var keys = getLangKeys(lang)
var extra = arrDiff(keys, baseKeys)
if (!extra.length) return
commandDeleteKey({
on: [lang],
key: extra
})
})
}
function commandSort (opts) {
var langs = opts.lang || languages
forEachLanguageFile(langs, function (lang, file) {
file.sort()
file.save().then(function () {
console.log('`' + lang + '` sorted.')
}).catch(function (err) {
throw err
})
})
}
function commandDeleteRepeatedValues (opts) {
var on = opts.on || languages
var baseLanguage = getLang(opts.base)
on.forEach(function (lang) {
var language = getLang(lang)
var toDelete = []
Object.keys(language).forEach(function (key) {
if (!baseLanguage[key]) return
if (baseLanguage[key] === language[key]) toDelete.push(key)
})
if (toDelete.length) {
commandDeleteKey({
on: [lang],
key: toDelete
})
}
})
}
/*
* Definition of terminal interface
*/
nomnom.command('list-missing')
.option('base', {
abbr: 'b',
help: 'Base language to use the keys as reference.',
choices: languages,
required: true
})
.option('on', {
abbr: 'o',
help: 'Target language, will default to all other languages.',
choices: languages,
required: false,
list: true
})
.callback(commandListMissing)
.help('Find all the missing keys.')
nomnom.command('translate-missing')
.option('base', {
abbr: 'b',
help: 'Base language to use as reference.',
choices: languages,
required: true
})
.option('on', {
abbr: 'o',
help: 'Target language, will default to all other languages.',
choices: languages,
required: false,
list: true
})
.option('test', {
abbr: 't',
flag: true,
help: 'Print translations but not save them.',
required: false
})
.callback(commandTranslateMissing)
.help('Translate missing keys.')
nomnom.command('delete-key')
.option('key', {
abbr: 'k',
help: 'Key(s) to delete.',
required: true,
list: true
})
.option('on', {
abbr: 'o',
help: 'Target language(s), will default to all languages.',
choices: languages,
required: false,
list: true
})
.callback(commandDeleteKey)
.help('Delete a given key from selected language files.')
nomnom.command('rename-key')
.option('key', {
abbr: 'k',
help: 'Key(s) to rename.',
required: true,
list: true
})
.option('name', {
abbr: 'n',
help: 'New name to set the key(s), keep in mind to have the same order as `--key` param.',
required: true,
list: true
})
.option('on', {
abbr: 'o',
help: 'Target language(s), will default to all languages.',
choices: languages,
required: false,
list: true
})
.callback(commandRenameKey)
.help('Rename a given key from selected language files.')
nomnom.command('list-extra')
.option('base', {
abbr: 'b',
help: 'Language to use as the base for the comparisons.',
required: true
})
.option('on', {
abbr: 'n',
help: 'Language(s) you want to list extra keys from. Defaults to all.',
list: true
})
.callback(commandListExtra)
.help('Prints a list of the keys of `--on` that are not present on `--base`.')
nomnom.command('delete-extra')
.option('base', {
abbr: 'b',
help: 'Language to use as the base for the comparisons.',
required: true
})
.option('on', {
abbr: 'n',
help: 'Language(s) you want to delete extra keys. Defaults to all.',
list: true
})
.callback(commandDeleteExtra)
.help('Delete extra keys from `--on` that are not present on `--base`.')
nomnom.command('delete-repeated-values')
.option('base', {
abbr: 'b',
help: 'Language to use as the base for the comparisons.',
required: true
})
.option('on', {
abbr: 'o',
help: 'Language(s) you want to delete keys from. Defaults to all.',
choices: languages,
required: false,
list: true
})
.callback(commandDeleteRepeatedValues)
.help('Delete keys from `--on` with the same values as `--base`.')
nomnom.command('sort')
.option('lang', {
abbr: 'l',
help: 'Languages to sort. Defaults to all.',
list: true
})
.callback(commandSort)
.help('Sort the file keys from a language(s).')
nomnom.nom()