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