|
@@ -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;
|
|
|
+}));
|