2 * jQuery Internationalization library
4 * Copyright (C) 2012 Santhosh Thottingal
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.
12 * @licence GNU General Public Licence 2.0 or later
13 * @licence MIT License
20 slice
= Array
.prototype.slice
;
23 * @param {Object} options
25 I18N = function ( options
) {
27 this.options
= $.extend( {}, I18N
.defaults
, options
);
29 this.parser
= this.options
.parser
;
30 this.locale
= this.options
.locale
;
31 this.messageStore
= this.options
.messageStore
;
37 * Localize a given messageKey to a locale.
38 * @param {String} messageKey
39 * @return {String} Localized message
41 localize: function ( messageKey
) {
42 var localeParts
, localePartIndex
, locale
, fallbackIndex
,
43 tryingLocale
, message
;
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
;
55 tryingLocale
= localeParts
.slice( 0, localePartIndex
).join( '-' );
56 message
= this.messageStore
.get( tryingLocale
, messageKey
);
63 } while ( localePartIndex
);
65 if ( locale
=== 'en' ) {
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
+ ')' );
82 * Destroy the i18n instance.
84 destroy: function () {
85 $.removeData( document
, 'i18n' );
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>
93 * To load a localization file for a locale:
95 * load('path/to/de-messages.json', 'de' );
98 * To load a localization file from a directory:
100 * load('path/to/i18n/directory', 'de' );
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.
107 * A data object containing message key- message translation mappings
108 * can also be passed. Example:
110 * load( { 'hello' : 'Hello' }, optionalLocale );
113 * A source map containing key-value pair of languagename and locations
114 * can also be passed. Example:
117 * bn: 'i18n/bn.json',
118 * he: 'i18n/he.json',
123 * If the data argument is null/undefined/false,
124 * all cached messages for the i18n instance will get reset.
126 * @param {string|Object} source
127 * @param {string} locale Language tag
128 * @return {jQuery.Promise}
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
;
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'
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';
149 return this.load( sourceMap
);
151 return this.messageStore
.load( source
, locale
);
157 * Does parameter and magic word substitution.
159 * @param {string} key Message key
160 * @param {Array} parameters Message parameters
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
=== '' ) {
172 return this.parser
.parse( message
, parameters
);
177 * Process a message from the $.I18N instance
178 * for the current document, stored in jQuery.data(document).
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.
185 $.i18n = function ( key
, param1
) {
187 i18n
= $.data( document
, 'i18n' ),
188 options
= typeof key
=== 'object' && key
;
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
;
202 i18n
= new I18N( options
);
203 $.data( document
, 'i18n', i18n
);
206 if ( typeof key
=== 'string' ) {
207 if ( param1
!== undefined ) {
208 parameters
= slice
.call( arguments
, 1 );
213 return i18n
.parse( key
, parameters
);
215 // FIXME: remove this feature/bug.
220 $.fn
.i18n = function () {
221 var i18n
= $.data( document
, 'i18n' );
225 $.data( document
, 'i18n', i18n
);
228 return this.each( function () {
229 var $this = $( this ),
230 messageKey
= $this.data( 'i18n' ),
231 lBracket
, rBracket
, type
, key
;
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
) );
242 $this.attr( type
, i18n
.parse( key
) );
245 $this.text( i18n
.parse( messageKey
) );
248 $this.find( '[data-i18n]' ).i18n();
253 function getDefaultLocale() {
254 var nav
, locale
= $( 'html' ).attr( 'lang' );
257 if ( typeof window
.navigator
!== undefined ) {
258 nav
= window
.navigator
;
259 locale
= nav
.language
|| nav
.userLanguage
|| '';
267 $.i18n
.languages
= {};
268 $.i18n
.messageStore
= $.i18n
.messageStore
|| {};
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
;
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
);
288 locale
: getDefaultLocale(),
289 fallbackLocale
: 'en',
290 parser
: $.i18n
.parser
,
291 messageStore
: $.i18n
.messageStore
294 // Expose constructor
295 $.i18n
.constructor = I18N
;