UNPKG

locize-cli

Version:

locize cli to import locales

323 lines (317 loc) 10.2 kB
const po2i18next = require('gettext-converter/cjs/po2i18next'); const csvjson = require('csvjson'); const xlsx = require('xlsx'); const yaml = require('yaml'); const asr2js = require('android-string-resource/cjs/asr2js'); const stringsFile = require('strings-file'); const xliff2js = require('xliff/cjs/xliff2js'); const xliff12ToJs = require('xliff/cjs/xliff12ToJs'); const targetOfjs = require('xliff/cjs/targetOfjs'); const sourceOfjs = require('xliff/cjs/sourceOfjs'); const resx2js = require('resx/cjs/resx2js'); const ftl2js = require('fluent_conv/cjs/ftl2js'); const tmx2js = require('tmexchange/cjs/tmx2js'); const laravel2js = require('laravelphp/cjs/laravel2js'); const javaProperties = require('@js.properties/properties'); const xcstrings2locize = require('locize-xcstrings/cjs/xcstrings2locize'); const flatten = require('flat'); const prepareCombinedImport = require('./combineSubkeyPreprocessor').prepareImport; const convertToFlatFormat = (opt, data, lng, cb) => { if (!cb) { cb = lng; lng = undefined; } try { if (opt.format === 'json' || opt.format === 'nested' || opt.format === 'flat') { const dataString = data.toString().trim(); if (dataString[0] !== '{' && dataString[0] !== '[') { return cb(new Error(`Not a valid json file: Content starts with "${dataString[0]}" but should start with "{"`)); } try { const jsonParsed = JSON.parse(dataString); cb(null, flatten(jsonParsed)); } catch (err) { return cb(err); } return; } if (opt.format === 'po' || opt.format === 'gettext') { try { const ret = po2i18next(data.toString(), { persistMsgIdPlural: true, ignoreCtx: true }); cb(null, flatten(ret)); } catch (err) { cb(err); } return; } if (opt.format === 'po_i18next' || opt.format === 'gettext_i18next') { try { const potxt = data.toString(); const compatibilityJSON = /msgctxt "(zero|one|two|few|many|other)"/.test(potxt) && 'v4'; const ret = po2i18next(potxt, { compatibilityJSON }); cb(null, flatten(ret)); } catch (err) { cb(err); } return; } if (opt.format === 'csv') { const options = { delimiter: ',', quote: '"' }; // CRLF => LF var text = data.toString().replace(/\r\n/g, '\n'); // handle multiline stuff const lines = text.split('\n'); const toHandle = []; lines.forEach((l) => { const amountOfOccurrencies = l.split('"').length - 1; if (amountOfOccurrencies % 2 === 1) toHandle.push(l); }); while (toHandle.length > 1) { var firstToHandle = toHandle.shift(); const secondToHandle = toHandle.shift(); const indexOfFirst = lines.indexOf(firstToHandle); const indexOfSecond = lines.indexOf(secondToHandle); var handlingIndex = indexOfFirst; while (handlingIndex < indexOfSecond) { firstToHandle += `\\NeWlInE\\${lines[handlingIndex + 1]}`; handlingIndex++; } lines[indexOfFirst] = firstToHandle; lines.splice(indexOfFirst + 1, indexOfSecond - indexOfFirst); } text = lines.join('\n'); // https://en.wikipedia.org/wiki/Delimiter-separated_values // temporary replace "" with \_\" so we can revert this 3 lines after const jsonData = csvjson.toObject(text.replace(/""/g, '\\_\\"'), options); data = jsonData.reduce((mem, entry) => { if (entry.key && typeof entry[opt.referenceLanguage] === 'string') { mem[ entry.key.replace(/\\_\\"/g, '"').replace(/\\NeWlInE\\/g, '\n') ] = entry[opt.referenceLanguage] .replace(/\\_\\"/g, '"') .replace(/\\NeWlInE\\/g, '\n'); } return mem; }, {}); cb(null, data); return; } if (opt.format === 'xlsx') { const wb = xlsx.read(data, { type: 'buffer' }); const jsonData = xlsx.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); data = jsonData.reduce((mem, entry) => { if (entry.key && typeof entry[opt.referenceLanguage] === 'string') { mem[entry.key] = entry[opt.referenceLanguage]; } return mem; }, {}); cb(null, data); return; } if ( opt.format === 'yaml' || opt.format === 'yml' ) { const d = data.toString(); if (!d || d === '' || d === '\n') return cb(null, {}); cb(null, flatten(yaml.parse(d))); return; } if ( opt.format === 'yaml-nested' || opt.format === 'yml-nested' ) { const d = data.toString(); if (!d || d === '' || d === '\n') return cb(null, {}); cb(null, flatten(yaml.parse(d))); return; } if ( opt.format === 'yaml-rails' || opt.format === 'yml-rails' ) { const d = data.toString(); if (!d || d.trim() === '') return cb(null, {}); const jsObj = yaml.parse(d); cb( null, flatten( jsObj[Object.keys(jsObj)[0]] ) ); return; } if ( opt.format === 'yaml-rails-ns' || opt.format === 'yml-rails-ns' ) { const dn = data.toString(); if (!dn || dn.trim() === '') return cb(null, {}); const jsObjn = yaml.parse(dn); cb( null, flatten( jsObjn[Object.keys(jsObjn)[0]][ Object.keys(jsObjn[Object.keys(jsObjn)[0]])[0] ] ) ); return; } if (opt.format === 'android') { asr2js(data.toString(), { comment: 'right' }, (err, res) => { if (err) return cb(err); Object.keys(res).forEach((k) => { if (res[k] !== 'string' && typeof res[k].comment === 'string') { res[k] = { value: res[k].value, context: { text: res[k].comment, }, }; } else { res[k] = { value: res[k].value || res[k] }; } }); cb(null, res); }); return; } if (opt.format === 'strings') { // CRLF => LF data = stringsFile.parse(data.toString().replace(/\r\n/g, '\n'), false); cb(null, data); return; } if ( opt.format === 'xliff2' || opt.format === 'xliff12' || opt.format === 'xlf2' || opt.format === 'xlf12' ) { const fn = opt.format === 'xliff12' || opt.format === 'xlf12' ? xliff12ToJs : xliff2js; fn(data.toString(), (err, res) => { if (err) return cb(err); res.resources = res.resources || {}; const ns = Object.keys(res.resources)[0]; const orgRes = res.resources[ns] || res.resources; function checkForPostProcessing(nsRes) { Object.keys(nsRes).forEach((k) => { if (orgRes[k].note && (typeof nsRes[k] === 'string' || !nsRes[k])) { nsRes[k] = { value: nsRes[k], context: { text: orgRes[k].note, } }; } }); return prepareCombinedImport(nsRes); } if (!res.targetLanguage) { sourceOfjs(res, (err, ret) => { if (err) return cb(err); cb(null, checkForPostProcessing(ret)); }); } else { let ret = targetOfjs(res); if (lng !== opt.referenceLanguage) return cb(null, checkForPostProcessing(ret)); ret = ret || {}; const keys = Object.keys(ret); if (keys.length === 0) return cb(null, checkForPostProcessing(ret)); const allEmpty = keys.filter((k) => ret[k] !== '').length === 0; if (!allEmpty) return cb(null, checkForPostProcessing(ret)); ret = sourceOfjs(res); cb(null, checkForPostProcessing(ret)); } }); return; } if (opt.format === 'resx') { resx2js(data.toString(), (err, res) => { if (err) return cb(err); res = Object.keys(res).reduce((mem, k) => { const value = res[k]; if (typeof value === 'string') { mem[k] = value; } else if (value.value) { mem[k] = { value: value.value, context: value.comment ? { text: value.comment } : null, }; } return mem; }, {}); cb(null, res); }); return; } if (opt.format === 'fluent') { const fluentJS = ftl2js( data .toString() .replace( new RegExp(String.fromCharCode(160), 'g'), String.fromCharCode(32) ) ); const comments = {}; Object.keys(fluentJS).forEach((prop) => { if (fluentJS[prop] && fluentJS[prop].comment) { comments[prop] = fluentJS[prop].comment; delete fluentJS[prop].comment; } }); const res = flatten(fluentJS); if (res && comments) { Object.keys(comments).forEach((prop) => { res[`${prop}.val`] = { value: res[`${prop}.val`], context: comments[prop] ? { text: comments[prop] } : null, }; }); } cb(null, res); return; } if (opt.format === 'tmx') { tmx2js(data.toString(), (err, jsonData) => { if (err) return cb(err); const tmxJsRes = jsonData.resources[Object.keys(jsonData.resources)[0]]; const res = {}; if (tmxJsRes) { Object.keys(tmxJsRes).forEach((k) => { res[k] = tmxJsRes[k][opt.referenceLanguage]; }); } cb(null, res); }); return; } if (opt.format === 'laravel') { laravel2js(data.toString(), (err, res) => cb(err, flatten(res))); return; } if (opt.format === 'properties') { cb(null, javaProperties.parseToProperties(data.toString())); return; } if (opt.format === 'xcstrings') { cb(null, xcstrings2locize(data.toString())); return; } cb(new Error(`${opt.format} is not a valid format!`)); } catch (err) { cb(err); } }; module.exports = convertToFlatFormat;