Permalink
Browse files

Improve fallback support

When fallbacks are enabled, look for less specific versions of the
requested locale before trying the default locale. Also allow custom
fallback rules to be specified for particular languages.
  • Loading branch information...
1 parent d6b7be0 commit 2dc3f59046c8540030423b6caf793c3113ed2459 @tomhughes tomhughes committed Feb 25, 2012
Showing with 70 additions and 14 deletions.
  1. +12 −5 README.rdoc
  2. +21 −0 spec/i18n_spec.js
  3. +37 −9 vendor/assets/javascripts/i18n.js
View
@@ -100,18 +100,25 @@ You can set default values for missing scopes:
// with interpolation
I18n.t("noun", {defaultValue: "I'm a {{noun}}", noun: "Mac"});
-Translation fallback can be handled by taking from I18n.defaultLocale.
-To allow this in your application.html.erb specify:
+Translation fallback can be enabled by adding to your <tt>application.html.erb</tt>:
<script type="text/javascript">
I18n.fallbacks = true;
</script>
-Then missing translation will be taken from your I18n.defaultLocale.
+By default missing translations will first be looked for in less
+specific versions of the requested locale and if that fails by taking
+them from your I18n.defaultLocale.
Example:
- // if I18n.defaultLocale = "en" and translation doesn't exist for I18n.locale = "de"
+ // if I18n.defaultLocale = "en" and translation doesn't exist for I18n.locale = "de-DE"
I18n.t("some.missing.scope");
- // this key will be taken from "en" locale scope
+ // this key will be taken from "de" locale scope
+ // or, if that also doesn't exist, from "en" locale scope
+
+Custom fallback rules can also be specified for a particular language,
+for example:
+
+ I18n.fallbackRules.no = [ "nb", "en" ];
Pluralization is possible as well and by default provides english rules:
View
@@ -91,6 +91,14 @@ describe("I18n.js", function(){
abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"],
meridian: ["am", "pm"]
}
+ },
+
+ "de": {
+ hello: "Hallo Welt!"
+ },
+
+ "nb": {
+ hello: "Hei Verden!"
}
};
});
@@ -157,6 +165,19 @@ describe("I18n.js", function(){
expect(I18n.t("greetings.stranger")).toBeEqualTo("Hello stranger!");
});
+ specify("translation should handle fallback to less specific locale", function(){
+ I18n.locale = "de-DE";
+ I18n.fallbacks = true;
+ expect(I18n.t("hello")).toBeEqualTo("Hallo Welt!");
+ });
+
+ specify("translation should handle fallback via custom rules", function(){
+ I18n.locale = "no";
+ I18n.fallbacks = true;
+ I18n.fallbackRules.no = [ "nb" ];
+ expect(I18n.t("hello")).toBeEqualTo("Hei Verden!");
+ });
+
specify("single interpolation", function(){
actual = I18n.t("greetings.name", {name: "John Doe"});
expect(actual).toBeEqualTo("Hello John Doe!");
@@ -16,10 +16,32 @@ I18n.locale = null;
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
+I18n.fallbackRules = {
+};
+
I18n.pluralizationRules = {
en: rule = function (n) { return n == 0 ? ["zero", "none", "other"] : n == 1 ? "one" : "other"; }
}
+I18n.getFallbacks = function(locale) {
+ if (locale === I18n.defaultLocale) {
+ return [];
+ } else if (!I18n.fallbackRules[locale]) {
+ var rules = []
+ , components = locale.split("-");
+
+ for (var l = 1; l < components.length; l++) {
+ rules.push(components.slice(0, l).join("-"));
+ }
+
+ rules.push(I18n.defaultLocale);
+
+ I18n.fallbackRules[locale] = rules;
+ }
+
+ return I18n.fallbackRules[locale];
+}
+
I18n.isValidNode = function(obj, node, undefined) {
return obj[node] !== null && obj[node] !== undefined;
};
@@ -28,7 +50,8 @@ I18n.lookup = function(scope, options) {
var options = options || {}
, lookupInitialScope = scope
, translations = this.prepareOptions(I18n.translations)
- , messages = translations[options.locale || I18n.currentLocale()] || {}
+ , locale = options.locale || I18n.currentLocale()
+ , messages = translations[locale] || {}
, options = this.prepareOptions(options)
, currentScope
;
@@ -43,20 +66,25 @@ I18n.lookup = function(scope, options) {
scope = scope.split(this.defaultSeparator);
- while (scope.length > 0) {
+ while (messages && scope.length > 0) {
currentScope = scope.shift();
messages = messages[currentScope];
+ }
- if (!messages) {
- if (I18n.fallbacks && !options.fallback) {
- messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: I18n.defaultLocale, fallback: true}, options));
+ if (!messages) {
+ if (I18n.fallbacks) {
+ var fallbacks = this.getFallbacks(locale);
+ for (var fallback = 0; fallback < fallbacks.length; fallbacks++) {
+ messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: fallbacks[fallback]}, options));
+ if (messages) {
+ break;
+ }
}
- break;
}
- }
- if (!messages && this.isValidNode(options, "defaultValue")) {
- messages = options.defaultValue;
+ if (!messages && this.isValidNode(options, "defaultValue")) {
+ messages = options.defaultValue;
+ }
}
return messages;

0 comments on commit 2dc3f59

Please sign in to comment.