2 * jQuery Internationalization library
4 * Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar
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
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
;
25 MessageParser
.prototype = {
27 constructor: MessageParser
,
29 simpleParse: function ( message
, parameters
) {
30 return message
.replace( /\$(\d+)/g, function ( str
, match
) {
31 var index
= parseInt( match
, 10 ) - 1;
33 return parameters
[index
] !== undefined ? parameters
[index
] : '$' + match
;
37 parse: function ( message
, replacements
) {
38 if ( message
.indexOf( '{{' ) < 0 ) {
39 return this.simpleParse( message
, replacements
);
42 this.emitter
.language
= $.i18n
.languages
[$.i18n().locale
] ||
43 $.i18n
.languages
['default'];
45 return this.emitter
.emit( this.ast( message
), replacements
);
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
,
55 // Try parsers until one works, if none work return null
56 function choice( parserSyntax
) {
60 for ( i
= 0; i
< parserSyntax
.length
; i
++ ) {
61 result
= parserSyntax
[i
]();
63 if ( result
!== null ) {
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
) {
80 for ( i
= 0; i
< parserSyntax
.length
; i
++ ) {
81 res
= parserSyntax
[i
]();
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
) {
99 var originalPos
= pos
,
103 while ( parsed
!== null ) {
104 result
.push( parsed
);
108 if ( result
.length
< n
) {
118 // Helpers -- just make parserSyntax out of simpler JS builtin types
120 function makeStringParser( s
) {
126 if ( message
.substr( pos
, len
) === s
) {
135 function makeRegexParser( regex
) {
137 var matches
= message
.substr( pos
).match( regex
);
139 if ( matches
=== null ) {
143 pos
+= matches
[0].length
;
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]/ );
159 // There is a general pattern:
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
) {
170 return result
=== null ? null : fn( result
);
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
)();
180 return result
=== null ? null : result
.join( '' );
184 var result
= nOrMore( 1, escapedOrRegularLiteral
)();
186 return result
=== null ? null : result
.join( '' );
189 function escapedLiteral() {
190 var result
= sequence( [ backslash
, anyCharacter
] );
192 return result
=== null ? null : result
[1];
195 choice( [ escapedLiteral
, regularLiteralWithoutSpace
] );
196 escapedOrLiteralWithoutBar
= choice( [ escapedLiteral
, regularLiteralWithoutBar
] );
197 escapedOrRegularLiteral
= choice( [ escapedLiteral
, regularLiteral
] );
199 function replacement() {
200 var result
= sequence( [ dollar
, digits
] );
202 if ( result
=== null ) {
206 return [ 'REPLACE', parseInt( result
[1], 10 ) - 1 ];
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+\-]+/ ),
214 function ( result
) {
215 return result
.toString();
219 function templateParam() {
221 result
= sequence( [ pipe
, nOrMore( 0, paramExpression
) ] );
223 if ( result
=== null ) {
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];
234 function templateWithReplacement() {
235 var result
= sequence( [ templateName
, colon
, replacement
] );
237 return result
=== null ? null : [ result
[0], result
[2] ];
240 function templateWithOutReplacement() {
241 var result
= sequence( [ templateName
, colon
, paramExpression
] );
243 return result
=== null ? null : [ result
[0], result
[2] ];
246 templateContents
= choice( [
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
)
257 return res
=== null ? null : res
[0].concat( res
[1] );
260 var res
= sequence( [ templateName
, nOrMore( 0, templateParam
) ] );
262 if ( res
=== null ) {
266 return [ res
[0] ].concat( res
[1] );
270 openTemplate
= makeStringParser( '{{' );
271 closeTemplate
= makeStringParser( '}}' );
273 function template() {
274 var result
= sequence( [ openTemplate
, templateContents
, closeTemplate
] );
276 return result
=== null ? null : result
[1];
279 expression
= choice( [ template
, replacement
, literal
] );
280 paramExpression
= choice( [ template
, replacement
, literalWithoutBar
] );
283 var result
= nOrMore( 0, expression
)();
285 if ( result
=== null ) {
289 return [ 'CONCAT' ].concat( result
);
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.
299 if ( result
=== null || pos
!== message
.length
) {
300 throw new Error( 'Parse error at position ' + pos
.toString() + ' in input: ' + message
);
308 $.extend( $.i18n
.parser
, new MessageParser() );