apostrophe
Version:
Apostrophe is a user-friendly content management system. You'll need more than this core module. See apostrophenow.org to get started.
237 lines (212 loc) • 7.46 kB
JavaScript
// (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) {
'use strict';
// ### Polyglot class constructor
function Polyglot(options) {
options = options || {};
this.phrases = options.phrases || {};
this.currentLocale = options.locale || 'en';
this.allowMissing = !!options.allowMissing;
}
// ### Version
Polyglot.VERSION = '0.2.0';
// ### 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.
Polyglot.prototype.extend = function(morePhrases) {
for (var key in morePhrases) {
if (morePhrases.hasOwnProperty(key)) {
this.phrases[key] = morePhrases[key];
}
}
};
// ### 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 result;
options = options == null ? {} : options;
// allow number as a pluralization shortcut
if (typeof options === 'number') {
options = {smart_count: options};
}
var phrase = this.phrases[key] || options._ || (this.allowMissing ? key : '');
if (phrase === '') {
warn('Missing translation for key: "'+key+'"');
result = key;
} else {
options = clone(options);
result = choosePluralForm(phrase, this.currentLocale, options.smart_count);
result = interpolate(result, options);
}
return result;
};
// #### 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: ['id', 'ja', 'ko', 'ms', 'th', 'tr', 'zh'],
german: ['da', 'de', 'en', 'es', 'fi', 'el', 'he', 'hu', 'it', 'nl', 'no', 'pt', 'sv'],
french: ['fr', 'tl'],
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;
}
// Export for Node, attach to `window` for browser.
if (typeof module !== 'undefined' && module.exports) {
module.exports = Polyglot;
} else {
root.Polyglot = Polyglot;
}
}(this);