1
|
|
|
/*! |
2
|
|
|
* jQuery Internationalization library |
3
|
|
|
* |
4
|
|
|
* Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar |
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 MessageParser = function ( options ) { |
20
|
|
|
this.options = $.extend( {}, $.i18n.parser.defaults, options ); |
21
|
|
|
this.language = $.i18n.languages[ String.locale ] || $.i18n.languages[ 'default' ]; |
22
|
|
|
this.emitter = $.i18n.parser.emitter; |
23
|
|
|
}; |
24
|
|
|
|
25
|
|
|
MessageParser.prototype = { |
26
|
|
|
|
27
|
|
|
constructor: MessageParser, |
28
|
|
|
|
29
|
|
|
simpleParse: function ( message, parameters ) { |
30
|
|
|
return message.replace( /\$(\d+)/g, function ( str, match ) { |
31
|
|
|
var index = parseInt( match, 10 ) - 1; |
32
|
|
|
|
33
|
|
|
return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match; |
34
|
|
|
} ); |
35
|
|
|
}, |
36
|
|
|
|
37
|
|
|
parse: function ( message, replacements ) { |
38
|
|
|
if ( message.indexOf( '{{' ) < 0 ) { |
39
|
|
|
return this.simpleParse( message, replacements ); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
this.emitter.language = $.i18n.languages[ $.i18n().locale ] || |
43
|
|
|
$.i18n.languages[ 'default' ]; |
44
|
|
|
|
45
|
|
|
return this.emitter.emit( this.ast( message ), replacements ); |
46
|
|
|
}, |
47
|
|
|
|
48
|
|
|
ast: function ( message ) { |
49
|
|
|
var pipe, colon, backslash, anyCharacter, dollar, digits, regularLiteral, |
50
|
|
|
regularLiteralWithoutBar, regularLiteralWithoutSpace, escapedOrLiteralWithoutBar, |
51
|
|
|
escapedOrRegularLiteral, templateContents, templateName, openTemplate, |
52
|
|
|
closeTemplate, expression, paramExpression, result, |
53
|
|
|
pos = 0; |
54
|
|
|
|
55
|
|
|
// Try parsers until one works, if none work return null |
56
|
|
|
function choice( parserSyntax ) { |
57
|
|
|
return function () { |
58
|
|
|
var i, result; |
59
|
|
|
|
60
|
|
|
for ( i = 0; i < parserSyntax.length; i++ ) { |
61
|
|
|
result = parserSyntax[ i ](); |
62
|
|
|
|
63
|
|
|
if ( result !== null ) { |
64
|
|
|
return result; |
65
|
|
|
} |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
return null; |
69
|
|
|
}; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
// Try several parserSyntax-es in a row. |
73
|
|
|
// All must succeed; otherwise, return null. |
74
|
|
|
// This is the only eager one. |
75
|
|
|
function sequence( parserSyntax ) { |
76
|
|
|
var i, res, |
77
|
|
|
originalPos = pos, |
78
|
|
|
result = []; |
79
|
|
|
|
80
|
|
|
for ( i = 0; i < parserSyntax.length; i++ ) { |
81
|
|
|
res = parserSyntax[ i ](); |
82
|
|
|
|
83
|
|
|
if ( res === null ) { |
84
|
|
|
pos = originalPos; |
85
|
|
|
|
86
|
|
|
return null; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
result.push( res ); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
return result; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
// Run the same parser over and over until it fails. |
96
|
|
|
// Must succeed a minimum of n times; otherwise, return null. |
97
|
|
|
function nOrMore( n, p ) { |
98
|
|
|
return function () { |
99
|
|
|
var originalPos = pos, |
100
|
|
|
result = [], |
101
|
|
|
parsed = p(); |
102
|
|
|
|
103
|
|
|
while ( parsed !== null ) { |
104
|
|
|
result.push( parsed ); |
105
|
|
|
parsed = p(); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
if ( result.length < n ) { |
109
|
|
|
pos = originalPos; |
110
|
|
|
|
111
|
|
|
return null; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return result; |
115
|
|
|
}; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// Helpers -- just make parserSyntax out of simpler JS builtin types |
119
|
|
|
|
120
|
|
|
function makeStringParser( s ) { |
121
|
|
|
var len = s.length; |
122
|
|
|
|
123
|
|
|
return function () { |
124
|
|
|
var result = null; |
125
|
|
|
|
126
|
|
|
if ( message.slice( pos, pos + len ) === s ) { |
127
|
|
|
result = s; |
128
|
|
|
pos += len; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
return result; |
132
|
|
|
}; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
function makeRegexParser( regex ) { |
136
|
|
|
return function () { |
137
|
|
|
var matches = message.slice( pos ).match( regex ); |
138
|
|
|
|
139
|
|
|
if ( matches === null ) { |
140
|
|
|
return null; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
pos += matches[ 0 ].length; |
144
|
|
|
|
145
|
|
|
return matches[ 0 ]; |
146
|
|
|
}; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
pipe = makeStringParser( '|' ); |
150
|
|
|
colon = makeStringParser( ':' ); |
151
|
|
|
backslash = makeStringParser( '\\' ); |
152
|
|
|
anyCharacter = makeRegexParser( /^./ ); |
153
|
|
|
dollar = makeStringParser( '$' ); |
154
|
|
|
digits = makeRegexParser( /^\d+/ ); |
155
|
|
|
regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ ); |
156
|
|
|
regularLiteralWithoutBar = makeRegexParser( /^[^{}\[\]$\\|]/ ); |
157
|
|
|
regularLiteralWithoutSpace = makeRegexParser( /^[^{}\[\]$\s]/ ); |
158
|
|
|
|
159
|
|
|
// There is a general pattern: |
160
|
|
|
// parse a thing; |
161
|
|
|
// if it worked, apply transform, |
162
|
|
|
// otherwise return null. |
163
|
|
|
// But using this as a combinator seems to cause problems |
164
|
|
|
// when combined with nOrMore(). |
165
|
|
|
// May be some scoping issue. |
166
|
|
|
function transform( p, fn ) { |
167
|
|
|
return function () { |
168
|
|
|
var result = p(); |
169
|
|
|
|
170
|
|
|
return result === null ? null : fn( result ); |
171
|
|
|
}; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
// Used to define "literals" within template parameters. The pipe |
175
|
|
|
// character is the parameter delimeter, so by default |
176
|
|
|
// it is not a literal in the parameter |
177
|
|
|
function literalWithoutBar() { |
178
|
|
|
var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); |
179
|
|
|
|
180
|
|
|
return result === null ? null : result.join( '' ); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
function literal() { |
184
|
|
|
var result = nOrMore( 1, escapedOrRegularLiteral )(); |
185
|
|
|
|
186
|
|
|
return result === null ? null : result.join( '' ); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
function escapedLiteral() { |
190
|
|
|
var result = sequence( [ backslash, anyCharacter ] ); |
191
|
|
|
|
192
|
|
|
return result === null ? null : result[ 1 ]; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
choice( [ escapedLiteral, regularLiteralWithoutSpace ] ); |
196
|
|
|
escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] ); |
197
|
|
|
escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] ); |
198
|
|
|
|
199
|
|
|
function replacement() { |
200
|
|
|
var result = sequence( [ dollar, digits ] ); |
201
|
|
|
|
202
|
|
|
if ( result === null ) { |
203
|
|
|
return null; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
return [ 'REPLACE', parseInt( result[ 1 ], 10 ) - 1 ]; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
templateName = transform( |
210
|
|
|
// see $wgLegalTitleChars |
211
|
|
|
// not allowing : due to the need to catch "PLURAL:$1" |
212
|
|
|
makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ), |
213
|
|
|
|
214
|
|
|
function ( result ) { |
215
|
|
|
return result.toString(); |
216
|
|
|
} |
217
|
|
|
); |
218
|
|
|
|
219
|
|
|
function templateParam() { |
220
|
|
|
var expr, |
221
|
|
|
result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] ); |
222
|
|
|
|
223
|
|
|
if ( result === null ) { |
224
|
|
|
return null; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
expr = result[ 1 ]; |
228
|
|
|
|
229
|
|
|
// use a "CONCAT" operator if there are multiple nodes, |
230
|
|
|
// otherwise return the first node, raw. |
231
|
|
|
return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[ 0 ]; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
function templateWithReplacement() { |
235
|
|
|
var result = sequence( [ templateName, colon, replacement ] ); |
236
|
|
|
|
237
|
|
|
return result === null ? null : [ result[ 0 ], result[ 2 ] ]; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
function templateWithOutReplacement() { |
241
|
|
|
var result = sequence( [ templateName, colon, paramExpression ] ); |
242
|
|
|
|
243
|
|
|
return result === null ? null : [ result[ 0 ], result[ 2 ] ]; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
templateContents = choice( [ |
247
|
|
|
function () { |
248
|
|
|
var res = sequence( [ |
249
|
|
|
// templates can have placeholders for dynamic |
250
|
|
|
// replacement eg: {{PLURAL:$1|one car|$1 cars}} |
251
|
|
|
// or no placeholders eg: |
252
|
|
|
// {{GRAMMAR:genitive|{{SITENAME}}} |
253
|
|
|
choice( [ templateWithReplacement, templateWithOutReplacement ] ), |
254
|
|
|
nOrMore( 0, templateParam ) |
255
|
|
|
] ); |
256
|
|
|
|
257
|
|
|
return res === null ? null : res[ 0 ].concat( res[ 1 ] ); |
258
|
|
|
}, |
259
|
|
|
function () { |
260
|
|
|
var res = sequence( [ templateName, nOrMore( 0, templateParam ) ] ); |
261
|
|
|
|
262
|
|
|
if ( res === null ) { |
263
|
|
|
return null; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
return [ res[ 0 ] ].concat( res[ 1 ] ); |
267
|
|
|
} |
268
|
|
|
] ); |
269
|
|
|
|
270
|
|
|
openTemplate = makeStringParser( '{{' ); |
271
|
|
|
closeTemplate = makeStringParser( '}}' ); |
272
|
|
|
|
273
|
|
|
function template() { |
274
|
|
|
var result = sequence( [ openTemplate, templateContents, closeTemplate ] ); |
275
|
|
|
|
276
|
|
|
return result === null ? null : result[ 1 ]; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
expression = choice( [ template, replacement, literal ] ); |
280
|
|
|
paramExpression = choice( [ template, replacement, literalWithoutBar ] ); |
281
|
|
|
|
282
|
|
|
function start() { |
283
|
|
|
var result = nOrMore( 0, expression )(); |
284
|
|
|
|
285
|
|
|
if ( result === null ) { |
286
|
|
|
return null; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
return [ 'CONCAT' ].concat( result ); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
result = start(); |
293
|
|
|
|
294
|
|
|
/* |
295
|
|
|
* For success, the pos must have gotten to the end of the input |
296
|
|
|
* and returned a non-null. |
297
|
|
|
* n.b. This is part of language infrastructure, so we do not throw an internationalizable message. |
298
|
|
|
*/ |
299
|
|
|
if ( result === null || pos !== message.length ) { |
300
|
|
|
throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + message ); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
return result; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
}; |
307
|
|
|
|
308
|
|
|
$.extend( $.i18n.parser, new MessageParser() ); |
309
|
|
|
}( jQuery ) ); |
310
|
|
|
|