Merge pull request #5120 from jhass/i18n_js_fallback

Add fallback system to frontend I18n system
This commit is contained in:
Jonne Haß 2014-08-15 15:07:24 +02:00
commit 98c3a0a871
12 changed files with 77 additions and 43 deletions

View file

@ -1,47 +1,70 @@
/* Copyright (c) 2010-2011, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
Diaspora.I18n = { Diaspora.I18n = {
language: "en", language: "en",
locale: {}, locale: {
pluralizationKey: function(n) { return this.fallback.pluralizationKey(n); },
data: {},
fallback: {
pluralizationKey: function(n) { return n == 1 ? "one" : "other"; },
data: {}
}
},
loadLocale: function(locale, language) { load: function(locale, language, fallbackLocale) {
this.locale = $.extend(this.locale, locale); this.updateLocale(this.locale, locale);
this.updateLocale(this.locale.fallback, fallbackLocale);
this.language = language; this.language = language;
rule = this.t('pluralization_rule'); },
if (rule === "")
rule = 'function (n) { return n == 1 ? "one" : "other" }'; updateLocale: function(locale, data) {
eval("this.pluralizationKey = "+rule); locale.data = $.extend(locale.data, data);
rule = this.resolve(locale, ['pluralization_rule']);
if (rule !== "") {
eval("locale.pluralizationKey = "+rule);
}
}, },
t: function(item, views) { t: function(item, views) {
var items = item.split("."), var items = item.split(".");
translatedMessage, return this.resolve(this.locale, items, views);
nextNamespace; },
resolve: function(locale, items, views) {
var translatedMessage, nextNamespace, originalItems = items.slice();
if(views && typeof views.count !== "undefined") { if(views && typeof views.count !== "undefined") {
items.push(this.pluralizationKey(views.count)); items.push(locale.pluralizationKey(views.count));
} }
while(nextNamespace = items.shift()) { while(nextNamespace = items.shift()) {
translatedMessage = (translatedMessage) translatedMessage = (translatedMessage)
? translatedMessage[nextNamespace] ? translatedMessage[nextNamespace]
: this.locale[nextNamespace]; : locale.data[nextNamespace];
if(typeof translatedMessage === "undefined") { if(typeof translatedMessage === "undefined") {
if (typeof locale.fallback === "undefined") {
return ""; return "";
} else {
return this.resolve(locale.fallback, originalItems, views);
}
} }
} }
try {
return _.template(translatedMessage, views || {}); return _.template(translatedMessage, views || {});
} catch (e) {
if (typeof locale.fallback === "undefined") {
return "";
} else {
return this.resolve(locale.fallback, originalItems, views);
}
}
}, },
reset: function() { reset: function() {
this.locale = {}; this.locale.data = {};
if( arguments.length > 0 && !(_.isEmpty(arguments[0])) ) if( arguments.length > 0 && !(_.isEmpty(arguments[0])) )
this.locale = arguments[0]; this.locale.data = arguments[0];
} }
}; };

View file

@ -31,7 +31,9 @@ module LayoutHelper
def load_javascript_locales(section = 'javascripts') def load_javascript_locales(section = 'javascripts')
content_tag(:script) do content_tag(:script) do
<<-JS.html_safe <<-JS.html_safe
Diaspora.I18n.loadLocale(#{get_javascript_strings_for(I18n.locale, section).to_json}, "#{I18n.locale}"); Diaspora.I18n.load(#{get_javascript_strings_for(I18n.locale, section).to_json},
"#{I18n.locale}",
#{get_javascript_strings_for(DEFAULT_LANGUAGE, section).to_json});
Diaspora.Page = "#{params[:controller].camelcase}#{params[:action].camelcase}"; Diaspora.Page = "#{params[:controller].camelcase}#{params[:action].camelcase}";
JS JS
end end

View file

@ -1,6 +1,6 @@
describe("app.collections.Aspects", function(){ describe("app.collections.Aspects", function(){
beforeEach(function(){ beforeEach(function(){
Diaspora.I18n.loadLocale({ Diaspora.I18n.load({
'and' : "and", 'and' : "and",
'comma' : ",", 'comma' : ",",
'my_aspects' : "My Aspects" 'my_aspects' : "My Aspects"

View file

@ -1,7 +1,7 @@
describe("app.views.AspectsList", function(){ describe("app.views.AspectsList", function(){
beforeEach(function(){ beforeEach(function(){
setFixtures('<ul id="aspects_list"></ul>'); setFixtures('<ul id="aspects_list"></ul>');
Diaspora.I18n.loadLocale({ aspect_navigation : { Diaspora.I18n.load({ aspect_navigation : {
'select_all' : 'Select all', 'select_all' : 'Select all',
'deselect_all' : 'Deselect all' 'deselect_all' : 'Deselect all'
}}); }});

View file

@ -58,7 +58,7 @@ describe("app.views.CommentStream", function(){
}); });
it("doesn't add the comment to the view, when the request fails", function(){ it("doesn't add the comment to the view, when the request fails", function(){
Diaspora.I18n.loadLocale({failed_to_post_message: "posting failed!"}); Diaspora.I18n.load({failed_to_post_message: "posting failed!"});
this.request.response({status: 500}); this.request.response({status: 500});
expect(this.view.$(".comment-content p").text()).not.toEqual("a new comment"); expect(this.view.$(".comment-content p").text()).not.toEqual("a new comment");

View file

@ -2,7 +2,7 @@ describe("app.views.Feedback", function(){
beforeEach(function(){ beforeEach(function(){
loginAs({id : -1, name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}}); loginAs({id : -1, name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
Diaspora.I18n.loadLocale({stream : { Diaspora.I18n.load({stream : {
'like' : "Like", 'like' : "Like",
'unlike' : "Unlike", 'unlike' : "Unlike",
'public' : "Public", 'public' : "Public",

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,7 @@ describe("app.views.LikesInfo", function(){
beforeEach(function(){ beforeEach(function(){
loginAs({id : -1, name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}}); loginAs({id : -1, name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
Diaspora.I18n.loadLocale({stream : { Diaspora.I18n.load({stream : {
pins : { pins : {
zero : "<%= count %> Pins", zero : "<%= count %> Pins",
one : "<%= count %> Pin"} one : "<%= count %> Pin"}

View file

@ -431,7 +431,7 @@ describe("app.views.Publisher", function() {
context('successful completion', function() { context('successful completion', function() {
beforeEach(function() { beforeEach(function() {
Diaspora.I18n.loadLocale({ photo_uploader: { completed: '<%= file %> completed' }}); Diaspora.I18n.load({ photo_uploader: { completed: '<%= file %> completed' }});
$('#photodropzone').html('<li class="publisher_photo loading"><img src="" /></li>'); $('#photodropzone').html('<li class="publisher_photo loading"><img src="" /></li>');
this.uploader.onComplete(null, 'test.jpg', { this.uploader.onComplete(null, 'test.jpg', {
@ -469,7 +469,7 @@ describe("app.views.Publisher", function() {
context('unsuccessful completion', function() { context('unsuccessful completion', function() {
beforeEach(function() { beforeEach(function() {
Diaspora.I18n.loadLocale({ photo_uploader: { completed: '<%= file %> completed' }}); Diaspora.I18n.load({ photo_uploader: { completed: '<%= file %> completed' }});
$('#photodropzone').html('<li class="publisher_photo loading"><img src="" /></li>'); $('#photodropzone').html('<li class="publisher_photo loading"><img src="" /></li>');
this.uploader.onComplete(null, 'test.jpg', { this.uploader.onComplete(null, 'test.jpg', {

View file

@ -21,7 +21,7 @@ describe("app.views.StreamPost", function(){
beforeEach(function(){ beforeEach(function(){
loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}}); loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
Diaspora.I18n.loadLocale({stream : { Diaspora.I18n.load({stream : {
reshares : { reshares : {
one : "<%= count %> reshare", one : "<%= count %> reshare",
other : "<%= count %> reshares" other : "<%= count %> reshares"

View file

@ -24,7 +24,7 @@ beforeEach(function() {
var Page = Diaspora.Pages["TestPage"]; var Page = Diaspora.Pages["TestPage"];
$.extend(Page.prototype, Diaspora.EventBroker.extend(Diaspora.BaseWidget)); $.extend(Page.prototype, Diaspora.EventBroker.extend(Diaspora.BaseWidget));
Diaspora.I18n.loadLocale({}, 'en'); Diaspora.I18n.load({}, 'en', {});
Diaspora.page = new Page(); Diaspora.page = new Page();
Diaspora.page.publish("page/ready", [$(document.body)]); Diaspora.page.publish("page/ready", [$(document.body)]);

View file

@ -24,27 +24,28 @@ describe("Diaspora.I18n", function() {
Diaspora.I18n.reset(); // run tests with clean locale Diaspora.I18n.reset(); // run tests with clean locale
}); });
describe("::loadLocale", function() { describe("::load", function() {
it("sets the class's locale variable", function() { it("sets the class's locale variable", function() {
Diaspora.I18n.loadLocale(locale); Diaspora.I18n.load(locale, "en", locale);
expect(Diaspora.I18n.locale).toEqual(locale); expect(Diaspora.I18n.locale.data).toEqual(locale);
expect(Diaspora.I18n.locale.fallback.data).toEqual(locale);
}); });
it("extends the class's locale variable on multiple calls", function() { it("extends the class's locale variable on multiple calls", function() {
var data = {another: 'section'}, var data = {another: 'section'},
extended = $.extend(locale, data); extended = $.extend(locale, data);
Diaspora.I18n.loadLocale(locale); Diaspora.I18n.load(locale, "en", locale);
Diaspora.I18n.loadLocale(data); Diaspora.I18n.load(data, "en", data);
expect(Diaspora.I18n.locale).toEqual(extended); expect(Diaspora.I18n.locale.data).toEqual(extended);
}); });
}); });
describe("::t", function() { describe("::t", function() {
var translation; var translation;
beforeEach(function() { Diaspora.I18n.loadLocale(locale); }); beforeEach(function() { Diaspora.I18n.load(locale, "en", {fallback: "fallback", namespace: {template: "no template"}}); });
it("returns the specified translation", function() { it("returns the specified translation", function() {
translation = Diaspora.I18n.t("namespace.message"); translation = Diaspora.I18n.t("namespace.message");
@ -67,20 +68,28 @@ describe("Diaspora.I18n", function() {
it("returns an empty string if the translation is not found", function() { it("returns an empty string if the translation is not found", function() {
expect(Diaspora.I18n.t("missing.locale")).toEqual(""); expect(Diaspora.I18n.t("missing.locale")).toEqual("");
}); });
it("falls back on missing key", function() {
expect(Diaspora.I18n.t("fallback")).toEqual("fallback");
});
it("falls back on interpolation errors", function() {
expect(Diaspora.I18n.t("namespace.template")).toEqual("no template");
});
}); });
describe("::reset", function(){ describe("::reset", function(){
it("clears the current locale", function() { it("clears the current locale", function() {
Diaspora.I18n.loadLocale(locale); Diaspora.I18n.load(locale, "en", locale);
Diaspora.I18n.reset() Diaspora.I18n.reset()
expect(Diaspora.I18n.locale).toEqual({}); expect(Diaspora.I18n.locale.data).toEqual({});
}); });
it("sets the locale to only a specific value", function() { it("sets the locale to only a specific value", function() {
var data = { some: 'value' }; var data = { some: 'value' };
Diaspora.I18n.loadLocale(locale); Diaspora.I18n.load(locale, "en", locale);
Diaspora.I18n.reset(data); Diaspora.I18n.reset(data);
expect(Diaspora.I18n.locale).toEqual(data); expect(Diaspora.I18n.locale.data).toEqual(data);
}); });
}); });
}); });