202 lines
5.3 KiB
JavaScript
202 lines
5.3 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var encoding = require('encoding');
|
||
|
var sharedFuncs = require('./shared');
|
||
|
|
||
|
/**
|
||
|
* Parses a binary MO object into translation table
|
||
|
*
|
||
|
* @param {Buffer} buffer Binary MO object
|
||
|
* @param {String} [defaultCharset] Default charset to use
|
||
|
* @return {Object} Translation object
|
||
|
*/
|
||
|
module.exports = function(buffer, defaultCharset) {
|
||
|
var parser = new Parser(buffer, defaultCharset);
|
||
|
return parser.parse();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Creates a MO parser object.
|
||
|
*
|
||
|
* @constructor
|
||
|
* @param {Buffer} fileContents Binary MO object
|
||
|
* @param {String} [defaultCharset] Default charset to use
|
||
|
*/
|
||
|
function Parser(fileContents, defaultCharset) {
|
||
|
|
||
|
this._fileContents = fileContents;
|
||
|
|
||
|
/**
|
||
|
* Method name for writing int32 values, default littleendian
|
||
|
*/
|
||
|
this._writeFunc = 'writeUInt32LE';
|
||
|
|
||
|
/**
|
||
|
* Method name for reading int32 values, default littleendian
|
||
|
*/
|
||
|
this._readFunc = 'readUInt32LE';
|
||
|
|
||
|
this._charset = defaultCharset || 'iso-8859-1';
|
||
|
|
||
|
this._table = {
|
||
|
charset: this._charset,
|
||
|
headers: undefined,
|
||
|
translations: {}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Magic constant to check the endianness of the input file
|
||
|
*/
|
||
|
Parser.prototype.MAGIC = 0x950412de;
|
||
|
|
||
|
/**
|
||
|
* Checks if number values in the input file are in big- or littleendian format.
|
||
|
*
|
||
|
* @return {Boolean} Return true if magic was detected
|
||
|
*/
|
||
|
Parser.prototype._checkMagick = function() {
|
||
|
if (this._fileContents.readUInt32LE(0) === this.MAGIC) {
|
||
|
this._readFunc = 'readUInt32LE';
|
||
|
this._writeFunc = 'writeUInt32LE';
|
||
|
return true;
|
||
|
} else if (this._fileContents.readUInt32BE(0) === this.MAGIC) {
|
||
|
this._readFunc = 'readUInt32BE';
|
||
|
this._writeFunc = 'writeUInt32BE';
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Read the original strings and translations from the input MO file. Use the
|
||
|
* first translation string in the file as the header.
|
||
|
*/
|
||
|
Parser.prototype._loadTranslationTable = function() {
|
||
|
var offsetOriginals = this._offsetOriginals,
|
||
|
offsetTranslations = this._offsetTranslations,
|
||
|
position, length,
|
||
|
msgid, msgstr;
|
||
|
|
||
|
for (var i = 0; i < this._total; i++) {
|
||
|
// msgid string
|
||
|
length = this._fileContents[this._readFunc](offsetOriginals);
|
||
|
offsetOriginals += 4;
|
||
|
position = this._fileContents[this._readFunc](offsetOriginals);
|
||
|
offsetOriginals += 4;
|
||
|
msgid = this._fileContents.slice(position, position + length);
|
||
|
|
||
|
// matching msgstr
|
||
|
length = this._fileContents[this._readFunc](offsetTranslations);
|
||
|
offsetTranslations += 4;
|
||
|
position = this._fileContents[this._readFunc](offsetTranslations);
|
||
|
offsetTranslations += 4;
|
||
|
msgstr = this._fileContents.slice(position, position + length);
|
||
|
|
||
|
if (!i && !msgid.toString()) {
|
||
|
this._handleCharset(msgstr);
|
||
|
}
|
||
|
|
||
|
msgid = encoding.convert(msgid, 'utf-8', this._charset).toString('utf-8');
|
||
|
msgstr = encoding.convert(msgstr, 'utf-8', this._charset).toString('utf-8');
|
||
|
|
||
|
this._addString(msgid, msgstr);
|
||
|
}
|
||
|
|
||
|
// dump the file contents object
|
||
|
this._fileContents = null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Detects charset for MO strings from the header
|
||
|
*
|
||
|
* @param {Buffer} headers Header value
|
||
|
*/
|
||
|
Parser.prototype._handleCharset = function(headers) {
|
||
|
|
||
|
var headersStr = headers.toString(),
|
||
|
match;
|
||
|
|
||
|
if ((match = headersStr.match(/[; ]charset\s*=\s*([\w\-]+)/i))) {
|
||
|
this._charset = this._table.charset = sharedFuncs.formatCharset(match[1], this._charset);
|
||
|
}
|
||
|
|
||
|
headers = encoding.convert(headers, 'utf-8', this._charset).toString('utf-8');
|
||
|
|
||
|
this._table.headers = sharedFuncs.parseHeader(headers);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Adds a translation to the translation object
|
||
|
*
|
||
|
* @param {String} msgid Original string
|
||
|
* @params {String} msgstr Translation for the original string
|
||
|
*/
|
||
|
Parser.prototype._addString = function(msgid, msgstr) {
|
||
|
var translation = {},
|
||
|
parts, msgctxt, msgid_plural;
|
||
|
|
||
|
msgid = msgid.split('\u0004');
|
||
|
if (msgid.length > 1) {
|
||
|
msgctxt = msgid.shift();
|
||
|
translation.msgctxt = msgctxt;
|
||
|
} else {
|
||
|
msgctxt = '';
|
||
|
}
|
||
|
msgid = msgid.join('\u0004');
|
||
|
|
||
|
parts = msgid.split('\u0000');
|
||
|
msgid = parts.shift();
|
||
|
|
||
|
translation.msgid = msgid;
|
||
|
|
||
|
if ((msgid_plural = parts.join('\u0000'))) {
|
||
|
translation.msgid_plural = msgid_plural;
|
||
|
}
|
||
|
|
||
|
msgstr = msgstr.split('\u0000');
|
||
|
translation.msgstr = [].concat(msgstr || []);
|
||
|
|
||
|
if (!this._table.translations[msgctxt]) {
|
||
|
this._table.translations[msgctxt] = {};
|
||
|
}
|
||
|
|
||
|
this._table.translations[msgctxt][msgid] = translation;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Parses the MO object and returns translation table
|
||
|
*
|
||
|
* @return {Object} Translation table
|
||
|
*/
|
||
|
Parser.prototype.parse = function() {
|
||
|
if (!this._checkMagick()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* GetText revision nr, usually 0
|
||
|
*/
|
||
|
this._revision = this._fileContents[this._readFunc](4);
|
||
|
|
||
|
/**
|
||
|
* Total count of translated strings
|
||
|
*/
|
||
|
this._total = this._fileContents[this._readFunc](8);
|
||
|
|
||
|
/**
|
||
|
* Offset position for original strings table
|
||
|
*/
|
||
|
this._offsetOriginals = this._fileContents[this._readFunc](12);
|
||
|
|
||
|
/**
|
||
|
* Offset position for translation strings table
|
||
|
*/
|
||
|
this._offsetTranslations = this._fileContents[this._readFunc](16);
|
||
|
|
||
|
// Load translations into this._translationTable
|
||
|
this._loadTranslationTable();
|
||
|
|
||
|
return this._table;
|
||
|
};
|