65 Commits

Author SHA1 Message Date
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
Ruben Vermeersch
1d74ea4a7a Release v0.2.5 2014-03-03 16:11:50 +01:00
Ruben Vermeersch
783a4129d5 Merge pull request #6 from gabegorelick/serialize-comments
Include comments in `PO.Item` output
2014-03-03 16:11:24 +01:00
Gabe Gorelick
8dba54b095 Document extractedComments 2014-03-02 13:20:18 -05:00
Gabe Gorelick
ac85ba0b9c Handle extracted comments 2014-03-02 13:17:20 -05:00
Gabe Gorelick
ada76116b0 Include comments in PO.Item output
Fixes #5
2014-03-02 12:41:40 -05:00
Ruben Vermeersch
c5515d6128 Add Julian Bäume to contributors. 2014-01-22 14:25:10 +01:00
Ruben Vermeersch
c58333c4d1 Fix capital and punctuation. 2014-01-22 14:22:03 +01:00
Ruben Vermeersch
136b969adb Release v0.2.4 2014-01-22 14:19:50 +01:00
Ruben Vermeersch
02471be49f Recompile browserified version. 2014-01-22 14:19:26 +01:00
Ruben Vermeersch
13283fedbe Merge pull request #4 from Open-Xchange-Frontend/fix_msgctxt_default
fix default value of msgctxt field
2014-01-22 05:19:14 -08:00
Julian Bäume
a5f3059661 fix default value of msgctxt field
I added a few more edge-cases to the tests for the new msgctxt field and
revealed a bug during that. The default value has been set to empty
string, but should have been null, since the spec says, this field is
optional.
2014-01-22 14:14:03 +01:00
Ruben Vermeersch
abca810905 Upgrade copyright years. 2014-01-21 16:05:01 +01:00
Ruben Vermeersch
ce410f3427 Release v0.2.3 2014-01-21 16:01:30 +01:00
Ruben Vermeersch
4fa1ce75c5 Recompile. 2014-01-21 16:01:08 +01:00
Ruben Vermeersch
b599a76557 Merge pull request #3 from Open-Xchange-Frontend/support_msgctxt
Support msgctxt field of items
2014-01-21 07:00:52 -08:00
Julian Bäume
87e29b6c21 add documentation for the new field
possibly makes developers happy to have this field documented
2014-01-21 15:46:31 +01:00
Julian Bäume
4ba2cf1cef add distfiles
as compiled by the grunt task. These come in an own commit, since I
didn’t want them to clutter the actual implementation commits.
2014-01-21 15:10:02 +01:00
Julian Bäume
c046b62873 implement writing msgctxt field
since the parser can extract this information, now, the writer should
also be able to write it back to a po file
2014-01-21 15:06:11 +01:00
Julian Bäume
6790bfb466 add parsing support for msgctxt field of poitems
The gettext format specifies a msgctxt field for translated items. An
example of how to use this field has been added to the tests and the
parsing of this attribute has been implemented.
2014-01-21 15:05:58 +01:00
Ruben Vermeersch
94ad44f953 Release v0.2.2 2013-12-23 09:37:40 +01:00
Ruben Vermeersch
c9811bb7aa More documentation. 2013-12-23 09:35:59 +01:00
Ruben Vermeersch
f40ab323dd Add migration notes. 2013-12-23 09:18:28 +01:00
Ruben Vermeersch
554ed47ba7 Update e-mail address. 2013-12-20 10:17:31 +01:00
Ruben Vermeersch
28683aa4b3 More README. 2013-12-20 10:06:22 +01:00
Ruben Vermeersch
fdf3988cd0 Release v0.2.1 2013-12-20 09:57:11 +01:00
Ruben Vermeersch
a79382fb9d Some more release notes. 2013-12-20 09:56:56 +01:00
Ruben Vermeersch
456d889ea1 Add more files to NPM ignore. 2013-12-20 09:43:38 +01:00
19 changed files with 936 additions and 190 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

@@ -2,3 +2,6 @@
/dist
/Gruntfile.coffee
/test
/.jshintrc
/.travis.yml
/bower.json

View File

@@ -1,7 +1,6 @@
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.11"
before_script:
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-checker')
@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:
@@ -49,5 +57,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 Ruben Vermeersch
Copyright (C) 2013-2014 Ruben Vermeersch
Copyright (C) 2012 Michael Holly
Permission is hereby granted, free of charge, to any person obtaining a copy of

165
README.md
View File

@@ -1,22 +1,165 @@
Used to load and save PO files.
# pofile - gettext .po parsing for JavaScript
> Parse and serialize Gettext PO files.
[![Build Status](https://travis-ci.org/rubenv/pofile.png?branch=master)](https://travis-ci.org/rubenv/pofile)
## Usage
Add pofile to your project:
### Installation (Node.JS, browser via Browserified)
```
npm install --save pofile
```
Reference it in your code:
```js
var PO = require('pofile');
```
### Installation (via bower)
```
bower install --save pofile
```
Add it to your HTML file:
```html
<script src="bower_components/pofile/dist/pofile.js"></script>
```
Reference it in your code:
```js
var PO = require('pofile');
```
### Loading and parsing
You can create a new empty PO file by using the class:
```js
var po = new PO();
```
Or by loading a file (Node.JS only):
```js
PO.load('text.po', function (err, po) {
// Handle err if needed
console.log(po.headers);
console.log(po.items);
po.save('copy.po', function (err) {
// Handle err if needed
console.log('We copied a PO file for no reason!');
});
// Do things with po
});
```
Or by parsing a string:
```js
var po = PO.parse(myString);
```
### The PO class
The `PO` class exposes three members:
* `comments`: An array of comments (found at the header of the file).
* `headers`: A dictionary of the headers.
* `items`: An array of `PO.Item` objects, each of which represents a string
from the gettext catalog.
There are two methods available:
* `save`: Accepts a filename and callback, writes the po file to disk.
```js
po.save('out.po', function (err) {
// Handle err if needed
});
```
* `toString`: Serializes the po file to a string.
### The PO.Item class
The `PO` class exposes the following members:
* `msgid`: The message id.
* `msgid_plural`: The plural message id (null if absent).
* `msgstr`: An array of translated strings. Items that have no plural msgid
only have one element in this array.
* `references`: An array of reference strings.
* `comments`: An array of string translator comments.
* `extractedComments`: An array of string extracted comments.
* `flags`: A dictionary of the string flags. Each flag is mapped to a key with
value true. For instance, a string with the fuzzy flag set will have
`item.flags.fuzzy == true`.
* `msgctxt`: Context of the message, an arbitrary string, can be used for disambiguation.
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding
style. Add unit tests for any new or changed functionality. Lint and test your
code using [Grunt](http://gruntjs.com/).
## Credits
Originally based on node-po (written by Michael Holly). Rebranded because
node-po is unmaintained and because this library is no longer limited to
Node.JS: it works in the browser too.
Originally based on node-po (written by Michael Holly). Rebranded because
node-po is unmaintained and because this library is no longer limited to
Node.JS: it works in the browser too.
### Changes compared to node-po
* Proper handling of async methods that won't crash your Node.JS process when
something goes wrong.
* Support for parsing string flags (e.g. fuzzy).
* A test suite.
* Browser support (through Browserified and bower).
### Migrating from node-po
You'll need to update the module reference: `require('pofile')` instead of
`require('node-po')`.
At the initial release, node-po and pofile have identical APIs, with one small
exception: the `save` and `load` methods now take a callback that has an `err`
parameter: `(err)` for `save` and `(err, po)` for `load`. This is similar to
Node.JS conventions.
Change code such as:
```js
PO.load('text.po', function (po) {
```
To:
```js
PO.load('text.po', function (err, po) {
// Handle err if needed
```
## License
(The MIT License)
Copyright (C) 2013-2014 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
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

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

235
dist/pofile.js vendored
View File

@@ -1,6 +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})({"GPUdqu":[function(require,module,exports){
var fs = require('fs'),
isArray = require('lodash.isarray');
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');
var isArray = require('lodash.isarray');
function trim(string) {
return string.replace(/^\s+|\s+$/g, '');
@@ -17,8 +17,7 @@ PO.prototype.save = function (filename, callback) {
};
PO.prototype.toString = function () {
var lines = [],
that = this;
var lines = [];
if (this.comments) {
this.comments.forEach(function (comment) {
@@ -30,8 +29,9 @@ PO.prototype.toString = function () {
lines.push('msgstr ""');
var keys = Object.keys(this.headers);
var self = this;
keys.forEach(function (key) {
lines.push('"' + key + ': ' + that.headers[key] + '\\n"');
lines.push('"' + key + ': ' + self.headers[key] + '\\n"');
});
lines.push('');
@@ -41,7 +41,7 @@ PO.prototype.toString = function () {
lines.push('');
});
return lines.join("\n");
return lines.join('\n');
};
PO.load = function (filename, callback) {
@@ -57,10 +57,10 @@ 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 = sections.shift();
var lines = sections.join('\n').split(/\n/);
po.headers = {
'Project-Id-Version': '',
@@ -75,25 +75,43 @@ PO.parse = function (data) {
'Plural-Forms': '',
};
headers.split(/\n/).forEach(function (header) {
headers.split(/\n/).reduce(function (acc, line) {
if (acc.merge) {
//join lines, remove last resp. first "
line = acc.pop().slice(0, -1) + line.slice(1);
delete acc.merge;
}
if (/^".*"$/.test(line) && !/^".*\\n"$/.test(line)) {
acc.merge = true;
}
acc.push(line);
return acc;
}, []).forEach(function (header) {
if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
}
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/),
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;
}
});
var item = new PO.Item(),
context = null,
plural = 0;
var item = new PO.Item();
var context = null;
var plural = 0;
var obsoleteCount = 0;
var noCommentLineCount = 0;
function finish() {
if (item.msgid.length > 0) {
if (obsoleteCount >= noCommentLineCount) {
item.obsolete = true;
}
obsoleteCount = 0;
noCommentLineCount = 0;
po.items.push(item);
item = new PO.Item();
}
@@ -102,57 +120,99 @@ PO.parse = function (data) {
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(/^#/)) { // Comment
} else if (line.match(/^#($|\s+)/)) { // Translator comment
finish();
item.comments.push(trim(line.replace(/^#/, '')));
}
else if (line.match(/^msgid_plural/)) { // Plural form
item.comments.push(trim(line.replace(/^#($|\s+)/, '')));
} else if (line.match(/^#\./)) { // Extracted comment
finish();
item.extractedComments.push(trim(line.replace(/^#\./, '')));
} else if (line.match(/^msgid_plural/)) { // Plural form
item.msgid_plural = extract(line);
context = 'msgid_plural';
}
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 { // Probably multiline string or blank
noCommentLineCount++;
} else if (line.match(/^msgctxt/)) { // Context
finish();
item.msgctxt = extract(line);
noCommentLineCount++;
} else { // Probably multiline string or blank
if (line.length > 0) {
noCommentLineCount++;
if (context === 'msgstr') {
item.msgstr[plural] += extract(line);
}
else if (context === 'msgid') {
} else if (context === 'msgid') {
item.msgid += extract(line);
}
else if (context === 'msgid_plural') {
} else if (context === 'msgid_plural') {
item.msgid_plural += extract(line);
}
}
}
if (lineObsolete) {
// Count obsolete lines for this item
obsoleteCount++;
}
}
finish();
@@ -161,66 +221,109 @@ PO.parse = function (data) {
PO.Item = function () {
this.msgid = '';
this.msgctxt = null;
this.references = [];
this.msgid_plural = null;
this.msgstr = [];
this.comments = [];
this.comments = []; // translator comments
this.extractedComments = [];
this.flags = {};
this.obsolete = false;
};
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;
};
if (this.references.length > 0) {
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
}
// https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
// says order is translator-comments, extracted-comments, references, flags
var flags = Object.keys(this.flags);
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
var flags = Object.keys(this.flags).filter(function (flag) {
return !!this.flags[flag];
}, this);
if (flags.length > 0) {
lines.push('#, ' + flags.join(","));
lines.push('#, ' + flags.join(','));
}
var mkObsolete = this.obsolete ? '#~ ' : '';
['msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = that[keyword];
['msgctxt', 'msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = self[keyword];
if (text != null) {
if (isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
lines = lines.concat(_process(keyword, t, i));
lines = lines.concat(mkObsolete + _process(keyword, t, i));
});
}
else {
} else {
text = isArray(text) ? text.join() : text;
lines = lines.concat(_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));
}
}
});
return lines.join("\n");
return lines.join('\n');
};
module.exports = PO;
},{"fs":3,"lodash.isarray":4}],"pofile":[function(require,module,exports){
module.exports=require('GPUdqu');
module.exports=require('W8CkM0');
},{}],3:[function(require,module,exports){
},{}],4:[function(require,module,exports){
@@ -306,4 +409,4 @@ function isNative(value) {
module.exports = isNative;
},{}]},{},["GPUdqu"])
},{}]},{},["W8CkM0"])

2
dist/pofile.min.js vendored

File diff suppressed because one or more lines are too long

229
lib/po.js
View File

@@ -1,5 +1,5 @@
var fs = require('fs'),
isArray = require('lodash.isarray');
var fs = require('fs');
var isArray = require('lodash.isarray');
function trim(string) {
return string.replace(/^\s+|\s+$/g, '');
@@ -16,8 +16,7 @@ PO.prototype.save = function (filename, callback) {
};
PO.prototype.toString = function () {
var lines = [],
that = this;
var lines = [];
if (this.comments) {
this.comments.forEach(function (comment) {
@@ -29,8 +28,9 @@ PO.prototype.toString = function () {
lines.push('msgstr ""');
var keys = Object.keys(this.headers);
var self = this;
keys.forEach(function (key) {
lines.push('"' + key + ': ' + that.headers[key] + '\\n"');
lines.push('"' + key + ': ' + self.headers[key] + '\\n"');
});
lines.push('');
@@ -40,7 +40,7 @@ PO.prototype.toString = function () {
lines.push('');
});
return lines.join("\n");
return lines.join('\n');
};
PO.load = function (filename, callback) {
@@ -56,10 +56,10 @@ 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 = sections.shift();
var lines = sections.join('\n').split(/\n/);
po.headers = {
'Project-Id-Version': '',
@@ -74,25 +74,43 @@ PO.parse = function (data) {
'Plural-Forms': '',
};
headers.split(/\n/).forEach(function (header) {
headers.split(/\n/).reduce(function (acc, line) {
if (acc.merge) {
//join lines, remove last resp. first "
line = acc.pop().slice(0, -1) + line.slice(1);
delete acc.merge;
}
if (/^".*"$/.test(line) && !/^".*\\n"$/.test(line)) {
acc.merge = true;
}
acc.push(line);
return acc;
}, []).forEach(function (header) {
if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
}
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/),
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;
}
});
var item = new PO.Item(),
context = null,
plural = 0;
var item = new PO.Item();
var context = null;
var plural = 0;
var obsoleteCount = 0;
var noCommentLineCount = 0;
function finish() {
if (item.msgid.length > 0) {
if (obsoleteCount >= noCommentLineCount) {
item.obsolete = true;
}
obsoleteCount = 0;
noCommentLineCount = 0;
po.items.push(item);
item = new PO.Item();
}
@@ -101,57 +119,99 @@ PO.parse = function (data) {
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(/^#/)) { // Comment
} else if (line.match(/^#($|\s+)/)) { // Translator comment
finish();
item.comments.push(trim(line.replace(/^#/, '')));
}
else if (line.match(/^msgid_plural/)) { // Plural form
item.comments.push(trim(line.replace(/^#($|\s+)/, '')));
} else if (line.match(/^#\./)) { // Extracted comment
finish();
item.extractedComments.push(trim(line.replace(/^#\./, '')));
} else if (line.match(/^msgid_plural/)) { // Plural form
item.msgid_plural = extract(line);
context = 'msgid_plural';
}
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 { // Probably multiline string or blank
noCommentLineCount++;
} else if (line.match(/^msgctxt/)) { // Context
finish();
item.msgctxt = extract(line);
noCommentLineCount++;
} else { // Probably multiline string or blank
if (line.length > 0) {
noCommentLineCount++;
if (context === 'msgstr') {
item.msgstr[plural] += extract(line);
}
else if (context === 'msgid') {
} else if (context === 'msgid') {
item.msgid += extract(line);
}
else if (context === 'msgid_plural') {
} else if (context === 'msgid_plural') {
item.msgid_plural += extract(line);
}
}
}
if (lineObsolete) {
// Count obsolete lines for this item
obsoleteCount++;
}
}
finish();
@@ -160,60 +220,103 @@ PO.parse = function (data) {
PO.Item = function () {
this.msgid = '';
this.msgctxt = null;
this.references = [];
this.msgid_plural = null;
this.msgstr = [];
this.comments = [];
this.comments = []; // translator comments
this.extractedComments = [];
this.flags = {};
this.obsolete = false;
};
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;
};
if (this.references.length > 0) {
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
}
// https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
// says order is translator-comments, extracted-comments, references, flags
var flags = Object.keys(this.flags);
this.comments.forEach(function (c) {
lines.push('# ' + c);
});
this.extractedComments.forEach(function (c) {
lines.push('#. ' + c);
});
this.references.forEach(function (ref) {
lines.push('#: ' + ref);
});
var flags = Object.keys(this.flags).filter(function (flag) {
return !!this.flags[flag];
}, this);
if (flags.length > 0) {
lines.push('#, ' + flags.join(","));
lines.push('#, ' + flags.join(','));
}
var mkObsolete = this.obsolete ? '#~ ' : '';
['msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = that[keyword];
['msgctxt', 'msgid', 'msgid_plural', 'msgstr'].forEach(function (keyword) {
var text = self[keyword];
if (text != null) {
if (isArray(text) && text.length > 1) {
text.forEach(function (t, i) {
lines = lines.concat(_process(keyword, t, i));
lines = lines.concat(mkObsolete + _process(keyword, t, i));
});
}
else {
} else {
text = isArray(text) ? text.join() : text;
lines = lines.concat(_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));
}
}
});
return lines.join("\n");
return lines.join('\n');
};
module.exports = PO;

View File

@@ -1,14 +1,18 @@
{
"name": "pofile",
"description": "Parse and serialize Gettext PO files.",
"version": "0.2.0",
"version": "0.3.0",
"author": {
"name": "Ruben Vermeersch",
"email": "ruben@savanne.be",
"url": "http://savanne.be/"
},
"contributors": [
"Mike Holly"
"Eyal Lewinsohn",
"Gabe Gorelick",
"Julian Bäume",
"Mike Holly",
"Sander Houttekier"
],
"homepage": "http://github.com/rubenv/pofile",
"repository": {
@@ -24,21 +28,23 @@
"po"
],
"scripts": {
"test": "grunt test"
"test": "grunt test",
"prepublish": "grunt build"
},
"directories": {
"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": "~0.4.2",
"grunt-browserify": "~1.3.0",
"grunt-bump": "0.0.13",
"grunt-contrib-clean": "~0.5.0",
"grunt-bump": "0.0.13"
"grunt-contrib-jshint": "~0.7.2",
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-watch": "~0.5.3",
"grunt-jscs-checker": "^0.5.1",
"grunt-mocha-cli": "~1.4.0"
},
"dependencies": {
"lodash.isarray": "~2.4.1"

10
test/fixtures/big.po vendored
View File

@@ -285,3 +285,13 @@ msgstr "Attribut 'title' du lien"
# Comment
msgid "Title, as plain text"
msgstr "Attribut title, en tant que texte brut"
# Empty should be adjective
msgctxt "folder display"
msgid "Empty folder"
msgstr "This folder is empty."
# Empty should be verb
msgctxt "folder action"
msgid "Empty folder"
msgstr "Make this folder empty."

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

@@ -0,0 +1,59 @@
# 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"

View File

@@ -15,6 +15,15 @@ msgstr ""
"Language: fr\n"
"X-Generator: Poedit 1.6.2\n"
# Comment
# Translator comment
#. 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"

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

@@ -6,49 +6,93 @@ 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, 67);
assert.equal(po.items.length, 69);
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 string comments', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/comment.po', 'utf8'));
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);
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.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('Handles string references', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/reference.po', 'utf8'));
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, 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(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, 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.extractedComments, ['Extracted comment']);
});
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, 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 () {
@@ -57,9 +101,72 @@ 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);
});
it('Parses item context', function () {
var po = PO.parse(fs.readFileSync(__dirname + '/fixtures/big.po', 'utf8'));
var ambiguousItems = po.items.filter(function (item) {
return item.msgid === 'Empty folder';
});
assert.equal(ambiguousItems[0].msgctxt, 'folder display');
assert.equal(ambiguousItems[1].msgctxt, 'folder action');
});
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

@@ -3,7 +3,7 @@ var fs = require('fs');
var PO = require('..');
function assertHasLine(str, line) {
var lines = str.split("\n");
var lines = str.split('\n');
var found = false;
for (var i = 0; i < lines.length; i++) {
@@ -13,7 +13,21 @@ 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 () {
@@ -21,20 +35,128 @@ 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 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');
});
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 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('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');
var po = PO.parse(input);
var str = po.toString();
assertHasLine(str, 'msgctxt "folder action"');
});
it('should ignore omitted context field', function () {
var po = new PO();
var item = new PO.Item();
po.items.push(item);
assert.ok(po.toString().indexOf('msgctxt') < 0);
});
it('should write empty context field', function () {
var po = new PO();
var item = new PO.Item();
item.msgctxt = '';
po.items.push(item);
assert.ok(po.toString().indexOf('msgctxt') >= 0);
});
});
});