UNPKG

carbone

Version:

Fast, Simple and Powerful report generator. Injects JSON and produces PDF, DOCX, XLSX, ODT, PPTX, ODS, ...!

296 lines (270 loc) 9.85 kB
const fs = require('fs'); const path = require('path'); const helper = require('./helper'); const params = require('./params'); const file = require('./file'); const parser = require('./parser'); const UNUSED_MARKER = '_@@@_UNUSED'; const CARBONE_HEADER = [ '\n/******************************************************************************\\', ' CARBONE ', ' ------------------------------------------------------------------------------\n' ].join('\n'); const CARBONE_FOOTER = [ '\n\\******************************************************************************/ ' ]; const USAGE = CARBONE_HEADER + [ 'Usage:', ' find all {t()} markers in all templates of the specified directory', ' and update json lang files.', '', 'Mandatory options:', ' -l, --lang [lang] lang file to udpate or create (fr, en, de, es, ...)', ' -p, --path [path] template\'s path to parse', '', 'Examples:', ' carbone translate -l fr -p path/to/docs/directory', ].join('\n') + CARBONE_FOOTER; var translator = { /** * Handle parameters * @param {array} args */ handleCLI : function (args) { var _lang = null; var _dirpath = null; while (args.length > 0) { var _argument = args.shift(); switch (_argument) { case '--lang': case '-l': _lang = args.shift(); break; case '--path': case '-p': _dirpath = args.shift(); break; case '--help': case '-h': console.log(USAGE); process.exit(); break; default: console.error('Error: unknown argument "'+_argument+'"'); process.exit(); break; } } if (_lang === null) { console.error('Error: No lang. Use -l'); process.exit(); } if (_dirpath === null) { console.error('Error: No path. Use -p'); process.exit(); } translator.generateLang(path.resolve(_dirpath), _lang, function (err, newObjLang, meta) { if (err) { console.log(err); } console.log([ '\nThe carbone lang file "'+ _lang +'.json" was successfully updated. Number of keys...', ' - added : '+ meta.addedKeys, ' - deleted : '+ meta.deletedKeys, ' - unaltered : '+ meta.unalteredKeys, ' - unused : '+ meta.unusedKeys, ' - untranslated : '+ meta.untranslatedKeys ].join('\n') + '\n'); }); }, /** * Load all lang in memory. Set the global variable params.translations * @param {String} templatePath template path */ loadTranslations : function (templatePath) { params.translations = {}; var _langPath = path.join(templatePath, 'lang'); if (fs.existsSync(_langPath) === true) { var _langFiles = fs.readdirSync(_langPath); for (var i = 0; i < _langFiles.length; i++) { var _filename = _langFiles[i]; if (_filename.endsWith('.json') === true) { var _lang = path.basename(_filename, '.json'); params.translations[_lang] = JSON.parse(fs.readFileSync(path.join(_langPath, _filename))); } } } }, /** * Read all files of a directory, find all translate markers, update lang file * * @param {String} dir path to the directory we want to scan * @param {String} lang lang file name * @param {Function} callback(err, newObjLang, meta) */ generateLang : function (dir, lang, callback) { var _langDir = path.join(dir, 'lang'); var _langFile = path.join(_langDir, lang + '.json'); if (fs.existsSync(dir) === false) { return callback('Error: Invalid path. "'+ dir +'" does not exist'); } if (fs.existsSync(_langDir) === false) { fs.mkdirSync(_langDir, parseInt('0766', 8)); } if (typeof(lang) === 'string' && fs.existsSync(_langFile) === false) { fs.writeFileSync(_langFile, '{}'); } var _currentLangObj = JSON.parse(fs.readFileSync(_langFile)); var _files = helper.walkDirSync(dir, params.extensionParsed); translator.parseFiles(_files, [], function (err, foundKeys) { if (err) { return callback(err); } translator.createLangObj(_currentLangObj, foundKeys, function (err, newLangObj, meta) { if (err) { return callback(err); } fs.writeFileSync(_langFile, JSON.stringify(newLangObj, null, 2) + '\n'); return callback(null, newLangObj, meta); }); }); }, /** * Parse all files and return an array with all lang keys * @param {Array} files list of template path * @param {Array} langKeys array to fill * @param {Function} callback (err, keys) keys contains all unique key found, sorted by alphabetical order */ parseFiles : function (files, langKeys, callback) { if (files.length === 0) { langKeys.sort(alphabeticalSort); langKeys = helper.removeDuplicatedRows(langKeys); return callback(null, langKeys); } var _file = files.pop(); translator.findTranslationMarkers(_file, langKeys, function (err) { if (err) { return callback(err, langKeys); } return translator.parseFiles(files, langKeys, callback); }); }, /** * find translation markers in a template file * @param {String} filePath template to parse * @param {Array} langKeys this array is updated with new keys * @param {Function} callback (err, arraytOfString) */ findTranslationMarkers : function (filePath, langKeys, callback) { file.openTemplate(filePath, function (err, template) { if (err) { return callback(err, []); } for (var k = 0; k < template.files.length; k++) { var _file = template.files[k]; var _xml = _file.data; if (_file.isMarked === false || typeof(_xml) !== 'string' ) { continue; } // capture {t } markers. The part "(?:\s*<[^>]*>\s*)*" is used to eleminate xml tags _xml.replace(/\{\s*((?:\s*<[^>]*>\s*)*t[\s\S]+?)\}/g, function (m, text) { // capture words inside () in order to translate them with the correct lang.json. var _pattern = /\((.*)\)/.exec(text); // If there is an expression inside () // _pattern contains [_cleanMarkerStr without 't', words inside (), index, _cleanMarkerStr] if (_pattern instanceof Array && _pattern.length > 1) { var _strToTranslate = parser.extractMarker(_pattern[1]); _strToTranslate = parser.replaceEncodedOperatorCharactersByNoEncodedCharacters(_strToTranslate); langKeys.push(_strToTranslate); } }); } return callback(null, langKeys); }); }, /** * Create lang object * @param {Object} currentLangObj existing lang object * @param {Array} sortedNewKeys array of unique lang key, sorted by alphabetical order * @param {Function} callback (err, newLangObj, meta) * newLangObj : new Lang object * meta : {Object} containts debug counters */ createLangObj : function (currentLangObj, sortedNewKeys, callback) { var _addedOrDeletedKeys = {}; var _unalteredKeys = {}; var _newLangObj = {}; var _meta = { addedKeys : 0, deletedKeys : 0, unalteredKeys : 0, untranslatedKeys : 0, unusedKeys : 0 }; // seperate added and existing keys for (var i = 0; i < sortedNewKeys.length; i++) { var _newKey = sortedNewKeys[i]; if (typeof(currentLangObj[_newKey]) === 'undefined') { _meta.addedKeys++; _addedOrDeletedKeys[_newKey] = ''; } else if (currentLangObj[_newKey] === '') { _meta.untranslatedKeys++; _meta.unalteredKeys++; _addedOrDeletedKeys[_newKey] = ''; } else { _meta.unalteredKeys++; _unalteredKeys[_newKey] = currentLangObj[_newKey]; } } // detect deleted keys, and keep old deleted keys for (var _currentKey in currentLangObj) { if (typeof(_unalteredKeys[_currentKey]) === 'undefined' && typeof(_addedOrDeletedKeys[_currentKey]) === 'undefined') { if (_currentKey.endsWith(UNUSED_MARKER) === true) { _meta.unusedKeys++; _addedOrDeletedKeys[_currentKey] = currentLangObj[_currentKey]; } else { _meta.deletedKeys++; if (currentLangObj[_currentKey] !== '') { // keep unused marker only if there was a translation _addedOrDeletedKeys[_currentKey + UNUSED_MARKER] = currentLangObj[_currentKey]; } } } } // sort added or deleted key separately var _addedOrDeletedKeysSorted = Object.keys(_addedOrDeletedKeys); _addedOrDeletedKeysSorted.sort(alphabeticalSort); // firstly, add new and deleted keys for (var j = 0; j < _addedOrDeletedKeysSorted.length; j++) { var _key = _addedOrDeletedKeysSorted[j]; _newLangObj[_key] = _addedOrDeletedKeys[_key]; } // then, add unalteredKeys (already sorted) for (var _unalteredkey in _unalteredKeys) { _newLangObj[_unalteredkey] = _unalteredKeys[_unalteredkey]; } _meta.unusedKeys += _meta.deletedKeys; _meta.untranslatedKeys += _meta.addedKeys; return callback(null, _newLangObj, _meta); } }; function alphabeticalSort (a, b) { var _a = String(a).toLowerCase(); var _b = String(b).toLowerCase(); // if the string are the same whatever the case is, order them constantly (upper case first) if (_a === _b) { _a = String(a); _b = String(b); } if (_a < _b) { return -1; } if (_a > _b) { return 1; } return 0; } module.exports = translator;