UNPKG

locize-cli

Version:

locize cli to import locales

317 lines (274 loc) 9.02 kB
const fs = require('fs'); const path = require('path'); const flatten = require('flat'); const async = require('async'); const colors = require('colors'); const request = require('./request'); const getRemoteLanguages = require('./getRemoteLanguages'); const getDirectories = (srcpath) => { return fs.readdirSync(srcpath).filter(function(file) { return fs.statSync(path.join(srcpath, file)).isDirectory(); }); }; const getFiles = (srcpath) => { return fs.readdirSync(srcpath).filter(function(file) { return !fs.statSync(path.join(srcpath, file)).isDirectory(); }); }; const load = (namespaces, cb) => { async.each(namespaces, (ns, done) => { fs.readFile(ns.path, 'utf8', (err, data) => { if (err) return done(err); try { ns.value = flatten(JSON.parse(data)); } catch (err) { console.error(colors.red(err.stack)); ns.value = {}; } done(); }); }, (err) => cb(err, namespaces)); }; const parseLanguage = (p, cb) => { const dirs = getDirectories(p); const namespaces = []; dirs.forEach((lng) => { const files = getFiles(path.join(p, lng)); files.forEach((file) => { if (path.extname(file) !== '.json') return; namespaces.push({ language: lng, namespace: path.basename(file, '.json'), path: path.join(p, lng, file) }); }); }); load(namespaces, cb); }; const transfer = (opt, ns, cb) => { var url = opt.addPath .replace('{{projectId}}', opt.projectId) .replace('{{ver}}', opt.version) .replace('{{version}}', opt.version) .replace('{{language}}', ns.language) .replace('{{lng}}', ns.language) .replace('{{ns}}', ns.namespace) .replace('{{namespace}}', ns.namespace); console.log(colors.yellow(`transfering ${opt.version}/${ns.language}/${ns.namespace}...`)); if (!opt.replace) url = url.replace('/update/', '/missing/'); var data = ns.value; var keysToSend = Object.keys(data).length; if (keysToSend === 0) return cb(null); var payloadKeysLimit = 1000; function send(d, so, isFirst, clb, isRetrying) { const queryParams = new URLSearchParams(); if (so) { queryParams.append('omitstatsgeneration', 'true'); } if (isFirst && opt.replace) { queryParams.append('replace', 'true'); } const queryString = queryParams.size > 0 ? '?' + queryParams.toString() : ''; request(url + queryString, { method: 'post', body: d, headers: { 'Authorization': opt.apiKey } }, (err, res, obj) => { if (err || (obj && (obj.errorMessage || obj.message))) { if (url.indexOf('/missing/') > -1 && res.status === 412) { console.log(colors.green(`transfered ${Object.keys(d).length} keys ${opt.version}/${ns.language}/${ns.namespace} (but all keys already existed)...`)); clb(null); return; } if (res.status === 504 && !isRetrying) { return setTimeout(() => send(d, so, isFirst, clb, true), 3000); } console.log(colors.red(`transfer failed for ${Object.keys(d).length} keys ${opt.version}/${ns.language}/${ns.namespace}...`)); if (err) return clb(err); if (obj && (obj.errorMessage || obj.message)) return clb(new Error((obj.errorMessage || obj.message))); } if (res.status >= 300 && res.status !== 412) { if (obj && (obj.errorMessage || obj.message)) { return clb(new Error((obj.errorMessage || obj.message))); } return clb(new Error(res.statusText + ' (' + res.status + ')')); } console.log(colors.green(`transfered ${Object.keys(d).length} keys ${opt.version}/${ns.language}/${ns.namespace}...`)); clb(null); }); } if (keysToSend > payloadKeysLimit) { var tasks = []; var keysInObj = Object.keys(data); while (keysInObj.length > payloadKeysLimit) { (function() { var pagedData = {}; keysInObj.splice(0, payloadKeysLimit).forEach((k) => pagedData[k] = data[k]); var hasMoreKeys = keysInObj.length > 0; tasks.push((c) => send(pagedData, hasMoreKeys, false, c)); })(); } if (keysInObj.length === 0) return cb(null); var finalPagedData = {}; keysInObj.splice(0, keysInObj.length).forEach((k) => finalPagedData[k] = data[k]); tasks.push((c) => send(finalPagedData, false, false, c)); async.series(tasks, cb); return; } send(data, false, true, cb); }; const upload = (opt, nss, cb) => { if (!opt.referenceLanguage) { async.eachLimit( nss, require('os').cpus().length, (ns, done) => transfer(opt, ns, done), cb ); return; } const nssRefLng = nss.filter((n) => n.language === opt.referenceLanguage); const nssNonRefLng = nss.filter((n) => n.language !== opt.referenceLanguage); async.eachLimit( nssRefLng, require('os').cpus().length, (ns, done) => transfer(opt, ns, done), (err) => { if (err) return cb(err); async.eachLimit( nssNonRefLng, require('os').cpus().length, (ns, done) => transfer(opt, ns, done), cb ); } ); }; const addLanguage = (opt, l, cb) => { var url = opt.apiPath + '/language/' + opt.projectId + '/' + l; request(url, { method: 'post', headers: { 'Authorization': opt.apiKey } }, (err, res, obj) => { if (err || (obj && (obj.errorMessage || obj.message))) { console.log(colors.red(`failed to add language ${l}...`)); if (err) return cb(err); if (obj && (obj.errorMessage || obj.message)) return cb(new Error((obj.errorMessage || obj.message))); } if (res.status >= 300 && res.status !== 412) return cb(new Error(res.statusText + ' (' + res.status + ')')); console.log(colors.green(`added language ${l}...`)); cb(null); }); }; const migrate = (opt, cb) => { if (opt.format !== 'json') { var err = new Error(`Format ${opt.format} is not accepted!`); if (!cb) throw err; if (cb) cb(err); return; } opt.apiPath = opt.apiPath || 'https://api.locize.app'; if (opt.language) { const files = getFiles(opt.path); const namespaces = files.map((file) => { return { language: opt.language, namespace: path.basename(file, '.json'), path: path.join(opt.path, file) }; }); load(namespaces, (err, nss) => { if (err) { if (!cb) { console.error(colors.red(err.stack)); process.exit(1); } if (cb) cb(err); return; } upload(opt, nss, (err) => { if (err) { if (!cb) { console.error(colors.red(err.stack)); process.exit(1); } if (cb) cb(err); return; } if (!cb) console.log(colors.green('FINISHED')); if (cb) cb(null); }); }); return; } if (opt.parseLanguage) { parseLanguage(opt.path, (err, nss) => { if (err) { if (!cb) console.error(colors.red(err.stack)); process.exit(1); if (cb) cb(err); return; } getRemoteLanguages(opt, (err, remoteLanguages) => { if (err) { if (!cb) { console.error(colors.red(err.stack)); process.exit(1); } if (cb) cb(err); return; } const localLanguages = []; nss.forEach((n) => { if (localLanguages.indexOf(n.language) < 0) localLanguages.push(n.language); }); const notExistingLanguages = localLanguages.filter((l) => remoteLanguages.indexOf(l) < 0); if (notExistingLanguages.length === 0) { upload(opt, nss, (err) => { if (err) { if (!cb) { console.error(colors.red(err.stack)); process.exit(1); } if (cb) cb(err); return; } if (!cb) console.log(colors.green('FINISHED')); if (cb) cb(null); }); return; } async.eachLimit( notExistingLanguages, require('os').cpus().length, (l, done) => addLanguage(opt, l, done), (err) => { if (err) { if (!cb) { console.error(colors.red(err.stack)); process.exit(1); } if (cb) cb(err); return; } setTimeout(() => { // wait a bit to make sure project is up-to-date also in cache upload(opt, nss, (err) => { if (err) { if (!cb) { console.error(colors.red(err.stack)); process.exit(1); } if (cb) cb(err); return; } if (!cb) console.log(colors.green('FINISHED')); if (cb) cb(null); }); }, 5000); } ); return; }); }); return; } }; module.exports = migrate;