Compare commits

92 Commits

Author SHA1 Message Date
Ruben Vermeersch
c62b82a98d Revert "Fix type of PO.Item" 2020-04-21 09:38:00 +02:00
Ruben Vermeersch
fe23027f32 Merge pull request #38 from kirillku/patch-1
Fix type of PO.Item
2020-04-21 09:37:31 +02:00
Kirill Kubryakov
d92e1f9e89 Fix type of PO.Item 2020-04-21 10:25:53 +03:00
Ruben Vermeersch
0359aa12a4 Release v1.1.0 2019-06-25 13:31:56 +02:00
Ruben Vermeersch
6357bf3edd Merge pull request #34 from septs/master
rewrite typescript definition file
2019-06-25 13:31:05 +02:00
Septs
3b28b3ed08 remove test code 2019-06-25 19:27:45 +08:00
Septs
207308a1ac rewrite ts define 2019-06-25 19:12:53 +08:00
Ruben Vermeersch
b8676a4fe6 Release v1.0.11 2018-05-25 11:34:42 +02:00
Ruben Vermeersch
bf879ca5ee Rebuild 2018-05-25 11:34:26 +02:00
Martin Bachtík
8bd7810703 header order 2018-05-25 11:21:23 +02:00
Ruben Vermeersch
ad71dba6ad Release v1.0.10 2017-12-04 14:08:38 +01:00
Ruben Vermeersch
ab3d6f8405 Merge pull request #31 from tafkanator/master
added support for multiline msgctxt strings
2017-12-04 14:06:04 +01:00
Taavi Sangel
58ee1abedb added compiled files #31 2017-12-04 13:57:12 +02:00
Taavi Sangel
0aef81f4e9 added test for multiline msgctxt #31 2017-12-04 13:53:16 +02:00
Taavi Sangel
579a5635a6 added support for multiline msgctxt strings 2017-11-21 13:29:28 +02:00
Ruben Vermeersch
03a257b40d Release v1.0.9 2017-09-14 15:27:44 +02:00
Ruben Vermeersch
58875dc119 Merge pull request #30 from janhommes/master
Fixed issue with plural multi line text.
2017-09-14 15:26:50 +02:00
janhommes
a6937bb7f0 Fixed issue with plural multiline text. 2017-09-14 14:37:45 +02:00
Ruben Vermeersch
cfe01e8aea Release v1.0.8 2017-04-24 20:38:10 +02:00
megaboich
14bad962eb Updated Typescript definitions (#29) 2017-04-24 20:36:49 +02:00
Ruben Vermeersch
ff0e5655f9 Release v1.0.7 2017-03-20 10:27:27 +01:00
Ruben Vermeersch
bb56f6e34c Rebuild for #28 2017-03-20 10:27:12 +01:00
Ruben Vermeersch
f035affef6 Merge pull request #28 from dedesite/patch-1
Avoid putting a space in empty comments
2017-03-20 10:26:46 +01:00
Andréas Livet
18a4cc0cb5 Add test to ensure no spaces are added on empty comments 2017-03-20 10:20:04 +01:00
Andréas Livet
0fcff887fe Avoid putting a space in empty comments
In our pipeline, empty comments in po files are generated without
a space after '#' and saving a po file using pofile lib generates
unwanted diff that need to be handle manually which is really annoying.
This pull request simply trim the comment line.
2017-03-17 14:25:20 +01:00
Ruben Vermeersch
9689ae5b7f Release v1.0.6 2017-03-13 19:39:23 +01:00
Ruben Vermeersch
5cbb657f20 Merge pull request #27 from ma2ciek/master
Add support for the typescript projects and IDE-s.
2017-03-13 19:39:04 +01:00
Maciej Bukowski
e038f25d5b Added support for the typescript projects and IDE-s. 2017-03-13 17:40:01 +01:00
Ruben Vermeersch
f26ecb0d63 Release v1.0.5 2017-03-09 07:45:36 +01:00
Ruben Vermeersch
3324041669 Rebuild 2017-03-09 07:45:14 +01:00
Ruben Vermeersch
d30a02f3c2 Merge pull request #25 from appropos/msgstr-nplurals
Stub out missing translations according to language plurals
2017-03-09 07:44:43 +01:00
rosston
0e3a6d74f3 Stub out missing translations according to language plurals. 2017-03-08 10:24:51 -05:00
Ruben Vermeersch
d783222d37 Test with "node" 2017-03-06 17:30:59 +01:00
Ruben Vermeersch
36f5d05828 Release v1.0.4 2017-03-06 17:26:55 +01:00
Ruben Vermeersch
8fe9cd7bf8 Fix alias 2017-03-06 17:26:45 +01:00
Ruben Vermeersch
48fa2c39f3 Bump 2017-02-10 17:47:45 +01:00
Ruben Vermeersch
037edff739 Release v1.0.3 2017-02-10 17:46:32 +01:00
Ruben Vermeersch
16f423879a Rebuild 2017-02-10 17:02:46 +01:00
Ruben Vermeersch
52c11f77e9 Merge pull request #24 from appropos/msgstr-0
Write proper plural msgstr[0]
2017-02-10 17:01:30 +01:00
rosston
115459160e Use msgstr[0] when stringifying an untranslated plural item. 2017-02-10 10:18:01 -05:00
rosston
79407fcf5e Remove lodash.isarray (use native Array.isArray).
Other code was already depending on Object.keys() and
Array.prototype.filter(), which are also ES5 along with
Array.isArray().
2017-02-09 22:44:46 -05:00
rosston
e2330bd433 Upgrade dev dependencies. 2017-02-09 22:44:45 -05:00
Ruben Vermeersch
853823f214 Merge pull request #22 from paulpdaniels/patch-1
Fix readme reference
2015-10-21 07:28:53 +02:00
Paul Daniels
832962d30d Fix readme reference 2015-10-20 16:07:21 -07:00
Ruben Vermeersch
f6119a57af Release v1.0.2 2015-09-23 07:41:22 +02:00
Ruben Vermeersch
6c09df93a2 Rebuild 2015-09-23 07:41:09 +02:00
Ruben Vermeersch
1bf498ecf7 Merge pull request #19 from rubenv/no_headers
Add proper support for no headers in po files
2015-09-23 07:40:44 +02:00
Julian Bäume
71bb04f046 add support for po files without headers
basically fix all the examples.
2015-09-22 23:36:12 +02:00
Julian Bäume
ba9a2db453 add examples for po files without any headers
those should at least be fixed
2015-09-22 10:38:43 +02:00
Ruben Vermeersch
fbe773c636 Release v1.0.1 2015-09-21 09:35:02 +02:00
Ruben Vermeersch
7ceda82794 Merge pull request #17 from rubenv/fix_issue16
fix issue #16
2015-09-21 09:34:41 +02:00
Julian Bäume
d1be0f51b0 fix issue #16
add a blank line in the comments of the headers of the big.po file. This
breaks parsing the headers. msgcat of gettext tools just ignores empty
lines until a header is found and treats everything above as header
comment.

The change to the library itself fixes the tests again, after the blank
linke broke 3.
2015-09-21 09:13:43 +02:00
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
Ruben Vermeersch
d202e39a60 Release v0.2.11 2014-06-23 14:12:25 +02:00
Ruben Vermeersch
f5056bc57f Merge pull request #11 from Open-Xchange-Frontend/fix_newline_in_msgid
restore previous behaviour with \n in strings
2014-06-23 14:11:52 +02:00
Julian Bäume
af94d8ff5e restore previous behaviour with \n in strings
An incompatible change (that actually breaks po parsing after writing) had
been introduced with commit e164fcfe9d. If
_process returned an array (which is the case for strings containing \n
character), array.toString will return a comma separated list, which is not
valid po syntax. Added a test to restore the behaviour from before the
e164fcfe9d.
2014-06-23 12:30:04 +02:00
Ruben Vermeersch
8e49417916 Release v0.2.10 2014-06-19 17:18:55 +02:00
Ruben Vermeersch
b9534d20fe Recompile. 2014-06-19 17:18:45 +02:00
Ruben Vermeersch
1b5668b5c7 Merge pull request #10 from Open-Xchange-Frontend/handle_empty_comments
handle empty comments correctly
2014-06-19 17:18:01 +02:00
Julian Bäume
08e7db58b3 handle empty comments correctly
since the lines in the parser have all newline characters removed, \s+ will
not match empty comments.

Added an example that makes other tests fail without this patch.
2014-06-19 17:04:03 +02:00
Ruben Vermeersch
eeb1382dfb Release v0.2.9 2014-06-19 14:16:52 +02:00
Ruben Vermeersch
656bfd0b8d Add JSCS, fix style. 2014-06-19 14:16:30 +02:00
Ruben Vermeersch
03572f9711 Merge pull request #9 from Open-Xchange-Frontend/fix_commented_obsolete_strings
Fix commented obsolete strings
2014-06-19 14:05:32 +02:00
Ruben Vermeersch
6af1cf741f Remove node 0.8 from Travis. 2014-06-19 14:02:54 +02:00
Julian Bäume
e164fcfe9d new implementation of items marked obsolete
the current implementation of items marked obsolete did not allow "plain"
comments for these items. However, this is perfectly fine according to
the original gettext tools. When writing a po file, comments for obsolete
items don't contain the '#~ ' mark (tested using msgcat), so this is now
also aligned with the behaviour of the original gettext tools.

For all these cases I added examples in the po files, that failed with the
current implementation and work fine after these changes.
2014-06-19 13:47:30 +02:00
Julian Bäume
8d40e1b3c4 unify string quotes 2014-06-17 21:19:14 +02:00
Ruben Vermeersch
04fe915389 Release v0.2.8 2014-03-21 11:08:59 +01:00
Ruben Vermeersch
2fb07fb1d0 Recompile browser versions for #8. 2014-03-21 11:08:30 +01:00
Ruben Vermeersch
06571c89fc Merge pull request #8 from Open-Xchange-Frontend/multiline_headers
add support for multiline string in headers
2014-03-21 11:08:05 +01:00
Julian Bäume
73b267b3e8 add support for multiline string in headers
Some languages (such as Polish, Russian or Romanian) do have more
complicated plural forms. Those are still expressible by a more
complicated mathematical expression. However, the msgmerge tool of
gettext will in these cases write multiline header fields. When parsing
such files with this lib, the headers get screwed up, so this patch
provides an example (from a pl_PL po file) and fixes this by joining the
lines in the header, before doing the actual parsing.
2014-03-21 10:54:32 +01:00
Ruben Vermeersch
42d6df5373 Release v0.2.7 2014-03-07 10:18:30 +01:00
Ruben Vermeersch
dba7465ba7 Remove unneeded if-clauses. 2014-03-07 10:17:52 +01:00
Ruben Vermeersch
d426c114c7 Add support for obsolete items to fix broken parsing. 2014-03-07 10:15:50 +01:00
Ruben Vermeersch
5a49a3400b Release v0.2.6 2014-03-05 11:48:45 +01:00
Ruben Vermeersch
c103f45002 Recompile for #7. 2014-03-05 11:48:14 +01:00
Ruben Vermeersch
cb12e69ef4 Merge pull request #7 from Open-Xchange-Frontend/write_escaped
add reverse method of extract(string)
2014-03-05 11:47:56 +01:00
Julian Bäume
851a87ebf2 add some real world examples for c-strings in po files
make sure, if a file containing c-string messages is identical after being
parsed and written afterwards.
2014-03-05 11:37:48 +01:00
Julian Bäume
94f5f4a83e add reverse method of extract(string)
during PO.parse, an extract(string) method is called on each string to
unescape some characters (like " and \). This process should be reverted
in the toString method.

The PO spec says, that all strings should be C-Strings. Otherwise tools
like msgmerge (from the gettext package) will fail parsing po files written
by this library.
2014-03-05 10:13:30 +01:00
Ruben Vermeersch
6903bbf967 Fix build. 2014-03-03 16:20:49 +01:00
Ruben Vermeersch
b82dea6e42 Update contributors. 2014-03-03 16:17:22 +01:00
28 changed files with 1652 additions and 319 deletions

34
.jscs.json Normal file
View File

@@ -0,0 +1,34 @@
{
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireParenthesesAroundIIFE": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
"disallowMultipleVarDecl": true,
"requireSpacesInsideObjectBrackets": "all",
"disallowSpaceAfterObjectKeys": true,
"requireCommaBeforeLineBreak": true,
"disallowSpaceBeforeBinaryOperators": [","],
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"requireSpaceAfterBinaryOperators": [",", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"validateQuoteMarks": true,
"validateIndentation": 4,
"disallowTrailingWhitespace": true,
"disallowKeywordsOnNewLine": ["else"],
"requireCapitalizedConstructors": true,
"safeContextKeyword": "self",
"requireDotNotation": true,
"disallowYodaConditions": true
}

View File

@@ -1,7 +1,5 @@
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.11"
before_script:
- "node"
before_install:
- npm install -g grunt-cli

View File

@@ -5,6 +5,7 @@ module.exports = (grunt) ->
@loadNpmTasks('grunt-contrib-jshint')
@loadNpmTasks('grunt-contrib-uglify')
@loadNpmTasks('grunt-contrib-watch')
@loadNpmTasks('grunt-jscs')
@loadNpmTasks('grunt-mocha-cli')
@initConfig
@@ -16,6 +17,13 @@ module.exports = (grunt) ->
options:
jshintrc: '.jshintrc'
jscs:
src:
options:
config: '.jscs.json'
files:
src: [ 'lib/*.js', 'test/*.js' ]
watch:
all:
options:
@@ -35,7 +43,8 @@ module.exports = (grunt) ->
files:
'dist/pofile.js': ['lib/po.js']
options:
alias: 'lib/po.js:pofile'
alias:
pofile: './lib/po.js'
uglify:
dist:
@@ -49,5 +58,5 @@ module.exports = (grunt) ->
pushTo: 'origin'
@registerTask 'default', ['test']
@registerTask 'build', ['clean', 'jshint', 'browserify', 'uglify']
@registerTask 'build', ['clean', 'jshint', 'jscs', 'browserify', 'uglify']
@registerTask 'test', ['build', 'mochacli']

View File

@@ -1,4 +1,4 @@
Copyright (C) 2013-2014 Ruben Vermeersch
Copyright (C) 2013-2017 Ruben Vermeersch
Copyright (C) 2012 Michael Holly
Permission is hereby granted, free of charge, to any person obtaining a copy of

View File

@@ -81,7 +81,7 @@ po.save('out.po', function (err) {
### The PO.Item class
The `PO` class exposes the following members:
The `PO.Item` class exposes the following members:
* `msgid`: The message id.
* `msgid_plural`: The plural message id (null if absent).
@@ -143,7 +143,7 @@ PO.load('text.po', function (err, po) {
(The MIT License)
Copyright (C) 2013-2014 by Ruben Vermeersch <ruben@rocketeer.be>
Copyright (C) 2013-2017 by Ruben Vermeersch <ruben@rocketeer.be>
Copyright (C) 2012 by Michael Holly
Permission is hereby granted, free of charge, to any person obtaining a copy

View File

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

417
dist/pofile.js vendored
View File

@@ -1,6 +1,7 @@
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){
var fs = require('fs'),
isArray = require('lodash.isarray');
require=(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
},{}],"pofile":[function(require,module,exports){
var fs = require('fs');
function trim(string) {
return string.replace(/^\s+|\s+$/g, '');
@@ -8,7 +9,9 @@ function trim(string) {
var PO = function () {
this.comments = [];
this.extractedComments = [];
this.headers = {};
this.headerOrder = [];
this.items = [];
};
@@ -17,21 +20,41 @@ PO.prototype.save = function (filename, callback) {
};
PO.prototype.toString = function () {
var lines = [],
that = this;
var lines = [];
if (this.comments) {
this.comments.forEach(function (comment) {
lines.push('# ' + comment);
lines.push(('# ' + comment).trim());
});
}
if (this.extractedComments) {
this.extractedComments.forEach(function (comment) {
lines.push(('#. ' + comment).trim());
});
}
lines.push('msgid ""');
lines.push('msgstr ""');
var self = this;
var headerOrder = [];
this.headerOrder.forEach(function (key) {
if (key in self.headers) {
headerOrder.push(key);
}
});
var keys = Object.keys(this.headers);
keys.forEach(function (key) {
lines.push('"' + key + ': ' + that.headers[key] + '\\n"');
if (headerOrder.indexOf(key) === -1) {
headerOrder.push(key);
}
});
headerOrder.forEach(function (key) {
lines.push('"' + key + ': ' + self.headers[key] + '\\n"');
});
lines.push('');
@@ -41,7 +64,7 @@ PO.prototype.toString = function () {
lines.push('');
});
return lines.join("\n");
return lines.join('\n');
};
PO.load = function (filename, callback) {
@@ -57,10 +80,20 @@ PO.load = function (filename, callback) {
PO.parse = function (data) {
//support both unix and windows newline formats.
data = data.replace(/\r\n/g, '\n');
var po = new PO(),
sections = data.split(/\n\n/),
headers = sections.shift(),
lines = sections.join("\n").split(/\n/);
var po = new PO();
var sections = data.split(/\n\n/);
var headers = [];
//everything until the first 'msgid ""' is considered header
while (sections[0] && (headers.length === 0 || headers[headers.length - 1].indexOf('msgid ""') < 0)) {
if (sections[0].match(/msgid "[^"]/)) {
//found first real string, adding a dummy header item
headers.push('msgid ""');
} else {
headers.push(sections.shift());
}
}
headers = headers.join('\n');
var lines = sections.join('\n').split(/\n/);
po.headers = {
'Project-Id-Version': '',
@@ -74,100 +107,179 @@ PO.parse = function (data) {
'Content-Transfer-Encoding': '',
'Plural-Forms': '',
};
po.headerOrder = [];
headers.split(/\n/).forEach(function (header) {
if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
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 (header.match(/^"/)) {
if (/^".*"$/.test(line) && !/^".*\\n"$/.test(line)) {
acc.merge = true;
}
acc.push(line);
return acc;
}, []).forEach(function (header) {
if (header.match(/^#\./)) {
po.extractedComments.push(header.replace(/^#\.\s*/, ''));
} else if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
} else if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/),
name = p.shift().trim(),
value = p.join(':').trim();
var p = header.split(/:/);
var name = p.shift().trim();
var value = p.join(':').trim();
po.headers[name] = value;
po.headerOrder.push(name);
}
});
var item = new PO.Item(),
context = null,
plural = 0;
var parsedPluralForms = PO.parsePluralForms(po.headers['Plural-Forms']);
var nplurals = parsedPluralForms.nplurals;
var item = new PO.Item({ nplurals: nplurals });
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();
item = new PO.Item({ nplurals: nplurals });
}
}
function extract(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) {
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;
}
while (lines.length > 0) {
var line = trim(lines.shift()),
add = false;
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
} else if (line.match(/^#,/)) { // Flags
finish();
var flags = trim(line.replace(/^#,/, '')).split(",");
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
} else if (line.match(/^#($|\s+)/)) { // Translator comment
finish();
item.comments.push(trim(line.replace(/^#\s+/, '')));
}
else if (line.match(/^#\./)) { // Extracted comment
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
} else if (line.match(/^msgid_plural/)) { // Plural form
item.msgid_plural = extract(line);
context = 'msgid_plural';
}
else if (line.match(/^msgid/)) { // Original
noCommentLineCount++;
} else if (line.match(/^msgid/)) { // Original
finish();
item.msgid = extract(line);
context = 'msgid';
}
else if (line.match(/^msgstr/)) { // Translation
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';
}
else if (line.match(/^msgctxt/)) { // Context
noCommentLineCount++;
} else if (line.match(/^msgctxt/)) { // Context
finish();
item.msgctxt = extract(line);
}
else { // Probably multiline string or blank
context = 'msgctxt';
noCommentLineCount++;
} else { // Probably multiline string or blank
if (line.length > 0) {
noCommentLineCount++;
if (context === 'msgstr') {
item.msgstr[plural] += extract(line);
}
else if (context === 'msgid') {
} else if (context === 'msgid') {
item.msgid += extract(line);
}
else if (context === 'msgid_plural') {
} else if (context === 'msgid_plural') {
item.msgid_plural += extract(line);
} else if (context === 'msgctxt') {
item.msgctxt += extract(line);
}
}
}
if (lineObsolete) {
// Count obsolete lines for this item
obsoleteCount++;
}
}
finish();
return po;
};
PO.Item = function () {
PO.parsePluralForms = function (pluralFormsString) {
var results = (pluralFormsString || '')
.split(';')
.reduce(function (acc, keyValueString) {
var trimmedString = keyValueString.trim();
var equalsIndex = trimmedString.indexOf('=');
var key = trimmedString.substring(0, equalsIndex).trim();
var value = trimmedString.substring(equalsIndex + 1).trim();
acc[key] = value;
return acc;
}, {});
return {
nplurals: results.nplurals,
plural: results.plural
};
};
PO.Item = function (options) {
var nplurals = options && options.nplurals;
this.msgid = '';
this.msgctxt = null;
this.references = [];
@@ -176,159 +288,120 @@ PO.Item = function () {
this.comments = []; // translator comments
this.extractedComments = [];
this.flags = {};
this.obsolete = false;
var npluralsNumber = Number(nplurals);
this.nplurals = (isNaN(npluralsNumber)) ? 2 : npluralsNumber;
};
PO.Item.prototype.toString = function () {
var lines = [],
that = this;
var lines = [];
var self = this;
// reverse what extract(string) method during PO.parse does
var _escape = function (string) {
// don't unescape \n, since string can never contain it
// 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 lines = [],
parts = text.split(/\n/),
index = typeof i !== 'undefined' ? '[' + 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('"' + part + '"');
lines.push('"' + _escape(part) + '"');
});
}
else {
lines.push(keyword + index + ' "' + text + '"');
} else {
lines.push(keyword + index + ' "' + _escape(text) + '"');
}
return lines;
};
//handle \n in single-line texts (can not be handled in _escape)
var _processLineBreak = function (keyword, text, index) {
var processed = _process(keyword, text, index);
for (var i = 1; i < processed.length - 1; i++) {
processed[i] = processed[i].slice(0, -1) + '\\n"';
}
return processed;
};
// https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
// says order is translator-comments, extracted-comments, references, flags
if (this.comments.length > 0) {
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
}
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
if (this.extractedComments.length > 0) {
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
}
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
if (this.references.length > 0) {
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
}
this.references.forEach(function (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) {
lines.push('#, ' + flags.join(","));
lines.push('#, ' + flags.join(','));
}
var mkObsolete = this.obsolete ? '#~ ' : '';
['msgctxt', 'msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = that[keyword];
var text = self[keyword];
if (text != null) {
if (isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
lines = lines.concat(_process(keyword, t, i));
var hasTranslation = false;
if (Array.isArray(text)) {
hasTranslation = text.some(function (text) {
return text;
});
}
else {
text = isArray(text) ? text.join() : text;
lines = lines.concat(_process(keyword, text));
if (Array.isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
var processed = _processLineBreak(keyword, t, i);
lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
});
} else if (self.msgid_plural && keyword === 'msgstr' && !hasTranslation) {
for (var pluralIndex = 0; pluralIndex < self.nplurals; pluralIndex++) {
lines = lines.concat(mkObsolete + _process(keyword, '', pluralIndex));
}
} else {
var index = (self.msgid_plural && Array.isArray(text)) ?
0 :
undefined;
text = Array.isArray(text) ? text.join() : text;
var processed = _processLineBreak(keyword, text, index);
lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
}
}
});
return lines.join("\n");
return lines.join('\n');
};
module.exports = PO;
},{"fs":3,"lodash.isarray":4}],"pofile":[function(require,module,exports){
module.exports=require('W8CkM0');
},{}],3:[function(require,module,exports){
},{}],4:[function(require,module,exports){
/**
* Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var isNative = require('lodash._isnative');
/** `Object#toString` result shortcuts */
var arrayClass = '[object Array]';
/** Used for native method references */
var objectProto = Object.prototype;
/** Used to resolve the internal [[Class]] of values */
var toString = objectProto.toString;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray;
/**
* Checks if `value` is an array.
*
* @static
* @memberOf _
* @type Function
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is an array, else `false`.
* @example
*
* (function() { return _.isArray(arguments); })();
* // => false
*
* _.isArray([1, 2, 3]);
* // => true
*/
var isArray = nativeIsArray || function(value) {
return value && typeof value == 'object' && typeof value.length == 'number' &&
toString.call(value) == arrayClass || false;
};
module.exports = isArray;
},{"lodash._isnative":5}],5:[function(require,module,exports){
/**
* Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used for native method references */
var objectProto = Object.prototype;
/** Used to resolve the internal [[Class]] of values */
var toString = objectProto.toString;
/** Used to detect if a method is native */
var reNative = RegExp('^' +
String(toString)
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/toString| for [^\]]+/g, '.*?') + '$'
);
/**
* Checks if `value` is a native function.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
*/
function isNative(value) {
return typeof value == 'function' && reNative.test(value);
}
module.exports = isNative;
},{}]},{},["W8CkM0"])
},{"fs":1}]},{},["pofile"]);

2
dist/pofile.min.js vendored

File diff suppressed because one or more lines are too long

324
lib/po.js
View File

@@ -1,5 +1,4 @@
var fs = require('fs'),
isArray = require('lodash.isarray');
var fs = require('fs');
function trim(string) {
return string.replace(/^\s+|\s+$/g, '');
@@ -7,7 +6,9 @@ function trim(string) {
var PO = function () {
this.comments = [];
this.extractedComments = [];
this.headers = {};
this.headerOrder = [];
this.items = [];
};
@@ -16,21 +17,41 @@ PO.prototype.save = function (filename, callback) {
};
PO.prototype.toString = function () {
var lines = [],
that = this;
var lines = [];
if (this.comments) {
this.comments.forEach(function (comment) {
lines.push('# ' + comment);
lines.push(('# ' + comment).trim());
});
}
if (this.extractedComments) {
this.extractedComments.forEach(function (comment) {
lines.push(('#. ' + comment).trim());
});
}
lines.push('msgid ""');
lines.push('msgstr ""');
var self = this;
var headerOrder = [];
this.headerOrder.forEach(function (key) {
if (key in self.headers) {
headerOrder.push(key);
}
});
var keys = Object.keys(this.headers);
keys.forEach(function (key) {
lines.push('"' + key + ': ' + that.headers[key] + '\\n"');
if (headerOrder.indexOf(key) === -1) {
headerOrder.push(key);
}
});
headerOrder.forEach(function (key) {
lines.push('"' + key + ': ' + self.headers[key] + '\\n"');
});
lines.push('');
@@ -40,7 +61,7 @@ PO.prototype.toString = function () {
lines.push('');
});
return lines.join("\n");
return lines.join('\n');
};
PO.load = function (filename, callback) {
@@ -56,10 +77,20 @@ PO.load = function (filename, callback) {
PO.parse = function (data) {
//support both unix and windows newline formats.
data = data.replace(/\r\n/g, '\n');
var po = new PO(),
sections = data.split(/\n\n/),
headers = sections.shift(),
lines = sections.join("\n").split(/\n/);
var po = new PO();
var sections = data.split(/\n\n/);
var headers = [];
//everything until the first 'msgid ""' is considered header
while (sections[0] && (headers.length === 0 || headers[headers.length - 1].indexOf('msgid ""') < 0)) {
if (sections[0].match(/msgid "[^"]/)) {
//found first real string, adding a dummy header item
headers.push('msgid ""');
} else {
headers.push(sections.shift());
}
}
headers = headers.join('\n');
var lines = sections.join('\n').split(/\n/);
po.headers = {
'Project-Id-Version': '',
@@ -73,100 +104,179 @@ PO.parse = function (data) {
'Content-Transfer-Encoding': '',
'Plural-Forms': '',
};
po.headerOrder = [];
headers.split(/\n/).forEach(function (header) {
if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
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 (header.match(/^"/)) {
if (/^".*"$/.test(line) && !/^".*\\n"$/.test(line)) {
acc.merge = true;
}
acc.push(line);
return acc;
}, []).forEach(function (header) {
if (header.match(/^#\./)) {
po.extractedComments.push(header.replace(/^#\.\s*/, ''));
} else if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
} else if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/),
name = p.shift().trim(),
value = p.join(':').trim();
var p = header.split(/:/);
var name = p.shift().trim();
var value = p.join(':').trim();
po.headers[name] = value;
po.headerOrder.push(name);
}
});
var item = new PO.Item(),
context = null,
plural = 0;
var parsedPluralForms = PO.parsePluralForms(po.headers['Plural-Forms']);
var nplurals = parsedPluralForms.nplurals;
var item = new PO.Item({ nplurals: nplurals });
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();
item = new PO.Item({ nplurals: nplurals });
}
}
function extract(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) {
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;
}
while (lines.length > 0) {
var line = trim(lines.shift()),
add = false;
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
} else if (line.match(/^#,/)) { // Flags
finish();
var flags = trim(line.replace(/^#,/, '')).split(",");
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
} else if (line.match(/^#($|\s+)/)) { // Translator comment
finish();
item.comments.push(trim(line.replace(/^#\s+/, '')));
}
else if (line.match(/^#\./)) { // Extracted comment
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
} else if (line.match(/^msgid_plural/)) { // Plural form
item.msgid_plural = extract(line);
context = 'msgid_plural';
}
else if (line.match(/^msgid/)) { // Original
noCommentLineCount++;
} else if (line.match(/^msgid/)) { // Original
finish();
item.msgid = extract(line);
context = 'msgid';
}
else if (line.match(/^msgstr/)) { // Translation
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';
}
else if (line.match(/^msgctxt/)) { // Context
noCommentLineCount++;
} else if (line.match(/^msgctxt/)) { // Context
finish();
item.msgctxt = extract(line);
}
else { // Probably multiline string or blank
context = 'msgctxt';
noCommentLineCount++;
} else { // Probably multiline string or blank
if (line.length > 0) {
noCommentLineCount++;
if (context === 'msgstr') {
item.msgstr[plural] += extract(line);
}
else if (context === 'msgid') {
} else if (context === 'msgid') {
item.msgid += extract(line);
}
else if (context === 'msgid_plural') {
} else if (context === 'msgid_plural') {
item.msgid_plural += extract(line);
} else if (context === 'msgctxt') {
item.msgctxt += extract(line);
}
}
}
if (lineObsolete) {
// Count obsolete lines for this item
obsoleteCount++;
}
}
finish();
return po;
};
PO.Item = function () {
PO.parsePluralForms = function (pluralFormsString) {
var results = (pluralFormsString || '')
.split(';')
.reduce(function (acc, keyValueString) {
var trimmedString = keyValueString.trim();
var equalsIndex = trimmedString.indexOf('=');
var key = trimmedString.substring(0, equalsIndex).trim();
var value = trimmedString.substring(equalsIndex + 1).trim();
acc[key] = value;
return acc;
}, {});
return {
nplurals: results.nplurals,
plural: results.plural
};
};
PO.Item = function (options) {
var nplurals = options && options.nplurals;
this.msgid = '';
this.msgctxt = null;
this.references = [];
@@ -175,70 +285,118 @@ PO.Item = function () {
this.comments = []; // translator comments
this.extractedComments = [];
this.flags = {};
this.obsolete = false;
var npluralsNumber = Number(nplurals);
this.nplurals = (isNaN(npluralsNumber)) ? 2 : npluralsNumber;
};
PO.Item.prototype.toString = function () {
var lines = [],
that = this;
var lines = [];
var self = this;
// reverse what extract(string) method during PO.parse does
var _escape = function (string) {
// don't unescape \n, since string can never contain it
// 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 lines = [],
parts = text.split(/\n/),
index = typeof i !== 'undefined' ? '[' + 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('"' + part + '"');
lines.push('"' + _escape(part) + '"');
});
}
else {
lines.push(keyword + index + ' "' + text + '"');
} else {
lines.push(keyword + index + ' "' + _escape(text) + '"');
}
return lines;
};
//handle \n in single-line texts (can not be handled in _escape)
var _processLineBreak = function (keyword, text, index) {
var processed = _process(keyword, text, index);
for (var i = 1; i < processed.length - 1; i++) {
processed[i] = processed[i].slice(0, -1) + '\\n"';
}
return processed;
};
// https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
// says order is translator-comments, extracted-comments, references, flags
if (this.comments.length > 0) {
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
}
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
if (this.extractedComments.length > 0) {
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
}
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
if (this.references.length > 0) {
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
}
this.references.forEach(function (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) {
lines.push('#, ' + flags.join(","));
lines.push('#, ' + flags.join(','));
}
var mkObsolete = this.obsolete ? '#~ ' : '';
['msgctxt', 'msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = that[keyword];
var text = self[keyword];
if (text != null) {
if (isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
lines = lines.concat(_process(keyword, t, i));
var hasTranslation = false;
if (Array.isArray(text)) {
hasTranslation = text.some(function (text) {
return text;
});
}
else {
text = isArray(text) ? text.join() : text;
lines = lines.concat(_process(keyword, text));
if (Array.isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
var processed = _processLineBreak(keyword, t, i);
lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
});
} else if (self.msgid_plural && keyword === 'msgstr' && !hasTranslation) {
for (var pluralIndex = 0; pluralIndex < self.nplurals; pluralIndex++) {
lines = lines.concat(mkObsolete + _process(keyword, '', pluralIndex));
}
} else {
var index = (self.msgid_plural && Array.isArray(text)) ?
0 :
undefined;
text = Array.isArray(text) ? text.join() : text;
var processed = _processLineBreak(keyword, text, index);
lines = lines.concat(mkObsolete + processed.join('\n' + mkObsolete));
}
}
});
return lines.join("\n");
return lines.join('\n');
};
module.exports = PO;

View File

@@ -1,15 +1,18 @@
{
"name": "pofile",
"description": "Parse and serialize Gettext PO files.",
"version": "0.2.5",
"version": "1.1.0",
"author": {
"name": "Ruben Vermeersch",
"email": "ruben@savanne.be",
"url": "http://savanne.be/"
},
"contributors": [
"Eyal Lewinsohn",
"Gabe Gorelick",
"Julian Bäume",
"Mike Holly"
"Mike Holly",
"Sander Houttekier"
],
"homepage": "http://github.com/rubenv/pofile",
"repository": {
@@ -17,6 +20,7 @@
"url": "http://github.com/rubenv/pofile.git"
},
"main": "./lib/po",
"types": "./pofile.d.ts",
"keywords": [
"i18n",
"l10n",
@@ -32,17 +36,16 @@
"test": "test"
},
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-watch": "~0.5.3",
"grunt-contrib-jshint": "~0.7.2",
"grunt-mocha-cli": "~1.4.0",
"grunt-contrib-uglify": "~0.2.7",
"browserify": "~3.11.1",
"grunt-browserify": "~1.3.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-bump": "0.0.13"
"browserify": "~14.0.0",
"grunt": "~1.0.1",
"grunt-browserify": "~5.0.0",
"grunt-bump": "0.8.0",
"grunt-contrib-clean": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~2.1.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-jscs": "~3.0.1",
"grunt-mocha-cli": "~3.0.0"
},
"dependencies": {
"lodash.isarray": "~2.4.1"
}
"dependencies": {}
}

45
pofile.d.ts vendored Normal file
View File

@@ -0,0 +1,45 @@
declare interface IHeaders {
'Project-Id-Version': string;
'Report-Msgid-Bugs-To': string;
'POT-Creation-Date': string;
'PO-Revision-Date': string;
'Last-Translator': string;
'Language': string;
'Language-Team': string;
'Content-Type': string;
'Content-Transfer-Encoding': string;
'Plural-Forms': string;
[name: string]: string;
}
declare class Item {
public msgid: string;
public msgctxt?: string;
public references: string[];
public msgid_plural?: string;
public msgstr: string[];
public comments: string[];
public extractedComments: string[];
public flags: Record<string, boolean | undefined>;
private nplurals: number;
private obsolete: boolean;
public toString(): string;
}
declare class PO {
public comments: string[];
public extractedComments: string[];
public items: Item[];
public headers: Partial<IHeaders>
public static parse(data: string): PO;
public static parsePluralForms(forms: string): PO;
public static load(fileName: string, callback: (err: NodeJS.ErrnoException, po: PO) => void): void;
public static Item: typeof Item;
public save(fileName: string, callback: (err: NodeJS.ErrnoException) => void): void;
public toString(): string;
}
export = PO

View File

@@ -1,4 +1,5 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
#
msgid ""
@@ -295,3 +296,9 @@ msgstr "This folder is empty."
msgctxt "folder action"
msgid "Empty folder"
msgstr "Make this folder empty."
msgctxt ""
"folder "
"meta"
msgid "Created Date"
msgstr "Date de création"

73
test/fixtures/c-strings.po vendored Normal file
View File

@@ -0,0 +1,73 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: 2013-12-17 14:59+0100\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: fr\n"
"Language-Team: French\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"MIME-Version: 1.0\n"
"X-Generator: Poedit 1.6.2\n"
msgid "The name field must not contain characters like \" or \\"
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"
# plural text must suport \n
msgid ""
"Test.\n"
" multiline."
msgid_plural ""
"Test\n"
" multiline plural."
msgstr[0] ""
"Test.\n"
" mehrzeilig."
msgstr[1] ""
"Test.\n"
" mehrzeilig Plural."

View File

@@ -1,6 +1,7 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
#
#. extracted from test
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
@@ -19,3 +20,11 @@ msgstr ""
#. Extracted comment
msgid "Title, as plain text"
msgstr "Attribut title, en tant que texte brut"
#
#.
#:
#, fuzzy
msgid "Empty comment"
msgstr "Empty"

33
test/fixtures/commented.po vendored Normal file
View File

@@ -0,0 +1,33 @@
msgid ""
msgstr ""
"Project-Id-Version: Test\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2014-02-17 14:11+0100\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language-Team: \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.6.4\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-POOTLE-MTIME: 1390921449.000000\n"
#: .tmp/ui/settings/views/console-modal.html
msgid "{{dataLoader.data.length}} results"
msgstr "{{dataLoader.data.length}} resultaten"
#~ msgid "Add order"
#~ msgstr "Order toevoegen"
#~ # commented obsolete item
#~ #, fuzzy
#~ msgid "Commented item"
#~ msgstr "not sure"
# commented obsolete item
#, fuzzy
#~ msgid "Second commented item"
#~ msgstr "also not sure"

View File

@@ -1,6 +1,8 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
#
## Plural-Forms by polish translation team to demonstrate multi-line ##
#
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
@@ -10,7 +12,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: fr\n"
"X-Generator: Poedit 1.6.2\n"

8
test/fixtures/no_header.po vendored Normal file
View File

@@ -0,0 +1,8 @@
# some comment
msgid "First id, no header"
msgstr ""
msgid "A second string"
msgstr ""

30
test/fixtures/plurals/messages.po vendored Normal file
View File

@@ -0,0 +1,30 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-13 09:59-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
# correct plurals
msgid "1 thing"
msgid_plural "{{$count}} things"
msgstr[0] ""
msgstr[1] ""
# incorrect plurals
msgid "1 mistake"
msgid_plural "{{$count}} mistakes"
msgstr ""

37
test/fixtures/plurals/nplurals-1.po vendored Normal file
View File

@@ -0,0 +1,37 @@
# Chinese translation of Link (6.x-2.9)
# Copyright (c) 2011 by the Chinese translation team
#
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: 2013-12-17 14:59+0100\n"
"Language-Team: Chinese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: zh_CN\n"
"X-Generator: Poedit 1.6.2\n"
# correct plurals, with translation
msgid "1 thing"
msgid_plural "{{$count}} things"
msgstr[0] "{{$count}} thing"
# correct plurals, with no translation
msgid "1 other thing"
msgid_plural "{{$count}} other things"
msgstr[0] ""
# incorrect plurals, with translation
msgid "1 mistake"
msgid_plural "{{$count}} mistakes"
msgstr[0] "1 mistake"
msgstr[1] "{{$count}} mistakes"
# incorrect plurals, with no translation
msgid "1 other mistake"
msgid_plural "{{$count}} other mistakes"
msgstr ""

38
test/fixtures/plurals/nplurals-2.po vendored Normal file
View File

@@ -0,0 +1,38 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
#
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: 2013-12-17 14:59+0100\n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: fr\n"
"X-Generator: Poedit 1.6.2\n"
# correct plurals, with translation
msgid "1 thing"
msgid_plural "{{$count}} things"
msgstr[0] "1 thing"
msgstr[1] "{{$count}} things"
# correct plurals, with no translation
msgid "1 other thing"
msgid_plural "{{$count}} other things"
msgstr[0] ""
msgstr[1] ""
# incorrect plurals, with translation
msgid "1 mistake"
msgid_plural "{{$count}} mistakes"
msgstr[0] "1 mistake"
# incorrect plurals, with no translation
msgid "1 other mistake"
msgid_plural "{{$count}} other mistakes"
msgstr ""

41
test/fixtures/plurals/nplurals-3.po vendored Normal file
View File

@@ -0,0 +1,41 @@
# Polish translation of Link (6.x-2.9)
# Copyright (c) 2011 by the Polish translation team
#
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: 2013-12-17 14:59+0100\n"
"Language-Team: Polish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: pl_PL\n"
"X-Generator: Poedit 1.6.2\n"
# correct plurals, with translation
msgid "1 thing"
msgid_plural "{{$count}} things"
msgstr[0] "1 thing"
msgstr[1] "{{$count}} things"
msgstr[2] "{{$count}} things"
# correct plurals, with no translation
msgid "1 other thing"
msgid_plural "{{$count}} other things"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
# incorrect plurals, with translation
msgid "1 mistake"
msgid_plural "{{$count}} mistakes"
msgstr[0] "1 mistake"
msgstr[1] "{{$count}} mistakes"
# incorrect plurals, with no translation
msgid "1 other mistake"
msgid_plural "{{$count}} other mistakes"
msgstr ""

49
test/fixtures/plurals/nplurals-6.po vendored Normal file
View File

@@ -0,0 +1,49 @@
# Arabic translation of Link (6.x-2.9)
# Copyright (c) 2011 by the Arabic translation team
#
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: 2013-12-17 14:59+0100\n"
"Language-Team: Arabic\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: ar\n"
"X-Generator: Poedit 1.6.2\n"
# correct plurals, with translation
msgid "1 thing"
msgid_plural "{{$count}} things"
msgstr[0] "1 thing"
msgstr[1] "{{$count}} things"
msgstr[2] "{{$count}} things"
msgstr[3] "{{$count}} things"
msgstr[4] "{{$count}} things"
msgstr[5] "{{$count}} things"
# correct plurals, with no translation
msgid "1 other thing"
msgid_plural "{{$count}} other things"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
msgstr[4] ""
msgstr[5] ""
# incorrect plurals, with translation
msgid "1 mistake"
msgid_plural "{{$count}} mistakes"
msgstr[0] "1 mistake"
msgstr[1] "{{$count}} mistakes"
msgstr[2] "{{$count}} mistakes"
# incorrect plurals, with no translation
msgid "1 other mistake"
msgid_plural "{{$count}} other mistakes"
msgstr ""

View File

@@ -0,0 +1,37 @@
# Chinese translation of Link (6.x-2.9)
# Copyright (c) 2011 by the Chinese translation team
#
msgid ""
msgstr ""
"Project-Id-Version: Link (6.x-2.9)\n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: 2013-12-17 14:59+0100\n"
"Language-Team: Chinese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\n"
"Language: zh_CN\n"
"X-Generator: Poedit 1.6.2\n"
# correct plurals, with translation
msgid "1 thing"
msgid_plural "{{$count}} things"
msgstr[0] "1 thing"
msgstr[1] "{{$count}} things"
# correct plurals, with no translation
msgid "1 other thing"
msgid_plural "{{$count}} other things"
msgstr[0] ""
msgstr[1] ""
# incorrect plurals, with translation
msgid "1 mistake"
msgid_plural "{{$count}} mistakes"
msgstr[0] "1 mistake"
# incorrect plurals, with no translation
msgid "1 other mistake"
msgid_plural "{{$count}} other mistakes"
msgstr ""

View File

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

View File

@@ -28,3 +28,46 @@ describe('Headers', function () {
assert.equal(Object.keys(po.headers).length, 12);
});
});
describe('PO files with no headers', function () {
it('Parses an empty string', function () {
var po = PO.parse('');
assert.notEqual(po, null);
// all headers should be empty
for (var key in po.headers) {
assert.equal(po.headers[key], '');
}
assert.equal(po.items.length, 0);
});
it('Parses a minimal example', function () {
var po = PO.parse('msgid "minimal PO"\nmsgstr ""');
assert.notEqual(po, null);
// all headers should be empty
for (var key in po.headers) {
assert.equal(po.headers[key], '');
}
assert.equal(po.items.length, 1);
});
describe('advanced example', function () {
var po;
before(function (done) {
PO.load(__dirname + '/fixtures/no_header.po', function (err, result) {
assert.equal(err, null);
po = result;
done();
});
});
it('Parses the po file', function () {
assert.notEqual(po, null);
});
it('Finds all items', function () {
assert.equal(po.items.length, 2);
});
});
});

View File

@@ -0,0 +1,52 @@
var assert = require('assert');
var fs = require('fs');
var PO = require('..');
describe('.parsePluralForms()', function () {
it('should return an object with empty nplurals and plural expression when there is no plural forms header', function () {
var expected = {
nplurals: undefined,
plural: undefined
};
assert.deepEqual(PO.parsePluralForms(), expected);
assert.deepEqual(PO.parsePluralForms(null), expected);
assert.deepEqual(PO.parsePluralForms(''), expected);
});
it('should return an object with nplurals and plural set to xgettext\'s default output', function () {
var pluralForms = 'nplurals=INTEGER; plural=EXPRESSION;';
var expected = {
nplurals: 'INTEGER',
plural: 'EXPRESSION'
};
var actual = PO.parsePluralForms(pluralForms);
assert.deepEqual(actual, expected);
});
it('should return an object with nplurals and plural set to typical string', function () {
var pluralForms = 'nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);';
var expected = {
nplurals: '3',
plural: '(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'
};
var actual = PO.parsePluralForms(pluralForms);
assert.deepEqual(actual, expected);
});
// node-gettext stores plural forms strings with spaces. They don't appear
// to write PO files at all, but it seems prudent to handle this case
// anyway. See
// https://github.com/alexanderwallin/node-gettext/blob/v1.1.0/lib/plurals.js#L14
it('should handle spaces around assignments in plural forms string', function () {
var pluralForms = 'nplurals = 3; plural = (n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);';
var expected = {
nplurals: '3',
plural: '(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'
};
var actual = PO.parsePluralForms(pluralForms);
assert.deepEqual(actual, expected);
});
});

View File

@@ -6,60 +6,96 @@ describe('Parse', function () {
it('Parses the big po file', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/big.po', 'utf8'));
assert.notEqual(po, null);
assert.equal(po.items.length, 69);
assert.equal(po.items.length, 70);
var item = po.items[0];
assert.equal(item.msgid, "Title");
assert.equal(item.msgstr, "Titre");
assert.equal(item.msgid, 'Title');
assert.equal(item.msgstr, 'Titre');
});
it('Handles multi-line strings', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/multi-line.po', 'utf8'));
assert.notEqual(po, null);
assert.equal(po.items.length, 1);
var item = po.items[0];
assert.equal(item.msgid, "The following placeholder tokens can be used in both paths and titles. When used in a path or title, they will be replaced with the appropriate values.");
assert.equal(item.msgstr, "Les ébauches de jetons suivantes peuvent être utilisées à la fois dans les chemins et dans les titres. Lorsqu'elles sont utilisées dans un chemin ou un titre, elles seront remplacées par les valeurs appropriées.");
assert.equal(item.msgid, 'The following placeholder tokens can be used in both paths and titles. When used in a path or title, they will be replaced with the appropriate values.');
assert.equal(item.msgstr, 'Les ébauches de jetons suivantes peuvent être utilisées à la fois dans les chemins et dans les titres. Lorsqu\'elles sont utilisées dans un chemin ou un titre, elles seront remplacées par les valeurs appropriées.');
});
it('Handles multi-line headers', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/multi-line.po', 'utf8'));
assert.notEqual(po, null);
assert.equal(po.items.length, 1);
assert.equal(po.headers['Plural-Forms'], 'nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;');
});
it('Handle empty comments', function (done) {
PO.load(__dirname + '/fixtures/comment.po', function (err, po) {
assert.equal(err, null);
var item = po.items[1];
assert.equal(item.msgid, 'Empty comment');
assert.equal(item.msgstr, 'Empty');
assert.deepEqual(item.comments, ['']);
assert.deepEqual(item.extractedComments, ['']);
assert.deepEqual(item.references, ['']);
done();
});
});
it('Handles translator comments', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8'));
assert.notEqual(po, null);
assert.equal(po.items.length, 1);
assert.equal(po.items.length, 2);
var item = po.items[0];
assert.equal(item.msgid, "Title, as plain text");
assert.equal(item.msgstr, "Attribut title, en tant que texte brut");
assert.deepEqual(item.comments, ["Translator comment"]);
assert.equal(item.msgid, 'Title, as plain text');
assert.equal(item.msgstr, 'Attribut title, en tant que texte brut');
assert.deepEqual(item.comments, ['Translator comment']);
});
it('Handles extracted comments', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8'));
assert.notEqual(po, null);
assert.equal(po.items.length, 1);
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];
assert.equal(item.msgid, "Title, as plain text");
assert.equal(item.msgstr, "Attribut title, en tant que texte brut");
assert.deepEqual(item.extractedComments, ["Extracted comment"]);
assert.equal(item.msgid, 'Title, as plain text');
assert.equal(item.msgstr, 'Attribut title, en tant que texte brut');
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'));
assert.notEqual(po, null);
assert.equal(po.items.length, 2);
var item = po.items[0];
assert.equal(item.msgid, "Title, as plain text");
assert.equal(item.msgstr, "Attribut title, en tant que texte brut");
assert.deepEqual(item.comments, ["Comment"]);
assert.deepEqual(item.references, [".tmp/crm/controllers/map.js"]);
item = po.items[1];
assert.equal(item.msgid, "X");
assert.equal(item.msgstr, "Y");
assert.deepEqual(item.references, ["a", "b"]);
assert.equal(po.items.length, 3);
it('in simple cases', function () {
var item = po.items[0];
assert.equal(item.msgid, 'Title, as plain text');
assert.equal(item.msgstr, 'Attribut title, en tant que texte brut');
assert.deepEqual(item.comments, ['Comment']);
assert.deepEqual(item.references, ['.tmp/crm/controllers/map.js']);
});
it('with two different references', function () {
var item = po.items[1];
assert.equal(item.msgid, 'X');
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 () {
@@ -68,8 +104,8 @@ describe('Parse', function () {
assert.equal(po.items.length, 1);
var item = po.items[0];
assert.equal(item.msgid, "Sources");
assert.equal(item.msgstr, "Source");
assert.equal(item.msgid, 'Sources');
assert.equal(item.msgstr, 'Source');
assert.notEqual(item.flags, null);
assert.equal(item.flags.fuzzy, true);
});
@@ -84,4 +120,67 @@ describe('Parse', function () {
assert.equal(ambiguousItems[0].msgctxt, 'folder display');
assert.equal(ambiguousItems[1].msgctxt, 'folder action');
});
it('Parses item multiline context', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/big.po', 'utf8'));
var item = po.items.find(function (item) {
return item.msgid === 'Created Date' && item.msgctxt === 'folder meta';
});
assert.notEqual(item, undefined);
assert.equal(item.msgctxt, 'folder meta');
});
it('Handles obsolete items', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/commented.po', 'utf8'));
assert.equal(po.items.length, 4);
var item = po.items[0];
assert.equal(item.obsolete, false);
assert.equal(item.msgid, '{{dataLoader.data.length}} results');
assert.equal(item.msgstr, '{{dataLoader.data.length}} resultaten');
item = po.items[1];
assert.equal(item.obsolete, true);
assert.equal(item.msgid, 'Add order');
assert.equal(item.msgstr, 'Order toevoegen');
item = po.items[2];
assert.equal(item.obsolete, true);
assert.equal(item.msgid, 'Commented item');
assert.equal(item.msgstr, 'not sure');
item = po.items[3];
assert.equal(item.obsolete, true);
assert.equal(item.msgid, 'Second commented item');
assert.equal(item.msgstr, 'also not sure');
});
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 () {
assert.notEqual(po, null);
});
it('should extract strings containing " and \\ characters', function () {
var items = po.items.filter(function (item) {
return (/^The name field must not contain/).test(item.msgid);
});
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

@@ -2,8 +2,38 @@ var assert = require('assert');
var fs = require('fs');
var PO = require('..');
function assertHasLine(str, line) {
var lines = str.split("\n");
function assertHasLine(str, line, doNotTrim) {
var lines = str.split('\n');
var found = false;
for (var i = 0; i < lines.length; i++) {
var lineToCompare = doNotTrim ? lines[i] : lines[i].trim();
if (lineToCompare === line) {
found = true;
break;
}
}
assert(found, 'Could not find line: ' + line);
}
function assertHasContiguousLines(str, assertedLines) {
var assertedLinesJoined = assertedLines.join('\n');
var trimmedStr = str
.split('\n')
.map(function (line) {
return line.trim();
})
.join('\n');
var found = trimmedStr.indexOf(assertedLinesJoined) !== -1;
assert(found, 'Could not find lines: \n' + assertedLinesJoined);
}
function assertDoesntHaveLine(str, line) {
var lines = str.split('\n');
var found = false;
for (var i = 0; i < lines.length; i++) {
@@ -13,7 +43,7 @@ function assertHasLine(str, line) {
}
}
assert(found, "Could not find line: " + line);
assert(!found, 'Shouldn\'t have line: ' + line);
}
describe('Write', function () {
@@ -21,37 +51,435 @@ describe('Write', function () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, "#, fuzzy");
assertHasLine(str, '#, fuzzy');
});
it('write empty comment without an unecessary space', function () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, '#', true);
});
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 () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, "msgid \"Sources\"");
assertHasLine(str, 'msgid "Sources"');
});
it('write msgstr', function () {
var input = fs.readFileSync(__dirname + '/fixtures/fuzzy.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, "msgstr \"Source\"");
assertHasLine(str, 'msgstr "Source"');
});
it('write translator comment', function () {
var input = fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, "# Translator comment");
assertHasLine(str, '# Translator comment');
});
it('write extracted comment', function () {
var input = fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, '#. extracted from test');
assertHasLine(str, '#. Extracted comment');
});
it('write obsolete items', function () {
var input = fs.readFileSync(__dirname + '/fixtures/commented.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, '#~ msgid "Add order"');
assertHasLine(str, '#~ msgstr "Order toevoegen"');
});
it('write obsolete items with comment', function () {
var input = fs.readFileSync(__dirname + '/fixtures/commented.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
//this is what msgcat tool of gettext does: don't print #~ for comments
assertHasLine(str, '# commented obsolete item');
assertHasLine(str, '#, fuzzy');
//items made obsolete by commenting every keyword with #~
assertHasLine(str, '#~ msgid "Commented item"');
assertHasLine(str, '#~ msgstr "not sure"');
assertHasLine(str, '#~ msgid "Second commented item"');
assertHasLine(str, '#~ msgstr "also not sure"');
});
describe('plurals', function () {
describe('nplurals INTEGER', function () {
it('should write 2 msgstrs when formatted correctly', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/messages.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} things"',
'msgstr[0] ""',
'msgstr[1] ""'
]);
});
it('should write 2 msgstrs when formatted incorrectly', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/messages.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} mistakes"',
'msgstr[0] ""',
'msgstr[1] ""'
]);
});
});
describe('nplurals missing', function () {
it('should write 2 msgstrs when formatted correctly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-missing.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} things"',
'msgstr[0] "1 thing"',
'msgstr[1] "{{$count}} things"'
]);
});
it('should write 2 msgstrs when formatted correctly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-missing.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other things"',
'msgstr[0] ""',
'msgstr[1] ""',
]);
});
it('should keep same number of msgstrs when formatted incorrectly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-missing.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} mistakes"',
'msgstr[0] "1 mistake"',
'',
'# incorrect plurals, with no translation'
]);
});
it('should write 2 msgstrs when formatted incorrectly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-missing.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other mistakes"',
'msgstr[0] ""',
'msgstr[1] ""'
]);
});
});
describe('nplurals=1', function () {
it('should write 1 msgstr when formatted correctly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-1.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} things"',
'msgstr[0] "{{$count}} thing"'
]);
});
it('should write 1 msgstr when formatted correctly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-1.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other things"',
'msgstr[0] ""',
'',
'# incorrect plurals, with translation'
]);
});
it('should keep same number of msgstrs when formatted incorrectly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-1.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} mistakes"',
'msgstr[0] "1 mistake"',
'msgstr[1] "{{$count}} mistakes"'
]);
});
it('should write 1 msgstr when formatted incorrectly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-1.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other mistakes"',
'msgstr[0] ""'
]);
});
});
describe('nplurals=2', function () {
it('should write 2 msgstrs when formatted correctly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-2.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} things"',
'msgstr[0] "1 thing"',
'msgstr[1] "{{$count}} things"'
]);
});
it('should write 2 msgstrs when formatted correctly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-2.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other things"',
'msgstr[0] ""',
'msgstr[1] ""',
]);
});
it('should keep same number of msgstrs when formatted incorrectly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-2.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} mistakes"',
'msgstr[0] "1 mistake"',
'',
'# incorrect plurals, with no translation'
]);
});
it('should write 2 msgstrs when formatted incorrectly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-2.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other mistakes"',
'msgstr[0] ""',
'msgstr[1] ""'
]);
});
});
describe('nplurals=3', function () {
it('should write 3 msgstrs when formatted correctly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-3.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} things"',
'msgstr[0] "1 thing"',
'msgstr[1] "{{$count}} things"',
'msgstr[2] "{{$count}} things"'
]);
});
it('should write 3 msgstrs when formatted correctly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-3.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other things"',
'msgstr[0] ""',
'msgstr[1] ""',
'msgstr[2] ""'
]);
});
it('should keep same number of msgstrs when formatted incorrectly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-3.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} mistakes"',
'msgstr[0] "1 mistake"',
'msgstr[1] "{{$count}} mistakes"',
'',
'# incorrect plurals, with no translation'
]);
});
it('should write 3 msgstrs when formatted incorrectly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-3.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other mistakes"',
'msgstr[0] ""',
'msgstr[1] ""',
'msgstr[2] ""'
]);
});
});
describe('nplurals=6', function () {
it('should write 6 msgstrs when formatted correctly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-6.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} things"',
'msgstr[0] "1 thing"',
'msgstr[1] "{{$count}} things"',
'msgstr[2] "{{$count}} things"',
'msgstr[3] "{{$count}} things"',
'msgstr[4] "{{$count}} things"',
'msgstr[5] "{{$count}} things"'
]);
});
it('should write 6 msgstrs when formatted correctly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-6.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other things"',
'msgstr[0] ""',
'msgstr[1] ""',
'msgstr[2] ""',
'msgstr[3] ""',
'msgstr[4] ""',
'msgstr[5] ""'
]);
});
it('should keep same number of msgstrs when formatted incorrectly with translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-6.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} mistakes"',
'msgstr[0] "1 mistake"',
'msgstr[1] "{{$count}} mistakes"',
'msgstr[2] "{{$count}} mistakes"',
'',
'# incorrect plurals, with no translation'
]);
});
it('should write 6 msgstrs when formatted incorrectly with no translation', function () {
var input = fs.readFileSync(
__dirname + '/fixtures/plurals/nplurals-6.po', 'utf8'
);
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid_plural "{{$count}} other mistakes"',
'msgstr[0] ""',
'msgstr[1] ""',
'msgstr[2] ""',
'msgstr[3] ""',
'msgstr[4] ""',
'msgstr[5] ""'
]);
});
});
});
describe('C-Strings', function () {
it('should escape "', function () {
var item = new PO.Item();
item.msgid = '" should be written escaped';
assertHasLine(item.toString(), 'msgid "\\" should be written escaped"');
});
it('shoudl escape \\', function () {
var item = new PO.Item();
item.msgid = '\\ should be written escaped';
assertHasLine(item.toString(), 'msgid "\\\\ should be written escaped"');
});
it('should escape \\n', function () {
var item = new PO.Item();
item.msgid = '\n should be written escaped';
assertHasLine(item.toString(), 'msgid ""');
assertHasLine(item.toString(), '"\\n"');
assertHasLine(item.toString(), '" should be written escaped"');
});
it('should write identical file after parsing a file', function () {
var input = fs.readFileSync(__dirname + '/fixtures/c-strings.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assert.equal(str, input);
});
});
describe('msgctxt', function () {
it('should write context field to file', function () {
var input = fs.readFileSync(__dirname + '/fixtures/big.po', 'utf8');
@@ -76,4 +504,26 @@ describe('Write', function () {
assert.ok(po.toString().indexOf('msgctxt') >= 0);
});
});
it('should keep the header order', function () {
var input = fs.readFileSync(__dirname + '/fixtures/big.po', 'utf8');
var po = PO.parse(input);
var str = po.toString();
assertHasContiguousLines(str, [
'msgid ""',
'msgstr ""',
'"Project-Id-Version: Link (6.x-2.9)\\n"',
'"POT-Creation-Date: 2011-12-31 23:39+0000\\n"',
'"PO-Revision-Date: 2013-12-17 14:21+0100\\n"',
'"Language-Team: French\\n"',
'"MIME-Version: 1.0\\n"',
'"Content-Type: text/plain; charset=UTF-8\\n"',
'"Content-Transfer-Encoding: 8bit\\n"',
'"Plural-Forms: nplurals=2; plural=(n > 1);\\n"',
'"Last-Translator: Ruben Vermeersch <ruben@rocketeer.be>\\n"',
'"Language: fr\\n"',
'"X-Generator: Poedit 1.6.2\\n"',
]);
});
});