pofile/lib/po.js
Julian Bäume d8fc514359 don't remove \n characters from written po file
in Item.toString, all \n characters are removed from the output.
The gettext tools however leave those characters intact. This
will now produce the same output as tools like msgcat.
2014-06-23 15:03:58 +02:00

277 lines
8.1 KiB
JavaScript

var fs = require('fs');
var isArray = require('lodash.isarray');
function trim(string) {
return string.replace(/^\s+|\s+$/g, '');
}
var PO = function () {
this.comments = [];
this.headers = {};
this.items = [];
};
PO.prototype.save = function (filename, callback) {
fs.writeFile(filename, this.toString(), callback);
};
PO.prototype.toString = function () {
var lines = [];
if (this.comments) {
this.comments.forEach(function (comment) {
lines.push('# ' + comment);
});
}
lines.push('msgid ""');
lines.push('msgstr ""');
var keys = Object.keys(this.headers);
var self = this;
keys.forEach(function (key) {
lines.push('"' + key + ': ' + self.headers[key] + '\\n"');
});
lines.push('');
this.items.forEach(function (item) {
lines.push(item.toString());
lines.push('');
});
return lines.join('\n');
};
PO.load = function (filename, callback) {
fs.readFile(filename, 'utf-8', function (err, data) {
if (err) {
return callback(err);
}
var po = PO.parse(data);
callback(null, po);
});
};
PO.parse = function (data) {
//support both unix and windows newline formats.
data = data.replace(/\r\n/g, '\n');
var po = new PO();
var sections = data.split(/\n\n/);
var headers = sections.shift();
var lines = sections.join('\n').split(/\n/);
po.headers = {
'Project-Id-Version': '',
'Report-Msgid-Bugs-To': '',
'POT-Creation-Date': '',
'PO-Revision-Date': '',
'Last-Translator': '',
'Language': '',
'Language-Team': '',
'Content-Type': '',
'Content-Transfer-Encoding': '',
'Plural-Forms': '',
};
headers.split(/\n/).reduce(function (acc, line) {
if (acc.merge) {
//join lines, remove last resp. first "
line = acc.pop().slice(0, -1) + line.slice(1);
delete acc.merge;
}
if (/^".*"$/.test(line) && !/^".*\\n"$/.test(line)) {
acc.merge = true;
}
acc.push(line);
return acc;
}, []).forEach(function (header) {
if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
}
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/);
var name = p.shift().trim();
var value = p.join(':').trim();
po.headers[name] = value;
}
});
var item = new PO.Item();
var context = null;
var plural = 0;
var obsoleteCount = 0;
var noCommentLineCount = 0;
function finish() {
if (item.msgid.length > 0) {
if (obsoleteCount >= noCommentLineCount) {
item.obsolete = true;
}
obsoleteCount = 0;
noCommentLineCount = 0;
po.items.push(item);
item = new PO.Item();
}
}
function extract(string) {
string = trim(string);
string = string.replace(/^[^"]*"|"$/g, '');
string = string.replace(/\\"/g, '"');
string = string.replace(/\\\\/g, '\\');
return string;
}
while (lines.length > 0) {
var line = trim(lines.shift());
var lineObsolete = false;
var add = false;
if (line.match(/^#\~/)) { // Obsolete item
//only remove the obsolte comment mark, here
//might be, this is a new item, so
//only remember, this line is marked obsolete, count after line is parsed
line = trim(line.substring(2));
lineObsolete = true;
}
if (line.match(/^#:/)) { // Reference
finish();
item.references.push(trim(line.replace(/^#:/, '')));
} else if (line.match(/^#,/)) { // Flags
finish();
var flags = trim(line.replace(/^#,/, '')).split(',');
for (var i = 0; i < flags.length; i++) {
item.flags[flags[i]] = true;
}
} else if (line.match(/^#($|\s+)/)) { // Translator comment
finish();
item.comments.push(trim(line.replace(/^#($|\s+)/, '')));
} else if (line.match(/^#\./)) { // Extracted comment
finish();
item.extractedComments.push(trim(line.replace(/^#\./, '')));
} else if (line.match(/^msgid_plural/)) { // Plural form
item.msgid_plural = extract(line);
context = 'msgid_plural';
noCommentLineCount++;
} else if (line.match(/^msgid/)) { // Original
finish();
item.msgid = extract(line);
context = 'msgid';
noCommentLineCount++;
} else if (line.match(/^msgstr/)) { // Translation
var m = line.match(/^msgstr\[(\d+)\]/);
plural = m && m[1] ? parseInt(m[1]) : 0;
item.msgstr[plural] = extract(line);
context = 'msgstr';
noCommentLineCount++;
} else if (line.match(/^msgctxt/)) { // Context
finish();
item.msgctxt = extract(line);
noCommentLineCount++;
} else { // Probably multiline string or blank
if (line.length > 0) {
noCommentLineCount++;
if (context === 'msgstr') {
item.msgstr[plural] += extract(line);
} else if (context === 'msgid') {
item.msgid += extract(line);
} else if (context === 'msgid_plural') {
item.msgid_plural += extract(line);
}
}
}
if (lineObsolete) {
// Count obsolete lines for this item
obsoleteCount++;
}
}
finish();
return po;
};
PO.Item = function () {
this.msgid = '';
this.msgctxt = null;
this.references = [];
this.msgid_plural = null;
this.msgstr = [];
this.comments = []; // translator comments
this.extractedComments = [];
this.flags = {};
this.obsolete = false;
};
PO.Item.prototype.toString = function () {
var lines = [];
var self = this;
// reverse what extract(string) method during PO.parse does
var _escape = function (string) {
string = string.replace(/\\/g, '\\\\');
return string.replace(/"/g, '\\"');
};
var _process = function (keyword, text, i) {
var lines = [];
var parts = text.split(/\n/);
var index = typeof i !== 'undefined' ? '[' + i + ']' : '';
if (parts.length > 1) {
lines.push(keyword + index + ' ""');
parts.forEach(function (part) {
lines.push('"' + _escape(part) + '"');
});
} else {
lines.push(keyword + index + ' "' + _escape(text) + '"');
}
return lines;
};
// https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
// says order is translator-comments, extracted-comments, references, flags
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
var flags = Object.keys(this.flags);
if (flags.length > 0) {
lines.push('#, ' + flags.join(','));
}
var mkObsolete = this.obsolete ? '#~ ' : '';
['msgctxt', 'msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = self[keyword];
if (text != null) {
if (isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
lines = lines.concat(mkObsolete + _process(keyword, t, i));
});
} else {
text = isArray(text) ? text.join() : text;
var processed = _process(keyword, text);
for (var i = 1; i < processed.length - 1; i++) {
processed[i] = processed[i].slice(0, -1) + '\\n"';
}
lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
}
}
});
return lines.join('\n');
};
module.exports = PO;