Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# support for translation #117

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
78 changes: 75 additions & 3 deletions lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,68 @@ var formats = {
});
return JSON.stringify(result);
}
},
csharp: {
addLocale: function (locale, strings) {
var result = 'var dictionary' + locale + ' = new Dictionary<string, string>();\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, function ($0, $1) { return $1 ? $0 : '\\"'; });
value = value.replace(/[\n\r]/g, ' ');
key = key.replace(/(\\)?"/g, function ($0, $1) { return $1 ? $0 : '\\"'; });
key = key.replace(/[\n\r]/g, ' ');
result += 'dictionary' + locale + '.Add("' + key + '","' + value + '");\n';
}
result += '_translations.Add("' + locale + '", dictionary' + locale + ');\n';

return result;
},
format: function (locales, options) {
var module =
'using System;\n' +
'using System.Collections.Generic;\n' +
'namespace ' + options.namespace + '\n' +
'{\n' +
'public static class Translations' +
'{\n' +
'public static readonly Dictionary<string, Dictionary<string, string>> _translations = new Dictionary<string, Dictionary<string, string>>();\n' +
'private static Dictionary<string, string> _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' +
'try\n' +
'{\n' +
'return _currentLanguage != null ? _currentLanguage[key] : key;\n' +
'}\n' +
'catch(Exception)\n' +
'{\n' +
'return key;\n' +
'}\n' +
'}\n' +
'}\n' +
'}\n';
return module;
}
}
};

Expand All @@ -52,16 +114,26 @@ var Compiler = (function () {
function Compiler(options) {
this.options = _.extend({
format: 'javascript',
module: 'gettext'
module: 'gettext',
namespace: 'Core.Common'
}, options);
}

Compiler.hasFormat = function (format) {
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();
format = extension === 'cs' ? formats.csharp : formats[this.options.format];
} else {
format = formats[this.options.format];
}

var locales = [];

inputs.forEach(function (input) {
Expand Down
35 changes: 33 additions & 2 deletions lib/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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*"(.*?)"\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 });
Expand Down Expand Up @@ -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);
};
Expand All @@ -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 () {
Expand Down
184 changes: 184 additions & 0 deletions test/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ function testCompile(filenames, options) {
return compiler.convertPo(inputs);
}

function testCompileCs(filenames, options) {
var compiler = new Compiler(options);
var inputs = filenames.map(function (filename) {
return fs.readFileSync(filename, 'utf8');
});
return compiler.convertPo(inputs, 'test.cs');
}

describe('Compile', function () {
it('Compiles a .po file into a .js catalog', function () {
var files = ['test/fixtures/nl.po'];
Expand Down Expand Up @@ -242,4 +250,180 @@ describe('Compile', function () {
vm.runInContext(output, context);
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<string, Dictionary<string, string>> _translations = new Dictionary<string, Dictionary<string, string>>();\n' +
'private static Dictionary<string, string> _currentLanguage;\n' +
'static Translations()\n' +
'{\n' +
'var dictionarynl = new Dictionary<string, string>();\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('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<string, Dictionary<string, string>> _translations = new Dictionary<string, Dictionary<string, string>>();\n' +
'private static Dictionary<string, string> _currentLanguage;\n' +
'static Translations()\n' +
'{\n' +
'var dictionarynl = new Dictionary<string, string>();\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);
});

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<string, Dictionary<string, string>> _translations = new Dictionary<string, Dictionary<string, string>>();\n' +
'private static Dictionary<string, string> _currentLanguage;\n' +
'static Translations()\n' +
'{\n' +
'var dictionarynl = new Dictionary<string, string>();\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<string, Dictionary<string, string>> _translations = new Dictionary<string, Dictionary<string, string>>();\n' +
'private static Dictionary<string, string> _currentLanguage;\n' +
'static Translations()\n' +
'{\n' +
'var dictionarynl = new Dictionary<string, string>();\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);
});
});
});
21 changes: 21 additions & 0 deletions test/extract_csharp.js
Original file line number Diff line number Diff line change
@@ -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']);
});
});
Loading