diff --git a/src/i18n/view.i18n.js b/src/i18n/view.i18n.js
index dd065c50..2775e96b 100644
--- a/src/i18n/view.i18n.js
+++ b/src/i18n/view.i18n.js
@@ -1,36 +1,54 @@
/*jshint multistr:true */
-// TODO probably some kind of mixin would be better, like: my.View - Backbone.View.extend({}); _.extend(my.View, Backbone.I18nView);
+"use strict";
+var I18nMessages = function(uniqueID, translations, languageResolverOrLocale, appHardcodedLocale) {
+ var defaultResolver = function() {
+ };
-Backbone.I18nView = Backbone.View.extend({
- defaultLocale: 'en',
- locale: 'en',
- cache: {},
- initializeI18n: function(locale, appHardcodedLocale) {
- this.defaultLocale = appHardcodedLocale || 'en';
- this.locale = locale || this.defaultLocale;
+ // which locale should we use?
+ languageResolverOrLocale = (typeof languageResolverOrLocale !== 'undefined') ? languageResolverOrLocale : defaultResolver;
+ appHardcodedLocale = appHardcodedLocale || 'en';
- this.cache[this.locale] = {};
- },
+ if (typeof(languageResolverOrLocale) === 'function') {
+ languageResolverOrLocale = languageResolverOrLocale();
+ }
+ if (languageResolverOrLocale == undefined) {
+ languageResolverOrLocale = appHardcodedLocale;
+ }
+
+ if (I18nMessages.prototype._formatters[uniqueID, languageResolverOrLocale]) {
+ return I18nMessages.prototype._formatters[uniqueID, languageResolverOrLocale];
+ }
+ I18nMessages.prototype._formatters[uniqueID, languageResolverOrLocale] = this;
+
+ // ========== VARIABLES & FUNCTIONS ==========
+ var self = this;
+
+ this.locale = languageResolverOrLocale;
+ this.cache= {};
+
+ this.getLocale = function() {
+ return this.locale;
+ };
+
+ // ============= FormatJS.io backend =========
+
+ this.t = function(key, values, defaultMessage) {
+ values = (typeof values !== 'undefined') ? values : {};
- // TODO how to use it from outside? an singleton instance of I18n? use case: pass translated strings into view initializer
- t: function(key) {
- this.t(key, {}, null);
- },
- t: function(key, values, defaultMessage) {
// get the message from current locale
- var msg = recline.View.translations[this.locale][key];
+ var msg = this.translations[this.locale][key];
// fallback to key or default message if no translation is defined
if (msg == null) {
- if (this.locale != this.defaultLocale) {
+ if (this.locale != this.appHardcodedLocale) {
console.warn("Missing locale for " + this.locale + "." + key);
}
msg = defaultMessage;
}
if (msg == null) {
msg = key;
- if (this.locale === this.defaultLocale) {
+ if (this.locale === this.appHardcodedLocale) {
// no need to define lang entry for short sentences, just use underscores as spaces
msg = msg.replace(/_/g, ' ');
}
@@ -38,9 +56,9 @@ Backbone.I18nView = Backbone.View.extend({
}
try {
- var formatter = this.cache[this.locale][msg];
+ var formatter = this.cache[msg];
if (formatter === undefined) {
- this.cache[this.locale][msg] = formatter = new IntlMessageFormat(msg, this.locale);
+ this.cache[msg] = formatter = new IntlMessageFormat(msg, this.locale);
}
var formatted = formatter.format(values);
@@ -56,16 +74,18 @@ Backbone.I18nView = Backbone.View.extend({
}
},
- MustacheFormatter: function() {
- var formatter = new Proxy(this, {
- get: function(view, name) {
+ // ============ Mustache integration ========
+
+ this.mustacheI18Tags = function() {
+ var tagsProxy = new Proxy(this, {
+ get: function(messages, name) {
return function() {
var f = function (text, render) {
- var trans = view.t(name, this, text);
+ var trans = messages.t(name, this, text);
return render(trans);
}
f.toString = function() {
- return view.t(name);
+ return messages.t(name);
}
return f;
};
@@ -76,7 +96,11 @@ Backbone.I18nView = Backbone.View.extend({
});
return {
- 't': formatter,
+ 't': tagsProxy,
};
},
-});
\ No newline at end of file
+
+ this.injectMustache = function(tmplData) {
+ return _.extend(tmplData, self.mustacheI18Tags());
+ }
+};
\ No newline at end of file
diff --git a/test/base.js b/test/base.js
index 93ebd69a..dcf016ed 100644
--- a/test/base.js
+++ b/test/base.js
@@ -22,6 +22,14 @@ var Fixture = {
];
var dataset = new recline.Model.Dataset({records: documents, fields: fields});
return dataset;
+ },
+
+ getTranslations: function() {
+ return {
+ pl: {
+ Grid: 'Tabela'
+ }
+ };
}
};
diff --git a/test/view.i18n.test.js b/test/view.i18n.test.js
index 52adfce5..40919b95 100644
--- a/test/view.i18n.test.js
+++ b/test/view.i18n.test.js
@@ -1,101 +1,80 @@
(function ($) {
-module("View - i18n support");
+module("I18nMessages");
test('translate simple key custom locale', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset,
- locale: 'pl' // todo or should it go in the state parameter?
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations(), 'pl');
- equal(view.t('Grid'), 'Tabela');
+ equal(fmt.t('Grid'), 'Tabela');
});
test('translate simple key default locale', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations());
- equal(view.t('Add_row'), 'Add row');
+ equal(fmt.t('Add_row'), 'Add row');
});
test('override custom locale', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset,
- locale: 'pl'
- });
+ var fmt = I18nMessages('recline', recline.View.translations, 'pl');
var oldTranslation = recline.View.translations['pl']['Grid'];
- // set custom strings in external app after recline script
+ // set custom strings in external app after including recline script
recline.View.translations['pl']['Grid'] = 'Dane';
- equal(view.t('Grid'), 'Dane');
+ equal(fmt.t('Grid'), 'Dane');
+
recline.View.translations['pl']['Grid'] = oldTranslation;
});
test('override default locale', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset
- });
+ var fmt = I18nMessages('recline', recline.View.translations, 'en');
var oldTranslation = recline.View.translations['en']['Grid'];
- // set custom strings in external app after recline script
+ // set custom strings in external app after including recline script
recline.View.translations['en']['Grid'] = 'Data';
- equal(view.t('Grid'), 'Data');
+ equal(fmt.t('Grid'), 'Data');
+
recline.View.translations['en']['Grid'] = oldTranslation;
});
test('fallback to key if translation not present', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations());
- equal(view.t('thiskeydoesnotexist'), 'thiskeydoesnotexist');
+ equal(fmt.t('thiskeydoesnotexist'), 'thiskeydoesnotexist');
});
test('fallback to default message', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations());
- equal(view.t('thiskeydoesnotexist', {}, 'Fallback to default message'), 'Fallback to default message');
+ equal(fmt.t('thiskeydoesnotexist', {}, 'Fallback to default message'), 'Fallback to default message');
});
test('mustache formatter - simple key', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset,
- locale: 'pl'
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations(), 'pl');
+ // test without template rendering
+ var mustacheIntegration = fmt.injectMustache({});
+ equal(mustacheIntegration.t.Grid, 'Tabela')
+
+ // test within template rendering
var template = '{{t.Grid}}';
var tmplData = {};
- // adding i18n support [do it in view before passing data to render functions]
- tmplData = _.extend(tmplData, view.MustacheFormatter());
+ tmplData = fmt.injectMustache(tmplData);
var out = Mustache.render(template, tmplData);
equal(out, 'Tabela');
});
test('mustache formatter - complex key', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset,
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations(), 'pl');
var template = '{{#t.num_records}}{recordCount} records{{/t.num_records}}';
var tmplData = {recordCount: 5};
- // adding i18n support [do it in view before passing data to render functions]
- tmplData = _.extend(tmplData, view.MustacheFormatter());
+ // injecting i18n support [do it in view before passing data to render functions]
+ tmplData = fmt.injectMustache(tmplData);
var out = Mustache.render(template, tmplData);
equal(out, '5 records');
@@ -103,46 +82,80 @@ test('mustache formatter - complex key', function () {
test('translate complex key default locale', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset
- });
+ var fmt = I18nMessages('somelib', Fixture.getTranslations(), 'pl');
equal(view.t('codeforall', {records: 3}, '{records} records'), '3 records');
});
-test('translate complex key custom locale', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset,
- locale: 'pl'
- });
+test('mustache formatter - translate complex key custom locale', function () {
+ var translations = {
+ pl: {
+ codeforall: '{records} rekordy'
+ }
+ };
+ var fmt = I18nMessages('somelib', translations, 'pl');
- recline.View.translations['pl']['codeforall'] = '{records} rekordy';
- equal(view.t('codeforall', {records: 3}, '{records} records'), '3 rekordy');
+ equal(fmt.t('codeforall', {records: 3}, '{records} records'), '3 rekordy');
});
-test('translate complex key custom locale custom count', function () {
- var dataset = Fixture.getDataset();
- var view = new recline.View.MultiView({
- model: dataset,
- locale: 'pl'
- });
-
- recline.View.translations['pl']['codeforall'] = '{records, plural, ' +
+test('mustache formatter - translate complex key custom locale custom count', function () {
+ var translations = {
+ pl: {
+ codeforall: {records, plural, ' +
'=0 {brak zdjęć}' +
'=1 {{records} zdjęcie}' +
'few {{records} zdjęcia}' +
- 'other {{records} zdjęć}}';
+ 'other {{records} zdjęć}}'
+ }
+ };
+ var fmt = I18nMessages('somelib', translations, 'pl');
- equal(view.t('codeforall', {records: 0}), 'brak zdjęć');
- equal(view.t('codeforall', {records: 1}), '1 zdjęcie');
- equal(view.t('codeforall', {records: 3}), '3 zdjęcia');
- equal(view.t('codeforall', {records: 5}), '5 zdjęć');
+ equal(fmt.t('codeforall', {records: 0}), 'brak zdjęć');
+ equal(fmt.t('codeforall', {records: 1}), '1 zdjęcie');
+ equal(fmt.t('codeforall', {records: 3}), '3 zdjęcia');
+ equal(fmt.t('codeforall', {records: 5}), '5 zdjęć');
});
+test('I18nMessages specified locale', function () {
+ var fmt = I18nMessages('somelib', {}, 'pl');
-// todo test dynamic language changes
+ equal(fmt.getLocale(), 'pl');
+});
+
+test('I18nMessages default locale', function () {
+ var fmt = I18nMessages('somelib', {});
+
+ // no language set in HTML tag
+ equal($('html').attr('lang'), undefined);
+
+ equal(fmt.getLocale(), 'en');
+});
+
+test('I18nMessages default html:lang default locale resolver', function () {
+ var fmt = I18nMessages('somelib', {});
+
+
+ $('html').attr('lang', 'de');
+ equal(fmt.getLocale(), 'de');
+ $('html').attr('lang', null);
+});
+
+test('I18nMessages default locale custom resolver', function () {
+ var localeResolver = function() { return 'fr'; };
+ var fmt = I18nMessages('somelib', {}, localeResolver);
+
+ equal(fmt.getLocale(), 'fr');
+});
+
+test('I18nMessages singletons, function () {
+ var lib1_pl = I18nMessages('lib1', {}, 'pl');
+ var lib2_pl = I18nMessages('lib2', {}, 'pl');
+ var lib1_en = I18nMessages('lib1', {}, 'en');
+
+ strictEqual(I18nMessages('lib1', {}, 'pl'), lib1_pl);
+ notStrictEqual(lib1_pl, lib1_en);
+ notStrictEqual(lib1_pl, lib2_pl);
+});
})(this.jQuery);