13 Commits

Author SHA1 Message Date
Ruben Vermeersch
869f763d80 Release v1.0.0 2015-08-20 15:13:21 +02:00
Ruben Vermeersch
b9394176b1 Handle extracted comments in headers 2015-08-20 15:12:56 +02:00
Ruben Vermeersch
9060221403 Release v0.3.0 2015-07-17 10:36:14 +02:00
Ruben Vermeersch
63c4209cd5 Clean up test names 2015-07-17 10:35:56 +02:00
Ruben Vermeersch
104d114d5d Only write flags when they evaluate to true. 2015-07-17 10:35:25 +02:00
Ruben Vermeersch
db5e540824 Merge pull request #14 from rubenv/fix_issue13
add tests to better document reference comments
2014-08-26 10:38:15 +02:00
Julian Bäume
cfc9b2ae82 add tests to better document reference comments
pofile does not process reference comments in any way, since the format of
references is not exactly specified. This test specifies, what users of
pofile can expect the library to do.
2014-08-12 17:47:47 +02:00
Ruben Vermeersch
2e1640d847 Release v0.2.12 2014-07-18 10:55:53 +02:00
Ruben Vermeersch
b499b7f449 Rebuild for #12. 2014-07-18 10:55:15 +02:00
Ruben Vermeersch
e42dc28fd2 Merge pull request #12 from Open-Xchange-Frontend/cstring_escapes
Cstring escapes
2014-07-18 10:54:52 +02:00
Julian Bäume
e1742e66a6 properly escape all unprintable characters
writing messages should no be in line with gettext tools. I tested
using msgcat, it provides the same results.

For some common use-cases I wrote explicit tests, for uncommon and
even unwanted use-cases I wrote one test to make sure pofile works
like msgcat for those messages
2014-06-23 18:24:02 +02:00
Candid Dauth
4cfebdee80 Fixed unescaping of all escaped C String characters.
Signed-off-by: Julian Bäume <julian@svg4all.de>

Conflicts:
	lib/po.js
2014-06-23 15:03:58 +02:00
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
10 changed files with 246 additions and 39 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "pofile", "name": "pofile",
"version": "0.2.11", "version": "1.0.0",
"authors": [ "authors": [
"Ruben Vermeersch <ruben@rocketeer.be>" "Ruben Vermeersch <ruben@rocketeer.be>"
], ],

80
dist/pofile.js vendored
View File

@@ -1,4 +1,6 @@
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"W8CkM0":[function(require,module,exports){ require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"pofile":[function(require,module,exports){
module.exports=require('W8CkM0');
},{}],"W8CkM0":[function(require,module,exports){
var fs = require('fs'); var fs = require('fs');
var isArray = require('lodash.isarray'); var isArray = require('lodash.isarray');
@@ -8,6 +10,7 @@ function trim(string) {
var PO = function () { var PO = function () {
this.comments = []; this.comments = [];
this.extractedComments = [];
this.headers = {}; this.headers = {};
this.items = []; this.items = [];
}; };
@@ -24,6 +27,11 @@ PO.prototype.toString = function () {
lines.push('# ' + comment); lines.push('# ' + comment);
}); });
} }
if (this.extractedComments) {
this.extractedComments.forEach(function (comment) {
lines.push('#. ' + comment);
});
}
lines.push('msgid ""'); lines.push('msgid ""');
lines.push('msgstr ""'); lines.push('msgstr ""');
@@ -87,10 +95,11 @@ PO.parse = function (data) {
acc.push(line); acc.push(line);
return acc; return acc;
}, []).forEach(function (header) { }, []).forEach(function (header) {
if (header.match(/^#/)) { if (header.match(/^#\./)) {
po.extractedComments.push(header.replace(/^#\.\s*/, ''));
} else if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, '')); po.comments.push(header.replace(/^#\s*/, ''));
} } else if (header.match(/^"/)) {
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, ''); header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/); var p = header.split(/:/);
var name = p.shift().trim(); var name = p.shift().trim();
@@ -120,8 +129,32 @@ PO.parse = function (data) {
function extract(string) { function extract(string) {
string = trim(string); string = trim(string);
string = string.replace(/^[^"]*"|"$/g, ''); string = string.replace(/^[^"]*"|"$/g, '');
string = string.replace(/\\"/g, '"'); string = string.replace(/\\([abtnvfr'"\\?]|([0-7]{3})|x([0-9a-fA-F]{2}))/g, function (match, esc, oct, hex) {
string = string.replace(/\\\\/g, '\\'); if (oct) {
return String.fromCharCode(parseInt(oct, 8));
}
if (hex) {
return String.fromCharCode(parseInt(hex, 16));
}
switch (esc) {
case 'a':
return '\x07';
case 'b':
return '\b';
case 't':
return '\t';
case 'n':
return '\n';
case 'v':
return '\v';
case 'f':
return '\f';
case 'r':
return '\r';
default:
return esc;
}
});
return string; return string;
} }
@@ -213,8 +246,27 @@ PO.Item.prototype.toString = function () {
// reverse what extract(string) method during PO.parse does // reverse what extract(string) method during PO.parse does
var _escape = function (string) { var _escape = function (string) {
string = string.replace(/\\/g, '\\\\'); // don't unescape \n, since string can never contain it
return string.replace(/"/g, '\\"'); // since split('\n') is called on it
string = string.replace(/[\x07\b\t\v\f\r"\\]/g, function (match) {
switch (match) {
case '\x07':
return '\\a';
case '\b':
return '\\b';
case '\t':
return '\\t';
case '\v':
return '\\v';
case '\f':
return '\\f';
case '\r':
return '\\r';
default:
return '\\' + match;
}
});
return string;
}; };
var _process = function (keyword, text, i) { var _process = function (keyword, text, i) {
@@ -247,7 +299,9 @@ PO.Item.prototype.toString = function () {
lines.push('#: ' + ref); lines.push('#: ' + ref);
}); });
var flags = Object.keys(this.flags); var flags = Object.keys(this.flags).filter(function (flag) {
return !!this.flags[flag];
}, this);
if (flags.length > 0) { if (flags.length > 0) {
lines.push('#, ' + flags.join(',')); lines.push('#, ' + flags.join(','));
} }
@@ -263,6 +317,10 @@ PO.Item.prototype.toString = function () {
} else { } else {
text = isArray(text) ? text.join() : text; text = isArray(text) ? text.join() : text;
var processed = _process(keyword, text); var processed = _process(keyword, text);
//handle \n in single-line texts (can not be handled in _escape)
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)); lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
} }
} }
@@ -273,9 +331,7 @@ PO.Item.prototype.toString = function () {
module.exports = PO; module.exports = PO;
},{"fs":3,"lodash.isarray":4}],"pofile":[function(require,module,exports){ },{"fs":3,"lodash.isarray":4}],3:[function(require,module,exports){
module.exports=require('W8CkM0');
},{}],3:[function(require,module,exports){
},{}],4:[function(require,module,exports){ },{}],4:[function(require,module,exports){
/** /**

2
dist/pofile.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,7 @@ function trim(string) {
var PO = function () { var PO = function () {
this.comments = []; this.comments = [];
this.extractedComments = [];
this.headers = {}; this.headers = {};
this.items = []; this.items = [];
}; };
@@ -23,6 +24,11 @@ PO.prototype.toString = function () {
lines.push('# ' + comment); lines.push('# ' + comment);
}); });
} }
if (this.extractedComments) {
this.extractedComments.forEach(function (comment) {
lines.push('#. ' + comment);
});
}
lines.push('msgid ""'); lines.push('msgid ""');
lines.push('msgstr ""'); lines.push('msgstr ""');
@@ -86,10 +92,11 @@ PO.parse = function (data) {
acc.push(line); acc.push(line);
return acc; return acc;
}, []).forEach(function (header) { }, []).forEach(function (header) {
if (header.match(/^#/)) { if (header.match(/^#\./)) {
po.extractedComments.push(header.replace(/^#\.\s*/, ''));
} else if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, '')); po.comments.push(header.replace(/^#\s*/, ''));
} } else if (header.match(/^"/)) {
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, ''); header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/); var p = header.split(/:/);
var name = p.shift().trim(); var name = p.shift().trim();
@@ -119,8 +126,32 @@ PO.parse = function (data) {
function extract(string) { function extract(string) {
string = trim(string); string = trim(string);
string = string.replace(/^[^"]*"|"$/g, ''); string = string.replace(/^[^"]*"|"$/g, '');
string = string.replace(/\\"/g, '"'); string = string.replace(/\\([abtnvfr'"\\?]|([0-7]{3})|x([0-9a-fA-F]{2}))/g, function (match, esc, oct, hex) {
string = string.replace(/\\\\/g, '\\'); if (oct) {
return String.fromCharCode(parseInt(oct, 8));
}
if (hex) {
return String.fromCharCode(parseInt(hex, 16));
}
switch (esc) {
case 'a':
return '\x07';
case 'b':
return '\b';
case 't':
return '\t';
case 'n':
return '\n';
case 'v':
return '\v';
case 'f':
return '\f';
case 'r':
return '\r';
default:
return esc;
}
});
return string; return string;
} }
@@ -212,8 +243,27 @@ PO.Item.prototype.toString = function () {
// reverse what extract(string) method during PO.parse does // reverse what extract(string) method during PO.parse does
var _escape = function (string) { var _escape = function (string) {
string = string.replace(/\\/g, '\\\\'); // don't unescape \n, since string can never contain it
return string.replace(/"/g, '\\"'); // since split('\n') is called on it
string = string.replace(/[\x07\b\t\v\f\r"\\]/g, function (match) {
switch (match) {
case '\x07':
return '\\a';
case '\b':
return '\\b';
case '\t':
return '\\t';
case '\v':
return '\\v';
case '\f':
return '\\f';
case '\r':
return '\\r';
default:
return '\\' + match;
}
});
return string;
}; };
var _process = function (keyword, text, i) { var _process = function (keyword, text, i) {
@@ -246,7 +296,9 @@ PO.Item.prototype.toString = function () {
lines.push('#: ' + ref); lines.push('#: ' + ref);
}); });
var flags = Object.keys(this.flags); var flags = Object.keys(this.flags).filter(function (flag) {
return !!this.flags[flag];
}, this);
if (flags.length > 0) { if (flags.length > 0) {
lines.push('#, ' + flags.join(',')); lines.push('#, ' + flags.join(','));
} }
@@ -262,6 +314,10 @@ PO.Item.prototype.toString = function () {
} else { } else {
text = isArray(text) ? text.join() : text; text = isArray(text) ? text.join() : text;
var processed = _process(keyword, text); var processed = _process(keyword, text);
//handle \n in single-line texts (can not be handled in _escape)
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)); lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "pofile", "name": "pofile",
"description": "Parse and serialize Gettext PO files.", "description": "Parse and serialize Gettext PO files.",
"version": "0.2.11", "version": "1.0.0",
"author": { "author": {
"name": "Ruben Vermeersch", "name": "Ruben Vermeersch",
"email": "ruben@savanne.be", "email": "ruben@savanne.be",

View File

@@ -17,3 +17,43 @@ msgstr ""
msgid "The name field must not contain characters like \" or \\" msgid "The name field must not contain characters like \" or \\"
msgstr "" msgstr ""
# possibility to reorder items depending on locale
#. Format of addresses
#. %1$s is the street
#. %2$s is the postal code
#. %3$s is the city
#. %4$s is the state
#. %5$s is the country
msgid ""
"%1$s\n"
"%2$s %3$s\n"
"%4$s\n"
"%5$s"
msgstr ""
# "i18"ned code
#. used in <pre> environment, so don't remove any control sequences
msgid ""
"define('some/test/module', function () {\n"
"\t'use strict';\n"
"\treturn {};\n"
"});\n"
""
msgstr ""
"define('random/test/file', function () {\n"
"\t'use strict';\n"
"\treturn {};\n"
"});\n"
""
# all one-letter escape characters
# be aware, that \a, \b, \v, \f and \r should not be used
# in i18ned messages (according to gettext tools)
# however, they should be properly parsed, anyway
msgid ""
"\a\b\t\n"
"\v\f\r"
msgstr ""
"\a\b\t\n"
"\v\f\r"

View File

@@ -1,6 +1,7 @@
# French translation of Link (6.x-2.9) # French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team # Copyright (c) 2011 by the French translation team
# #
#. extracted from test
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n" "Project-Id-Version: Link (6.x-2.9)\n"

View File

@@ -24,3 +24,7 @@ msgstr "Attribut title, en tant que texte brut"
#: b #: b
msgid "X" msgid "X"
msgstr "Y" msgstr "Y"
#: standard input:12 standard input:17
msgid "Z"
msgstr "ZZ"

View File

@@ -61,27 +61,41 @@ describe('Parse', function () {
assert.notEqual(po, null); assert.notEqual(po, null);
assert.equal(po.items.length, 2); assert.equal(po.items.length, 2);
assert.equal(po.extractedComments.length, 1);
assert.equal(po.extractedComments[0], 'extracted from test');
var item = po.items[0]; var item = po.items[0];
assert.equal(item.msgid, 'Title, as plain text'); assert.equal(item.msgid, 'Title, as plain text');
assert.equal(item.msgstr, 'Attribut title, en tant que texte brut'); assert.equal(item.msgstr, 'Attribut title, en tant que texte brut');
assert.deepEqual(item.extractedComments, ['Extracted comment']); assert.deepEqual(item.extractedComments, ['Extracted comment']);
}); });
it('Handles string references', function () { describe('Handles string references', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/reference.po', 'utf8')); var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/reference.po', 'utf8'));
assert.notEqual(po, null); assert.notEqual(po, null);
assert.equal(po.items.length, 2); assert.equal(po.items.length, 3);
var item = po.items[0]; it('in simple cases', function () {
assert.equal(item.msgid, 'Title, as plain text'); var item = po.items[0];
assert.equal(item.msgstr, 'Attribut title, en tant que texte brut'); assert.equal(item.msgid, 'Title, as plain text');
assert.deepEqual(item.comments, ['Comment']); assert.equal(item.msgstr, 'Attribut title, en tant que texte brut');
assert.deepEqual(item.references, ['.tmp/crm/controllers/map.js']); assert.deepEqual(item.comments, ['Comment']);
assert.deepEqual(item.references, ['.tmp/crm/controllers/map.js']);
});
item = po.items[1]; it('with two different references', function () {
assert.equal(item.msgid, 'X'); var item = po.items[1];
assert.equal(item.msgstr, 'Y'); assert.equal(item.msgid, 'X');
assert.deepEqual(item.references, ['a', 'b']); assert.equal(item.msgstr, 'Y');
assert.deepEqual(item.references, ['a', 'b']);
});
it('and does not process reference items', function () {
var item = po.items[2];
assert.equal(item.msgid, 'Z');
assert.equal(item.msgstr, 'ZZ');
assert.deepEqual(item.references, ['standard input:12 standard input:17']);
});
}); });
it('Parses flags', function () { it('Parses flags', function () {
@@ -133,19 +147,29 @@ describe('Parse', function () {
}); });
describe('C-Strings', function () { describe('C-Strings', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/c-strings.po', 'utf8'));
it('should parse the c-strings.po file', function () { it('should parse the c-strings.po file', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/c-strings.po', 'utf8'));
assert.notEqual(po, null); assert.notEqual(po, null);
}); });
it('should extract strings containing " and \\ characters', function () { it('should extract strings containing " and \\ characters', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/c-strings.po', 'utf8'));
var items = po.items.filter(function (item) { var items = po.items.filter(function (item) {
return (/^The name field must not contain/).test(item.msgid); return (/^The name field must not contain/).test(item.msgid);
}); });
assert.equal(items[0].msgid, 'The name field must not contain characters like " or \\'); assert.equal(items[0].msgid, 'The name field must not contain characters like " or \\');
}); });
it('should handle \\n characters', function () {
var item = po.items[1];
assert.equal(item.msgid, '%1$s\n%2$s %3$s\n%4$s\n%5$s');
});
it('should handle \\t characters', function () {
var item = po.items[2];
assert.equal(item.msgid, 'define(\'some/test/module\', function () {\n' +
'\t\'use strict\';\n' +
'\treturn {};\n' +
'});\n');
});
}); });
}); });

View File

@@ -16,6 +16,20 @@ function assertHasLine(str, line) {
assert(found, 'Could not find line: ' + line); assert(found, 'Could not find line: ' + line);
} }
function assertDoesntHaveLine(str, line) {
var lines = str.split('\n');
var found = false;
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim() === line) {
found = true;
break;
}
}
assert(!found, 'Shouldn\'t have line: ' + line);
}
describe('Write', function () { describe('Write', function () {
it('write flags', function () { it('write flags', function () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8'); var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
@@ -24,6 +38,17 @@ describe('Write', function () {
assertHasLine(str, '#, fuzzy'); assertHasLine(str, '#, fuzzy');
}); });
it('write flags only when true', function () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
var po = PO.parse(input);
// Flip flag
po.items[0].flags.fuzzy = false;
var str = po.toString();
assertDoesntHaveLine(str, '#, fuzzy');
});
it('write msgid', function () { it('write msgid', function () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8'); var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
var po = PO.parse(input); var po = PO.parse(input);
@@ -49,6 +74,7 @@ describe('Write', function () {
var input = fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8'); var input = fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8');
var po = PO.parse(input); var po = PO.parse(input);
var str = po.toString(); var str = po.toString();
assertHasLine(str, '#. extracted from test');
assertHasLine(str, '#. Extracted comment'); assertHasLine(str, '#. Extracted comment');
}); });
@@ -97,7 +123,7 @@ describe('Write', function () {
item.msgid = '\n should be written escaped'; item.msgid = '\n should be written escaped';
assertHasLine(item.toString(), 'msgid ""'); assertHasLine(item.toString(), 'msgid ""');
assertHasLine(item.toString(), '""'); assertHasLine(item.toString(), '"\\n"');
assertHasLine(item.toString(), '" should be written escaped"'); assertHasLine(item.toString(), '" should be written escaped"');
}); });