Skip to content

Commit

Permalink
Added extracting plural forms using standard "n"-prefixed gettext fun…
Browse files Browse the repository at this point in the history
…ctions
  • Loading branch information
vrouet committed Jun 18, 2015
1 parent de3b22e commit a4216d9
Show file tree
Hide file tree
Showing 20 changed files with 128 additions and 15 deletions.
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -4,9 +4,13 @@ A node module with a CLI that extracts gettext strings from JavaScript, EJS, Jad

```javascript
gettext("Hello world!");
gettext("Hello" + ' world!');
gettext("Hello " + 'world!');
myModule.gettext("Hello " + 'world!');
gettext.call(myObj, "Hello " + 'world!');
ngettext("Here's an apple for you", "Here are %s apples for you", 3);
ngettext("Here's an apple" + ' for you', "Here are %s apples" + ' for you', 3);
myModule.ngettext("Here's an apple" + ' for you', "Here are %s apples" + ' for you', 3);
ngettext.call(myObj, "Here's an apple" + ' for you', "Here are %s apples" + ' for you', 3);
```

It also extracts comments that begin with "L10n:" when they appear above or next to a `gettext` call:
Expand Down
25 changes: 22 additions & 3 deletions lib/jsxgettext.js
Expand Up @@ -44,7 +44,8 @@ function getTranslatable(node, options) {
if (callee.property.name === 'call') {
prop = callee.object.property;
funcName = callee.object.name || prop && (prop.name || prop.value);
arg = node.arguments[1]; // skip context object
node.arguments = node.arguments.slice( 1 ); // skip context object
arg = node.arguments[0];
} else {
funcName = callee.property.name;
}
Expand All @@ -53,6 +54,10 @@ function getTranslatable(node, options) {
if (options.keyword.indexOf(funcName) === -1)
return false;

// 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
if (arg && funcName.substr(0, 1) === "n" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1])))
return [arg, node.arguments[1]];

if (arg && (isStrConcatExpr(arg) || isStringLiteral(arg)))
return arg;

Expand Down Expand Up @@ -116,7 +121,14 @@ function parse(sources, options) {
throw err;
}

options.keyword = options.keyword || ['gettext'];
if( options.keyword ) {
Object.keys(options.keyword).forEach(function (index) {
options.keyword.push('n' + options.keyword[index]);
});
}
else {
options.keyword = ['gettext', 'ngettext'];
}
var tagName = options.addComments || "L10n:";
var commentRegex = new RegExp([
"^\\s*" + regExpEscape(tagName), // The "TAG" provided externally or "L10n:" by default
Expand Down Expand Up @@ -156,7 +168,10 @@ function parse(sources, options) {
if (!arg)
return;

var str = extractStr(arg);
var msgid = arg;
if( arg.constructor === Array )
msgid = arg[0];
var str = extractStr(msgid);
var line = node.loc.start.line;
var comments = findComments(astComments, line);

Expand All @@ -170,6 +185,10 @@ function parse(sources, options) {
reference: ref
}
};
if( arg.constructor === Array ) {
translations[str].msgid_plural = extractStr(arg[1]);
translations[str].msgstr = ['', ''];
}
} else {
if(translations[str].comments) {
translations[str].comments.reference += '\n' + ref;
Expand Down
4 changes: 3 additions & 1 deletion test/inputs/anonymous_functions.js
Expand Up @@ -2,8 +2,10 @@

var testObj = {
somemethod: function () {},
gettext: function () {}
gettext: function () {},
ngettext: function () {}
};

testObj.somemethod('I shall not pass');
testObj.gettext("I'm gonna get translated, yay!");
testObj.ngettext("I'm also gonna get translated!", "I'm the plural form!", 2);
10 changes: 10 additions & 0 deletions test/inputs/concat.js
Expand Up @@ -3,3 +3,13 @@ gettext(
"and we'd like to concatenate it in our source "+
"code to avoid really wide files."
);

ngettext(
"This is another quite long sentence "+
"and we'd like to concatenate it in our source "+
"code to avoid really wide files.",
"This is the plural version of our quite long sentence "+
"that we'd like to concatenate it in our source "+
"code to avoid really wide files.",
3
);
5 changes: 4 additions & 1 deletion test/inputs/expressions.js
Expand Up @@ -3,7 +3,10 @@ var templates = {
landing: 'pin_verification',
subject: test.gettext("Confirm email address for Persona"),
subject2: test.gettext.call(test, "Confirm email address for Persona 2"),
subject3: test.something.someotherthing['gettext'].call(test, "Confirm email address for Persona 3", somethingelse)
subject3: test.something.someotherthing['gettext'].call(test, "Confirm email address for Persona 3", somethingelse),
subject4: test.ngettext("Confirm email address for Persona 4", "Confirm email address for Persona 4 plural", 4),
subject5: test.ngettext.call(test, "Confirm email address for Persona 5", "Confirm email address for Persona 5 plural", 5),
subject6: test.something.someotherthing['ngettext'].call(test, "Confirm email address for Persona 6", "Confirm email address for Persona 6 plural", 6, somethingelse)
}
};

Expand Down
1 change: 1 addition & 0 deletions test/inputs/filter.ejs
@@ -1 +1,2 @@
<%=: gettext("this is a localizable string") | capitalize %>
<%=: ngettext("this is a localizable singular string", "this is a localizable plural string", 2) | capitalize %>
3 changes: 2 additions & 1 deletion test/inputs/first.js
@@ -1,2 +1,3 @@
var msg = gettext('Hello World');
var dup = gettext('This message is used twice.');
var dup = gettext('This message is used twice.');
var dup2 = ngettext('This other message is used twice.', 'This other message is used twice. - plural', 2);
1 change: 1 addition & 0 deletions test/inputs/include.ejs
@@ -1,2 +1,3 @@
<% include this/include/syntax/is/kinda/dumb %>
<%= gettext("this is a localizable string") %>
<%= ngettext("this is a localizable singular string", "this is a localizable plural string", 2) %>
4 changes: 4 additions & 0 deletions test/inputs/multiple_keywords.js
@@ -1,3 +1,7 @@
_('should be translatable');
t('should also be translatable');
gettext('should NOT be translatable since we did not define it as keyword');

n_('should be translatable - singular', 'should be translatable - plural', 2);
nt('should also be translatable - singular', 'should also be translatable - plural', 2);
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);
2 changes: 2 additions & 0 deletions test/inputs/raw.ejs
@@ -1 +1,3 @@
<%== gettext("this is a raw localizable string") %>
<%== gettext("this is a raw localizable string") %>
<%== ngettext("this is a raw localizable singular string", "this is a raw localizable plural string", 2) %>
3 changes: 2 additions & 1 deletion test/inputs/second.js
@@ -1 +1,2 @@
var dup = gettext('This message is used twice.');
var dup = gettext('This message is used twice.');
var dup2 = ngettext('This other message is used twice.', 'This other message is used twice. - plural', 2);
10 changes: 8 additions & 2 deletions test/outputs/anonymous_functions.pot
@@ -1,3 +1,9 @@
#: inputs/anonymous_functions.js:9
#: inputs/anonymous_functions.js:10
msgid "I'm gonna get translated, yay!"
msgstr ""
msgstr ""

#: inputs/anonymous_functions.js:11
msgid "I'm also gonna get translated!"
msgid_plural "I'm the plural form!"
msgstr[0] ""
msgstr[1] ""
12 changes: 11 additions & 1 deletion test/outputs/concat.pot
Expand Up @@ -2,4 +2,14 @@
msgid ""
"The second string is significantly longer and we'd like to concatenate it "
"in our source code to avoid really wide files."
msgstr ""
msgstr ""

#: inputs/concat.js:7
msgid ""
"This is another quite long sentence and we'd like to concatenate it in our "
"source code to avoid really wide files."
msgid_plural ""
"This is the plural version of our quite long sentence that we'd like to "
"concatenate it in our source code to avoid really wide files."
msgstr[0] ""
msgstr[1] ""
20 changes: 19 additions & 1 deletion test/outputs/expressions.pot
Expand Up @@ -8,4 +8,22 @@ msgstr ""

#: inputs/expressions.js:6
msgid "Confirm email address for Persona 3"
msgstr ""
msgstr ""

#: inputs/expressions.js:7
msgid "Confirm email address for Persona 4"
msgid_plural "Confirm email address for Persona 4 plural"
msgstr[0] ""
msgstr[1] ""

#: inputs/expressions.js:8
msgid "Confirm email address for Persona 5"
msgid_plural "Confirm email address for Persona 5 plural"
msgstr[0] ""
msgstr[1] ""

#: inputs/expressions.js:9
msgid "Confirm email address for Persona 6"
msgid_plural "Confirm email address for Persona 6 plural"
msgstr[0] ""
msgstr[1] ""
8 changes: 7 additions & 1 deletion test/outputs/messages_firstpass.pot
Expand Up @@ -4,4 +4,10 @@ msgstr ""

#: inputs/first.js:2
msgid "This message is used twice."
msgstr ""
msgstr ""

#: inputs/first.js:3
msgid "This other message is used twice."
msgid_plural "This other message is used twice. - plural"
msgstr[0] ""
msgstr[1] ""
9 changes: 8 additions & 1 deletion test/outputs/messages_secondpass.pot
Expand Up @@ -5,4 +5,11 @@ msgstr ""
#: inputs/first.js:2
#: inputs/second.js:1
msgid "This message is used twice."
msgstr ""
msgstr ""

#: inputs/first.js:3
#: inputs/second.js:2
msgid "This other message is used twice."
msgid_plural "This other message is used twice. - plural"
msgstr[0] ""
msgstr[1] ""
14 changes: 13 additions & 1 deletion test/outputs/multiple_keywords.pot
Expand Up @@ -4,4 +4,16 @@ msgstr ""

#: inputs/multiple_keywords.js:2
msgid "should also be translatable"
msgstr ""
msgstr ""

#: inputs/multiple_keywords.js:5
msgid "should be translatable - singular"
msgid_plural "should be translatable - plural"
msgstr[0] ""
msgstr[1] ""

#: inputs/multiple_keywords.js:6
msgid "should also be translatable - singular"
msgid_plural "should also be translatable - plural"
msgstr[0] ""
msgstr[1] ""
2 changes: 2 additions & 0 deletions test/tests/ejs.js
Expand Up @@ -18,6 +18,8 @@ exports['test ejs'] = function (assert, cb) {
assert.ok(result.length > 1, 'result is not empty');
assert.ok(result.indexOf('this is a localizable string') !== -1,
'localizable strings are extracted');
assert.ok(result.indexOf('this is a localizable plural string') !== -1,
'localizable plural strings are extracted');
cb();
});
};
Expand Down
2 changes: 2 additions & 0 deletions test/tests/ejs_filter.js
Expand Up @@ -18,6 +18,8 @@ exports['test ejs'] = function (assert, cb) {
assert.ok(result.length > 1, 'result is not empty');
assert.ok(result.indexOf('this is a localizable string') !== -1,
'localizable strings are extracted');
assert.ok(result.indexOf('this is a localizable plural string') !== -1,
'localizable plural strings are extracted');
cb();
});
};
Expand Down
2 changes: 2 additions & 0 deletions test/tests/ejs_raw.js
Expand Up @@ -18,6 +18,8 @@ exports['test ejs'] = function (assert, cb) {
assert.ok(result.length > 1, 'raw result is not empty');
assert.ok(result.indexOf('this is a raw localizable string') !== -1,
'raw localizable strings are extracted');
assert.ok(result.indexOf('this is a raw localizable plural string') !== -1,
'raw localizable plural strings are extracted');
cb();
});
};
Expand Down

0 comments on commit a4216d9

Please sign in to comment.