Merge "Don't check namespace in SpecialWantedtemplates"
[lhc/web/wiklou.git] / resources / lib / jquery.i18n / src / jquery.i18n.js
1 /**
2 * jQuery Internationalization library
3 *
4 * Copyright (C) 2012 Santhosh Thottingal
5 *
6 * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do
7 * anything special to choose one license or the other and you don't have to
8 * notify anyone which license you are using. You are free to use
9 * UniversalLanguageSelector in commercial projects as long as the copyright
10 * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
11 *
12 * @licence GNU General Public Licence 2.0 or later
13 * @licence MIT License
14 */
15
16 ( function ( $ ) {
17 'use strict';
18
19 var nav, I18N,
20 slice = Array.prototype.slice;
21 /**
22 * @constructor
23 * @param {Object} options
24 */
25 I18N = function ( options ) {
26 // Load defaults
27 this.options = $.extend( {}, I18N.defaults, options );
28
29 this.parser = this.options.parser;
30 this.locale = this.options.locale;
31 this.messageStore = this.options.messageStore;
32 this.languages = {};
33
34 this.init();
35 };
36
37 I18N.prototype = {
38 /**
39 * Initialize by loading locales and setting up
40 * String.prototype.toLocaleString and String.locale.
41 */
42 init: function () {
43 var i18n = this;
44
45 // Set locale of String environment
46 String.locale = i18n.locale;
47
48 // Override String.localeString method
49 String.prototype.toLocaleString = function () {
50 var localeParts, localePartIndex, value, locale, fallbackIndex,
51 tryingLocale, message;
52
53 value = this.valueOf();
54 locale = i18n.locale;
55 fallbackIndex = 0;
56
57 while ( locale ) {
58 // Iterate through locales starting at most-specific until
59 // localization is found. As in fi-Latn-FI, fi-Latn and fi.
60 localeParts = locale.split( '-' );
61 localePartIndex = localeParts.length;
62
63 do {
64 tryingLocale = localeParts.slice( 0, localePartIndex ).join( '-' );
65 message = i18n.messageStore.get( tryingLocale, value );
66
67 if ( message ) {
68 return message;
69 }
70
71 localePartIndex--;
72 } while ( localePartIndex );
73
74 if ( locale === 'en' ) {
75 break;
76 }
77
78 locale = ( $.i18n.fallbacks[i18n.locale] && $.i18n.fallbacks[i18n.locale][fallbackIndex] ) ||
79 i18n.options.fallbackLocale;
80 $.i18n.log( 'Trying fallback locale for ' + i18n.locale + ': ' + locale );
81
82 fallbackIndex++;
83 }
84
85 // key not found
86 return '';
87 };
88 },
89
90 /*
91 * Destroy the i18n instance.
92 */
93 destroy: function () {
94 $.removeData( document, 'i18n' );
95 },
96
97 /**
98 * General message loading API This can take a URL string for
99 * the json formatted messages. Example:
100 * <code>load('path/to/all_localizations.json');</code>
101 *
102 * To load a localization file for a locale:
103 * <code>
104 * load('path/to/de-messages.json', 'de' );
105 * </code>
106 *
107 * To load a localization file from a directory:
108 * <code>
109 * load('path/to/i18n/directory', 'de' );
110 * </code>
111 * The above method has the advantage of fallback resolution.
112 * ie, it will automatically load the fallback locales for de.
113 * For most usecases, this is the recommended method.
114 * It is optional to have trailing slash at end.
115 *
116 * A data object containing message key- message translation mappings
117 * can also be passed. Example:
118 * <code>
119 * load( { 'hello' : 'Hello' }, optionalLocale );
120 * </code>
121 *
122 * A source map containing key-value pair of languagename and locations
123 * can also be passed. Example:
124 * <code>
125 * load( {
126 * bn: 'i18n/bn.json',
127 * he: 'i18n/he.json',
128 * en: 'i18n/en.json'
129 * } )
130 * </code>
131 *
132 * If the data argument is null/undefined/false,
133 * all cached messages for the i18n instance will get reset.
134 *
135 * @param {String|Object} source
136 * @param {String} locale Language tag
137 * @returns {jQuery.Promise}
138 */
139 load: function ( source, locale ) {
140 var fallbackLocales, locIndex, fallbackLocale, sourceMap = {};
141 if ( !source && !locale ) {
142 source = 'i18n/' + $.i18n().locale + '.json';
143 locale = $.i18n().locale;
144 }
145 if ( typeof source === 'string' &&
146 source.split( '.' ).pop() !== 'json'
147 ) {
148 // Load specified locale then check for fallbacks when directory is specified in load()
149 sourceMap[locale] = source + '/' + locale + '.json';
150 fallbackLocales = ( $.i18n.fallbacks[locale] || [] )
151 .concat( this.options.fallbackLocale );
152 for ( locIndex in fallbackLocales ) {
153 fallbackLocale = fallbackLocales[locIndex];
154 sourceMap[fallbackLocale] = source + '/' + fallbackLocale + '.json';
155 }
156 return this.load( sourceMap );
157 } else {
158 return this.messageStore.load( source, locale );
159 }
160
161 },
162
163 /**
164 * Does parameter and magic word substitution.
165 *
166 * @param {string} key Message key
167 * @param {Array} parameters Message parameters
168 * @return {string}
169 */
170 parse: function ( key, parameters ) {
171 var message = key.toLocaleString();
172 // FIXME: This changes the state of the I18N object,
173 // should probably not change the 'this.parser' but just
174 // pass it to the parser.
175 this.parser.language = $.i18n.languages[$.i18n().locale] || $.i18n.languages['default'];
176 if ( message === '' ) {
177 message = key;
178 }
179 return this.parser.parse( message, parameters );
180 }
181 };
182
183 /**
184 * Process a message from the $.I18N instance
185 * for the current document, stored in jQuery.data(document).
186 *
187 * @param {string} key Key of the message.
188 * @param {string} param1 [param...] Variadic list of parameters for {key}.
189 * @return {string|$.I18N} Parsed message, or if no key was given
190 * the instance of $.I18N is returned.
191 */
192 $.i18n = function ( key, param1 ) {
193 var parameters,
194 i18n = $.data( document, 'i18n' ),
195 options = typeof key === 'object' && key;
196
197 // If the locale option for this call is different then the setup so far,
198 // update it automatically. This doesn't just change the context for this
199 // call but for all future call as well.
200 // If there is no i18n setup yet, don't do this. It will be taken care of
201 // by the `new I18N` construction below.
202 // NOTE: It should only change language for this one call.
203 // Then cache instances of I18N somewhere.
204 if ( options && options.locale && i18n && i18n.locale !== options.locale ) {
205 String.locale = i18n.locale = options.locale;
206 }
207
208 if ( !i18n ) {
209 i18n = new I18N( options );
210 $.data( document, 'i18n', i18n );
211 }
212
213 if ( typeof key === 'string' ) {
214 if ( param1 !== undefined ) {
215 parameters = slice.call( arguments, 1 );
216 } else {
217 parameters = [];
218 }
219
220 return i18n.parse( key, parameters );
221 } else {
222 // FIXME: remove this feature/bug.
223 return i18n;
224 }
225 };
226
227 $.fn.i18n = function () {
228 var i18n = $.data( document, 'i18n' );
229
230 if ( !i18n ) {
231 i18n = new I18N();
232 $.data( document, 'i18n', i18n );
233 }
234 String.locale = i18n.locale;
235 return this.each( function () {
236 var $this = $( this ),
237 messageKey = $this.data( 'i18n' );
238
239 if ( messageKey ) {
240 $this.text( i18n.parse( messageKey ) );
241 } else {
242 $this.find( '[data-i18n]' ).i18n();
243 }
244 } );
245 };
246
247 String.locale = String.locale || $( 'html' ).attr( 'lang' );
248
249 if ( !String.locale ) {
250 if ( typeof window.navigator !== undefined ) {
251 nav = window.navigator;
252 String.locale = nav.language || nav.userLanguage || '';
253 } else {
254 String.locale = '';
255 }
256 }
257
258 $.i18n.languages = {};
259 $.i18n.messageStore = $.i18n.messageStore || {};
260 $.i18n.parser = {
261 // The default parser only handles variable substitution
262 parse: function ( message, parameters ) {
263 return message.replace( /\$(\d+)/g, function ( str, match ) {
264 var index = parseInt( match, 10 ) - 1;
265 return parameters[index] !== undefined ? parameters[index] : '$' + match;
266 } );
267 },
268 emitter: {}
269 };
270 $.i18n.fallbacks = {};
271 $.i18n.debug = false;
272 $.i18n.log = function ( /* arguments */ ) {
273 if ( window.console && $.i18n.debug ) {
274 window.console.log.apply( window.console, arguments );
275 }
276 };
277 /* Static members */
278 I18N.defaults = {
279 locale: String.locale,
280 fallbackLocale: 'en',
281 parser: $.i18n.parser,
282 messageStore: $.i18n.messageStore
283 };
284
285 // Expose constructor
286 $.i18n.constructor = I18N;
287 }( jQuery ) );