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
|
|
|
* @return {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
|
|
|
lBracket, rBracket, type, key; |
239
|
|
|
|
240
|
|
|
if ( messageKey ) { |
241
|
|
|
lBracket = messageKey.indexOf( '[' ); |
242
|
|
|
rBracket = messageKey.indexOf( ']' ); |
243
|
|
|
if ( lBracket !== -1 && rBracket !== -1 && lBracket < rBracket ) { |
244
|
|
|
type = messageKey.slice( lBracket + 1, rBracket ); |
245
|
|
|
key = messageKey.slice( rBracket + 1 ); |
246
|
|
|
if ( type === 'html' ) { |
247
|
|
|
$this.html( i18n.parse( key ) ); |
248
|
|
|
} else { |
249
|
|
|
$this.attr( type, i18n.parse( key ) ); |
250
|
|
|
} |
251
|
|
|
} else { |
252
|
|
|
$this.text( i18n.parse( messageKey ) ); |
253
|
|
|
} |
254
|
|
|
} else { |
255
|
|
|
$this.find( '[data-i18n]' ).i18n(); |
256
|
|
|
} |
257
|
|
|
} ); |
258
|
|
|
}; |
259
|
|
|
|
260
|
|
|
String.locale = String.locale || $( 'html' ).attr( 'lang' ); |
261
|
|
|
|
262
|
|
|
if ( !String.locale ) { |
263
|
|
|
if ( typeof window.navigator !== undefined ) { |
264
|
|
|
nav = window.navigator; |
265
|
|
|
String.locale = nav.language || nav.userLanguage || ''; |
266
|
|
|
} else { |
267
|
|
|
String.locale = ''; |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
$.i18n.languages = {}; |
272
|
|
|
$.i18n.messageStore = $.i18n.messageStore || {}; |
273
|
|
|
$.i18n.parser = { |
274
|
|
|
// The default parser only handles variable substitution |
275
|
|
|
parse: function ( message, parameters ) { |
276
|
|
|
return message.replace( /\$(\d+)/g, function ( str, match ) { |
277
|
|
|
var index = parseInt( match, 10 ) - 1; |
278
|
|
|
return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match; |
279
|
|
|
} ); |
280
|
|
|
}, |
281
|
|
|
emitter: {} |
282
|
|
|
}; |
283
|
|
|
$.i18n.fallbacks = {}; |
284
|
|
|
$.i18n.debug = false; |
285
|
|
|
$.i18n.log = function ( /* arguments */ ) { |
286
|
|
|
if ( window.console && $.i18n.debug ) { |
287
|
|
|
window.console.log.apply( window.console, arguments ); |
288
|
|
|
} |
289
|
|
|
}; |
290
|
|
|
/* Static members */ |
291
|
|
|
I18N.defaults = { |
292
|
|
|
locale: String.locale, |
293
|
|
|
fallbackLocale: 'en', |
294
|
|
|
parser: $.i18n.parser, |
295
|
|
|
messageStore: $.i18n.messageStore |
296
|
|
|
}; |
297
|
|
|
|
298
|
|
|
// Expose constructor |
299
|
|
|
$.i18n.constructor = I18N; |
300
|
|
|
}( jQuery ) ); |
301
|
|
|
|