polyglot.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. // (c) 2012 Airbnb, Inc.
  2. //
  3. // polyglot.js may be freely distributed under the terms of the BSD
  4. // license. For all licensing information, details, and documention:
  5. // http://airbnb.github.com/polyglot.js
  6. //
  7. //
  8. // Polyglot.js is an I18n helper library written in JavaScript, made to
  9. // work both in the browser and in Node. It provides a simple solution for
  10. // interpolation and pluralization, based off of Airbnb's
  11. // experience adding I18n functionality to its Backbone.js and Node apps.
  12. //
  13. // Polylglot is agnostic to your translation backend. It doesn't perform any
  14. // translation; it simply gives you a way to manage translated phrases from
  15. // your client- or server-side JavaScript application.
  16. //
  17. !function(root) {
  18. 'use strict';
  19. // ### Polyglot class constructor
  20. function Polyglot(options) {
  21. options = options || {};
  22. this.phrases = {};
  23. this.extend(options.phrases || {});
  24. this.currentLocale = options.locale || 'en';
  25. this.allowMissing = !!options.allowMissing;
  26. this.warn = options.warn || warn;
  27. }
  28. // ### Version
  29. Polyglot.VERSION = '0.4.1';
  30. // ### polyglot.locale([locale])
  31. //
  32. // Get or set locale. Internally, Polyglot only uses locale for pluralization.
  33. Polyglot.prototype.locale = function(newLocale) {
  34. if (newLocale) this.currentLocale = newLocale;
  35. return this.currentLocale;
  36. };
  37. // ### polyglot.extend(phrases)
  38. //
  39. // Use `extend` to tell Polyglot how to translate a given key.
  40. //
  41. // polyglot.extend({
  42. // "hello": "Hello",
  43. // "hello_name": "Hello, %{name}"
  44. // });
  45. //
  46. // The key can be any string. Feel free to call `extend` multiple times;
  47. // it will override any phrases with the same key, but leave existing phrases
  48. // untouched.
  49. //
  50. // It is also possible to pass nested phrase objects, which get flattened
  51. // into an object with the nested keys concatenated using dot notation.
  52. //
  53. // polyglot.extend({
  54. // "nav": {
  55. // "hello": "Hello",
  56. // "hello_name": "Hello, %{name}",
  57. // "sidebar": {
  58. // "welcome": "Welcome"
  59. // }
  60. // }
  61. // });
  62. //
  63. // console.log(polyglot.phrases);
  64. // // {
  65. // // 'nav.hello': 'Hello',
  66. // // 'nav.hello_name': 'Hello, %{name}',
  67. // // 'nav.sidebar.welcome': 'Welcome'
  68. // // }
  69. //
  70. // `extend` accepts an optional second argument, `prefix`, which can be used
  71. // to prefix every key in the phrases object with some string, using dot
  72. // notation.
  73. //
  74. // polyglot.extend({
  75. // "hello": "Hello",
  76. // "hello_name": "Hello, %{name}"
  77. // }, "nav");
  78. //
  79. // console.log(polyglot.phrases);
  80. // // {
  81. // // 'nav.hello': 'Hello',
  82. // // 'nav.hello_name': 'Hello, %{name}'
  83. // // }
  84. //
  85. // This feature is used internally to support nested phrase objects.
  86. Polyglot.prototype.extend = function(morePhrases, prefix) {
  87. var phrase;
  88. for (var key in morePhrases) {
  89. if (morePhrases.hasOwnProperty(key)) {
  90. phrase = morePhrases[key];
  91. if (prefix) key = prefix + '.' + key;
  92. if (typeof phrase === 'object') {
  93. this.extend(phrase, key);
  94. } else {
  95. this.phrases[key] = phrase;
  96. }
  97. }
  98. }
  99. };
  100. // ### polyglot.clear()
  101. //
  102. // Clears all phrases. Useful for special cases, such as freeing
  103. // up memory if you have lots of phrases but no longer need to
  104. // perform any translation. Also used internally by `replace`.
  105. Polyglot.prototype.clear = function() {
  106. this.phrases = {};
  107. };
  108. // ### polyglot.replace(phrases)
  109. //
  110. // Completely replace the existing phrases with a new set of phrases.
  111. // Normally, just use `extend` to add more phrases, but under certain
  112. // circumstances, you may want to make sure no old phrases are lying around.
  113. Polyglot.prototype.replace = function(newPhrases) {
  114. this.clear();
  115. this.extend(newPhrases);
  116. };
  117. // ### polyglot.t(key, options)
  118. //
  119. // The most-used method. Provide a key, and `t` will return the
  120. // phrase.
  121. //
  122. // polyglot.t("hello");
  123. // => "Hello"
  124. //
  125. // The phrase value is provided first by a call to `polyglot.extend()` or
  126. // `polyglot.replace()`.
  127. //
  128. // Pass in an object as the second argument to perform interpolation.
  129. //
  130. // polyglot.t("hello_name", {name: "Spike"});
  131. // => "Hello, Spike"
  132. //
  133. // If you like, you can provide a default value in case the phrase is missing.
  134. // Use the special option key "_" to specify a default.
  135. //
  136. // polyglot.t("i_like_to_write_in_language", {
  137. // _: "I like to write in %{language}.",
  138. // language: "JavaScript"
  139. // });
  140. // => "I like to write in JavaScript."
  141. //
  142. Polyglot.prototype.t = function(key, options) {
  143. var result;
  144. options = options == null ? {} : options;
  145. // allow number as a pluralization shortcut
  146. if (typeof options === 'number') {
  147. options = {smart_count: options};
  148. }
  149. var phrase = this.phrases[key] || options._ || (this.allowMissing ? key : '');
  150. if (phrase === '') {
  151. this.warn('Missing translation for key: "'+key+'"');
  152. result = key;
  153. } else {
  154. options = clone(options);
  155. result = choosePluralForm(phrase, this.currentLocale, options.smart_count);
  156. result = interpolate(result, options);
  157. }
  158. return result;
  159. };
  160. // #### Pluralization methods
  161. // The string that separates the different phrase possibilities.
  162. var delimeter = '||||';
  163. // Mapping from pluralization group plural logic.
  164. var pluralTypes = {
  165. chinese: function(n) { return 0; },
  166. german: function(n) { return n !== 1 ? 1 : 0; },
  167. french: function(n) { return n > 1 ? 1 : 0; },
  168. 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; },
  169. czech: function(n) { return (n === 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; },
  170. polish: function(n) { return (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); },
  171. icelandic: function(n) { return (n % 10 !== 1 || n % 100 === 11) ? 1 : 0; }
  172. };
  173. // Mapping from pluralization group to individual locales.
  174. var pluralTypeToLanguages = {
  175. chinese: ['fa', 'id', 'ja', 'ko', 'lo', 'ms', 'th', 'tr', 'zh'],
  176. german: ['da', 'de', 'en', 'es', 'fi', 'el', 'he', 'hu', 'it', 'nl', 'no', 'pt', 'sv'],
  177. french: ['fr', 'tl', 'pt-br'],
  178. russian: ['hr', 'ru'],
  179. czech: ['cs'],
  180. polish: ['pl'],
  181. icelandic: ['is']
  182. };
  183. function langToTypeMap(mapping) {
  184. var type, langs, l, ret = {};
  185. for (type in mapping) {
  186. if (mapping.hasOwnProperty(type)) {
  187. langs = mapping[type];
  188. for (l in langs) {
  189. ret[langs[l]] = type;
  190. }
  191. }
  192. }
  193. return ret;
  194. }
  195. // Trim a string.
  196. function trim(str){
  197. var trimRe = /^\s+|\s+$/g;
  198. return str.replace(trimRe, '');
  199. }
  200. // Based on a phrase text that contains `n` plural forms separated
  201. // by `delimeter`, a `locale`, and a `count`, choose the correct
  202. // plural form, or none if `count` is `null`.
  203. function choosePluralForm(text, locale, count){
  204. var ret, texts, chosenText;
  205. if (count != null && text) {
  206. texts = text.split(delimeter);
  207. chosenText = texts[pluralTypeIndex(locale, count)] || texts[0];
  208. ret = trim(chosenText);
  209. } else {
  210. ret = text;
  211. }
  212. return ret;
  213. }
  214. function pluralTypeName(locale) {
  215. var langToPluralType = langToTypeMap(pluralTypeToLanguages);
  216. return langToPluralType[locale] || langToPluralType.en;
  217. }
  218. function pluralTypeIndex(locale, count) {
  219. return pluralTypes[pluralTypeName(locale)](count);
  220. }
  221. // ### interpolate
  222. //
  223. // Does the dirty work. Creates a `RegExp` object for each
  224. // interpolation placeholder.
  225. function interpolate(phrase, options) {
  226. for (var arg in options) {
  227. if (arg !== '_' && options.hasOwnProperty(arg)) {
  228. // We create a new `RegExp` each time instead of using a more-efficient
  229. // string replace so that the same argument can be replaced multiple times
  230. // in the same phrase.
  231. phrase = phrase.replace(new RegExp('%\\{'+arg+'\\}', 'g'), options[arg]);
  232. }
  233. }
  234. return phrase;
  235. }
  236. // ### warn
  237. //
  238. // Provides a warning in the console if a phrase key is missing.
  239. function warn(message) {
  240. root.console && root.console.warn && root.console.warn('WARNING: ' + message);
  241. }
  242. // ### clone
  243. //
  244. // Clone an object.
  245. function clone(source) {
  246. var ret = {};
  247. for (var prop in source) {
  248. ret[prop] = source[prop];
  249. }
  250. return ret;
  251. }
  252. // Export for Node, attach to `window` for browser.
  253. if (typeof module !== 'undefined' && module.exports) {
  254. module.exports = Polyglot;
  255. } else {
  256. root.Polyglot = Polyglot;
  257. }
  258. }(this);