123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- // (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;
- }));
|