'use strict'; var encoding = require('encoding'); var sharedFuncs = require('./shared'); /** * Exposes general compiler function. Takes a translation * object as a parameter and returns PO object * * @param {Object} table Translation object * @return {Buffer} Compiled PO object */ module.exports = function(table) { var compiler = new Compiler(table); return compiler.compile(); }; /** * Creates a PO compiler object. * * @constructor * @param {Object} table Translation table to be compiled */ function Compiler(table) { this._table = table || {}; this._table.headers = this._table.headers || {}; this._table.translations = this._table.translations || {}; this._translations = []; this._handleCharset(); } /** * Converts a comments object to a comment string. The comment object is * in the form of {translator:'', reference: '', extracted: '', flag: '', previous:''} * * @param {Object} comments A comments object * @return {String} A comment string for the PO file */ Compiler.prototype._drawComments = function(comments) { var lines = []; var types = [{ key: 'translator', prefix: '# ' }, { key: 'reference', prefix: '#: ' }, { key: 'extracted', prefix: '#. ' }, { key: 'flag', prefix: '#, ' }, { key: 'previous', prefix: '#| ' }]; types.forEach(function(type) { if (!comments[type.key]) { return; } comments[type.key].split(/\r?\n|\r/).forEach(function(line) { lines.push(type.prefix + line); }); }); return lines.join('\n'); }; /** * Builds a PO string for a single translation object * * @param {Object} block Translation object * @param {Object} [override] Properties of this object will override `block` properties * @return {String} Translation string for a single object */ Compiler.prototype._drawBlock = function(block, override) { override = override || {}; var response = [], comments = override.comments || block.comments, msgctxt = override.msgctxt || block.msgctxt, msgid = override.msgid || block.msgid, msgid_plural = override.msgid_plural || block.msgid_plural, msgstr = [].concat(override.msgstr || block.msgstr); // add comments if (comments && (comments = this._drawComments(comments))) { response.push(comments); } if (msgctxt) { response.push(this._addPOString('msgctxt', msgctxt)); } response.push(this._addPOString('msgid', msgid || '')); if (msgid_plural) { response.push(this._addPOString('msgid_plural', msgid_plural)); } if (msgstr.length <= 1) { response.push(this._addPOString('msgstr', msgstr[0] || '')); } else { msgstr.forEach((function(msgstr, i) { response.push(this._addPOString('msgstr[' + i + ']', msgstr || '')); }).bind(this)); } return response.join('\n'); }; /** * Escapes and joins a key and a value for the PO string * * @param {String} key Key name * @param {String} value Key value * @return {String} Joined and escaped key-value pair */ Compiler.prototype._addPOString = function(key, value) { var line; key = (key || '').toString(); // escape newlines and quotes value = (value || '').toString(). replace(/\\/g, '\\\\'). replace(/"/g, '\\"'). replace(/\t/g, '\\t'). replace(/\r/g, '\\r'). replace(/\n/g, '\\n'); var lines = sharedFuncs.foldLine(value); if (lines.length < 2) { return key + ' "' + (lines.shift() || '') + '"'; } else { return key + ' ""\n"' + lines.join('"\n"') + '"'; } if (value.match(/\n/)) { value = value.replace(/\n/g, '\\n\n').replace(/\n$/, ''); line = ('\n' + value).split('\n').map(function(l) { return '"' + l + '"'; }).join('\n'); } else { line = '"' + value + '"'; } return key + ' ' + line; }; /** * Handles header values, replaces or adds (if needed) a charset property */ Compiler.prototype._handleCharset = function() { var parts = (this._table.headers['content-type'] || 'text/plain').split(';'); var contentType = parts.shift(); var charset = sharedFuncs.formatCharset(this._table.charset); var params = []; params = parts.map(function(part) { var parts = part.split('='), key = parts.shift().trim(), value = parts.join('='); if (key.toLowerCase() === 'charset') { if (!charset) { charset = sharedFuncs.formatCharset(value.trim() || 'utf-8'); } return 'charset=' + charset; } return part; }); if (!charset) { charset = this._table.charset || 'utf-8'; params.push('charset=' + charset); } this._table.charset = charset; this._table.headers['content-type'] = contentType + '; ' + params.join('; '); this._charset = charset; }; /** * Compiles translation object into a PO object * * @return {Buffer} Compiled PO object */ Compiler.prototype.compile = function() { var response = [], headerBlock = this._table.translations[''] && this._table.translations[''][''] || {}; response.push(this._drawBlock(headerBlock, { msgstr: sharedFuncs.generateHeader(this._table.headers) })); Object.keys(this._table.translations).forEach((function(msgctxt) { if (typeof this._table.translations[msgctxt] !== 'object') { return; } Object.keys(this._table.translations[msgctxt]).forEach((function(msgid) { if (typeof this._table.translations[msgctxt][msgid] !== 'object') { return; } if (msgctxt === '' && msgid === '') { return; } response.push(this._drawBlock(this._table.translations[msgctxt][msgid])); }).bind(this)); }).bind(this)); if (this._charset === 'utf-8' || this._charset === 'ascii') { return new Buffer(response.join('\n\n'), 'utf-8'); } else { return encoding.convert(response.join('\n\n'), this._charset); } };