From 1ed25b8831b5997eac9543562b236785672a05ae Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Tue, 10 Nov 2015 18:03:39 +0200 Subject: [PATCH 01/11] Implement c# support to extract from .cs files the properties marked with _translate_ sufix and implement Translations.cs file generation containing a dictionary with all the translations. Translations.cs is generated when running the compile command and you can use it in c# by calling Translations.Translate(key) method --- lib/compile.js | 64 ++++++++++++++++++++++++++++++++++++-- lib/extract.js | 35 +++++++++++++++++++-- test/extract_csharp.js | 21 +++++++++++++ test/fixtures/Constants.cs | 30 ++++++++++++++++++ 4 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 test/extract_csharp.js create mode 100644 test/fixtures/Constants.cs diff --git a/lib/compile.js b/lib/compile.js index 6d3e426..b1f5333 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -43,6 +43,50 @@ var formats = { }); return JSON.stringify(result); } + }, + csharp: { + addLocale: function (locale, strings) { + var result = 'var dictionary' + locale + ' = new Dictionary();\n'; + for (var key in strings) { + result += 'dictionary' + locale + '.Add("' + key + '","' + strings[key] + '");\n'; + } + result += '_translations.Add("' + locale + '", dictionary' + locale + ');\n'; + + return result; + }, + format: function (locales, options) { + var module = 'using System.Collections.Generic;\n' + + 'namespace ' + options.namespace + '\n' + + '{\n' + + 'public static class Translations' + + '{\n' + + 'public static readonly Dictionary> _translations = new Dictionary>();\n' + + 'private static Dictionary _currentLanguage;\n' + + 'static Translations()\n' + + '{\n' + + locales.join(''); + + if (options.defaultLanguage) { + module += 'SetCurrentLanguage("' + options.defaultLanguage + '");\n'; + } + module += '}\n' + + 'public static void SetCurrentLanguage(string languageCode)\n' + + '{\n' + + 'foreach (var translation in _translations)\n' + + '{\n' + + 'if (translation.Key != languageCode) continue;\n' + + '_currentLanguage = translation.Value;\n' + + 'return;\n' + + '}\n' + + '}\n' + + 'public static string Translate(string key)\n' + + '{\n' + + 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + + '}\n' + + '}\n' + + '}\n'; + return module; + } } }; @@ -52,7 +96,8 @@ var Compiler = (function () { function Compiler(options) { this.options = _.extend({ format: 'javascript', - module: 'gettext' + module: 'gettext', + namespace: 'Core.Common' }, options); } @@ -60,8 +105,21 @@ var Compiler = (function () { return formats.hasOwnProperty(format); }; - Compiler.prototype.convertPo = function (inputs) { - var format = formats[this.options.format]; + Compiler.prototype.convertPo = function (inputs, filename) { + + var format; + + if (filename) { + var extension = filename.split('.').pop(); + if (extension === 'cs') { + format = formats['csharp']; + } else { + format = formats[this.options.format]; + } + } else { + format = formats[this.options.format]; + } + var locales = []; inputs.forEach(function (input) { diff --git a/lib/extract.js b/lib/extract.js index cae964a..44b5261 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -79,9 +79,10 @@ var Extractor = (function () { erb: 'html', js: 'js', tag: 'html', - jsp: 'html' + jsp: 'html', + cs: 'c#' }, - postProcess: function (po) {} + postProcess: function (po) { } }, options); this.options.markerNames.unshift(this.options.markerName); this.options.attributes.unshift(this.options.attribute); @@ -285,6 +286,30 @@ var Extractor = (function () { }); }; + + Extractor.prototype.extractCs = function (filename, src) { + var self = this; + var newlines = function (index) { + return src.substr(0, index).match(/\n/g) || []; + }; + + var reference = function (index) { + return { + file: filename, + location: { + start: { + line: newlines(index).length + 1 + } + } + }; + }; + var pattern = /_translate_\s*=\s*"(.*?)"/g; + var match; + while ((match = pattern.exec(src))) { + self.addString(reference(match.index), match[1]); + } + }; + Extractor.prototype.extractHtml = function (filename, src) { var extractHtml = function (src, lineNumber) { var $ = cheerio.load(src, { decodeEntities: false, withStartIndices: true }); @@ -365,6 +390,9 @@ var Extractor = (function () { extractHtml(src, 0); }; + + + Extractor.prototype.isSupportedByStrategy = function (strategy, extension) { return (extension in this.options.extensions) && (this.options.extensions[extension] === strategy); }; @@ -378,6 +406,9 @@ var Extractor = (function () { if (this.isSupportedByStrategy('js', extension)) { this.extractJs(filename, content); } + if (this.isSupportedByStrategy('c#', extension)) { + this.extractCs(filename, content); + } }; Extractor.prototype.toString = function () { diff --git a/test/extract_csharp.js b/test/extract_csharp.js new file mode 100644 index 0000000..f432149 --- /dev/null +++ b/test/extract_csharp.js @@ -0,0 +1,21 @@ +'use strict'; + +var assert = require('assert'); +var testExtract = require('./utils').testExtract; + +describe('Extracting from C#', function () { + it('should extract the properties having _translate_ sufix', function () { + var files = [ + 'test/fixtures/Constants.cs' + ]; + var catalog = testExtract(files); + + assert.equal(catalog.items.length, 2); + assert.equal(catalog.items[0].msgid, 'Successfully saved!'); + assert.equal(catalog.items[0].msgstr, ''); + assert.equal(catalog.items[1].msgid, 'The selected items were deleted successfully.'); + assert.equal(catalog.items[1].msgstr, ''); + assert.deepEqual(catalog.items[0].references, ['test/fixtures/Constants.cs:14']); + assert.deepEqual(catalog.items[1].references, ['test/fixtures/Constants.cs:16']); + }); +}); \ No newline at end of file diff --git a/test/fixtures/Constants.cs b/test/fixtures/Constants.cs new file mode 100644 index 0000000..e64e8fd --- /dev/null +++ b/test/fixtures/Constants.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; + + +namespace Core.Common +{ + public static class Constants + { + + public static class Messages + { + public const string SuccessfullySaved_translate_ = + "Successfully saved!" ; + public const string OccurrencesDeleted_translate_ = "The selected items were deleted successfully." ; + public const string StatusUpdated = "Investigation status was updated successfully."; + + public static class EmailGroupsMessages + { + public const string EmailGroupDeleted = "Email Group successfully deleted!"; + public const string EmailUpdated = "Email successfully updated!"; + public const string EmailDeleted = "Email successfully deleted!"; + } + } + + } +} + + From 66d361d3de371dab802480b7545403e800d07919 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Wed, 11 Nov 2015 11:43:50 +0200 Subject: [PATCH 02/11] change the extraction sufix from _translate_ to _translate --- lib/extract.js | 2 +- test/fixtures/Constants.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/extract.js b/lib/extract.js index 44b5261..86d8221 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -303,7 +303,7 @@ var Extractor = (function () { } }; }; - var pattern = /_translate_\s*=\s*"(.*?)"/g; + var pattern = /_translate\s*=\s*"(.*?)"/g; var match; while ((match = pattern.exec(src))) { self.addString(reference(match.index), match[1]); diff --git a/test/fixtures/Constants.cs b/test/fixtures/Constants.cs index e64e8fd..4188142 100644 --- a/test/fixtures/Constants.cs +++ b/test/fixtures/Constants.cs @@ -11,9 +11,9 @@ public static class Constants public static class Messages { - public const string SuccessfullySaved_translate_ = + public const string SuccessfullySaved_translate = "Successfully saved!" ; - public const string OccurrencesDeleted_translate_ = "The selected items were deleted successfully." ; + public const string OccurrencesDeleted_translate = "The selected items were deleted successfully." ; public const string StatusUpdated = "Investigation status was updated successfully."; public static class EmailGroupsMessages From 4830bb80dfc8b7c9605f65b0b025ac65582f6fc8 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Wed, 11 Nov 2015 11:57:37 +0200 Subject: [PATCH 03/11] correct the _translate_ sufix in extract_csharp test --- test/extract_csharp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extract_csharp.js b/test/extract_csharp.js index f432149..a96b22b 100644 --- a/test/extract_csharp.js +++ b/test/extract_csharp.js @@ -4,7 +4,7 @@ var assert = require('assert'); var testExtract = require('./utils').testExtract; describe('Extracting from C#', function () { - it('should extract the properties having _translate_ sufix', function () { + it('should extract the properties having _translate sufix', function () { var files = [ 'test/fixtures/Constants.cs' ]; From 6814bb2bf43d2dada1690a24550b719effd3c364 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Wed, 11 Nov 2015 13:07:57 +0200 Subject: [PATCH 04/11] change formats.csharp method to support dot notation --- lib/compile.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index b1f5333..02adb99 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -111,11 +111,7 @@ var Compiler = (function () { if (filename) { var extension = filename.split('.').pop(); - if (extension === 'cs') { - format = formats['csharp']; - } else { - format = formats[this.options.format]; - } + format = extension === 'cs' ? formats.csharp : formats[this.options.format]; } else { format = formats[this.options.format]; } From 1eb6e2e6ce4143f083753ec9db2672e97876f9a1 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Thu, 12 Nov 2015 14:03:58 +0200 Subject: [PATCH 05/11] escape quotes and new lines in order to compile in c# code --- lib/compile.js | 7 ++++++- lib/extract.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index 02adb99..f92d082 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -48,7 +48,12 @@ var formats = { addLocale: function (locale, strings) { var result = 'var dictionary' + locale + ' = new Dictionary();\n'; for (var key in strings) { - result += 'dictionary' + locale + '.Add("' + key + '","' + strings[key] + '");\n'; + // escape quotes and new lines in order to compile in c# code + var value = strings[key].replace(/"/g, '\\"'); + value = value.replace(/[\n\r]/g, ' '); + key = key.replace(/"/g, '\\"'); + key = key.replace(/[\n\r]/g, ' '); + result += 'dictionary' + locale + '.Add("' + key + '","' + value + '");\n'; } result += '_translations.Add("' + locale + '", dictionary' + locale + ');\n'; diff --git a/lib/extract.js b/lib/extract.js index 86d8221..71875ec 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -303,7 +303,7 @@ var Extractor = (function () { } }; }; - var pattern = /_translate\s*=\s*"(.*?)"/g; + var pattern = /_translate\s*=\s*"(.*?)"\s*;/g; var match; while ((match = pattern.exec(src))) { self.addString(reference(match.index), match[1]); From 79a65dbf4c6a727b9959bf2aefa7fea41f25c315 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Thu, 12 Nov 2015 18:50:29 +0200 Subject: [PATCH 06/11] fix escape quoutes preceded by backshlash --- lib/compile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index f92d082..563c425 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -49,9 +49,9 @@ var formats = { var result = 'var dictionary' + locale + ' = new Dictionary();\n'; for (var key in strings) { // escape quotes and new lines in order to compile in c# code - var value = strings[key].replace(/"/g, '\\"'); + var value = strings[key].replace(/[^\\]"/g, '\\"'); value = value.replace(/[\n\r]/g, ' '); - key = key.replace(/"/g, '\\"'); + key = key.replace(/[^\\]"/g, '\\"'); key = key.replace(/[\n\r]/g, ' '); result += 'dictionary' + locale + '.Add("' + key + '","' + value + '");\n'; } From d3fc7e82681c89fbccba2f5018263d04dce55708 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Fri, 13 Nov 2015 10:08:34 +0200 Subject: [PATCH 07/11] treat dictionary not found exception in generated translations.cs file --- lib/compile.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/compile.js b/lib/compile.js index 563c425..48135ff 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -60,7 +60,9 @@ var formats = { return result; }, format: function (locales, options) { - var module = 'using System.Collections.Generic;\n' + + var module = + 'using System;\n' + + 'using System.Collections.Generic;\n' + 'namespace ' + options.namespace + '\n' + '{\n' + 'public static class Translations' + @@ -86,8 +88,15 @@ var formats = { '}\n' + 'public static string Translate(string key)\n' + '{\n' + + 'try\n' + + '{\n'+ 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + '}\n' + + 'catch(Exception)\n' + + '{\n' + + 'return key;\n' + + '}\n'+ + '}\n' + '}\n' + '}\n'; return module; From eecefa8f8e5a6097a0ee8c88271064e9a837dbc5 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Fri, 13 Nov 2015 10:15:03 +0200 Subject: [PATCH 08/11] fix some warnings --- lib/compile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index 48135ff..3b641cd 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -89,13 +89,13 @@ var formats = { 'public static string Translate(string key)\n' + '{\n' + 'try\n' + - '{\n'+ + '{\n' + 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + '}\n' + 'catch(Exception)\n' + '{\n' + 'return key;\n' + - '}\n'+ + '}\n' + '}\n' + '}\n' + '}\n'; From 9fb932b0ba911a7b7cd711e990c28682cb70bc60 Mon Sep 17 00:00:00 2001 From: Liviu Nicolae Date: Fri, 13 Nov 2015 14:36:34 +0200 Subject: [PATCH 09/11] check if strings[key] is an array in addLocale method for csharp --- lib/compile.js | 8 ++++++-- test/compile.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index 3b641cd..6f41b19 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -48,10 +48,14 @@ var formats = { addLocale: function (locale, strings) { var result = 'var dictionary' + locale + ' = new Dictionary();\n'; for (var key in strings) { + //supports only singular + if (Object.prototype.toString.call(strings[key]) === '[object Array]') { + strings[key] = strings[key][0]; + } // escape quotes and new lines in order to compile in c# code - var value = strings[key].replace(/[^\\]"/g, '\\"'); + var value = strings[key].replace('/(? Date: Fri, 13 Nov 2015 15:00:38 +0200 Subject: [PATCH 10/11] fix negative lookbehind bug --- lib/compile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile.js b/lib/compile.js index 6f41b19..dc6eca6 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -53,9 +53,9 @@ var formats = { strings[key] = strings[key][0]; } // escape quotes and new lines in order to compile in c# code - var value = strings[key].replace('/(? Date: Mon, 16 Nov 2015 13:01:32 +0200 Subject: [PATCH 11/11] implement tests for compiled c# dictionary --- test/compile.js | 177 +++++++++++++++++++++++++++++++++++-- test/fixtures/nl_c#test.po | 18 ++++ test/fixtures/nl_escape.po | 29 ++++++ 3 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/nl_c#test.po create mode 100644 test/fixtures/nl_escape.po diff --git a/test/compile.js b/test/compile.js index da663cd..bb16104 100644 --- a/test/compile.js +++ b/test/compile.js @@ -59,7 +59,7 @@ function testCompileCs(filenames, options) { var inputs = filenames.map(function (filename) { return fs.readFileSync(filename, 'utf8'); }); - return compiler.convertPo(inputs,'test.cs'); + return compiler.convertPo(inputs, 'test.cs'); } describe('Compile', function () { @@ -251,12 +251,179 @@ describe('Compile', function () { assert(catalog.called); }); + describe('C# dictionary file', function () { + it('Generates the expected c# dictionary', function () { + var files = ['test/fixtures/nl_c#test.po']; + var output = testCompileCs(files); + var expectedOutput = 'using System;\n' + + 'using System.Collections.Generic;\n' + + 'namespace Core.Common\n' + + '{\n' + + 'public static class Translations{\n' + + 'public static readonly Dictionary> _translations = new Dictionary>();\n' + + 'private static Dictionary _currentLanguage;\n' + + 'static Translations()\n' + + '{\n' + + 'var dictionarynl = new Dictionary();\n' + + 'dictionarynl.Add("Hello!","Hallo!");\n' + + '_translations.Add("nl", dictionarynl);\n' + + '}\n' + + 'public static void SetCurrentLanguage(string languageCode)\n' + + '{\n' + + 'foreach (var translation in _translations)\n' + + '{\n' + + 'if (translation.Key != languageCode) continue;\n' + + '_currentLanguage = translation.Value;\n' + + 'return;\n' + + '}\n' + + '}\n' + + 'public static string Translate(string key)\n' + + '{\n' + + 'try\n' + + '{\n' + + 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + + '}\n' + + 'catch(Exception)\n' + + '{\n' + + 'return key;\n' + + '}\n' + + '}\n' + + '}\n' + + '}\n'; + + assert.equal(output, expectedOutput); + }); - it('Generates ', function () { - var files = ['test/fixtures/nl.po']; - var output = testCompileCs(files); + it('Escapes quotes and new lines', function () { + var files = ['test/fixtures/nl_escape.po']; + var output = testCompileCs(files); + var expectedOutput = 'using System;\n' + + 'using System.Collections.Generic;\n' + + 'namespace Core.Common\n' + + '{\n' + + 'public static class Translations{\n' + + 'public static readonly Dictionary> _translations = new Dictionary>();\n' + + 'private static Dictionary _currentLanguage;\n' + + 'static Translations()\n' + + '{\n' + + 'var dictionarynl = new Dictionary();\n' + + 'dictionarynl.Add("This is a test","Dit is een test");\n' + + 'dictionarynl.Add("Bird","Vogel");\n' + + 'dictionarynl.Add("Hello \\"world\\"","Hallo \\"wereld\\"");\n' + + '_translations.Add("nl", dictionarynl);\n' + + '}\n' + + 'public static void SetCurrentLanguage(string languageCode)\n' + + '{\n' + + 'foreach (var translation in _translations)\n' + + '{\n' + + 'if (translation.Key != languageCode) continue;\n' + + '_currentLanguage = translation.Value;\n' + + 'return;\n' + + '}\n' + + '}\n' + + 'public static string Translate(string key)\n' + + '{\n' + + 'try\n' + + '{\n' + + 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + + '}\n' + + 'catch(Exception)\n' + + '{\n' + + 'return key;\n' + + '}\n' + + '}\n' + + '}\n' + + '}\n'; + + assert.equal(output, expectedOutput); + }); - console.log('The output is:' + output); + it('Accepts a defaultLanguage parameter', function () { + var files = ['test/fixtures/nl_c#test.po']; + var output = testCompileCs(files, { + defaultLanguage: 'nl' + }); + var expectedOutput = 'using System;\n' + + 'using System.Collections.Generic;\n' + + 'namespace Core.Common\n' + + '{\n' + + 'public static class Translations{\n' + + 'public static readonly Dictionary> _translations = new Dictionary>();\n' + + 'private static Dictionary _currentLanguage;\n' + + 'static Translations()\n' + + '{\n' + + 'var dictionarynl = new Dictionary();\n' + + 'dictionarynl.Add("Hello!","Hallo!");\n' + + '_translations.Add("nl", dictionarynl);\n' + + 'SetCurrentLanguage("nl");\n' + + '}\n' + + 'public static void SetCurrentLanguage(string languageCode)\n' + + '{\n' + + 'foreach (var translation in _translations)\n' + + '{\n' + + 'if (translation.Key != languageCode) continue;\n' + + '_currentLanguage = translation.Value;\n' + + 'return;\n' + + '}\n' + + '}\n' + + 'public static string Translate(string key)\n' + + '{\n' + + 'try\n' + + '{\n' + + 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + + '}\n' + + 'catch(Exception)\n' + + '{\n' + + 'return key;\n' + + '}\n' + + '}\n' + + '}\n' + + '}\n'; + + assert.equal(output, expectedOutput); + }); + it('Accepts a namaspace parameter', function () { + var files = ['test/fixtures/nl_c#test.po']; + var output = testCompileCs(files, { + namespace: 'Test.Namaspace' + }); + var expectedOutput = 'using System;\n' + + 'using System.Collections.Generic;\n' + + 'namespace Test.Namaspace\n' + + '{\n' + + 'public static class Translations{\n' + + 'public static readonly Dictionary> _translations = new Dictionary>();\n' + + 'private static Dictionary _currentLanguage;\n' + + 'static Translations()\n' + + '{\n' + + 'var dictionarynl = new Dictionary();\n' + + 'dictionarynl.Add("Hello!","Hallo!");\n' + + '_translations.Add("nl", dictionarynl);\n' + + '}\n' + + 'public static void SetCurrentLanguage(string languageCode)\n' + + '{\n' + + 'foreach (var translation in _translations)\n' + + '{\n' + + 'if (translation.Key != languageCode) continue;\n' + + '_currentLanguage = translation.Value;\n' + + 'return;\n' + + '}\n' + + '}\n' + + 'public static string Translate(string key)\n' + + '{\n' + + 'try\n' + + '{\n' + + 'return _currentLanguage != null ? _currentLanguage[key] : key;\n' + + '}\n' + + 'catch(Exception)\n' + + '{\n' + + 'return key;\n' + + '}\n' + + '}\n' + + '}\n' + + '}\n'; + assert.equal(output, expectedOutput); + }); }); }); diff --git a/test/fixtures/nl_c#test.po b/test/fixtures/nl_c#test.po new file mode 100644 index 0000000..d1ad8c2 --- /dev/null +++ b/test/fixtures/nl_c#test.po @@ -0,0 +1,18 @@ +msgid "" +msgstr "" +"Language: nl\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/single.html test/fixtures/second.html +msgid "Hello!" +msgstr "Hallo!" + diff --git a/test/fixtures/nl_escape.po b/test/fixtures/nl_escape.po new file mode 100644 index 0000000..6828b73 --- /dev/null +++ b/test/fixtures/nl_escape.po @@ -0,0 +1,29 @@ +msgid "" +msgstr "" +"Language: nl\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.8.6\n" + +#: test/fixtures/second.html +msgid "" +"This is a\n" +"test" +msgstr "Dit is een test" + +#: test/fixtures/plural.html +msgid "Bird" +msgid_plural "Birds" +msgstr[0] "Vogel" +msgstr[1] "Vogels" + +#: test/fixtures/quotes.html +msgid "Hello \"world\"" +msgstr "Hallo \"wereld\""