diff --git a/lib/compile.js b/lib/compile.js index 1a7bc28..d995cab 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -71,7 +71,21 @@ var Compiler = (function () { for (var i = 0; i < catalog.items.length; i++) { var item = catalog.items[i]; if (item.msgstr[0].length > 0 && !item.flags.fuzzy) { - strings[item.msgid] = item.msgstr.length === 1 ? item.msgstr[0] : item.msgstr; + var translation = item.msgstr.length === 1 ? item.msgstr[0] : item.msgstr; + if (item.msgctxt || + _.isPlainObject(strings[item.msgid])){ + var translationContext = {}; + if (item.msgctxt){ + translationContext[item.msgctxt] = _.isArray(translation) ? translation : [translation]; + } + if (_.isPlainObject(strings[item.msgid])){ + strings[item.msgid] = _.extend(strings[item.msgid], translationContext); + } else { + strings[item.msgid] = translationContext; + } + } else { + strings[item.msgid] = translation; + } } } diff --git a/lib/extract.js b/lib/extract.js index a5f26f4..7397c44 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -70,18 +70,47 @@ var Extractor = (function () { Extractor.mkAttrRegex = mkAttrRegex; - Extractor.prototype.addString = function (file, string, plural, extractedComment) { + Extractor.prototype.addString = function (file, string, plural, extractedComment, translateContext) { string = string.trim(); - - if (!this.strings[string]) { - this.strings[string] = new Po.Item(); + if (translateContext){ + translateContext = translateContext.trim(); + } + var item; + + var missingContextError = false; + if (_.isEmpty(this.strings[string])){ + this.strings[string] = [new Po.Item()]; + } else { + if (translateContext){ + if (_.all(this.strings[string], 'msgctxt')){ + item = _.head(_.remove(this.strings[string], { + msgid: string, + msgctxt: translateContext + })) || new Po.Item(); + this.strings[string].push(item); + } else { + missingContextError = true; + } + } else { + if (_.any(this.strings[string], 'msgctxt')){ + missingContextError = true; + } + } } - var item = this.strings[string]; + item = _.last(this.strings[string]); item.msgid = string; + item.msgctxt = translateContext; if (item.references.indexOf(file) < 0) { item.references.push(file); } + if (missingContextError){ + var errorMessage = 'Found, for the same msgid '; + errorMessage += string; + errorMessage += ', translations with and without msgctxt'; + errorMessage += ' (in: ' + item.references.join(', '); + throw new Error(errorMessage); + } if (plural && plural !== '') { if (item.msgid_plural && item.msgid_plural !== plural) { throw new Error('Incompatible plural definitions for ' + string + ': ' + item.msgid_plural + ' / ' + plural + ' (in: ' + (item.references.join(', ')) + ')'); @@ -150,11 +179,18 @@ var Extractor = (function () { var singular; var plural; var extractedComments = []; + var translateContext; if (isGettext(node) || isGetString(node)) { str = getJSExpression(node.arguments[0]); + if (node.arguments.length === 3) { + translateContext = getJSExpression(node.arguments[2]); + } } else if (isGetPlural(node)) { singular = getJSExpression(node.arguments[1]); plural = getJSExpression(node.arguments[2]); + if (node.arguments.length === 5) { + translateContext = getJSExpression(node.arguments[4]); + } } if (str || singular) { if (parentComment) { @@ -165,9 +201,9 @@ var Extractor = (function () { }); } if (str) { - self.addString(filename, str, plural, extractedComments); + self.addString(filename, str, plural, extractedComments, translateContext); } else if (singular) { - self.addString(filename, singular, plural, extractedComments); + self.addString(filename, singular, plural, extractedComments, translateContext); } } }); @@ -189,6 +225,7 @@ var Extractor = (function () { var str; var plural; var extractedComment; + var translateContext; node = $(n); for (var attr in node.attr()) { @@ -196,7 +233,8 @@ var Extractor = (function () { str = node.html(); plural = node.attr('translate-plural'); extractedComment = node.attr('translate-comment'); - self.addString(filename, str, plural, extractedComment); + translateContext = node.attr('translate-context'); + self.addString(filename, str, plural, extractedComment, translateContext); } else if (matches = noDelimRegex.exec(node.attr(attr))) { str = matches[2].replace(/\\\'/g, '\''); self.addString(filename, str); @@ -206,6 +244,7 @@ var Extractor = (function () { if (typeof node.attr('data-translate') !== 'undefined') { str = node.html(); plural = node.attr('data-translate-plural'); + translateContext = node.attr('data-translate-context'); self.addString(filename, str, plural); } }); @@ -241,7 +280,7 @@ var Extractor = (function () { }; for (var key in this.strings) { - catalog.items.push(this.strings[key]); + catalog.items.push.apply(catalog.items, this.strings[key]); } catalog.items.sort(function (a, b) { diff --git a/test/compile.js b/test/compile.js index ff18341..b0f9358 100644 --- a/test/compile.js +++ b/test/compile.js @@ -73,6 +73,30 @@ describe('Compile', function () { assert(catalog.called); }); + it('Adds objects to the catalog if translations have a msgctxt', function () { + var files = ['test/fixtures/pt.po']; + var output = testCompile(files); + console.log(output); + var catalog = { + called: false, + setStrings: function (language, strings) { + this.called = true; + assert.equal(language, 'pt-BR'); + assert.deepEqual(strings['Hello!'], { + male:['Olá!'], + female:['Olá Olá!'] + }); + assert.deepEqual(strings.Bird, { + cage:['Pássaro Oprimido', 'Pássaros Oprimidos'], + free:['Pássaro Livre', 'Pássaros Livres'] + }); + } + }; + + var context = vm.createContext(makeEnv('gettext', catalog)); + vm.runInContext(output, context); + }); + it('Accepts a module parameter', function () { var files = ['test/fixtures/nl.po']; var output = testCompile(files, { diff --git a/test/extract.coffee b/test/extract.coffee index 100ad60..63f6003 100644 --- a/test/extract.coffee +++ b/test/extract.coffee @@ -133,7 +133,7 @@ describe 'Extract', -> ] catalog = testExtract(files) - assert.equal(catalog.items.length, 2) + assert.equal(catalog.items.length, 4) assert.equal(catalog.items[0].msgid, 'Hello') assert.equal(catalog.items[0].msgstr, '') assert.equal(catalog.items[0].references.length, 1) @@ -143,6 +143,16 @@ describe 'Extract', -> assert.equal(catalog.items[1].msgstr, '') assert.equal(catalog.items[1].references.length, 1) assert.equal(catalog.items[1].references[0], 'test/fixtures/filter.html') + assert.equal(catalog.items[2].msgid, 'Bird') + assert.equal(catalog.items[2].msgstr, '') + assert.equal(catalog.items[2].msgctxt, 'cage') + assert.equal(catalog.items[2].references.length, 1) + assert.equal(catalog.items[2].references[0], 'test/fixtures/filter.html') + assert.equal(catalog.items[3].msgid, 'Bird') + assert.equal(catalog.items[3].msgstr, '') + assert.equal(catalog.items[3].msgctxt, 'free') + assert.equal(catalog.items[3].references.length, 1) + assert.equal(catalog.items[2].references[0], 'test/fixtures/filter.html') it 'Extracts concatenated filter strings', -> files = [ @@ -150,7 +160,7 @@ describe 'Extract', -> ] catalog = testExtract(files) - assert.equal(catalog.items.length, 2) + assert.equal(catalog.items.length, 4) assert.equal(catalog.items[0].msgid, 'Hello') assert.equal(catalog.items[0].msgstr, '') assert.equal(catalog.items[0].references.length, 1) @@ -160,6 +170,16 @@ describe 'Extract', -> assert.equal(catalog.items[1].msgstr, '') assert.equal(catalog.items[1].references.length, 1) assert.equal(catalog.items[1].references[0], 'test/fixtures/multifilter.html') + assert.equal(catalog.items[2].msgid, 'Bird') + assert.equal(catalog.items[2].msgstr, '') + assert.equal(catalog.items[2].msgctxt, 'cage') + assert.equal(catalog.items[2].references.length, 1) + assert.equal(catalog.items[2].references[0], 'test/fixtures/multifilter.html') + assert.equal(catalog.items[3].msgid, 'Bird') + assert.equal(catalog.items[3].msgstr, '') + assert.equal(catalog.items[3].msgctxt, 'free') + assert.equal(catalog.items[3].references.length, 1) + assert.equal(catalog.items[2].references[0], 'test/fixtures/multifilter.html') it 'Extracts flagged strings from JavaScript source', -> files = [ @@ -476,3 +496,60 @@ describe 'Extract', -> assert.equal(catalog.items[0].msgstr, '') assert.equal(catalog.items[0].references.length, 1) assert.equal(catalog.items[0].references[0], 'test/fixtures/tapestry.tml') + + it 'Can extract context aware translations from html', () -> + files = [ + 'test/fixtures/homonyms.html' + ] + catalog = testExtract(files) + + assert.equal(catalog.items.length, 2) + assert.equal(catalog.items[1].msgid, 'Hello!') + assert.equal(catalog.items[1].msgstr, '') + assert.equal(catalog.items[1].msgctxt, 'male') + assert.equal(catalog.items[1].references.length, 1) + assert.equal(catalog.items[1].references[0], 'test/fixtures/homonyms.html') + assert.equal(catalog.items[0].msgid, 'Hello!') + assert.equal(catalog.items[0].msgstr, '') + assert.equal(catalog.items[0].msgctxt, 'female') + assert.equal(catalog.items[0].references.length, 1) + assert.equal(catalog.items[0].references[0], 'test/fixtures/homonyms.html') + + it 'Can extract context aware translations from javascript', () -> + files = [ + 'test/fixtures/homonyms.js' + ] + catalog = testExtract(files) + + assert.equal(catalog.items.length, 4) + assert.equal(catalog.items[0].msgid, 'Bird') + assert.equal(catalog.items[0].msgid_plural, 'Birds') + assert.equal(catalog.items[0].msgstr.length, 2) + assert.equal(catalog.items[0].msgctxt, 'cage') + assert.equal(catalog.items[0].references.length, 1) + assert.equal(catalog.items[0].references[0], 'test/fixtures/homonyms.js') + assert.equal(catalog.items[1].msgid, 'Bird') + assert.equal(catalog.items[1].msgid_plural, 'Birds') + assert.equal(catalog.items[1].msgstr.length, 2) + assert.equal(catalog.items[1].msgctxt, 'free') + assert.equal(catalog.items[1].references.length, 1) + assert.equal(catalog.items[1].references[0], 'test/fixtures/homonyms.js') + assert.equal(catalog.items[2].msgid, 'Hello!') + assert.equal(catalog.items[2].msgstr, '') + assert.equal(catalog.items[2].msgctxt, 'male') + assert.equal(catalog.items[2].references.length, 1) + assert.equal(catalog.items[2].references[0], 'test/fixtures/homonyms.js') + assert.equal(catalog.items[3].msgid, 'Hello!') + assert.equal(catalog.items[3].msgstr, '') + assert.equal(catalog.items[3].msgctxt, 'female') + assert.equal(catalog.items[3].references.length, 1) + assert.equal(catalog.items[3].references[0], 'test/fixtures/homonyms.js') + + it 'Warns for msgid with and without msgctxt in the same code base', -> + files = [ + 'test/fixtures/homonyms_corrupt_with_msgctxt_first.html', + 'test/fixtures/homonyms_corrupt_without_msgctxt_first.html', + 'test/fixtures/homonyms_corrupt.js', + ] + assert.throws -> + testExtract(files) diff --git a/test/fixtures/filter.html b/test/fixtures/filter.html index 08fae33..8354868 100644 --- a/test/fixtures/filter.html +++ b/test/fixtures/filter.html @@ -2,5 +2,7 @@
{{'Second'|translate}} + {{'Bird'|translate:'cage'}} + {{'Bird'|translate:'free'}}