├── .gitignore
├── test
├── inputs
│ ├── insane.js
│ ├── comment.ejs
│ ├── pizza.js
│ ├── second_attribute.ejs
│ ├── hash.js
│ ├── .jshintrc
│ ├── second.js
│ ├── po_quotes.js
│ ├── filter.ejs
│ ├── first.js
│ ├── include.ejs
│ ├── raw.ejs
│ ├── test.js
│ ├── react_component.jsx
│ ├── second_attribute.jade
│ ├── anonymous_functions.js
│ ├── extracted_comments.js
│ ├── arbitrary_comments.js
│ ├── example.swig
│ ├── multiple_keywords.js
│ ├── concat.js
│ ├── expressions.js
│ ├── example.handlebars
│ └── example.jade
├── .jshintrc
├── index.js
├── outputs
│ ├── anonymous_functions.pot
│ ├── messages_firstpass.pot
│ ├── react_component.pot
│ ├── messages_secondpass.pot
│ ├── extracted_comments.pot
│ ├── arbitrary_comments.pot
│ ├── multiple_keywords.pot
│ ├── concat.pot
│ ├── expressions.pot
│ └── jade.pot
├── utils.js
└── tests
│ ├── index.js
│ ├── sanity_checking.js
│ ├── comments.js
│ ├── leading_hash.js
│ ├── creation_date.js
│ ├── second_attribute.js
│ ├── react_component.js
│ ├── extracted_comments.js
│ ├── multiple_keywords.js
│ ├── arbitrary_comments.js
│ ├── ejs_comment.js
│ ├── ejs.js
│ ├── ejs_filter.js
│ ├── po_header.js
│ ├── po_quotes.js
│ ├── ejs_raw.js
│ ├── anonymous_functions.js
│ ├── po_custom_headers.js
│ ├── swig.js
│ ├── handlebars.js
│ ├── jade.js
│ ├── expressions.js
│ └── join_existing.js
├── .jshintignore
├── .travis.yml
├── .jshintrc
├── lib
├── parsers
│ ├── index.js
│ ├── ejs.js
│ ├── swig.js
│ ├── jade.js
│ └── handlebars.js
├── cli.js
└── jsxgettext.js
├── package.json
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/test/inputs/insane.js:
--------------------------------------------------------------------------------
1 | gettext(foo);
2 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | tests/inputs
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 4
4 | - 5
5 |
--------------------------------------------------------------------------------
/test/inputs/comment.ejs:
--------------------------------------------------------------------------------
1 | <%# gettext("this is a non localizable comment string") %>
2 |
--------------------------------------------------------------------------------
/test/inputs/pizza.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | //foobar;
3 | var x = gettext('pizza');
4 | })();
5 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.jshintrc",
3 |
4 | "predef": [
5 | "gettext"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | if (module === require.main)
4 | require('test').run(require('./tests'));
5 |
--------------------------------------------------------------------------------
/test/inputs/second_attribute.ejs:
--------------------------------------------------------------------------------
1 |
2 | <%= _('hey') %>
3 |
4 |
--------------------------------------------------------------------------------
/test/inputs/hash.js:
--------------------------------------------------------------------------------
1 | #!/user/bin/env node
2 |
3 | var msg = gettext('Hello World');
4 | var dup = gettext('This message is used twice.');
5 |
--------------------------------------------------------------------------------
/test/inputs/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.jshintrc",
3 |
4 | "undef": false,
5 | "unused": false,
6 | "strict": false,
7 | "sub": true
8 | }
9 |
--------------------------------------------------------------------------------
/test/inputs/second.js:
--------------------------------------------------------------------------------
1 | var dup = gettext('This message is used twice.');
2 | var dup2 = ngettext('This other message is used twice.', 'This other message is used twice. - plural', 2);
--------------------------------------------------------------------------------
/test/inputs/po_quotes.js:
--------------------------------------------------------------------------------
1 | var shrtString = gettext("Hello \"World\"\n");
2 | var longString = gettext("This is a long string with \"quotes\", newlines \n and such. The line should get folded");
--------------------------------------------------------------------------------
/test/inputs/filter.ejs:
--------------------------------------------------------------------------------
1 | <%=: gettext("this is a localizable string") | capitalize %>
2 | <%=: ngettext("this is a localizable singular string", "this is a localizable plural string", 2) | capitalize %>
3 |
--------------------------------------------------------------------------------
/test/inputs/first.js:
--------------------------------------------------------------------------------
1 | var msg = gettext('Hello World');
2 | var dup = gettext('This message is used twice.');
3 | var dup2 = ngettext('This other message is used twice.', 'This other message is used twice. - plural', 2);
--------------------------------------------------------------------------------
/test/inputs/include.ejs:
--------------------------------------------------------------------------------
1 | <% include this/include/syntax/is/kinda/dumb %>
2 | <%= gettext("this is a localizable string") %>
3 | <%= ngettext("this is a localizable singular string", "this is a localizable plural string", 2) %>
4 |
--------------------------------------------------------------------------------
/test/inputs/raw.ejs:
--------------------------------------------------------------------------------
1 | <%== gettext("this is a raw localizable string") %>
2 | <%== gettext("this is a raw localizable string") %>
3 | <%== ngettext("this is a raw localizable singular string", "this is a raw localizable plural string", 2) %>
4 |
--------------------------------------------------------------------------------
/test/outputs/anonymous_functions.pot:
--------------------------------------------------------------------------------
1 | #: inputs/anonymous_functions.js:10
2 | msgid "I'm gonna get translated, yay!"
3 | msgstr ""
4 |
5 | #: inputs/anonymous_functions.js:11
6 | msgid "I'm also gonna get translated!"
7 | msgid_plural "I'm the plural form!"
8 | msgstr[0] ""
9 | msgstr[1] ""
--------------------------------------------------------------------------------
/test/inputs/test.js:
--------------------------------------------------------------------------------
1 | // The "gettext" function here is only used for static analysis.
2 | var templates = {
3 | "pin_verification": {
4 | landing: 'pin_verification',
5 | subject: gettext("Confirm email address for Persona")
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/test/outputs/messages_firstpass.pot:
--------------------------------------------------------------------------------
1 | #: inputs/first.js:1
2 | msgid "Hello World"
3 | msgstr ""
4 |
5 | #: inputs/first.js:2
6 | msgid "This message is used twice."
7 | msgstr ""
8 |
9 | #: inputs/first.js:3
10 | msgid "This other message is used twice."
11 | msgid_plural "This other message is used twice. - plural"
12 | msgstr[0] ""
13 | msgstr[1] ""
--------------------------------------------------------------------------------
/test/outputs/react_component.pot:
--------------------------------------------------------------------------------
1 | #: inputs/react_component.jsx:4
2 | msgid "attr"
3 | msgstr ""
4 |
5 | #: inputs/react_component.jsx:4
6 | msgid "spread"
7 | msgstr ""
8 |
9 | #: inputs/react_component.jsx:5
10 | msgid "child component"
11 | msgstr ""
12 |
13 | #: inputs/react_component.jsx:7
14 | msgid "nested child component"
15 | msgstr ""
16 |
--------------------------------------------------------------------------------
/test/inputs/react_component.jsx:
--------------------------------------------------------------------------------
1 | export default React.createClass({
2 | render: function() {
3 | return (
4 |
5 | {gettext('child component')}
6 |
7 | {gettext('nested child component')}
8 |
9 |
10 | );
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/test/inputs/second_attribute.jade:
--------------------------------------------------------------------------------
1 |
2 | p(title=gettext('foo'))= gettext('bar')
3 | | #{ gettext('same-line') } #{ gettext('in text') }
4 | span= gettext('foobar')
5 | p= _('underscored')
6 | img(alt=_('underscored 1'),title=_('underscored 2'))
7 | a(
8 | data-tip=_("data attribute")
9 | title=_("attribute - one per line")
10 | )= gettext("value - one per line")
11 |
--------------------------------------------------------------------------------
/test/inputs/anonymous_functions.js:
--------------------------------------------------------------------------------
1 | (function () {})('Ignore me');
2 |
3 | var testObj = {
4 | somemethod: function () {},
5 | gettext: function () {},
6 | ngettext: function () {}
7 | };
8 |
9 | testObj.somemethod('I shall not pass');
10 | testObj.gettext("I'm gonna get translated, yay!");
11 | testObj.ngettext("I'm also gonna get translated!", "I'm the plural form!", 2);
12 |
--------------------------------------------------------------------------------
/test/outputs/messages_secondpass.pot:
--------------------------------------------------------------------------------
1 | #: inputs/first.js:1
2 | msgid "Hello World"
3 | msgstr ""
4 |
5 | #: inputs/first.js:2
6 | #: inputs/second.js:1
7 | msgid "This message is used twice."
8 | msgstr ""
9 |
10 | #: inputs/first.js:3
11 | #: inputs/second.js:2
12 | msgid "This other message is used twice."
13 | msgid_plural "This other message is used twice. - plural"
14 | msgstr[0] ""
15 | msgstr[1] ""
--------------------------------------------------------------------------------
/test/inputs/extracted_comments.js:
--------------------------------------------------------------------------------
1 | // I am a normal comment
2 | // L10n: I am an extracted comment
3 | var msg = gettext('Hello World'); // L10n: I am a comment on the same line
4 | // L10n: I belong to dup
5 | var dup = gettext('Hello translators'); // I am an ordinary, inline code comment
6 | /// I'm a standards compliant extracted comment!
7 | var tis = gettext('Hello everyone'); ///I am an inline triple-slash comment
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "strict": true,
4 |
5 | "undef": true,
6 | "unused": true,
7 | "eqnull": true,
8 | "white": true,
9 | "eqeqeq": true,
10 | "bitwise": true,
11 | "indent": 2,
12 | "newcap": true,
13 | "immed": true,
14 | "freeze": true,
15 | "latedef": true,
16 | "noarg": true,
17 | "noempty": true,
18 | "nonew": true,
19 |
20 | "-W092": true,
21 | "-W107": true
22 | }
23 |
--------------------------------------------------------------------------------
/test/inputs/arbitrary_comments.js:
--------------------------------------------------------------------------------
1 | // I am a normal comment
2 | // comment: I am an extracted comment
3 | var msg = gettext('Hello World'); // comment: I am a comment on the same line
4 | // comment: I belong to dup
5 | var dup = gettext('Hello translators'); // I am an ordinary, inline code comment
6 | /// I'm a standards compliant extracted comment!
7 | var tis = gettext('Hello everyone'); ///I am an inline triple-slash comment
8 |
--------------------------------------------------------------------------------
/test/inputs/example.swig:
--------------------------------------------------------------------------------
1 | foobar
2 | {% trans "Hello foobar" %}
3 |
4 | {% set name="World" %}
5 | {% set url="/home" %}
6 |
7 | {{url}}
8 |
9 | {% set foobar = gettext("Test gettext directly") %}
10 | {% set foobar = gettext("Test with additional params on new line", {
11 | 'foo': 'bar'
12 | }) %}
13 |
14 | {% blocktrans with name=name url=url %}
15 | Hello %(name)s.
16 | Pleasure to meet you.
17 | {% endblocktrans %}
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 |
5 | exports.compareResultWithFile = function (result, filePath, assert, cb, msg) {
6 | // Ignore the header
7 | result = result.slice(result.indexOf('\n\n') + 2).trimRight();
8 |
9 |
10 | fs.readFile(filePath, function (err, source) {
11 | var sourceContent = source.toString('utf8').trimRight();
12 | assert.equal(result, sourceContent, msg || 'Results match.');
13 | cb();
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/test/outputs/extracted_comments.pot:
--------------------------------------------------------------------------------
1 | #: inputs/extracted_comments.js:3
2 | #. L10n: I am an extracted comment
3 | #. L10n: I am a comment on the same line
4 | msgid "Hello World"
5 | msgstr ""
6 |
7 | #: inputs/extracted_comments.js:5
8 | #. L10n: I belong to dup
9 | msgid "Hello translators"
10 | msgstr ""
11 |
12 | #: inputs/extracted_comments.js:7
13 | #. I'm a standards compliant extracted comment!
14 | #. I am an inline triple-slash comment
15 | msgid "Hello everyone"
16 | msgstr ""
--------------------------------------------------------------------------------
/test/outputs/arbitrary_comments.pot:
--------------------------------------------------------------------------------
1 | #: inputs/arbitrary_comments.js:3
2 | #. comment: I am an extracted comment
3 | #. comment: I am a comment on the same line
4 | msgid "Hello World"
5 | msgstr ""
6 |
7 | #: inputs/arbitrary_comments.js:5
8 | #. comment: I belong to dup
9 | msgid "Hello translators"
10 | msgstr ""
11 |
12 | #: inputs/arbitrary_comments.js:7
13 | #. I'm a standards compliant extracted comment!
14 | #. I am an inline triple-slash comment
15 | msgid "Hello everyone"
16 | msgstr ""
--------------------------------------------------------------------------------
/test/tests/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var self = path.basename(__filename);
7 |
8 | fs.readdirSync(__dirname).forEach(function (item) {
9 | if (item === self || path.extname(item) !== '.js' && !fs.statSync(item).isDirectory())
10 | return;
11 |
12 | exports['test ' + path.basename(item, '.js')] = require(path.join(__dirname, item));
13 | });
14 |
15 | if (module === require.main)
16 | require('test').run(exports);
17 |
--------------------------------------------------------------------------------
/test/inputs/multiple_keywords.js:
--------------------------------------------------------------------------------
1 | _('should be translatable');
2 | t('should also be translatable');
3 | gettext('should NOT be translatable since we did not define it as keyword');
4 |
5 | n_('should be translatable - singular', 'should be translatable - plural', 2);
6 | nt('should also be translatable - singular', 'should also be translatable - plural', 2);
7 | ngettext('should NOT be translatable since we did not define it as keyword - singular', 'should NOT be translatable since we did not define it as keyword - plural', 2);
8 |
--------------------------------------------------------------------------------
/test/inputs/concat.js:
--------------------------------------------------------------------------------
1 | gettext(
2 | "The second string is significantly longer "+
3 | "and we'd like to concatenate it in our source "+
4 | "code to avoid really wide files."
5 | );
6 |
7 | ngettext(
8 | "This is another quite long sentence "+
9 | "and we'd like to concatenate it in our source "+
10 | "code to avoid really wide files.",
11 | "This is the plural version of our quite long sentence "+
12 | "that we'd like to concatenate it in our source "+
13 | "code to avoid really wide files.",
14 | 3
15 | );
--------------------------------------------------------------------------------
/lib/parsers/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var self = path.basename(__filename);
7 |
8 | fs.readdirSync(__dirname).forEach(function (item) {
9 | if (item === self || path.extname(item) !== '.js' && !fs.statSync(item).isDirectory())
10 | return;
11 |
12 | var parser = require(path.join(__dirname, item));
13 | // Exports from a parser is lang -> parserMethod()
14 | Object.keys(parser).forEach(function (lang) {
15 | exports[lang] = parser[lang];
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/outputs/multiple_keywords.pot:
--------------------------------------------------------------------------------
1 | #: inputs/multiple_keywords.js:1
2 | msgid "should be translatable"
3 | msgstr ""
4 |
5 | #: inputs/multiple_keywords.js:2
6 | msgid "should also be translatable"
7 | msgstr ""
8 |
9 | #: inputs/multiple_keywords.js:5
10 | msgid "should be translatable - singular"
11 | msgid_plural "should be translatable - plural"
12 | msgstr[0] ""
13 | msgstr[1] ""
14 |
15 | #: inputs/multiple_keywords.js:6
16 | msgid "should also be translatable - singular"
17 | msgid_plural "should also be translatable - plural"
18 | msgstr[0] ""
19 | msgstr[1] ""
--------------------------------------------------------------------------------
/test/outputs/concat.pot:
--------------------------------------------------------------------------------
1 | #: inputs/concat.js:1
2 | msgid ""
3 | "The second string is significantly longer and we'd like to concatenate it "
4 | "in our source code to avoid really wide files."
5 | msgstr ""
6 |
7 | #: inputs/concat.js:7
8 | msgid ""
9 | "This is another quite long sentence and we'd like to concatenate it in our "
10 | "source code to avoid really wide files."
11 | msgid_plural ""
12 | "This is the plural version of our quite long sentence that we'd like to "
13 | "concatenate it in our source code to avoid really wide files."
14 | msgstr[0] ""
15 | msgstr[1] ""
--------------------------------------------------------------------------------
/test/tests/sanity_checking.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 |
8 | exports['test sanity checking'] = function (assert, cb) {
9 | var inputFilename = path.join(__dirname, '..', 'inputs', 'insane.js');
10 |
11 | fs.readFile(inputFilename, "utf8", function (err, source) {
12 | assert.throws(function () {
13 | jsxgettext.generate(
14 | {'inputs/insane.js': source},
15 | {sanity: true});
16 | },
17 | /Could not parse translatable:/,
18 | "error was thrown for bad input");
19 |
20 | cb();
21 | });
22 | };
23 |
24 | if (module === require.main) require('test').run(exports);
25 |
--------------------------------------------------------------------------------
/test/tests/comments.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 |
8 | // Tests parsing files with comments
9 |
10 | exports['test comments'] = function (assert, cb) {
11 | // check that files with leading hash parse
12 | var inputFilename = path.join(__dirname, '..', 'inputs', 'test.js');
13 | fs.readFile(inputFilename, "utf8", function (err, source) {
14 | var result = jsxgettext.generate({'inputs/test.js': source}, {});
15 | assert.equal(typeof result, 'string', 'result is a string');
16 | assert.ok(result.length > 0, 'result is not empty');
17 | cb();
18 | });
19 | };
20 |
21 | if (module === require.main) require('test').run(exports);
22 |
--------------------------------------------------------------------------------
/test/tests/leading_hash.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 |
8 | // Tests parsing files with leading hashes
9 | exports['test with leading hash'] = function (assert, cb) {
10 | // check that files with leading hash parse
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'hash.js');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate({'inputs/first.js': source}, {});
14 | assert.equal(typeof result, 'string', 'result is a string');
15 | assert.ok(result.length > 0, 'result is not empty');
16 | cb();
17 | });
18 | };
19 |
20 | if (module === require.main) require('test').run(exports);
21 |
--------------------------------------------------------------------------------
/test/tests/creation_date.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var jsxgettext = require('../../lib/jsxgettext');
4 | var gettextParser = require('gettext-parser');
5 |
6 | exports['test creation date'] = function (assert, cb) {
7 | var opts = {},
8 | sources = {'dummy': ''},
9 | result = jsxgettext.generate(sources, opts);
10 |
11 | var parsed = gettextParser.po.parse(new Buffer(result));
12 | var timestamp = parsed.headers["pot-creation-date"].match(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}[+-]\d{4})/)[1];
13 | assert.ok(timestamp.length > 0, 'Valid timestamp');
14 | // Timestamp should be at most 2 minutes in the past since it is truncated to minute
15 | assert.ok(Date.now() - new Date(timestamp).valueOf() < 120000, 'Timestamp up-to-date');
16 |
17 | cb();
18 | };
19 |
20 | if (module === require.main) require('test').run(exports);
21 |
--------------------------------------------------------------------------------
/test/outputs/expressions.pot:
--------------------------------------------------------------------------------
1 | #: inputs/expressions.js:4
2 | msgid "Confirm email address for Persona"
3 | msgstr ""
4 |
5 | #: inputs/expressions.js:5
6 | msgid "Confirm email address for Persona 2"
7 | msgstr ""
8 |
9 | #: inputs/expressions.js:6
10 | msgid "Confirm email address for Persona 3"
11 | msgstr ""
12 |
13 | #: inputs/expressions.js:7
14 | msgid "Confirm email address for Persona 4"
15 | msgid_plural "Confirm email address for Persona 4 plural"
16 | msgstr[0] ""
17 | msgstr[1] ""
18 |
19 | #: inputs/expressions.js:8
20 | msgid "Confirm email address for Persona 5"
21 | msgid_plural "Confirm email address for Persona 5 plural"
22 | msgstr[0] ""
23 | msgstr[1] ""
24 |
25 | #: inputs/expressions.js:9
26 | msgid "Confirm email address for Persona 6"
27 | msgid_plural "Confirm email address for Persona 6 plural"
28 | msgstr[0] ""
29 | msgstr[1] ""
--------------------------------------------------------------------------------
/test/tests/second_attribute.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var ejs = require('../../lib/parsers/ejs').ejs;
8 |
9 | exports['test second attribute'] = function (assert, cb) {
10 | // check that files with leading hash parse
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'second_attribute.ejs');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate.apply(jsxgettext, ejs(
14 | {'inputs/second_attribute.ejs': source}, {})
15 | );
16 |
17 | assert.equal(typeof result, 'string', 'result is a string');
18 | assert.ok(result.length > 1, 'result is not empty');
19 | cb();
20 | });
21 | };
22 |
23 | if (module === require.main) require('test').run(exports);
24 |
--------------------------------------------------------------------------------
/test/tests/react_component.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | exports['test jsx react component'] = function (assert, cb) {
10 | var inputFilename = path.join(__dirname, '..', 'inputs', 'react_component.jsx');
11 | fs.readFile(inputFilename, "utf8", function (err, source) {
12 | var result = jsxgettext.generate({'inputs/react_component.jsx': source}, {});
13 | assert.equal(typeof result, 'string', 'result is a string');
14 | assert.ok(result.length > 0, 'result is not empty');
15 |
16 | var outputFilename = path.join(__dirname, '..', 'outputs', 'react_component.pot');
17 | utils.compareResultWithFile(result, outputFilename, assert, cb);
18 | });
19 | };
20 |
21 | if (module === require.main) require('test').run(exports);
22 |
--------------------------------------------------------------------------------
/test/inputs/expressions.js:
--------------------------------------------------------------------------------
1 | var templates = {
2 | "pin_verification": {
3 | landing: 'pin_verification',
4 | subject: test.gettext("Confirm email address for Persona"),
5 | subject2: test.gettext.call(test, "Confirm email address for Persona 2"),
6 | subject3: test.something.someotherthing['gettext'].call(test, "Confirm email address for Persona 3", somethingelse),
7 | subject4: test.ngettext("Confirm email address for Persona 4", "Confirm email address for Persona 4 plural", 4),
8 | subject5: test.ngettext.call(test, "Confirm email address for Persona 5", "Confirm email address for Persona 5 plural", 5),
9 | subject6: test.something.someotherthing['ngettext'].call(test, "Confirm email address for Persona 6", "Confirm email address for Persona 6 plural", 6, somethingelse)
10 | }
11 | };
12 |
13 | test.something.someotherthing.random.call(test, "I shall not exist", somethingelse);
--------------------------------------------------------------------------------
/test/tests/extracted_comments.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | exports['test gettext from first file'] = function (assert, cb) {
10 | var inputFilename = path.join(__dirname, '..', 'inputs', 'extracted_comments.js');
11 | fs.readFile(inputFilename, 'utf8', function (err, source) {
12 | var result = jsxgettext.generate({'inputs/extracted_comments.js': source}, {});
13 |
14 | assert.equal(typeof result, 'string', 'Result should be a string');
15 | assert.ok(result.length > 0, 'Result should not be empty');
16 |
17 | var outputFilename = path.join(__dirname, '..', 'outputs', 'extracted_comments.pot');
18 | utils.compareResultWithFile(result, outputFilename, assert, cb);
19 | });
20 | };
21 |
22 | if (module === require.main) require('test').run(exports);
23 |
--------------------------------------------------------------------------------
/test/tests/multiple_keywords.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | exports['test with multiple keywords'] = function (assert, cb) {
10 | var inputFilename = path.join(__dirname, '..', 'inputs', 'multiple_keywords.js');
11 | fs.readFile(inputFilename, "utf8", function (err, source) {
12 | var options = {keyword: ['_', 't']};
13 | var result = jsxgettext.generate({'inputs/multiple_keywords.js': source}, options);
14 | assert.equal(typeof result, 'string', 'result is a string');
15 | assert.ok(result.length > 0, 'result is not empty');
16 |
17 | var outputFilename = path.join(__dirname, '..', 'outputs', 'multiple_keywords.pot');
18 |
19 | utils.compareResultWithFile(result, outputFilename, assert, cb);
20 | });
21 | };
22 |
23 | if (module === require.main) require('test').run(exports);
24 |
--------------------------------------------------------------------------------
/test/inputs/example.handlebars:
--------------------------------------------------------------------------------
1 | Some {{ gettext "translated text"}} is matched.
2 | But non-translated text is not matched.
3 | Even with a {{ value }} embedded.
4 | Or if it contains the word gettext or even the _ character.
5 | You might also use a {{#gettext}}block helper{{/gettext}} like this.
6 | As long as it is {{#helper}}named properly{{/helper}} of course.
7 | And {{#helper}}nesting of {{#gettext}}helpers{{/gettext}} should be ok{{/helper}} too.
8 | {{! comment strings }} are passed through
9 | {{!-- including long-form {} comments with }} markup in them --}}
10 | Things that {{dont}} {{ pattern match a gettext call}} are ignored.
11 | And spurious { or }} or whatever markup doesn't confuse the parser.
12 | Escaped quotes are ok, {{gettext 'so let\'s test'}} them.
13 | Watch {{#helper}}out {{#foo}}{{#gettext}}for "quotes"{{/gettext}}{{/foo}}{{/helper}} too.
14 | So, do we {{gettext good}}?
15 | There might be {{#gettext context}}a context argument{{/gettext}}
16 |
--------------------------------------------------------------------------------
/test/tests/arbitrary_comments.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | exports['test --add-comments option'] = function (assert, cb) {
10 | var inputFilename = path.join(__dirname, '..', 'inputs', 'arbitrary_comments.js');
11 | fs.readFile(inputFilename, 'utf8', function (err, source) {
12 | var options = {"addComments": "comment:"};
13 | var result = jsxgettext.generate({'inputs/arbitrary_comments.js': source}, options);
14 |
15 | assert.equal(typeof result, 'string', 'Result should be a string');
16 | assert.ok(result.length > 0, 'Result should not be empty');
17 |
18 | var outputFilename = path.join(__dirname, '..', 'outputs', 'arbitrary_comments.pot');
19 | utils.compareResultWithFile(result, outputFilename, assert, cb);
20 | });
21 | };
22 |
23 | if (module === require.main) require('test').run(exports);
24 |
--------------------------------------------------------------------------------
/test/tests/ejs_comment.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var ejs = require('../../lib/parsers/ejs').ejs;
8 |
9 | exports['test ejs'] = function (assert, cb) {
10 | // check that include syntax doesn't break extraction
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'comment.ejs');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate.apply(jsxgettext, ejs(
14 | {'inputs/include.ejs': source}, {})
15 | );
16 |
17 | assert.equal(typeof result, 'string', 'comment result is a string');
18 | assert.ok(result.length > 1, 'comment result is not empty');
19 | assert.ok(result.indexOf('this is a non localizable comment string') === -1,
20 | 'comment strings are not extracted');
21 | cb();
22 | });
23 | };
24 |
25 | if (module === require.main) require('test').run(exports);
26 |
--------------------------------------------------------------------------------
/test/tests/ejs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var ejs = require('../../lib/parsers/ejs').ejs;
8 |
9 | exports['test ejs'] = function (assert, cb) {
10 | // check that include syntax doesn't break extraction
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'include.ejs');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate.apply(jsxgettext, ejs(
14 | {'inputs/include.ejs': source}, {})
15 | );
16 |
17 | assert.equal(typeof result, 'string', 'result is a string');
18 | assert.ok(result.length > 1, 'result is not empty');
19 | assert.ok(result.indexOf('this is a localizable string') !== -1,
20 | 'localizable strings are extracted');
21 | assert.ok(result.indexOf('this is a localizable plural string') !== -1,
22 | 'localizable plural strings are extracted');
23 | cb();
24 | });
25 | };
26 |
27 | if (module === require.main) require('test').run(exports);
28 |
--------------------------------------------------------------------------------
/test/tests/ejs_filter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var ejs = require('../../lib/parsers/ejs').ejs;
8 |
9 | exports['test ejs'] = function (assert, cb) {
10 | // check that include syntax doesn't break extraction
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'filter.ejs');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate.apply(jsxgettext, ejs(
14 | {'inputs/filter.ejs': source}, {})
15 | );
16 |
17 | assert.equal(typeof result, 'string', 'result is a string');
18 | assert.ok(result.length > 1, 'result is not empty');
19 | assert.ok(result.indexOf('this is a localizable string') !== -1,
20 | 'localizable strings are extracted');
21 | assert.ok(result.indexOf('this is a localizable plural string') !== -1,
22 | 'localizable plural strings are extracted');
23 | cb();
24 | });
25 | };
26 |
27 | if (module === require.main) require('test').run(exports);
28 |
--------------------------------------------------------------------------------
/test/tests/po_header.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var jsxgettext = require('../../lib/jsxgettext');
4 | var gettextParser = require('gettext-parser');
5 |
6 | exports['test po header'] = function (assert, cb) {
7 | var opts = {},
8 | sources = {'dummy': ''},
9 | result = jsxgettext.generate(sources, opts),
10 | parsed = gettextParser.po.parse(new Buffer(result));
11 |
12 | var headerDefaults = {
13 | "project-id-version": "PACKAGE VERSION",
14 | "language-team": "LANGUAGE ",
15 | "po-revision-date": "YEAR-MO-DA HO:MI+ZONE",
16 | "language": "",
17 | "mime-version": "1.0",
18 | "content-type": "text/plain; charset=utf-8",
19 | "content-transfer-encoding": "8bit"
20 | };
21 |
22 | var headers = parsed.headers;
23 | var headerKeys = Object.keys(headerDefaults);
24 | var i, header;
25 | for (i = 0; i < headerKeys.length; i++) {
26 | header = headerKeys[i];
27 | assert.equal(headerDefaults[header], headers[header], header);
28 | }
29 |
30 | cb();
31 | };
32 |
33 | if (module === require.main) require('test').run(exports);
34 |
--------------------------------------------------------------------------------
/test/tests/po_quotes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 |
8 | // Tests parsing files with comments
9 | exports['test quotes and newlines when folding msgid'] = function (assert, cb) {
10 | // check that files with leading hash parse
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'po_quotes.js');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate({'inputs/po_quotes.js': source}, {});
14 |
15 | // short line is escaped properly
16 | assert.ok(result.indexOf('\nmsgid "Hello \\"World\\"\\n"\n') >= 0, 'short line');
17 |
18 | // long folded line should also get escaped
19 | assert.ok(result.indexOf('\n"This is a long string with \\"quotes\\", newlines \\n"\n') >= 0, 'long folded line, 1');
20 | assert.ok(result.indexOf('\n" and such. The line should get folded"\n') >= 0, 'long folded line, 2');
21 |
22 | cb();
23 | });
24 | };
25 |
26 | if (module === require.main) require('test').run(exports);
27 |
--------------------------------------------------------------------------------
/test/tests/ejs_raw.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var ejs = require('../../lib/parsers/ejs').ejs;
8 |
9 | exports['test ejs'] = function (assert, cb) {
10 | // check that include syntax doesn't break extraction
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'raw.ejs');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 | var result = jsxgettext.generate.apply(jsxgettext, ejs(
14 | {'inputs/include.ejs': source}, {})
15 | );
16 |
17 | assert.equal(typeof result, 'string', 'raw result is a string');
18 | assert.ok(result.length > 1, 'raw result is not empty');
19 | assert.ok(result.indexOf('this is a raw localizable string') !== -1,
20 | 'raw localizable strings are extracted');
21 | assert.ok(result.indexOf('this is a raw localizable plural string') !== -1,
22 | 'raw localizable plural strings are extracted');
23 | cb();
24 | });
25 | };
26 |
27 | if (module === require.main) require('test').run(exports);
28 |
--------------------------------------------------------------------------------
/test/tests/anonymous_functions.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | exports['test anonymous functions and method calls'] = function (assert, cb) {
10 | // method calls usually result in CallExpressions that does have an empty
11 | // .callee which, combined with an empty "keyword" argument, results them
12 | // being treated as a gettext call variant.
13 | var inputFilename = path.join(__dirname, '..', 'inputs', 'anonymous_functions.js');
14 | fs.readFile(inputFilename, "utf8", function (err, source) {
15 | var result = jsxgettext.generate({'inputs/anonymous_functions.js': source}, {});
16 | assert.equal(typeof result, 'string', 'result is a string');
17 | assert.ok(result.length > 0, 'result is not empty');
18 |
19 | var outputFilename = path.join(__dirname, '..', 'outputs', 'anonymous_functions.pot');
20 | utils.compareResultWithFile(result, outputFilename, assert, cb);
21 | });
22 | };
23 |
24 | if (module === require.main) require('test').run(exports);
25 |
--------------------------------------------------------------------------------
/test/tests/po_custom_headers.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var jsxgettext = require('../../lib/jsxgettext');
4 | var gettextParser = require('gettext-parser');
5 |
6 | exports['test po custom headers'] = function (assert, cb) {
7 | var opts = {
8 | "projectIdVersion": "MyProject 1.0",
9 | "reportBugsTo": "bugs@project.org",
10 | "languageTeam": "this cannot be overridden"
11 | },
12 | sources = {'dummy': ''},
13 | result = jsxgettext.generate(sources, opts),
14 | parsed = gettextParser.po.parse(new Buffer(result));
15 |
16 | var headerCustom = {
17 | "project-id-version": "MyProject 1.0",
18 | "report-msgid-bugs-to": "bugs@project.org"
19 | };
20 |
21 | var headers = parsed.headers;
22 | var headerKeys = Object.keys(headerCustom);
23 | var i, header;
24 |
25 | assert.equal(headers['language-team'], "LANGUAGE ", "According to gettext specs, this should not be overridden.");
26 | for (i = 0; i < headerKeys.length; i++) {
27 | header = headerKeys[i];
28 | assert.equal(headerCustom[header], headers[header], header);
29 | }
30 |
31 | cb();
32 | };
33 |
34 | if (module === require.main) require('test').run(exports);
35 |
--------------------------------------------------------------------------------
/test/tests/swig.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var swigParser = require('../../lib/parsers/swig').swig;
8 |
9 | exports['test single file parsing'] = function (assert, cb) {
10 | // check that files with leading hash parse
11 | var inputFilename = path.join(__dirname, '..', 'inputs', 'example.swig');
12 | fs.readFile(inputFilename, "utf8", function (err, source) {
13 |
14 | var sources = {'inputs/example.swig': source},
15 | result = jsxgettext.generate.apply(jsxgettext, swigParser(sources, {}));
16 |
17 | assert.equal(typeof result, 'string', 'result is a string');
18 | assert.ok(result.length > 1, 'result is not empty');
19 | assert.ok(result.indexOf('msgid "Hello foobar"') > -1, 'Trans tag found');
20 | assert.ok(result.indexOf('msgid "Test gettext directly"') > -1, 'Gettext function call found');
21 | assert.ok(result.indexOf('msgid "Test with additional params on new line"') > -1, 'Gettext with newline function call found');
22 | assert.ok(result.indexOf('msgid "Hello %(name)s. Pleasure to meet you."') > -1, 'Blocktrans tag found');
23 | cb();
24 | });
25 | };
26 |
27 | if (module === require.main) require('test').run(exports);
28 |
--------------------------------------------------------------------------------
/test/inputs/example.jade:
--------------------------------------------------------------------------------
1 | - var value = "v"
2 | p
3 | | Some #{ gettext("translated text") } is matched.
4 | | Event if it #{ _("uses underscore") }.
5 | | Or #{ _('has several translations') } #{ gettext("in one line") }
6 | | But non-translated text is not matched.
7 | | Even with a #{value} embedded.
8 | | Or if it contains the word gettext or even the _ character.
9 |
10 | p.
11 | Modern #{ _('pipeless multiline syntax') }
12 | is absolutely supported
13 | and BTW #{ gettext("this is line 13") }
14 |
15 | //- L10n: comments for translator would be cool
16 | p= _("because sometimes one need context")
17 | span= gettext('and yeah - feel free to use " here')
18 | // look out for those quotes
19 | span= _("or to use ' here")
20 |
21 | //- other types of comments are ignored
22 | //- even if the look like L10n: or gettext('invocation')
23 | p Have fun with #{ _('attributes') }
24 | a(
25 | data-custom=gettext("because jade generally doesn't care")
26 | date-extrea=_('how you format your line')
27 | href='/this/is/not/something/you/want/to/translate'
28 | )= _('This link is in line 28 and it better points somewhere!')
29 |
30 | span(data-custom=gettext("or you can"), data-extra=_('put multiple attributes'))= _('in line one line (30)')
31 |
32 | a(href="http://some-link-here/" title=n_('Potato', 'Potatoes', 5))=n_('Apple', 'Apples', 1)
33 |
34 | +mixin(_("Parameters in mixins"), _("They will be extracted as well"))
35 |
--------------------------------------------------------------------------------
/test/tests/handlebars.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var handlebars = require('../../lib/parsers/handlebars').handlebars;
8 |
9 | exports['test handlebars'] = function (assert, cb) {
10 | var inputFilename = path.join(__dirname, '..', 'inputs', 'example.handlebars');
11 | fs.readFile(inputFilename, "utf8", function (err, source) {
12 | var result = jsxgettext.generate.apply(jsxgettext, handlebars(
13 | {'inputs/example.handlebars': source}, { foo: true })
14 | );
15 |
16 | assert.equal(typeof result, 'string', 'result is a string');
17 | assert.ok(result.length > 1, 'result is not empty');
18 | assert.equal(result.split(/msgid ".+"/).length, 7, 'exactly six strings are found');
19 | assert.notEqual(result.indexOf('msgid "translated text"'), -1, 'result contains the first string');
20 | assert.notEqual(result.indexOf('msgid "block helper"'), -1, 'result contains the second string');
21 | assert.notEqual(result.indexOf('msgid "helpers"'), -1, 'result contains the third string');
22 | assert.notEqual(result.indexOf('msgid "so let\'s test"'), -1, 'result contains the fourth string');
23 | assert.notEqual(result.indexOf('msgid "for \\"quotes\\""'), -1, 'result contains the fifth string');
24 | assert.notEqual(result.indexOf('msgid "a context argument"'), -1, 'result contains the sixth string');
25 | cb();
26 | });
27 | };
28 |
29 | if (module === require.main) require('test').run(exports);
30 |
--------------------------------------------------------------------------------
/test/tests/jade.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var jade = require('../../lib/parsers/jade').jade;
8 |
9 | var utils = require('../utils');
10 |
11 | exports['test parsing'] = function (assert, cb) {
12 | var inputFilename = path.join(__dirname, '..', 'inputs', 'example.jade');
13 | fs.readFile(inputFilename, "utf8", function (err, source) {
14 | var opts = {keyword: ['gettext', '_']},
15 | sources = {'inputs/example.jade': source},
16 | result = jsxgettext.generate.apply(jsxgettext, jade(sources, opts));
17 |
18 | assert.equal(typeof result, 'string', 'result is a string');
19 | assert.ok(result.length > 0, 'result is not empty');
20 |
21 | var outputFilename = path.join(__dirname, '..', 'outputs', 'jade.pot');
22 |
23 | utils.compareResultWithFile(result, outputFilename, assert, cb);
24 | });
25 | };
26 |
27 | exports['test regexp escaping'] = function (assert, cb) {
28 | // check that files with leading hash parse
29 | var inputFilename = path.join(__dirname, '..', 'inputs', 'second_attribute.jade');
30 | fs.readFile(inputFilename, "utf8", function (err, source) {
31 | // if keyword is not escaped, this will throw an exception
32 | var opts = {keyword: ['foo)bar']},
33 | sources = {'inputs/second_attribute.jade': source};
34 |
35 | jsxgettext.generate.apply(jsxgettext, jade(sources, opts));
36 | // ..and won't reach to here.
37 | assert.ok(true, 'regexp should not throw');
38 | cb();
39 | });
40 | };
41 |
42 | if (module === require.main) require('test').run(exports);
43 |
--------------------------------------------------------------------------------
/test/outputs/jade.pot:
--------------------------------------------------------------------------------
1 | #: inputs/example.jade:3
2 | msgid "translated text"
3 | msgstr ""
4 |
5 | #: inputs/example.jade:4
6 | msgid "uses underscore"
7 | msgstr ""
8 |
9 | #: inputs/example.jade:5
10 | msgid "has several translations"
11 | msgstr ""
12 |
13 | #: inputs/example.jade:5
14 | msgid "in one line"
15 | msgstr ""
16 |
17 | #: inputs/example.jade:11
18 | msgid "pipeless multiline syntax"
19 | msgstr ""
20 |
21 | #: inputs/example.jade:13
22 | msgid "this is line 13"
23 | msgstr ""
24 |
25 | #: inputs/example.jade:16
26 | #. L10n: comments for translator would be cool
27 | msgid "because sometimes one need context"
28 | msgstr ""
29 |
30 | #: inputs/example.jade:17
31 | msgid "and yeah - feel free to use \" here"
32 | msgstr ""
33 |
34 | #: inputs/example.jade:19
35 | msgid "or to use ' here"
36 | msgstr ""
37 |
38 | #: inputs/example.jade:23
39 | msgid "attributes"
40 | msgstr ""
41 |
42 | #: inputs/example.jade:24
43 | msgid "because jade generally doesn't care"
44 | msgstr ""
45 |
46 | #: inputs/example.jade:24
47 | msgid "how you format your line"
48 | msgstr ""
49 |
50 | #: inputs/example.jade:28
51 | msgid "This link is in line 28 and it better points somewhere!"
52 | msgstr ""
53 |
54 | #: inputs/example.jade:30
55 | msgid "or you can"
56 | msgstr ""
57 |
58 | #: inputs/example.jade:30
59 | msgid "put multiple attributes"
60 | msgstr ""
61 |
62 | #: inputs/example.jade:30
63 | msgid "in line one line (30)"
64 | msgstr ""
65 |
66 | #: inputs/example.jade:32
67 | msgid "Potato"
68 | msgid_plural "Potatoes"
69 | msgstr[0] ""
70 | msgstr[1] ""
71 |
72 | #: inputs/example.jade:32
73 | msgid "Apple"
74 | msgid_plural "Apples"
75 | msgstr[0] ""
76 | msgstr[1] ""
77 |
78 | #: inputs/example.jade:34
79 | msgid "Parameters in mixins"
80 | msgstr ""
81 |
82 | #: inputs/example.jade:34
83 | msgid "They will be extracted as well"
84 | msgstr ""
85 |
--------------------------------------------------------------------------------
/test/tests/expressions.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | exports['test different expression contexts for gettext'] = function (assert, cb) {
10 | var inputFilename = path.join(__dirname, '..', 'inputs', 'expressions.js');
11 | fs.readFile(inputFilename, "utf8", function (err, source) {
12 | var result = jsxgettext.generate({'inputs/expressions.js': source}, {});
13 | assert.equal(typeof result, 'string', 'result is a string');
14 | assert.ok(result.length > 0, 'result is not empty');
15 |
16 | var outputFilename = path.join(__dirname, '..', 'outputs', 'expressions.pot');
17 |
18 | utils.compareResultWithFile(result, outputFilename, assert, cb);
19 | });
20 | };
21 |
22 | exports['test issue #25'] = function (assert, cb) {
23 | // check that files with leading hash parse
24 | var inputFilename = path.join(__dirname, '..', 'inputs', 'pizza.js');
25 | fs.readFile(inputFilename, "utf8", function (err, source) {
26 | var result = jsxgettext.generate({'inputs/pizza.js': source}, {});
27 | assert.equal(typeof result, 'string', 'result is a string');
28 | assert.ok(result.length > 0, 'result is not empty');
29 | cb();
30 | });
31 | };
32 |
33 | exports['test concatenated strings (issue #10)'] = function (assert, cb) {
34 | var inputFilename = path.join(__dirname, '..', 'inputs', 'concat.js');
35 | var outputFilename = path.join(__dirname, '..', 'outputs', 'concat.pot');
36 | fs.readFile(inputFilename, "utf8", function (err, source) {
37 | var result = jsxgettext.generate({'inputs/concat.js': source}, {});
38 | assert.equal(typeof result, 'string', 'result is a string');
39 | assert.ok(result.length > 0, 'result is not empty');
40 |
41 | utils.compareResultWithFile(result, outputFilename, assert, cb);
42 | });
43 | };
44 |
45 | if (module === require.main) require('test').run(exports);
46 |
--------------------------------------------------------------------------------
/lib/parsers/ejs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // strips everything but the javascript bits
4 | function parseEJS(str, options) {
5 | options = options || {};
6 | var open = options.open || '<%',
7 | close = options.close || '%>';
8 |
9 | var buf = [],
10 | comment,
11 | filtered;
12 |
13 | for (var i = 0, len = str.length; i < len; ++i) {
14 | if (str.slice(i, open.length + i) === open) {
15 | comment = false;
16 | i += open.length;
17 | switch (str.substr(i, 1)) {
18 | case '=':
19 | case '-':
20 | ++i;
21 | break;
22 | case '#':
23 | ++i;
24 | comment = true;
25 | }
26 |
27 | // Check for <%== style opening tag
28 | if (str.substr(i, 1) === '=') {
29 | ++i;
30 | } else if (str.substr(i, 1) === ':') {
31 | ++i;
32 | filtered = true;
33 | }
34 |
35 | var end = str.indexOf(close, i), js = str.substring(i, end), start = i, n = 0;
36 | if ('-' === js[js.length - 1]) {
37 | js = js.substring(0, js.length - 2);
38 | }
39 | // visionmedia/ejs treats everything after the first | as filter definitions
40 | if (filtered) {
41 | js = js.split('|', 1)[0];
42 | }
43 |
44 | while ((n = js.indexOf("\n", n)) > -1) {
45 | n += 1;
46 | buf.push("\n");
47 | }
48 |
49 | // skip EJS comments and EJS include statements which are not valid javascript
50 | if (comment || /^\s*include\s*[^\s]+\s*$/.test(js)) js = "";
51 |
52 | buf.push(js, ';');
53 | i += end - start + close.length - 1;
54 |
55 | } else if (str.substr(i, 1) === "\n") {
56 | buf.push("\n");
57 | }
58 | }
59 |
60 | return buf.join('');
61 | }
62 |
63 | // generate extracted strings file from EJS
64 | exports.ejs = function EJS(ejsSources, options) {
65 | Object.keys(ejsSources).forEach(function (filename) {
66 | ejsSources[filename] = parseEJS(ejsSources[filename]);
67 | });
68 |
69 | return [ejsSources, options];
70 | };
71 |
72 | // generate extracted strings file from Jinja2 templates
73 | exports.jinja = function Jinja(jinjaSources, options) {
74 | Object.keys(jinjaSources).forEach(function (filename) {
75 | jinjaSources[filename] = parseEJS(jinjaSources[filename], {open: "{{", close: "}}"});
76 | });
77 |
78 | return [jinjaSources, options];
79 | };
--------------------------------------------------------------------------------
/lib/parsers/swig.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var unescapedDoubleQuote = /([^\\])"|^"/g;
4 | function escapeQuotes(str) {
5 | return str.replace(unescapedDoubleQuote, function (match, group) {
6 | var prefix = group || '';
7 | return prefix + '\\"';
8 | });
9 | }
10 |
11 | function parseSwig(str, filename, options) {
12 | var keyword = options.keyword || ['gettext'];
13 | // as swig is a slightly different parser (not looking for the keyword in the source,
14 | // just using the keyword for the function call) it will just take the first
15 | // keyword in case the keyword is an array
16 | if(Array.isArray(keyword)) {
17 | keyword = keyword[0];
18 | }
19 |
20 | var buf = [];
21 | var transTagRegex = /\{%\s*trans\s+['"]{1}(.+?)['"]{1}\s*%\}/mig;
22 | var gettextRegex = new RegExp(keyword + '\\([\'"]{1}(.+?)[\'"]{1}[^)]*\\)', 'mig');
23 | var blockTransRegex = /\{%\s*blocktrans.+?%}(((?!(\{%\s*endblocktrans\s*%\}))(?:\n|.))+)\{%\s*endblocktrans\s*%\}/mig;
24 | var matches, i, l, lineMatch, match, blockStr;
25 |
26 | matches = str.match(transTagRegex);
27 | if(matches) {
28 | for(i = 0, l = matches.length; i < l; i++) {
29 | lineMatch = matches[i];
30 | while((match = transTagRegex.exec(lineMatch)) !== null) {
31 | buf.push(keyword + '("' + escapeQuotes(match[1]) + '")');
32 | }
33 | }
34 | }
35 | matches = str.match(gettextRegex);
36 | if(matches) {
37 | for(i = 0, l = matches.length; i < l; i++) {
38 | lineMatch = matches[i];
39 | while((match = gettextRegex.exec(lineMatch)) !== null) {
40 | buf.push(keyword + '("' + escapeQuotes(match[1]) + '")');
41 | }
42 | }
43 | }
44 | matches = str.match(blockTransRegex);
45 | if(matches) {
46 | for(i = 0, l = matches.length; i < l; i++) {
47 | lineMatch = matches[i];
48 | while((match = blockTransRegex.exec(lineMatch)) !== null) {
49 | blockStr = escapeQuotes(match[1]).replace(/\n|\t/g, ' ');
50 | blockStr = blockStr.replace(/ +(?= )/g, '').trim();
51 |
52 | buf.push(keyword + '("' + blockStr + '")');
53 | }
54 | }
55 | }
56 |
57 | return buf.join('\n');
58 | }
59 |
60 | exports.swig = function swig(swigSources, options) {
61 | Object.keys(swigSources).forEach(function (filename) {
62 | swigSources[filename] = parseSwig(swigSources[filename], filename, options);
63 | });
64 |
65 | return [swigSources, options];
66 | };
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Zach Carter (http://twitter.com/zii)",
3 | "contributors": [
4 | "Szigetvári Áron (https://github.com/AronSzigetvari)",
5 | "Arpad Borsos (http://swatinem.de/)",
6 | "Damian Krzeminski (http://pirxpilot.me/)",
7 | "Doug Beck (http://douglasbeck.com/)",
8 | "Chia-liang Kao (http://www.clkao.org/)",
9 | "Vlad Filippov (http://vf.io)",
10 | "Lloyd Hilaiel (http://lloyd.io)",
11 | "Burak Yigit Kaya (http://byk.im)",
12 | "Ryan Kelly (http://www.rfk.id.au)",
13 | "Austin King (https://ozten.com)",
14 | "Kaare A. Larsen (http://bottleno.se)",
15 | "Francois Marier (http://fmarier.org)",
16 | "Eduardo Vaz de Mello (https://github.com/wirapuru)",
17 | "Justin van der Merwe (https://github.com/justinvdm)",
18 | "Andris Reinman (http://www.andrisreinman.com)",
19 | "Valentin Rouet (https://github.com/vrouet)",
20 | "Sergey Slipchenko (https://github.com/faergeek)",
21 | "Gero Takke ",
22 | "Michael Weibel (https://github.com/mweibel)",
23 | "Yasu (https://github.com/mahata)",
24 | "Marcus (https://github.com/mphasize)"
25 | ],
26 | "name": "jsxgettext-andris",
27 | "version": "0.9.0-patch.1",
28 | "license": "MPL-2.0",
29 | "description": "Extracts gettext strings from JavaScript, EJS, Jade, Jinja and Handlebars files. A fork of https://github.com/zaach/jsxgettext",
30 | "keywords": [
31 | "i18n",
32 | "internationalization",
33 | "gettext",
34 | "xgettext"
35 | ],
36 | "homepage": "https://github.com/andris9/jsxgettext",
37 | "bugs": "https://github.com/andris9/jsxgettext/issues",
38 | "repository": {
39 | "type": "git",
40 | "url": "git://github.com/andris9/jsxgettext.git"
41 | },
42 | "main": "lib/jsxgettext.js",
43 | "bin": "lib/cli.js",
44 | "preferGlobal": true,
45 | "engineStrict": true,
46 | "engines": {
47 | "node": ">= 4.0.0"
48 | },
49 | "dependencies": {
50 | "acorn": "^3.0.0",
51 | "acorn-jsx": "^3.0.0",
52 | "commander": "^2.9.0",
53 | "escape-string-regexp": "^1.0.4",
54 | "gettext-parser": "^1.1.2",
55 | "jade": "^1.11.0"
56 | },
57 | "devDependencies": {
58 | "jshint": "2.5.5",
59 | "test": "0.6.0",
60 | "i18n-abide": "0.0.17"
61 | },
62 | "scripts": {
63 | "test": "node ./node_modules/jshint/bin/jshint . && node test"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jsxgettext [](https://travis-ci.org/zaach/jsxgettext) [](http://badge.fury.io/js/jsxgettext)
2 |
3 | > This is a custom fork of https://github.com/zaach/jsxgettext
4 |
5 | A node module with a CLI that extracts gettext strings from JavaScript, EJS, Jade, Jinja, Swig and Handlebars files. Uses a real parser, [acorn](https://github.com/marijnh/acorn), for JavaScript files and recognizes the following uses:
6 |
7 | ```javascript
8 | gettext("Hello world!");
9 | gettext("Hello " + 'world!');
10 | myModule.gettext("Hello " + 'world!');
11 | gettext.call(myObj, "Hello " + 'world!');
12 | ngettext("Here's an apple for you", "Here are %s apples for you", 3);
13 | ngettext("Here's an apple" + ' for you', "Here are %s apples" + ' for you', 3);
14 | myModule.ngettext("Here's an apple" + ' for you', "Here are %s apples" + ' for you', 3);
15 | ngettext.call(myObj, "Here's an apple" + ' for you', "Here are %s apples" + ' for you', 3);
16 | ```
17 |
18 | It also extracts comments that begin with "L10n:" when they appear above or next to a `gettext` call:
19 |
20 | ```javascript
21 | // L10n: Don't forget the exclamation mark
22 | gettext("Hello world!"); // L10n: Salutation to the world
23 | ```
24 |
25 | "L10n:" is a default value and you can change it with `-c` option.
26 |
27 | ## Install
28 |
29 | npm install jsxgettext
30 |
31 | Or from source:
32 |
33 | git clone https://github.com/zaach/jsxgettext.git
34 | cd jsxgettext
35 | npm link
36 |
37 | ## Use
38 |
39 | $ jsxgettext
40 |
41 |
42 | Usage: jsxgettext [options] [file ...]
43 |
44 | Options:
45 |
46 | -h, --help output usage information
47 | -V, --version output the version number
48 | -o, --output write output to specified
49 | -p, --output-dir output files will be placed in directory
50 | -k, --keyword [keywords] additional keywords to be looked for
51 | -j, --join-existing join messages with existing file
52 | -L, --language [lang] use the specified language (javascript, ejs, jinja, handlebars, jade, swig) [javascript]
53 | -s, --sanity sanity check during the extraction
54 | --project-id-version [version] This is the project name and version of the generated package/catalog.
55 | --report-bugs-to [bug address] An email address or URL where you can report bugs in the untranslated strings.
56 | -c, --add-comments [tag] place comment blocks starting with TAG and preceding keyword lines in output file (default: "L10n:").
57 |
--------------------------------------------------------------------------------
/lib/parsers/jade.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var jade = require('jade');
4 |
5 | // From MDN:
6 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters
7 | function escapeRegExp(string) {
8 | return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
9 | }
10 |
11 | function parseJade(str, options) {
12 | options = options || {};
13 |
14 | var parser = new jade.Parser(str);
15 | var lexer = parser.lexer;
16 | var token;
17 |
18 | options.keyword = options.keyword || ['gettext'];
19 |
20 | var gettextRegexPrefix = '\\w*(?:' + options.keyword.map(escapeRegExp).join('|') + ')';
21 | var argRegex = /\s*(?:"[^"]+"|'[^']+')\s*/;
22 | var gettexRegex = new RegExp(gettextRegexPrefix + '\\(' + argRegex.source + '(?:,' + argRegex.source + ')?', 'gi');
23 |
24 | function extractGettext(str) {
25 | if (typeof(str) !== 'string') return '';
26 |
27 | var tmp = str.match(gettexRegex) || [];
28 | return tmp.map(function (t) {
29 | return t + ')';
30 | }).join(';');
31 | }
32 |
33 | function extractFromObj(key) {
34 | /* jshint -W040 */
35 | return extractGettext(this[key].val);
36 | }
37 |
38 | var buf = [];
39 |
40 | function append(text, offset) {
41 | /* jshint -W040 */
42 | if (this.type === 'attrs') {
43 | // offset for attribute tokens are invalid
44 | // we treat all attr tokens to be on the same line as the first one =(
45 | offset = 0;
46 | }
47 | var line = this.line + (offset || 0) - 1;
48 | if (text.length) {
49 | buf[line] = [buf[line], text, ';'].join('');
50 | }
51 | }
52 |
53 | do {
54 | token = lexer.next();
55 | switch (token.type) {
56 | case 'attrs':
57 | Object.keys(token.attrs)
58 | .map(extractFromObj, token.attrs)
59 | .forEach(append, token);
60 | break;
61 | case 'call':
62 | append.call(token, extractGettext(token.args));
63 | break;
64 | case 'text':
65 | case 'code':
66 | append.call(token, extractGettext(token.val));
67 | break;
68 | case 'pipeless-text':
69 | token.line -= token.val.length - 1;
70 | token.val
71 | .map(extractGettext)
72 | .forEach(append, token);
73 | break;
74 | case 'comment':
75 | if (/^\s*L10n:/.test(token.val)) {
76 | append.call(token, ['/*', token.val, '*/'].join(''));
77 | }
78 | break;
79 | }
80 | } while (token.type !== 'eos');
81 |
82 | return buf.join('\n');
83 | }
84 |
85 | // generate extracted strings file from Jade templates
86 | exports.jade = function Jade(jadeSources, options) {
87 | Object.keys(jadeSources).forEach(function (filename) {
88 | jadeSources[filename] = parseJade(jadeSources[filename], options);
89 | });
90 |
91 | return [jadeSources, options];
92 | };
93 |
--------------------------------------------------------------------------------
/lib/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | var fs = require('fs');
5 | var path = require('path');
6 | var parsers = require('./parsers');
7 | var jsxgettext = require('./jsxgettext');
8 |
9 | var optAsList = function (val) { return val.split(','); };
10 | var opts = require("commander")
11 | .version(require('../package.json').version)
12 | .usage('[options] [file ...]')
13 | .option('-o, --output ', 'write output to specified ', '-') // default to stdout
14 | .option('-p, --output-dir ', 'output files will be placed in directory ')
15 | .option('-k, --keyword [keywords]', 'additional keywords to be looked for', optAsList)
16 | .option('-j, --join-existing', 'join messages with existing file')
17 | .option('-L, --language [lang]', 'use the specified language (' + ['javascript'].concat(Object.keys(parsers)).join(', ') + ') [javascript]', 'javascript')
18 | .option('-s, --sanity', "sanity check during the extraction")
19 | .option('--project-id-version [version]', 'This is the project name and version of the generated package/catalog.')
20 | .option('--report-bugs-to [bug address]', 'An email address or URL where you can report bugs in the untranslated strings.')
21 | .option('-c, --add-comments [tag]', 'place comment blocks starting with TAG and preceding keyword lines in output file (default: "L10n:").')
22 | .parse(process.argv);
23 |
24 | function gen(sources) {
25 | var result;
26 | var lang = opts.language.toLowerCase();
27 | if (!opts.keyword) {
28 | opts.keyword = ['gettext'];
29 | }
30 | if (opts.keyword.indexOf('gettext') === -1) {
31 | // when called from the cli, gettext should always be one of the default keywords
32 | opts.keyword.push('gettext');
33 | }
34 |
35 | if (lang === 'javascript') {
36 | result = jsxgettext.generate(sources, opts);
37 | } else if (lang in parsers) {
38 | result = jsxgettext.generate.apply(jsxgettext, parsers[lang](sources, opts));
39 | } else {
40 | throw new Error("Unsupported language: " + opts.language);
41 | }
42 |
43 | if (opts.output === '-') { // use stdout
44 | console.log(result);
45 | } else {
46 | fs.writeFileSync(
47 | path.resolve(path.join(opts.outputDir || '', opts.output)),
48 | result,
49 | "utf8"
50 | );
51 | }
52 | }
53 |
54 | function main() {
55 | var files = opts.args;
56 | var sources = {};
57 |
58 | if (!files.length || files[0] === '-') { // read from stdin, and do it by default
59 | var data = '';
60 | process.stdin.resume();
61 | process.stdin.setEncoding('utf8');
62 |
63 | process.stdin.on('data', function (chunk) {
64 | data += chunk;
65 | });
66 |
67 | process.stdin.on('end', function () {
68 | gen({'stdin': data});
69 | });
70 | } else {
71 | files.forEach(function (filename) {
72 | sources[filename] = fs.readFileSync(filename, "utf8");
73 | });
74 | gen(sources);
75 | }
76 | }
77 |
78 | main();
79 |
--------------------------------------------------------------------------------
/test/tests/join_existing.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 |
6 | var jsxgettext = require('../../lib/jsxgettext');
7 | var utils = require('../utils');
8 |
9 | // Tests the --join-existing feature
10 |
11 | var sourceFirstPass;
12 |
13 | var test2 = function (assert, cb) {
14 | // We'll extract strings from inputs/second.js
15 | // This should match outputs/messages.pot
16 | var inputFilename = path.join(__dirname, '..', 'inputs', 'second.js');
17 | fs.readFile(inputFilename, 'utf8', function (err, source) {
18 | var result = jsxgettext.generate({
19 | 'inputs/first.js': sourceFirstPass,
20 | 'inputs/second.js': source
21 | }, {
22 | output: 'messages.pot',
23 | joinExisting: true
24 | });
25 |
26 | assert.equal(typeof result, 'string', 'Result should be a string');
27 | assert.ok(result.length > 0, 'Result should not be empty');
28 | var outputFilename = path.join(__dirname, '..', 'outputs', 'messages_secondpass.pot');
29 |
30 | utils.compareResultWithFile(result, outputFilename, assert, function () {
31 | fs.unlink('messages.pot', cb); // cleanup
32 | });
33 | });
34 | };
35 |
36 | /*
37 | * We use xgettext on files under inputs and save it's output
38 | * under outputs. These tests run jsxgettext against the
39 | * same inputs and test for identical output.
40 | */
41 | exports['test gettext from first file'] = function (assert, cb) {
42 | // We'll extract strings from inputs/first.js
43 | // This should match outputs/messages_firstpass.js
44 | var inputFilename = path.join(__dirname, '..', 'inputs', 'first.js');
45 | fs.readFile(inputFilename, 'utf8', function (err, source) {
46 | var result = jsxgettext.generate({'inputs/first.js': source}, {});
47 |
48 | assert.equal(typeof result, 'string', 'Result should be a string');
49 | assert.ok(result.length > 0, 'Result should not be empty');
50 | var outputFilename = path.join(__dirname, '..', 'outputs', 'messages_firstpass.pot');
51 |
52 | utils.compareResultWithFile(result, outputFilename, assert, function () {
53 | sourceFirstPass = source;
54 |
55 | // write to filesystem as join-existing will implicitly look for it, but...
56 | // TODO: So jsxgettext does the right thing with or without messages.po
57 | // that seems odd...
58 | fs.writeFileSync('messages.pot', result, "utf8");
59 | test2(assert, cb);
60 | });
61 | });
62 | };
63 |
64 | exports['test joining extracted messages'] = function (assert, cb) {
65 | var sources = { 'hello.js': 'gettext("hello");' };
66 | var options = { output: 'messages.pot', joinExisting: true };
67 | var setup = function() {
68 | // note pot has the string "hello", therein lies the rub.
69 | var potStr = '';
70 | potStr +='msgid ""\nmsgstr ""\n';
71 | potStr += '"Content-Type: text/plain; charset=UTF-8\\n"\n\n';
72 | potStr += 'msgid "hello"\nmsgstr "hola"\n';
73 | fs.writeFileSync('messages.pot', potStr, 'utf8');
74 | };
75 | var teardown = function() {
76 | fs.unlink('messages.pot', cb);
77 | };
78 | var doesNotThrow = function() {
79 | try {
80 | jsxgettext.generate(sources, options);
81 | } catch (e) {
82 | assert.fail(e);
83 | return false;
84 | }
85 | return true;
86 | };
87 |
88 | setup();
89 | assert.ok(doesNotThrow(), 'ignores missings comments');
90 | teardown();
91 |
92 | };
93 |
94 | if (module === require.main) require('test').run(exports);
95 |
--------------------------------------------------------------------------------
/lib/parsers/handlebars.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // Turn handlebars helper calls into javascript-syntax functions.
4 | // Also comment blocks are turned into javascript comments.
5 | function parseHandlebars(str, recurse) {
6 | // Using regexes for parsing, ooooh yeeeahhh!
7 | // Short comments: {{! this is a comment }}
8 | var shortCommentRE = /\{\{\!(.*?)\}\}/;
9 | // Long comments: {{!-- this comment has {{markup}} in it --}}
10 | var longCommentRE = /\{\{\!--(.*?)--\}\}/;
11 | // Block helpers: {{#helper}}template content{{/helper}}
12 | var blockHelperStartRE = /\{\{#(translate)?\}\}/;
13 | var blockHelperEndRE = /\{\{\/(translate)\}\}/;
14 | // Function helpers: {{ helper value }} or {{ helper "some string" }}
15 | var singleQuotedStringWithEscapes = "'(([^']*?(\\\\')?)+)'";
16 | var doubleQuotedStringWithEscapes = '"(([^"]*?(\\\\")?)+)"';
17 | var funcHelperRE = new RegExp("\\{\\{\\s*(\\w+)\\s+((\\w+)|(" +
18 | singleQuotedStringWithEscapes + ")|(" +
19 | doubleQuotedStringWithEscapes + "))\\s*\\}\\}");
20 |
21 | // an unescaped double quote starts with anything but an escape (\) or nothing
22 | var unescapedDoubleQuote = /([^\\])"|^"/g;
23 |
24 | var buf = [];
25 | var tempBuf;
26 | var match = null;
27 |
28 | function addComment(comment) {
29 | buf.push("//");
30 | buf.push(comment);
31 | buf.push("\n");
32 | }
33 |
34 | function escapeQuotes(str) {
35 | return str.replace(unescapedDoubleQuote, function (match, group) {
36 | var prefix = group || '';
37 | return prefix + '\\"';
38 | });
39 | }
40 |
41 |
42 | while (str.length) {
43 | // Find the earliest match of any type of tag in the string.
44 | match = str.match(shortCommentRE);
45 | if (match) {
46 | match.type = 'comment';
47 | }
48 | var nextMatch = str.match(longCommentRE);
49 | if (nextMatch) {
50 | if (!match || nextMatch.index < match.index) {
51 | match = nextMatch;
52 | match.type = 'comment';
53 | }
54 | }
55 | nextMatch = str.match(blockHelperStartRE);
56 | if (nextMatch) {
57 | if (!match || nextMatch.index < match.index) {
58 | match = nextMatch;
59 | match.type = 'block';
60 | }
61 | }
62 | nextMatch = str.match(funcHelperRE);
63 | if (nextMatch) {
64 | if (!match || nextMatch.index < match.index) {
65 | match = nextMatch;
66 | match.type = 'func';
67 | }
68 | }
69 | if (!match) {
70 | if (recurse) buf.push(str.replace(/\n/g, '\\n'));
71 | break;
72 | } else if (recurse) {
73 | buf.push(str.substring(0, match.index).replace(/\n/g, '\\n'));
74 | }
75 | str = str.substring(match.index + match[0].length);
76 |
77 |
78 | // Translate the match into an appropriate chunk of javascript.
79 | if (match.type === 'comment') {
80 | // Template comment => javascript comment
81 | match[1].split("\n").forEach(addComment);
82 | } else if (match.type === 'block') {
83 | if (recurse) buf.push('" + ');
84 | // Template block helper => javascript function call
85 | var helperName = match[1];
86 | buf.push(helperName);
87 | buf.push('("');
88 |
89 | tempBuf = [];
90 |
91 | var endMatch = str.match(blockHelperEndRE);
92 |
93 | while (endMatch && endMatch[1] !== helperName) {
94 | var skipTo = endMatch.index + endMatch[0].length;
95 | tempBuf.push(str.substring(0, skipTo));
96 | str = str.substring(skipTo);
97 | endMatch = str.match(blockHelperEndRE);
98 | }
99 |
100 | if (endMatch) {
101 | tempBuf.push(str.substring(0, endMatch.index));
102 | str = str.substring(endMatch.index + endMatch[0].length);
103 | } else {
104 | tempBuf.push(str);
105 | str = '';
106 | }
107 |
108 | var result = parseHandlebars(escapeQuotes(tempBuf.join('')), true);
109 | buf.push(result);
110 |
111 | buf.push('")\n');
112 | if (recurse) buf.push(' + "');
113 |
114 | } else if (match.type === 'func') {
115 | if (recurse) buf.push('" + ');
116 | // Template function helper => javascript function call
117 | buf.push(match[1]);
118 | buf.push('(');
119 | buf.push(match[2]);
120 | buf.push(')\n');
121 | if (recurse) buf.push(' + "');
122 | }
123 | }
124 |
125 | return buf.join('');
126 | }
127 |
128 | // generate extracted strings file from Handlebars/Mustache templates
129 | exports.handlebars = function Handlebars(hbSources, options) {
130 | Object.keys(hbSources).forEach(function (filename) {
131 | hbSources[filename] = parseHandlebars(hbSources[filename]);
132 | });
133 |
134 | return [hbSources, options];
135 | };
136 |
--------------------------------------------------------------------------------
/lib/jsxgettext.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 |
7 | var fs = require('fs');
8 | var path = require('path');
9 |
10 | var parser = require('acorn-jsx');
11 | var walk = require('acorn/dist/walk');
12 | var gettextParser = require('gettext-parser');
13 | var regExpEscape = require('escape-string-regexp');
14 |
15 | var walkBase = Object.assign({}, walk.base, {
16 | JSXElement: function (node, st, c) {
17 | var i;
18 |
19 | for (i = 0; i < node.openingElement.attributes.length; i++) {
20 | c(node.openingElement.attributes[i], st);
21 | }
22 |
23 | for (i = 0; i < node.children.length; i++) {
24 | c(node.children[i], st);
25 | }
26 | },
27 |
28 | JSXAttribute: function (node, st, c) {
29 | if (node.value.type === 'JSXExpressionContainer') {
30 | c(node.value, st);
31 | }
32 | },
33 |
34 | JSXSpreadAttribute: function (node, st, c) {
35 | c(node.argument, st);
36 | },
37 |
38 | JSXExpressionContainer: function (node, st, c) {
39 | c(node.expression, st);
40 | },
41 |
42 | JSXEmptyExpression: function () {}
43 | });
44 |
45 | function isStringLiteral(node) {
46 | return node.type === 'Literal' && (typeof node.value === 'string');
47 | }
48 |
49 | function isStrConcatExpr(node) {
50 | var left = node.left;
51 | var right = node.right;
52 |
53 | return node.type === "BinaryExpression" && node.operator === '+' && (
54 | (isStringLiteral(left) || isStrConcatExpr(left)) &&
55 | (isStringLiteral(right) || isStrConcatExpr(right))
56 | );
57 | }
58 |
59 | function getTranslatable(node, options) {
60 | // must be a call expression with arguments
61 | if (!node.arguments)
62 | return false;
63 |
64 | var callee = node.callee;
65 | var funcName = callee.name;
66 | var arg = node.arguments[0];
67 | var prop;
68 |
69 | if (!funcName) {
70 | if (callee.type !== 'MemberExpression')
71 | return false;
72 |
73 | // Special case for functionName.call calls
74 | if (callee.property.name === 'call') {
75 | prop = callee.object.property;
76 | funcName = callee.object.name || prop && (prop.name || prop.value);
77 | node.arguments = node.arguments.slice( 1 ); // skip context object
78 | arg = node.arguments[0];
79 | } else {
80 | funcName = callee.property.name;
81 | }
82 | }
83 |
84 | if (options.keyword.indexOf(funcName) === -1)
85 | return false;
86 |
87 | // If the gettext function's name starts with "n" (i.e. ngettext or n_) and its first 2 arguments are strings, we regard it as a plural function
88 | if (arg && funcName.substr(0, 1) === "n" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1])))
89 | return [arg, node.arguments[1]];
90 |
91 | if (arg && (isStrConcatExpr(arg) || isStringLiteral(arg)))
92 | return arg;
93 |
94 | if (options.sanity)
95 | throw new Error("Could not parse translatable: " + JSON.stringify(arg, null, 2));
96 | }
97 |
98 | // Assumes node is either a string Literal or a strConcatExpression
99 | function extractStr(node) {
100 | if (isStringLiteral(node))
101 | return node.value;
102 | else
103 | return extractStr(node.left) + extractStr(node.right);
104 | }
105 |
106 | function loadStrings(poFile) {
107 | try {
108 | return gettextParser.po.parse(fs.readFileSync(path.resolve(poFile)), "utf-8");
109 | } catch (e) {
110 | return null;
111 | }
112 | }
113 |
114 | function parse(sources, options) {
115 | var useExisting = options.joinExisting;
116 | var poJSON;
117 | if (useExisting)
118 | poJSON = loadStrings(path.resolve(path.join(options.outputDir || '', options.output)));
119 |
120 | if (!poJSON) {
121 | var headers = {
122 | "project-id-version": options.projectIdVersion || "PACKAGE VERSION",
123 | "language-team": "LANGUAGE ",
124 | "report-msgid-bugs-to": options.reportBugsTo,
125 | "po-revision-date": "YEAR-MO-DA HO:MI+ZONE",
126 | "language": "",
127 | "mime-version": "1.0",
128 | "content-type": "text/plain; charset=utf-8",
129 | "content-transfer-encoding": "8bit"
130 | };
131 |
132 | poJSON = {
133 | charset: "utf-8",
134 | headers: headers,
135 | translations: {'': {} }
136 | };
137 | }
138 |
139 | var translations;
140 |
141 | try {
142 | poJSON.headers["pot-creation-date"] = new Date().toISOString().replace('T', ' ').replace(/:\d{2}.\d{3}Z/, '+0000');
143 |
144 | // Always use the default context for now
145 | // TODO: Take into account different contexts
146 | translations = poJSON.translations[''];
147 | } catch (err) {
148 | if (useExisting)
149 | throw new Error("An error occurred while using the provided PO file. Please make sure it is valid by using `msgfmt -c`.");
150 | else
151 | throw err;
152 | }
153 |
154 | if( options.keyword ) {
155 | Object.keys(options.keyword).forEach(function (index) {
156 | options.keyword.push('n' + options.keyword[index]);
157 | });
158 | }
159 | else {
160 | options.keyword = ['gettext', 'ngettext'];
161 | }
162 | var tagName = options.addComments || "L10n:";
163 | var commentRegex = new RegExp([
164 | "^\\s*" + regExpEscape(tagName), // The "TAG" provided externally or "L10n:" by default
165 | "^\\/" // The "///" style comments which is the xgettext standard
166 | ].join("|"));
167 | Object.keys(sources).forEach(function (filename) {
168 | var source = sources[filename].replace(/^#.*/, ''); // strip leading hash-bang
169 | var astComments = [];
170 | var ast = parser.parse(source, {
171 | ecmaVersion: 6,
172 | sourceType: 'module',
173 | plugins: { jsx: { allowNamespaces: false } },
174 | onComment: function (block, text, start, end, line/*, column*/) {
175 | text = text.match(commentRegex) && text.replace(/^\//, '').trim();
176 |
177 | if (!text)
178 | return;
179 |
180 | astComments.push({
181 | line : line,
182 | value: text
183 | });
184 | },
185 | locations: true
186 | });
187 |
188 | // finds comments that end on the previous line
189 | function findComments(comments, line) {
190 | return comments.map(function (node) {
191 | var commentLine = node.line.line;
192 | if (commentLine === line || commentLine + 1 === line) {
193 | return node.value;
194 | }
195 | }).filter(Boolean).join('\n');
196 | }
197 |
198 | walk.simple(ast, {'CallExpression': function (node) {
199 | var arg = getTranslatable(node, options);
200 | if (!arg)
201 | return;
202 |
203 | var msgid = arg;
204 | if( arg.constructor === Array )
205 | msgid = arg[0];
206 | var str = extractStr(msgid);
207 | var line = node.loc.start.line;
208 | var comments = findComments(astComments, line);
209 |
210 | var ref = filename + ':' + line;
211 | if (!translations[str]) {
212 | translations[str] = {
213 | msgid: str,
214 | msgstr: [],
215 | comments: {
216 | extracted: comments,
217 | reference: ref
218 | }
219 | };
220 | if( arg.constructor === Array ) {
221 | translations[str].msgid_plural = extractStr(arg[1]);
222 | translations[str].msgstr = ['', ''];
223 | }
224 | } else {
225 | if(translations[str].comments) {
226 | translations[str].comments.reference += '\n' + ref;
227 | }
228 | if (comments)
229 | translations[str].comments.extracted += '\n' + comments;
230 | }
231 | }
232 | }, walkBase);
233 |
234 |
235 | function dedupeNCoalesce(item, i, arr) {
236 | return item && arr.indexOf(item) === i;
237 | }
238 |
239 | Object.keys(translations).forEach(function (msgid) {
240 | var comments = translations[msgid].comments;
241 |
242 | if (!comments)
243 | return;
244 |
245 | if (comments.reference)
246 | comments.reference = comments.reference.split('\n').filter(dedupeNCoalesce).join('\n');
247 | if (comments.extracted)
248 | comments.extracted = comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n');
249 | });
250 | });
251 |
252 | return poJSON;
253 | }
254 | exports.parse = parse;
255 |
256 | // generate extracted strings file
257 | function gen(sources, options) {
258 | return gettextParser.po.compile(parse(sources, options)).toString();
259 | }
260 |
261 | exports.generate = gen;
262 |
263 | // Backwards compatibility interface for 0.3.x - Deprecated!
264 | var parsers = require('./parsers');
265 |
266 | Object.keys(parsers).forEach(function (parser) {
267 | parser = parsers[parser];
268 | exports['generateFrom' + parser.name] = parser;
269 | });
270 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------