From 806ecb36779362ee98c74c70ed33f9a608f7aa02 Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Fri, 7 Apr 2017 11:50:58 +0200 Subject: [PATCH 1/7] Context implemenation and some test cases https://github.com/zaach/jsxgettext/issues/95 --- lib/jsxgettext.js | 84 ++++++++++++++++++---------- test/inputs/anonymous_functions.js | 6 +- test/inputs/example.swig | 2 +- test/inputs/filter.ejs | 5 ++ test/inputs/include.ejs | 5 ++ test/inputs/raw.ejs | 6 ++ test/outputs/anonymous_functions.pot | 24 ++++++++ test/tests/ejs.js | 6 ++ test/tests/ejs_filter.js | 6 ++ test/tests/ejs_raw.js | 6 ++ test/utils.js | 10 +++- 11 files changed, 126 insertions(+), 34 deletions(-) diff --git a/lib/jsxgettext.js b/lib/jsxgettext.js index 4f08cb5..d37ac67 100644 --- a/lib/jsxgettext.js +++ b/lib/jsxgettext.js @@ -83,13 +83,22 @@ function getTranslatable(node, options) { if (options.keyword.indexOf(funcName) === -1) return false; + + // Always add the funcName as last element in return array + // If the gettext function's name starts with "np" (i.e. npgettext or np_) and its 3 arguments are strings, we regard it as context + if (arg && funcName.substr(0, 2) === "np" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1])) && node.arguments[2] && (isStrConcatExpr(node.arguments[2]) || isStringLiteral(node.arguments[2]))) + return [arg, node.arguments[1], node.arguments[2], funcName]; // 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]]; - + return [arg, node.arguments[1], funcName]; + + // If the gettext function's name starts with "p" (i.e. pgettext or p_) and its 2 arguments are strings, we regard it as context + if (arg && funcName.substr(0, 1) === "p" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1]))) + return [arg, node.arguments[1], funcName]; + if (arg && (isStrConcatExpr(arg) || isStringLiteral(arg))) - return arg; + return [arg, funcName]; if (options.sanity) throw new Error("Could not parse translatable: " + JSON.stringify(arg, null, 2)); @@ -143,7 +152,7 @@ function parse(sources, options) { // Always use the default context for now // TODO: Take into account different contexts - translations = poJSON.translations['']; + translations = poJSON.translations; } catch (err) { if (useExisting) throw new Error("An error occurred while using the provided PO file. Please make sure it is valid by using `msgfmt -c`."); @@ -157,7 +166,7 @@ function parse(sources, options) { }); } else { - options.keyword = ['gettext', 'ngettext']; + options.keyword = ['gettext', 'ngettext', 'pgettext', 'npgettext']; } var tagName = options.addComments || "L10n:"; var commentRegex = new RegExp([ @@ -197,37 +206,52 @@ function parse(sources, options) { } walk.simple(ast, {'CallExpression': function (node) { - var arg = getTranslatable(node, options); - if (!arg) + var args = getTranslatable(node, options); + if (!args) return; - + var funcName = args.pop(); + // If args.length = 1, it is either gettext or pgettext + var arg = args.length === 1 ? args[0] : args; + var msgCtxt = ""; var msgid = arg; - if( arg.constructor === Array ) - msgid = arg[0]; + if((funcName.substr(0, 1) === "p" || funcName.substr(0, 2) === "np") && arg.constructor === Array){ + msgCtxt = extractStr(arg[0]); + msgid = arg[1]; + } + else if( funcName.substr(0, 1) === "n" && arg.constructor === Array ) + msgid = arg[0]; var str = extractStr(msgid); var line = node.loc.start.line; var comments = findComments(astComments, line); var ref = filename + ':' + line; - if (!translations[str]) { - translations[str] = { + if(!translations[msgCtxt]) + translations[msgCtxt] = {}; + if(!translations[msgCtxt][str]){ + translations[msgCtxt][str] = { msgid: str, msgstr: [], comments: { extracted: comments, 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; + if(funcName.substr(0, 2) === "np" && arg.constructor === Array){ + translations[msgCtxt][str].msgctxt = msgCtxt; + translations[msgCtxt][str].msgid_plural = extractStr(arg[2]); + translations[msgCtxt][str].msgstr = ['', '']; + } else if( funcName.substr(0, 1) === "n" && arg.constructor === Array ) { + translations[msgCtxt][str].msgid_plural = extractStr(arg[1]); + translations[msgCtxt][str].msgstr = ['', '']; + } else if(funcName.substr(0, 1) === "p" && arg.constructor === Array){ + translations[msgCtxt][str].msgctxt = msgCtxt; + translations[msgCtxt][str].msgstr = ['', '']; } + } else { + if(translations[msgCtxt][str].comments) + translations[msgCtxt][str].comments.reference += '\n' + ref; if (comments) - translations[str].comments.extracted += '\n' + comments; + translations[msgCtxt][str].comments.extracted += '\n' + comments; } } }, walkBase); @@ -237,16 +261,16 @@ function parse(sources, options) { return item && arr.indexOf(item) === i; } - Object.keys(translations).forEach(function (msgid) { - var comments = translations[msgid].comments; - - if (!comments) - return; - - if (comments.reference) - comments.reference = comments.reference.split('\n').filter(dedupeNCoalesce).join('\n'); - if (comments.extracted) - comments.extracted = comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n'); + Object.keys(translations).forEach(function (msgctxt) { + Object.keys(translations[msgctxt]).forEach(function (msgid){ + var comments = translations[msgctxt][msgid].comments; + if (!comments) + return; + if (comments.reference) + comments.reference = comments.reference.split('\n').filter(dedupeNCoalesce).join('\n'); + if (comments.extracted) + comments.extracted = comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n'); + }); }); }); diff --git a/test/inputs/anonymous_functions.js b/test/inputs/anonymous_functions.js index 1e31187..f191365 100644 --- a/test/inputs/anonymous_functions.js +++ b/test/inputs/anonymous_functions.js @@ -7,5 +7,9 @@ var testObj = { }; testObj.somemethod('I shall not pass'); -testObj.gettext("I'm gonna get translated, yay!"); +testObj.pgettext("I'm gonna get translated, yay!"); testObj.ngettext("I'm also gonna get translated!", "I'm the plural form!", 2); +testObj.pgettext("context1", "I am translated in context!"); +testObj.pgettext("context2", "I am translated in context!"); +testObj.npgettext("context3", "I am also translated in context!", "I'm the plural form!", 2); +testObj.npgettext("context4", "I am also translated in context!", "I'm the plural form!", 2); diff --git a/test/inputs/example.swig b/test/inputs/example.swig index 1f9f2d0..01ab121 100644 --- a/test/inputs/example.swig +++ b/test/inputs/example.swig @@ -8,7 +8,7 @@ foobar {% set foobar = gettext("Test gettext directly") %} {% set foobar = gettext("Test with additional params on new line", { - 'foo': 'bar' + 'foo': 'bar' }) %} {% blocktrans with name=name url=url %} diff --git a/test/inputs/filter.ejs b/test/inputs/filter.ejs index 292ea1c..4c6fc17 100644 --- a/test/inputs/filter.ejs +++ b/test/inputs/filter.ejs @@ -1,2 +1,7 @@ <%=: gettext("this is a localizable string") | capitalize %> <%=: ngettext("this is a localizable singular string", "this is a localizable plural string", 2) | capitalize %> +<%=: pgettext("context_1","this is a localizable string in context") | capitalize %> +<%=: npgettext("context_1", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) | capitalize %> +<%=: pgettext("context_2","this is a localizable string in context") | capitalize %> +<%=: npgettext("context_2", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) | capitalize %> + diff --git a/test/inputs/include.ejs b/test/inputs/include.ejs index de37201..74af5cc 100644 --- a/test/inputs/include.ejs +++ b/test/inputs/include.ejs @@ -1,3 +1,8 @@ <% include this/include/syntax/is/kinda/dumb %> +<%# gettext("this is a non localizable comment string") %> <%= gettext("this is a localizable string") %> <%= ngettext("this is a localizable singular string", "this is a localizable plural string", 2) %> +<%= pgettext("context_1","this is a localizable string in context") %> +<%= npgettext("context_1", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> +<%= pgettext("context_2","this is a localizable string in context") %> +<%= npgettext("context_2", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> \ No newline at end of file diff --git a/test/inputs/raw.ejs b/test/inputs/raw.ejs index de4b752..36ec48d 100644 --- a/test/inputs/raw.ejs +++ b/test/inputs/raw.ejs @@ -1,3 +1,9 @@ <%== 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) %> +<%=: pgettext("context_1","this is a localizable string in context") %> +<%=: npgettext("context_1", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> +<%=: pgettext("context_2","this is a localizable string in context") %> +<%=: npgettext("context_2", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> + + diff --git a/test/outputs/anonymous_functions.pot b/test/outputs/anonymous_functions.pot index dc9cf5b..0c7e8c5 100644 --- a/test/outputs/anonymous_functions.pot +++ b/test/outputs/anonymous_functions.pot @@ -6,4 +6,28 @@ msgstr "" msgid "I'm also gonna get translated!" msgid_plural "I'm the plural form!" msgstr[0] "" +msgstr[1] "" + +#: inputs/anonymous_functions.js:12 +msgctxt "context1" +msgid "I am translated in context!" +msgstr "" + +#: inputs/anonymous_functions.js:13 +msgctxt "context2" +msgid "I am translated in context!" +msgstr "" + +#: inputs/anonymous_functions.js:14 +msgctxt "context3" +msgid "I am also translated in context!" +msgid_plural "I'm the plural form!" +msgstr[0] "" +msgstr[1] "" + +#: inputs/anonymous_functions.js:15 +msgctxt "context4" +msgid "I am also translated in context!" +msgid_plural "I'm the plural form!" +msgstr[0] "" msgstr[1] "" \ No newline at end of file diff --git a/test/tests/ejs.js b/test/tests/ejs.js index 2cc6126..9bf9634 100644 --- a/test/tests/ejs.js +++ b/test/tests/ejs.js @@ -2,6 +2,7 @@ var fs = require('fs'); var path = require('path'); +var utils = require('../utils'); var jsxgettext = require('../../lib/jsxgettext'); var ejs = require('../../lib/parsers/ejs').ejs; @@ -20,6 +21,11 @@ exports['test ejs'] = function (assert, cb) { 'localizable strings are extracted'); assert.ok(result.indexOf('this is a localizable plural string') !== -1, 'localizable plural strings are extracted'); + var singleLineResult = utils.getSingleLineString(result); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 1 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 1 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_2"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 2 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 2 are extracted'); cb(); }); }; diff --git a/test/tests/ejs_filter.js b/test/tests/ejs_filter.js index 1227619..cad86ff 100644 --- a/test/tests/ejs_filter.js +++ b/test/tests/ejs_filter.js @@ -2,6 +2,7 @@ var fs = require('fs'); var path = require('path'); +var utils = require('../utils'); var jsxgettext = require('../../lib/jsxgettext'); var ejs = require('../../lib/parsers/ejs').ejs; @@ -20,6 +21,11 @@ exports['test ejs'] = function (assert, cb) { 'localizable strings are extracted'); assert.ok(result.indexOf('this is a localizable plural string') !== -1, 'localizable plural strings are extracted'); + var singleLineResult = utils.getSingleLineString(result); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 1 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 1 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_2"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 2 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 2 are extracted'); cb(); }); }; diff --git a/test/tests/ejs_raw.js b/test/tests/ejs_raw.js index 79d9b56..c19cd4e 100644 --- a/test/tests/ejs_raw.js +++ b/test/tests/ejs_raw.js @@ -2,6 +2,7 @@ var fs = require('fs'); var path = require('path'); +var utils = require('../utils'); var jsxgettext = require('../../lib/jsxgettext'); var ejs = require('../../lib/parsers/ejs').ejs; @@ -20,6 +21,11 @@ exports['test ejs'] = function (assert, cb) { 'raw localizable strings are extracted'); assert.ok(result.indexOf('this is a raw localizable plural string') !== -1, 'raw localizable plural strings are extracted'); + var singleLineResult = utils.getSingleLineString(result); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 1 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 1 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_2"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 2 are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 2 are extracted'); cb(); }); }; diff --git a/test/utils.js b/test/utils.js index 264c926..fbb4e50 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,14 +2,20 @@ var fs = require('fs'); -exports.compareResultWithFile = function (result, filePath, assert, cb, msg) { +var compareResultWithFile = function (result, filePath, assert, cb, msg) { // Ignore the header result = result.slice(result.indexOf('\n\n') + 2).trimRight(); fs.readFile(filePath, function (err, source) { var sourceContent = source.toString('utf8').trimRight(); - assert.equal(result, sourceContent, msg || 'Results match.'); + assert.equal(getSingleLineString(result), getSingleLineString(sourceContent), msg || 'Results match.'); cb(); }); }; +exports.compareResultWithFile = compareResultWithFile; + +var getSingleLineString = function(string){ + return string.replace(/[\r\n]*/g, "") +} +exports.getSingleLineString = getSingleLineString; From 6aacdf512fbb5359cf856ce50dcf24c0e1d091a3 Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Fri, 7 Apr 2017 12:31:27 +0200 Subject: [PATCH 2/7] Correction jshint errors --- lib/jsxgettext.js | 2 +- test/utils.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/jsxgettext.js b/lib/jsxgettext.js index d37ac67..5f6cf45 100644 --- a/lib/jsxgettext.js +++ b/lib/jsxgettext.js @@ -235,7 +235,7 @@ function parse(sources, options) { extracted: comments, reference: ref } - } + }; if(funcName.substr(0, 2) === "np" && arg.constructor === Array){ translations[msgCtxt][str].msgctxt = msgCtxt; translations[msgCtxt][str].msgid_plural = extractStr(arg[2]); diff --git a/test/utils.js b/test/utils.js index fbb4e50..f199ff2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,6 +2,11 @@ var fs = require('fs'); +var getSingleLineString = function(string){ + return string.replace(/[\r\n]*/g, ""); +}; +exports.getSingleLineString = getSingleLineString; + var compareResultWithFile = function (result, filePath, assert, cb, msg) { // Ignore the header result = result.slice(result.indexOf('\n\n') + 2).trimRight(); @@ -15,7 +20,4 @@ var compareResultWithFile = function (result, filePath, assert, cb, msg) { }; exports.compareResultWithFile = compareResultWithFile; -var getSingleLineString = function(string){ - return string.replace(/[\r\n]*/g, "") -} -exports.getSingleLineString = getSingleLineString; + From aff99ba9ddcd021dd674b8c64f9775d69d84a6a8 Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Fri, 7 Apr 2017 15:49:54 +0200 Subject: [PATCH 3/7] Adding ngettext, pgettext, npgettext as keyword options in cli --- lib/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli.js b/lib/cli.js index 39b5e53..cb7a7b1 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -26,7 +26,7 @@ function gen(sources) { var result; var lang = opts.language.toLowerCase(); if (!opts.keyword) { - opts.keyword = ['gettext']; + opts.keyword = ['gettext', 'ngettext', 'pgettext', 'npgettext']; } if (opts.keyword.indexOf('gettext') === -1) { // when called from the cli, gettext should always be one of the default keywords From e82823203604ca75fbd10070b907461587b0d3eb Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Wed, 12 Apr 2017 15:33:35 +0200 Subject: [PATCH 4/7] Implementing domain and context from https://github.com/zaach/jsxgettext/issues/95 - if domain is mentioned it is ignored as it is not used in extraction. --- lib/cli.js | 2 +- lib/jsxgettext.js | 9 ++++++++- test/inputs/include.ejs | 6 +++++- test/tests/ejs.js | 4 ++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index cb7a7b1..12f7513 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -26,7 +26,7 @@ function gen(sources) { var result; var lang = opts.language.toLowerCase(); if (!opts.keyword) { - opts.keyword = ['gettext', 'ngettext', 'pgettext', 'npgettext']; + opts.keyword = ['gettext', 'ngettext', 'pgettext', 'npgettext', 'dgettext', 'dngettext', 'dpgettext', 'dnpgettext']; } if (opts.keyword.indexOf('gettext') === -1) { // when called from the cli, gettext should always be one of the default keywords diff --git a/lib/jsxgettext.js b/lib/jsxgettext.js index 5f6cf45..1faaded 100644 --- a/lib/jsxgettext.js +++ b/lib/jsxgettext.js @@ -80,6 +80,13 @@ function getTranslatable(node, options) { funcName = callee.property.name; } } + // Domain is not used during extraction, so ignore it + if(funcName.substr(0, 1) === "d"){ + funcName = funcName.substr(1, funcName.length); + node.arguments.splice(0, 1); + arg = node.arguments[0]; + } + if (options.keyword.indexOf(funcName) === -1) return false; @@ -166,7 +173,7 @@ function parse(sources, options) { }); } else { - options.keyword = ['gettext', 'ngettext', 'pgettext', 'npgettext']; + options.keyword = ['gettext', 'ngettext', 'pgettext', 'npgettext', 'dgettext', 'dngettext', 'dpgettext', 'dnpgettext']; } var tagName = options.addComments || "L10n:"; var commentRegex = new RegExp([ diff --git a/test/inputs/include.ejs b/test/inputs/include.ejs index 74af5cc..75b86df 100644 --- a/test/inputs/include.ejs +++ b/test/inputs/include.ejs @@ -5,4 +5,8 @@ <%= pgettext("context_1","this is a localizable string in context") %> <%= npgettext("context_1", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> <%= pgettext("context_2","this is a localizable string in context") %> -<%= npgettext("context_2", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> \ No newline at end of file +<%= npgettext("context_2", "this is a localizable singular string in context", "this is a localizable plural string in context", 2) %> +<%= dgettext("domain", "this is a localizable string in domain") %> +<%= dngettext("domain", "this is a localizable singular string in domain", "this is a localizable plural string in domain", 2) %> +<%= dpgettext("domain", "context", "this is a localizable string in domain and context") %> +<%= dnpgettext("domain", "context", "this is a localizable singular string in domain and context", "this is a localizable plural string in domain and context", 2) %> \ No newline at end of file diff --git a/test/tests/ejs.js b/test/tests/ejs.js index 9bf9634..1892833 100644 --- a/test/tests/ejs.js +++ b/test/tests/ejs.js @@ -26,6 +26,10 @@ exports['test ejs'] = function (assert, cb) { assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 1 are extracted'); assert.ok(singleLineResult.indexOf('msgctxt "context_2"msgid "this is a localizable string in context"') !== -1, 'localizable string in context 2 are extracted'); assert.ok(singleLineResult.indexOf('msgctxt "context_1"msgid "this is a localizable singular string in context"msgid_plural "this is a localizable plural string in context"') !== -1, 'localizable plural strings in context 2 are extracted'); + assert.ok(singleLineResult.indexOf('this is a localizable string in domain') !== -1, 'localizable string in domain are extracted'); + assert.ok(singleLineResult.indexOf('this is a localizable singular string in domain') !== -1, 'localizable plural strings in domain are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context"msgid "this is a localizable string in domain and context"') !== -1, 'localizable string in domain and context are extracted'); + assert.ok(singleLineResult.indexOf('msgctxt "context"msgid "this is a localizable singular string in domain and context"') !== -1, 'localizable plural strings in domain and context are extracted'); cb(); }); }; From b0c55d61174b5a1802583c64ed714f2014691f8a Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Tue, 18 Apr 2017 16:15:48 +0200 Subject: [PATCH 5/7] Ignore domain functions after cheking if they are defined in options keyword --- lib/jsxgettext.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/jsxgettext.js b/lib/jsxgettext.js index 1faaded..245d002 100644 --- a/lib/jsxgettext.js +++ b/lib/jsxgettext.js @@ -80,6 +80,10 @@ function getTranslatable(node, options) { funcName = callee.property.name; } } + + if (options.keyword.indexOf(funcName) === -1) + return false; + // Domain is not used during extraction, so ignore it if(funcName.substr(0, 1) === "d"){ funcName = funcName.substr(1, funcName.length); @@ -87,10 +91,6 @@ function getTranslatable(node, options) { arg = node.arguments[0]; } - - if (options.keyword.indexOf(funcName) === -1) - return false; - // Always add the funcName as last element in return array // If the gettext function's name starts with "np" (i.e. npgettext or np_) and its 3 arguments are strings, we regard it as context if (arg && funcName.substr(0, 2) === "np" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1])) && node.arguments[2] && (isStrConcatExpr(node.arguments[2]) || isStringLiteral(node.arguments[2]))) From f8221578bc65a058a68dd60e2a6bc94360a09273 Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Mon, 26 Jun 2017 15:09:13 +0200 Subject: [PATCH 6/7] Implemented requested changes from the review --- lib/jsxgettext.js | 136 +++++++++++++++-------------- test/inputs/anonymous_functions.js | 2 +- 2 files changed, 71 insertions(+), 67 deletions(-) diff --git a/lib/jsxgettext.js b/lib/jsxgettext.js index 245d002..613444f 100644 --- a/lib/jsxgettext.js +++ b/lib/jsxgettext.js @@ -4,13 +4,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -var fs = require('fs'); +var fs = require('fs'); var path = require('path'); var parser = require('acorn-jsx'); var walk = require('acorn/dist/walk'); var gettextParser = require('gettext-parser'); -var regExpEscape = require('escape-string-regexp'); +var regExpEscape = require('escape-string-regexp'); var walkBase = Object.assign({}, walk.base, { JSXElement: function (node, st, c) { @@ -39,7 +39,7 @@ var walkBase = Object.assign({}, walk.base, { c(node.expression, st); }, - JSXEmptyExpression: function () {} + JSXEmptyExpression: function () { } }); function isStringLiteral(node) { @@ -51,11 +51,18 @@ function isStrConcatExpr(node) { var right = node.right; return node.type === "BinaryExpression" && node.operator === '+' && ( - (isStringLiteral(left) || isStrConcatExpr(left)) && - (isStringLiteral(right) || isStrConcatExpr(right)) + (isStringLiteral(left) || isStrConcatExpr(left)) && + (isStringLiteral(right) || isStrConcatExpr(right)) ); } +// check if the args (object or array of objects) are strings +function areArgsString(args) { + return args && [].concat(args).every(function (arg) { + return arg && (isStringLiteral(arg) || isStrConcatExpr(arg)) + }); +} + function getTranslatable(node, options) { // must be a call expression with arguments if (!node.arguments) @@ -74,7 +81,7 @@ function getTranslatable(node, options) { if (callee.property.name === 'call') { prop = callee.object.property; funcName = callee.object.name || prop && (prop.name || prop.value); - node.arguments = node.arguments.slice( 1 ); // skip context object + node.arguments = node.arguments.slice(1); // skip context object arg = node.arguments[0]; } else { funcName = callee.property.name; @@ -85,27 +92,29 @@ function getTranslatable(node, options) { return false; // Domain is not used during extraction, so ignore it - if(funcName.substr(0, 1) === "d"){ + if (funcName.substr(0, 1) === "d") { funcName = funcName.substr(1, funcName.length); node.arguments.splice(0, 1); arg = node.arguments[0]; } - // Always add the funcName as last element in return array - // If the gettext function's name starts with "np" (i.e. npgettext or np_) and its 3 arguments are strings, we regard it as context - if (arg && funcName.substr(0, 2) === "np" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1])) && node.arguments[2] && (isStrConcatExpr(node.arguments[2]) || isStringLiteral(node.arguments[2]))) - return [arg, node.arguments[1], node.arguments[2], funcName]; - - // 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], funcName]; - - // If the gettext function's name starts with "p" (i.e. pgettext or p_) and its 2 arguments are strings, we regard it as context - if (arg && funcName.substr(0, 1) === "p" && (isStrConcatExpr(arg) || isStringLiteral(arg)) && node.arguments[1] && (isStrConcatExpr(node.arguments[1]) || isStringLiteral(node.arguments[1]))) - return [arg, node.arguments[1], funcName]; - - if (arg && (isStrConcatExpr(arg) || isStringLiteral(arg))) - return [arg, funcName]; + // Always return array of context and translatables i.e. [context, textToTranslate1, textToTranslate2 ...] + // If the gettext function's name starts with "np" (i.e. npgettext or np_) and its 3 arguments are strings and last argument is a number, we regard it as context + // npgettext is mentioned in https://www.gnu.org/software/gettext/manual/gettext.html#Language-specific-options + if (funcName.substr(0, 2) === "np" && areArgsString(node.arguments.slice(0, 3))) + return [arg, node.arguments[1], node.arguments[2]]; + + // If the gettext function's name starts with "n" (i.e. ngettext or n_) and its first 2 arguments are strings and last argument is a number, we regard it as a plural function + if (funcName.substr(0, 1) === "n" && areArgsString(node.arguments.slice(0, 2))) + return [null, arg, node.arguments[1]]; + + // If the gettext function's name starts with "p" (i.e. pgettext or p_) and its 2 arguments are strings, we regard it as context + if (funcName.substr(0, 1) === "p" && areArgsString(node.arguments.slice(1))) + return [arg, node.arguments[1]]; + + // else it is gettext if its 1st argument is string + if (areArgsString(node.arguments[0])) + return [null, arg]; if (options.sanity) throw new Error("Could not parse translatable: " + JSON.stringify(arg, null, 2)); @@ -148,7 +157,7 @@ function parse(sources, options) { poJSON = { charset: "utf-8", headers: headers, - translations: {'': {'': {comments: {flag: 'fuzzy'}}} } + translations: { '': { '': { comments: { flag: 'fuzzy' } } } } }; } @@ -156,9 +165,6 @@ function parse(sources, options) { try { poJSON.headers["pot-creation-date"] = new Date().toISOString().replace('T', ' ').replace(/:\d{2}.\d{3}Z/, '+0000'); - - // Always use the default context for now - // TODO: Take into account different contexts translations = poJSON.translations; } catch (err) { if (useExisting) @@ -167,9 +173,11 @@ function parse(sources, options) { throw err; } - if( options.keyword ) { + if (options.keyword) { Object.keys(options.keyword).forEach(function (index) { - options.keyword.push('n' + options.keyword[index]); + ['n', 'p', 'np', 'd', 'dn', 'dp', 'dnp'].forEach(function (keyword) { + options.keyword.push(keyword + options.keyword[index]); + }); }); } else { @@ -181,7 +189,7 @@ function parse(sources, options) { "^\\/" // The "///" style comments which is the xgettext standard ].join("|")); Object.keys(sources).forEach(function (filename) { - var source = sources[filename].replace(/^#.*/, ''); // strip leading hash-bang + var source = sources[filename].replace(/^#.*/, ''); // strip leading hash-bang var astComments = []; var parserOptions = Object.assign({}, { ecmaVersion: 6, @@ -194,13 +202,13 @@ function parse(sources, options) { return; astComments.push({ - line : line, + line: line, value: text }); }, locations: true }, options.parserOptions && JSON.parse(options.parserOptions)); - var ast = parser.parse(source, parserOptions); + var ast = parser.parse(source, parserOptions); // finds comments that end on the previous line function findComments(comments, line) { @@ -212,29 +220,21 @@ function parse(sources, options) { }).filter(Boolean).join('\n'); } - walk.simple(ast, {'CallExpression': function (node) { + walk.simple(ast, { + 'CallExpression': function (node) { var args = getTranslatable(node, options); if (!args) return; - var funcName = args.pop(); - // If args.length = 1, it is either gettext or pgettext - var arg = args.length === 1 ? args[0] : args; - var msgCtxt = ""; - var msgid = arg; - if((funcName.substr(0, 1) === "p" || funcName.substr(0, 2) === "np") && arg.constructor === Array){ - msgCtxt = extractStr(arg[0]); - msgid = arg[1]; - } - else if( funcName.substr(0, 1) === "n" && arg.constructor === Array ) - msgid = arg[0]; + var msgCtxt = args[0] ? extractStr(args[0]) : ""; + var msgid = args[1]; var str = extractStr(msgid); var line = node.loc.start.line; var comments = findComments(astComments, line); var ref = filename + ':' + line; - if(!translations[msgCtxt]) + if (!translations[msgCtxt]) translations[msgCtxt] = {}; - if(!translations[msgCtxt][str]){ + if (!translations[msgCtxt][str]) { translations[msgCtxt][str] = { msgid: str, msgstr: [], @@ -243,20 +243,23 @@ function parse(sources, options) { reference: ref } }; - if(funcName.substr(0, 2) === "np" && arg.constructor === Array){ - translations[msgCtxt][str].msgctxt = msgCtxt; - translations[msgCtxt][str].msgid_plural = extractStr(arg[2]); - translations[msgCtxt][str].msgstr = ['', '']; - } else if( funcName.substr(0, 1) === "n" && arg.constructor === Array ) { - translations[msgCtxt][str].msgid_plural = extractStr(arg[1]); - translations[msgCtxt][str].msgstr = ['', '']; - } else if(funcName.substr(0, 1) === "p" && arg.constructor === Array){ - translations[msgCtxt][str].msgctxt = msgCtxt; - translations[msgCtxt][str].msgstr = ['', '']; + // if it's npgettext + if (args.length === 3 && args[0]) { + translations[msgCtxt][str].msgctxt = msgCtxt; + translations[msgCtxt][str].msgid_plural = extractStr(args[2]); + translations[msgCtxt][str].msgstr = ['', '']; + } else if (args.length === 3 && !args[0]) { + // if it's ngettext + translations[msgCtxt][str].msgid_plural = extractStr(args[2]); + translations[msgCtxt][str].msgstr = ['', '']; + } else if (args.length === 2 && args[0]) { + // if it's pgettext + translations[msgCtxt][str].msgctxt = msgCtxt; + translations[msgCtxt][str].msgstr = ['', '']; } } else { - if(translations[msgCtxt][str].comments) - translations[msgCtxt][str].comments.reference += '\n' + ref; + if (translations[msgCtxt][str].comments) + translations[msgCtxt][str].comments.reference += '\n' + ref; if (comments) translations[msgCtxt][str].comments.extracted += '\n' + comments; } @@ -268,16 +271,17 @@ function parse(sources, options) { return item && arr.indexOf(item) === i; } + function extractComments(msgid) { + if (!this[msgid].comments) + return; + if (this[msgid].comments.reference) + this[msgid].comments.reference = this[msgid].comments.reference.split('\n').filter(dedupeNCoalesce).join('\n'); + if (this[msgid].comments.extracted) + this[msgid].comments.extracted = this[msgid].comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n'); + } + Object.keys(translations).forEach(function (msgctxt) { - Object.keys(translations[msgctxt]).forEach(function (msgid){ - var comments = translations[msgctxt][msgid].comments; - if (!comments) - return; - if (comments.reference) - comments.reference = comments.reference.split('\n').filter(dedupeNCoalesce).join('\n'); - if (comments.extracted) - comments.extracted = comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n'); - }); + Object.keys(translations[msgctxt]).forEach(extractComments, translations[msgctxt]); }); }); diff --git a/test/inputs/anonymous_functions.js b/test/inputs/anonymous_functions.js index f191365..33ed32e 100644 --- a/test/inputs/anonymous_functions.js +++ b/test/inputs/anonymous_functions.js @@ -7,7 +7,7 @@ var testObj = { }; testObj.somemethod('I shall not pass'); -testObj.pgettext("I'm gonna get translated, yay!"); +testObj.gettext("I'm gonna get translated, yay!"); testObj.ngettext("I'm also gonna get translated!", "I'm the plural form!", 2); testObj.pgettext("context1", "I am translated in context!"); testObj.pgettext("context2", "I am translated in context!"); From a9041971cd662c9f8cbb4415521320b95c43e879 Mon Sep 17 00:00:00 2001 From: Sameer KC Date: Mon, 26 Jun 2017 15:59:11 +0200 Subject: [PATCH 7/7] Correcting jshint errors --- lib/jsxgettext.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/jsxgettext.js b/lib/jsxgettext.js index 613444f..5c2441c 100644 --- a/lib/jsxgettext.js +++ b/lib/jsxgettext.js @@ -59,7 +59,7 @@ function isStrConcatExpr(node) { // check if the args (object or array of objects) are strings function areArgsString(args) { return args && [].concat(args).every(function (arg) { - return arg && (isStringLiteral(arg) || isStrConcatExpr(arg)) + return arg && (isStringLiteral(arg) || isStrConcatExpr(arg)); }); } @@ -271,17 +271,18 @@ function parse(sources, options) { return item && arr.indexOf(item) === i; } - function extractComments(msgid) { - if (!this[msgid].comments) + function extractComments(msgctxt, msgid) { + var comments = translations[msgctxt][msgid].comments; + if (!comments) return; - if (this[msgid].comments.reference) - this[msgid].comments.reference = this[msgid].comments.reference.split('\n').filter(dedupeNCoalesce).join('\n'); - if (this[msgid].comments.extracted) - this[msgid].comments.extracted = this[msgid].comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n'); + if (comments.reference) + comments.reference = comments.reference.split('\n').filter(dedupeNCoalesce).join('\n'); + if (comments.extracted) + comments.extracted = comments.extracted.split('\n').filter(dedupeNCoalesce).join('\n'); } Object.keys(translations).forEach(function (msgctxt) { - Object.keys(translations[msgctxt]).forEach(extractComments, translations[msgctxt]); + Object.keys(translations[msgctxt]).forEach(extractComments.bind(null, msgctxt)); }); });