Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
57 changes: 48 additions & 9 deletions lib/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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], {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really hard to read. What exactly is happening here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rubenv It's not great I agree.
The complexity introduced arises from the need to abort the extraction if the user doesn't indicate a context for a msgid that has (or will) provide a context for the same msgid.
If in some place a msgctx is indicated for a msgid then all the msgid appearences must be accompanied by a msgctx.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cristiano2lopes Is that a feature of the gettext API? It seems very limiting to be forced to annotate every instance of a msgid with a context just because you want one instance contextualized. Is there something I'm missing that makes this necessary?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot back my decision based on the get text API (I haven't read it). The reference i read was:
"The context serves to disambiguate messages with the same untranslated-string. It is possible to have several entries with the same untranslated-string in a PO file, provided that they each have a different context. Note that an empty context string and an absent msgctxt line do not mean the same thing." from here.

You can't have duplicate msgid without a msgctx so you assume some default context (empty?) for the ones that don't provide a context or prevent it from happening. Also how to decide which translation to retrieve if one does not have a context?

I can be wrong (and this complicates the implementation) so maybe we should change it.

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(', ')) + ')');
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
});
Expand All @@ -189,14 +225,16 @@ var Extractor = (function () {
var str;
var plural;
var extractedComment;
var translateContext;
node = $(n);

for (var attr in node.attr()) {
if (attr === 'translate') {
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);
Expand All @@ -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);
}
});
Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 24 additions & 0 deletions test/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
81 changes: 79 additions & 2 deletions test/extract.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -143,14 +143,24 @@ 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 = [
'test/fixtures/multifilter.html'
]
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)
Expand All @@ -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 = [
Expand Down Expand Up @@ -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)
2 changes: 2 additions & 0 deletions test/fixtures/filter.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
<body>
<input type="text" placeholder="{{'Hello'|translate}}" />
<span>{{'Second'|translate}}</span>
<span>{{'Bird'|translate:'cage'}}</span>
<span>{{'Bird'|translate:'free'}}</span>
</body>
</html>
7 changes: 7 additions & 0 deletions test/fixtures/homonyms.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<body>
<h1 translate translate-context="male">Hello!</h1>
<p translate translate-context="female">Hello!</p>
<h2 translate translate-context="male">Hello!</h2>
</body>
</html>
8 changes: 8 additions & 0 deletions test/fixtures/homonyms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
angular.module("myApp").controller("helloController", function (gettext) {
gettext(); // Should be ignored.
var myStringContext1 = gettextCatalog.getString("Hello!", null, "male");
var myStringContext2 = gettextCatalog.getString("Hello!", null, "female");
var myString2Context1 = gettextCatalog.getPlural(3, "Bird", "Birds", null, "cage");
var myString2Context2 = gettextCatalog.getPlural(3, "Bird", "Birds", null, "free");

});
8 changes: 8 additions & 0 deletions test/fixtures/homonyms_corrupt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
angular.module("myApp").controller("helloController", function (gettext) {
gettext(); // Should be ignored.
var myString = gettextCatalog.getString("Hello!");
var myStringContext2 = gettextCatalog.getString("Hello!", null, "female");
var myString2Context1 = gettextCatalog.getPlural(3, "Bird", "Birds", null, "cage");
var myString2Context2 = gettextCatalog.getPlural(3, "Bird", "Birds", null, "free");

});
6 changes: 6 additions & 0 deletions test/fixtures/homonyms_corrupt_with_msgctxt_first.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<body>
<h1 translate translate-context="male">Hello!</h1>
<p translate>Hello!</p>
</body>
</html>
6 changes: 6 additions & 0 deletions test/fixtures/homonyms_corrupt_without_msgctxt_first.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<body>
<h1 translate>Hello!</h1>
<p translate translate-context="female">Hello!</p>
</body>
</html>
2 changes: 2 additions & 0 deletions test/fixtures/multifilter.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
<body>
<input type="text" placeholder="{{'Hello'|translate|lowercase}}" />
<span>{{'Second'|translate|lowercase}}</span>
<span>{{'Bird'|translate:'free'|lowercase}}</span>
<span>{{'Bird'|translate:'cage'|uppercase}}</span>
</body>
</html>
37 changes: 37 additions & 0 deletions test/fixtures/pt.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
msgid ""
msgstr ""
"Language: pt-BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n>1;\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"X-Generator: Poedit 1.5.7\n"

#: test/fixtures/homonyms.js
msgctxt "cage"
msgid "Bird"
msgid_plural "Birds"
msgstr[0] "Pássaro Oprimido"
msgstr[1] "Pássaros Oprimidos"

#: test/fixtures/homonyms.js
msgctxt "free"
msgid "Bird"
msgid_plural "Birds"
msgstr[0] "Pássaro Livre"
msgstr[1] "Pássaros Livres"

#: test/fixtures/homonyms.js
msgctxt "male"
msgid "Hello!"
msgstr "Olá!"

#: test/fixtures/homonyms.js
msgctxt "female"
msgid "Hello!"
msgstr "Olá Olá!"