|  | @@ -0,0 +1,301 @@
 | 
	
		
			
				|  |  | +//     (c) 2012 Airbnb, Inc.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +//     polyglot.js may be freely distributed under the terms of the BSD
 | 
	
		
			
				|  |  | +//     license. For all licensing information, details, and documention:
 | 
	
		
			
				|  |  | +//     http://airbnb.github.com/polyglot.js
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// Polyglot.js is an I18n helper library written in JavaScript, made to
 | 
	
		
			
				|  |  | +// work both in the browser and in Node. It provides a simple solution for
 | 
	
		
			
				|  |  | +// interpolation and pluralization, based off of Airbnb's
 | 
	
		
			
				|  |  | +// experience adding I18n functionality to its Backbone.js and Node apps.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +// Polylglot is agnostic to your translation backend. It doesn't perform any
 | 
	
		
			
				|  |  | +// translation; it simply gives you a way to manage translated phrases from
 | 
	
		
			
				|  |  | +// your client- or server-side JavaScript application.
 | 
	
		
			
				|  |  | +//
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +(function(root, factory) {
 | 
	
		
			
				|  |  | +  if (typeof define === 'function' && define.amd) {
 | 
	
		
			
				|  |  | +    define([], function() {
 | 
	
		
			
				|  |  | +      return factory(root);
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +  } else if (typeof exports === 'object') {
 | 
	
		
			
				|  |  | +    module.exports = factory(root);
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    root.Polyglot = factory(root);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}(this, function(root) {
 | 
	
		
			
				|  |  | +  'use strict';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### Polyglot class constructor
 | 
	
		
			
				|  |  | +  function Polyglot(options) {
 | 
	
		
			
				|  |  | +    options = options || {};
 | 
	
		
			
				|  |  | +    this.phrases = {};
 | 
	
		
			
				|  |  | +    this.extend(options.phrases || {});
 | 
	
		
			
				|  |  | +    this.currentLocale = options.locale || 'en';
 | 
	
		
			
				|  |  | +    this.allowMissing = !!options.allowMissing;
 | 
	
		
			
				|  |  | +    this.warn = options.warn || warn;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### Version
 | 
	
		
			
				|  |  | +  Polyglot.VERSION = '0.4.3';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### polyglot.locale([locale])
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Get or set locale. Internally, Polyglot only uses locale for pluralization.
 | 
	
		
			
				|  |  | +  Polyglot.prototype.locale = function(newLocale) {
 | 
	
		
			
				|  |  | +    if (newLocale) this.currentLocale = newLocale;
 | 
	
		
			
				|  |  | +    return this.currentLocale;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### polyglot.extend(phrases)
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Use `extend` to tell Polyglot how to translate a given key.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     polyglot.extend({
 | 
	
		
			
				|  |  | +  //       "hello": "Hello",
 | 
	
		
			
				|  |  | +  //       "hello_name": "Hello, %{name}"
 | 
	
		
			
				|  |  | +  //     });
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // The key can be any string.  Feel free to call `extend` multiple times;
 | 
	
		
			
				|  |  | +  // it will override any phrases with the same key, but leave existing phrases
 | 
	
		
			
				|  |  | +  // untouched.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // It is also possible to pass nested phrase objects, which get flattened
 | 
	
		
			
				|  |  | +  // into an object with the nested keys concatenated using dot notation.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     polyglot.extend({
 | 
	
		
			
				|  |  | +  //       "nav": {
 | 
	
		
			
				|  |  | +  //         "hello": "Hello",
 | 
	
		
			
				|  |  | +  //         "hello_name": "Hello, %{name}",
 | 
	
		
			
				|  |  | +  //         "sidebar": {
 | 
	
		
			
				|  |  | +  //           "welcome": "Welcome"
 | 
	
		
			
				|  |  | +  //         }
 | 
	
		
			
				|  |  | +  //       }
 | 
	
		
			
				|  |  | +  //     });
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     console.log(polyglot.phrases);
 | 
	
		
			
				|  |  | +  //     // {
 | 
	
		
			
				|  |  | +  //     //   'nav.hello': 'Hello',
 | 
	
		
			
				|  |  | +  //     //   'nav.hello_name': 'Hello, %{name}',
 | 
	
		
			
				|  |  | +  //     //   'nav.sidebar.welcome': 'Welcome'
 | 
	
		
			
				|  |  | +  //     // }
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // `extend` accepts an optional second argument, `prefix`, which can be used
 | 
	
		
			
				|  |  | +  // to prefix every key in the phrases object with some string, using dot
 | 
	
		
			
				|  |  | +  // notation.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     polyglot.extend({
 | 
	
		
			
				|  |  | +  //       "hello": "Hello",
 | 
	
		
			
				|  |  | +  //       "hello_name": "Hello, %{name}"
 | 
	
		
			
				|  |  | +  //     }, "nav");
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     console.log(polyglot.phrases);
 | 
	
		
			
				|  |  | +  //     // {
 | 
	
		
			
				|  |  | +  //     //   'nav.hello': 'Hello',
 | 
	
		
			
				|  |  | +  //     //   'nav.hello_name': 'Hello, %{name}'
 | 
	
		
			
				|  |  | +  //     // }
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // This feature is used internally to support nested phrase objects.
 | 
	
		
			
				|  |  | +  Polyglot.prototype.extend = function(morePhrases, prefix) {
 | 
	
		
			
				|  |  | +    var phrase;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    for (var key in morePhrases) {
 | 
	
		
			
				|  |  | +      if (morePhrases.hasOwnProperty(key)) {
 | 
	
		
			
				|  |  | +        phrase = morePhrases[key];
 | 
	
		
			
				|  |  | +        if (prefix) key = prefix + '.' + key;
 | 
	
		
			
				|  |  | +        if (typeof phrase === 'object') {
 | 
	
		
			
				|  |  | +          this.extend(phrase, key);
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          this.phrases[key] = phrase;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### polyglot.clear()
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Clears all phrases. Useful for special cases, such as freeing
 | 
	
		
			
				|  |  | +  // up memory if you have lots of phrases but no longer need to
 | 
	
		
			
				|  |  | +  // perform any translation. Also used internally by `replace`.
 | 
	
		
			
				|  |  | +  Polyglot.prototype.clear = function() {
 | 
	
		
			
				|  |  | +    this.phrases = {};
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### polyglot.replace(phrases)
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Completely replace the existing phrases with a new set of phrases.
 | 
	
		
			
				|  |  | +  // Normally, just use `extend` to add more phrases, but under certain
 | 
	
		
			
				|  |  | +  // circumstances, you may want to make sure no old phrases are lying around.
 | 
	
		
			
				|  |  | +  Polyglot.prototype.replace = function(newPhrases) {
 | 
	
		
			
				|  |  | +    this.clear();
 | 
	
		
			
				|  |  | +    this.extend(newPhrases);
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### polyglot.t(key, options)
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // The most-used method. Provide a key, and `t` will return the
 | 
	
		
			
				|  |  | +  // phrase.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     polyglot.t("hello");
 | 
	
		
			
				|  |  | +  //     => "Hello"
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // The phrase value is provided first by a call to `polyglot.extend()` or
 | 
	
		
			
				|  |  | +  // `polyglot.replace()`.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Pass in an object as the second argument to perform interpolation.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     polyglot.t("hello_name", {name: "Spike"});
 | 
	
		
			
				|  |  | +  //     => "Hello, Spike"
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // If you like, you can provide a default value in case the phrase is missing.
 | 
	
		
			
				|  |  | +  // Use the special option key "_" to specify a default.
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  //     polyglot.t("i_like_to_write_in_language", {
 | 
	
		
			
				|  |  | +  //       _: "I like to write in %{language}.",
 | 
	
		
			
				|  |  | +  //       language: "JavaScript"
 | 
	
		
			
				|  |  | +  //     });
 | 
	
		
			
				|  |  | +  //     => "I like to write in JavaScript."
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  Polyglot.prototype.t = function(key, options) {
 | 
	
		
			
				|  |  | +    var phrase, result;
 | 
	
		
			
				|  |  | +    options = options == null ? {} : options;
 | 
	
		
			
				|  |  | +    // allow number as a pluralization shortcut
 | 
	
		
			
				|  |  | +    if (typeof options === 'number') {
 | 
	
		
			
				|  |  | +      options = {smart_count: options};
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (typeof this.phrases[key] === 'string') {
 | 
	
		
			
				|  |  | +      phrase = this.phrases[key];
 | 
	
		
			
				|  |  | +    } else if (typeof options._ === 'string') {
 | 
	
		
			
				|  |  | +      phrase = options._;
 | 
	
		
			
				|  |  | +    } else if (this.allowMissing) {
 | 
	
		
			
				|  |  | +      phrase = key;
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      this.warn('Missing translation for key: "'+key+'"');
 | 
	
		
			
				|  |  | +      result = key;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    if (typeof phrase === 'string') {
 | 
	
		
			
				|  |  | +      options = clone(options);
 | 
	
		
			
				|  |  | +      result = choosePluralForm(phrase, this.currentLocale, options.smart_count);
 | 
	
		
			
				|  |  | +      result = interpolate(result, options);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### polyglot.has(key)
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Check if polyglot has a translation for given key
 | 
	
		
			
				|  |  | +  Polyglot.prototype.has = function(key) {
 | 
	
		
			
				|  |  | +    return key in this.phrases;
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // #### Pluralization methods
 | 
	
		
			
				|  |  | +  // The string that separates the different phrase possibilities.
 | 
	
		
			
				|  |  | +  var delimeter = '||||';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Mapping from pluralization group plural logic.
 | 
	
		
			
				|  |  | +  var pluralTypes = {
 | 
	
		
			
				|  |  | +    chinese:   function(n) { return 0; },
 | 
	
		
			
				|  |  | +    german:    function(n) { return n !== 1 ? 1 : 0; },
 | 
	
		
			
				|  |  | +    french:    function(n) { return n > 1 ? 1 : 0; },
 | 
	
		
			
				|  |  | +    russian:   function(n) { return n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; },
 | 
	
		
			
				|  |  | +    czech:     function(n) { return (n === 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; },
 | 
	
		
			
				|  |  | +    polish:    function(n) { return (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); },
 | 
	
		
			
				|  |  | +    icelandic: function(n) { return (n % 10 !== 1 || n % 100 === 11) ? 1 : 0; }
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Mapping from pluralization group to individual locales.
 | 
	
		
			
				|  |  | +  var pluralTypeToLanguages = {
 | 
	
		
			
				|  |  | +    chinese:   ['fa', 'id', 'ja', 'ko', 'lo', 'ms', 'th', 'tr', 'zh'],
 | 
	
		
			
				|  |  | +    german:    ['da', 'de', 'en', 'es', 'fi', 'el', 'he', 'hu', 'it', 'nl', 'no', 'pt', 'sv'],
 | 
	
		
			
				|  |  | +    french:    ['fr', 'tl', 'pt-br'],
 | 
	
		
			
				|  |  | +    russian:   ['hr', 'ru'],
 | 
	
		
			
				|  |  | +    czech:     ['cs'],
 | 
	
		
			
				|  |  | +    polish:    ['pl'],
 | 
	
		
			
				|  |  | +    icelandic: ['is']
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function langToTypeMap(mapping) {
 | 
	
		
			
				|  |  | +    var type, langs, l, ret = {};
 | 
	
		
			
				|  |  | +    for (type in mapping) {
 | 
	
		
			
				|  |  | +      if (mapping.hasOwnProperty(type)) {
 | 
	
		
			
				|  |  | +        langs = mapping[type];
 | 
	
		
			
				|  |  | +        for (l in langs) {
 | 
	
		
			
				|  |  | +          ret[langs[l]] = type;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return ret;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Trim a string.
 | 
	
		
			
				|  |  | +  function trim(str){
 | 
	
		
			
				|  |  | +    var trimRe = /^\s+|\s+$/g;
 | 
	
		
			
				|  |  | +    return str.replace(trimRe, '');
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // Based on a phrase text that contains `n` plural forms separated
 | 
	
		
			
				|  |  | +  // by `delimeter`, a `locale`, and a `count`, choose the correct
 | 
	
		
			
				|  |  | +  // plural form, or none if `count` is `null`.
 | 
	
		
			
				|  |  | +  function choosePluralForm(text, locale, count){
 | 
	
		
			
				|  |  | +    var ret, texts, chosenText;
 | 
	
		
			
				|  |  | +    if (count != null && text) {
 | 
	
		
			
				|  |  | +      texts = text.split(delimeter);
 | 
	
		
			
				|  |  | +      chosenText = texts[pluralTypeIndex(locale, count)] || texts[0];
 | 
	
		
			
				|  |  | +      ret = trim(chosenText);
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +      ret = text;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return ret;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function pluralTypeName(locale) {
 | 
	
		
			
				|  |  | +    var langToPluralType = langToTypeMap(pluralTypeToLanguages);
 | 
	
		
			
				|  |  | +    return langToPluralType[locale] || langToPluralType.en;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function pluralTypeIndex(locale, count) {
 | 
	
		
			
				|  |  | +    return pluralTypes[pluralTypeName(locale)](count);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### interpolate
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Does the dirty work. Creates a `RegExp` object for each
 | 
	
		
			
				|  |  | +  // interpolation placeholder.
 | 
	
		
			
				|  |  | +  function interpolate(phrase, options) {
 | 
	
		
			
				|  |  | +    for (var arg in options) {
 | 
	
		
			
				|  |  | +      if (arg !== '_' && options.hasOwnProperty(arg)) {
 | 
	
		
			
				|  |  | +        // We create a new `RegExp` each time instead of using a more-efficient
 | 
	
		
			
				|  |  | +        // string replace so that the same argument can be replaced multiple times
 | 
	
		
			
				|  |  | +        // in the same phrase.
 | 
	
		
			
				|  |  | +        phrase = phrase.replace(new RegExp('%\\{'+arg+'\\}', 'g'), options[arg]);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return phrase;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### warn
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Provides a warning in the console if a phrase key is missing.
 | 
	
		
			
				|  |  | +  function warn(message) {
 | 
	
		
			
				|  |  | +    root.console && root.console.warn && root.console.warn('WARNING: ' + message);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // ### clone
 | 
	
		
			
				|  |  | +  //
 | 
	
		
			
				|  |  | +  // Clone an object.
 | 
	
		
			
				|  |  | +  function clone(source) {
 | 
	
		
			
				|  |  | +    var ret = {};
 | 
	
		
			
				|  |  | +    for (var prop in source) {
 | 
	
		
			
				|  |  | +      ret[prop] = source[prop];
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return ret;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  return Polyglot;
 | 
	
		
			
				|  |  | +}));
 |