mocompiler.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. 'use strict';
  2. var Buffer = require('safe-buffer').Buffer;
  3. var encoding = require('encoding');
  4. var sharedFuncs = require('./shared');
  5. /**
  6. * Exposes general compiler function. Takes a translation
  7. * object as a parameter and returns binary MO object
  8. *
  9. * @param {Object} table Translation object
  10. * @return {Buffer} Compiled binary MO object
  11. */
  12. module.exports = function (table) {
  13. var compiler = new Compiler(table);
  14. return compiler.compile();
  15. };
  16. /**
  17. * Creates a MO compiler object.
  18. *
  19. * @constructor
  20. * @param {Object} table Translation table as defined in the README
  21. */
  22. function Compiler (table) {
  23. this._table = table || {};
  24. this._table.headers = this._table.headers || {};
  25. this._table.translations = this._table.translations || {};
  26. this._translations = [];
  27. this._writeFunc = 'writeUInt32LE';
  28. this._handleCharset();
  29. }
  30. /**
  31. * Magic bytes for the generated binary data
  32. */
  33. Compiler.prototype.MAGIC = 0x950412de;
  34. /**
  35. * Handles header values, replaces or adds (if needed) a charset property
  36. */
  37. Compiler.prototype._handleCharset = function () {
  38. var parts = (this._table.headers['content-type'] || 'text/plain').split(';');
  39. var contentType = parts.shift();
  40. var charset = sharedFuncs.formatCharset(this._table.charset);
  41. var params = [];
  42. params = parts.map(function (part) {
  43. var parts = part.split('=');
  44. var key = parts.shift().trim();
  45. var value = parts.join('=');
  46. if (key.toLowerCase() === 'charset') {
  47. if (!charset) {
  48. charset = sharedFuncs.formatCharset(value.trim() || 'utf-8');
  49. }
  50. return 'charset=' + charset;
  51. }
  52. return part;
  53. });
  54. if (!charset) {
  55. charset = this._table.charset || 'utf-8';
  56. params.push('charset=' + charset);
  57. }
  58. this._table.charset = charset;
  59. this._table.headers['content-type'] = contentType + '; ' + params.join('; ');
  60. this._charset = charset;
  61. };
  62. /**
  63. * Generates an array of translation strings
  64. * in the form of [{msgid:... , msgstr:...}]
  65. *
  66. * @return {Array} Translation strings array
  67. */
  68. Compiler.prototype._generateList = function () {
  69. var list = [];
  70. list.push({
  71. msgid: Buffer.alloc(0),
  72. msgstr: encoding.convert(sharedFuncs.generateHeader(this._table.headers), this._charset)
  73. });
  74. Object.keys(this._table.translations).forEach(function (msgctxt) {
  75. if (typeof this._table.translations[msgctxt] !== 'object') {
  76. return;
  77. }
  78. Object.keys(this._table.translations[msgctxt]).forEach(function (msgid) {
  79. if (typeof this._table.translations[msgctxt][msgid] !== 'object') {
  80. return;
  81. }
  82. if (msgctxt === '' && msgid === '') {
  83. return;
  84. }
  85. var msgidPlural = this._table.translations[msgctxt][msgid].msgid_plural;
  86. var key = msgid;
  87. var value;
  88. if (msgctxt) {
  89. key = msgctxt + '\u0004' + key;
  90. }
  91. if (msgidPlural) {
  92. key += '\u0000' + msgidPlural;
  93. }
  94. value = [].concat(this._table.translations[msgctxt][msgid].msgstr || []).join('\u0000');
  95. list.push({
  96. msgid: encoding.convert(key, this._charset),
  97. msgstr: encoding.convert(value, this._charset)
  98. });
  99. }.bind(this));
  100. }.bind(this));
  101. return list;
  102. };
  103. /**
  104. * Calculate buffer size for the final binary object
  105. *
  106. * @param {Array} list An array of translation strings from _generateList
  107. * @return {Object} Size data of {msgid, msgstr, total}
  108. */
  109. Compiler.prototype._calculateSize = function (list) {
  110. var msgidLength = 0;
  111. var msgstrLength = 0;
  112. var totalLength = 0;
  113. list.forEach(function (translation) {
  114. msgidLength += translation.msgid.length + 1; // + extra 0x00
  115. msgstrLength += translation.msgstr.length + 1; // + extra 0x00
  116. });
  117. totalLength = 4 + // magic number
  118. 4 + // revision
  119. 4 + // string count
  120. 4 + // original string table offset
  121. 4 + // translation string table offset
  122. 4 + // hash table size
  123. 4 + // hash table offset
  124. (4 + 4) * list.length + // original string table
  125. (4 + 4) * list.length + // translations string table
  126. msgidLength + // originals
  127. msgstrLength; // translations
  128. return {
  129. msgid: msgidLength,
  130. msgstr: msgstrLength,
  131. total: totalLength
  132. };
  133. };
  134. /**
  135. * Generates the binary MO object from the translation list
  136. *
  137. * @param {Array} list translation list
  138. * @param {Object} size Byte size information
  139. * @return {Buffer} Compiled MO object
  140. */
  141. Compiler.prototype._build = function (list, size) {
  142. var returnBuffer = Buffer.alloc(size.total);
  143. var curPosition = 0;
  144. var i;
  145. var len;
  146. // magic
  147. returnBuffer[this._writeFunc](this.MAGIC, 0);
  148. // revision
  149. returnBuffer[this._writeFunc](0, 4);
  150. // string count
  151. returnBuffer[this._writeFunc](list.length, 8);
  152. // original string table offset
  153. returnBuffer[this._writeFunc](28, 12);
  154. // translation string table offset
  155. returnBuffer[this._writeFunc](28 + (4 + 4) * list.length, 16);
  156. // hash table size
  157. returnBuffer[this._writeFunc](0, 20);
  158. // hash table offset
  159. returnBuffer[this._writeFunc](28 + (4 + 4) * list.length * 2, 24);
  160. // build originals table
  161. curPosition = 28 + 2 * (4 + 4) * list.length;
  162. for (i = 0, len = list.length; i < len; i++) {
  163. list[i].msgid.copy(returnBuffer, curPosition);
  164. returnBuffer[this._writeFunc](list[i].msgid.length, 28 + i * 8);
  165. returnBuffer[this._writeFunc](curPosition, 28 + i * 8 + 4);
  166. returnBuffer[curPosition + list[i].msgid.length] = 0x00;
  167. curPosition += list[i].msgid.length + 1;
  168. }
  169. // build translations table
  170. for (i = 0, len = list.length; i < len; i++) {
  171. list[i].msgstr.copy(returnBuffer, curPosition);
  172. returnBuffer[this._writeFunc](list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8);
  173. returnBuffer[this._writeFunc](curPosition, 28 + (4 + 4) * list.length + i * 8 + 4);
  174. returnBuffer[curPosition + list[i].msgstr.length] = 0x00;
  175. curPosition += list[i].msgstr.length + 1;
  176. }
  177. return returnBuffer;
  178. };
  179. /**
  180. * Compiles translation object into a binary MO object
  181. *
  182. * @return {Buffer} Compiled MO object
  183. */
  184. Compiler.prototype.compile = function () {
  185. var list = this._generateList();
  186. var size = this._calculateSize(list);
  187. // sort by msgid
  188. list.sort(function (a, b) {
  189. if (a.msgid > b.msgid) {
  190. return 1;
  191. }
  192. if (a.msgid < b.msgid) {
  193. return -1;
  194. }
  195. return 0;
  196. });
  197. return this._build(list, size);
  198. };