'use strict'; var Buffer = require('safe-buffer').Buffer; var encoding = require('encoding'); var sharedFuncs = require('./shared'); /** * Exposes general compiler function. Takes a translation * object as a parameter and returns binary MO object * * @param {Object} table Translation object * @return {Buffer} Compiled binary MO object */ module.exports = function (table) { var compiler = new Compiler(table); return compiler.compile(); }; /** * Creates a MO compiler object. * * @constructor * @param {Object} table Translation table as defined in the README */ function Compiler (table) { this._table = table || {}; this._table.headers = this._table.headers || {}; this._table.translations = this._table.translations || {}; this._translations = []; this._writeFunc = 'writeUInt32LE'; this._handleCharset(); } /** * Magic bytes for the generated binary data */ Compiler.prototype.MAGIC = 0x950412de; /** * 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('='); var key = parts.shift().trim(); var 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; }; /** * Generates an array of translation strings * in the form of [{msgid:... , msgstr:...}] * * @return {Array} Translation strings array */ Compiler.prototype._generateList = function () { var list = []; list.push({ msgid: Buffer.alloc(0), msgstr: encoding.convert(sharedFuncs.generateHeader(this._table.headers), this._charset) }); 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; } var msgidPlural = this._table.translations[msgctxt][msgid].msgid_plural; var key = msgid; var value; if (msgctxt) { key = msgctxt + '\u0004' + key; } if (msgidPlural) { key += '\u0000' + msgidPlural; } value = [].concat(this._table.translations[msgctxt][msgid].msgstr || []).join('\u0000'); list.push({ msgid: encoding.convert(key, this._charset), msgstr: encoding.convert(value, this._charset) }); }.bind(this)); }.bind(this)); return list; }; /** * Calculate buffer size for the final binary object * * @param {Array} list An array of translation strings from _generateList * @return {Object} Size data of {msgid, msgstr, total} */ Compiler.prototype._calculateSize = function (list) { var msgidLength = 0; var msgstrLength = 0; var totalLength = 0; list.forEach(function (translation) { msgidLength += translation.msgid.length + 1; // + extra 0x00 msgstrLength += translation.msgstr.length + 1; // + extra 0x00 }); totalLength = 4 + // magic number 4 + // revision 4 + // string count 4 + // original string table offset 4 + // translation string table offset 4 + // hash table size 4 + // hash table offset (4 + 4) * list.length + // original string table (4 + 4) * list.length + // translations string table msgidLength + // originals msgstrLength; // translations return { msgid: msgidLength, msgstr: msgstrLength, total: totalLength }; }; /** * Generates the binary MO object from the translation list * * @param {Array} list translation list * @param {Object} size Byte size information * @return {Buffer} Compiled MO object */ Compiler.prototype._build = function (list, size) { var returnBuffer = Buffer.alloc(size.total); var curPosition = 0; var i; var len; // magic returnBuffer[this._writeFunc](this.MAGIC, 0); // revision returnBuffer[this._writeFunc](0, 4); // string count returnBuffer[this._writeFunc](list.length, 8); // original string table offset returnBuffer[this._writeFunc](28, 12); // translation string table offset returnBuffer[this._writeFunc](28 + (4 + 4) * list.length, 16); // hash table size returnBuffer[this._writeFunc](0, 20); // hash table offset returnBuffer[this._writeFunc](28 + (4 + 4) * list.length * 2, 24); // build originals table curPosition = 28 + 2 * (4 + 4) * list.length; for (i = 0, len = list.length; i < len; i++) { list[i].msgid.copy(returnBuffer, curPosition); returnBuffer[this._writeFunc](list[i].msgid.length, 28 + i * 8); returnBuffer[this._writeFunc](curPosition, 28 + i * 8 + 4); returnBuffer[curPosition + list[i].msgid.length] = 0x00; curPosition += list[i].msgid.length + 1; } // build translations table for (i = 0, len = list.length; i < len; i++) { list[i].msgstr.copy(returnBuffer, curPosition); returnBuffer[this._writeFunc](list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8); returnBuffer[this._writeFunc](curPosition, 28 + (4 + 4) * list.length + i * 8 + 4); returnBuffer[curPosition + list[i].msgstr.length] = 0x00; curPosition += list[i].msgstr.length + 1; } return returnBuffer; }; /** * Compiles translation object into a binary MO object * * @return {Buffer} Compiled MO object */ Compiler.prototype.compile = function () { var list = this._generateList(); var size = this._calculateSize(list); // sort by msgid list.sort(function (a, b) { if (a.msgid > b.msgid) { return 1; } if (a.msgid < b.msgid) { return -1; } return 0; }); return this._build(list, size); };