2021-05-01 18:05:45 +02:00
|
|
|
/*!
|
2016-12-04 19:32:23 +01:00
|
|
|
* jQuery Internationalization library
|
|
|
|
*
|
|
|
|
* Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar
|
|
|
|
*
|
|
|
|
* jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do
|
|
|
|
* anything special to choose one license or the other and you don't have to
|
|
|
|
* notify anyone which license you are using. You are free to use
|
|
|
|
* UniversalLanguageSelector in commercial projects as long as the copyright
|
|
|
|
* header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
|
|
|
|
*
|
|
|
|
* @licence GNU General Public Licence 2.0 or later
|
|
|
|
* @licence MIT License
|
|
|
|
*/
|
|
|
|
|
|
|
|
( function ( $ ) {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var MessageParser = function ( options ) {
|
|
|
|
this.options = $.extend( {}, $.i18n.parser.defaults, options );
|
2021-05-01 18:05:45 +02:00
|
|
|
this.language = $.i18n.languages[ String.locale ] || $.i18n.languages[ 'default' ];
|
2016-12-04 19:32:23 +01:00
|
|
|
this.emitter = $.i18n.parser.emitter;
|
|
|
|
};
|
|
|
|
|
|
|
|
MessageParser.prototype = {
|
|
|
|
|
|
|
|
constructor: MessageParser,
|
|
|
|
|
|
|
|
simpleParse: function ( message, parameters ) {
|
|
|
|
return message.replace( /\$(\d+)/g, function ( str, match ) {
|
|
|
|
var index = parseInt( match, 10 ) - 1;
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match;
|
2016-12-04 19:32:23 +01:00
|
|
|
} );
|
|
|
|
},
|
|
|
|
|
|
|
|
parse: function ( message, replacements ) {
|
|
|
|
if ( message.indexOf( '{{' ) < 0 ) {
|
|
|
|
return this.simpleParse( message, replacements );
|
|
|
|
}
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
this.emitter.language = $.i18n.languages[ $.i18n().locale ] ||
|
|
|
|
$.i18n.languages[ 'default' ];
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
return this.emitter.emit( this.ast( message ), replacements );
|
|
|
|
},
|
|
|
|
|
|
|
|
ast: function ( message ) {
|
|
|
|
var pipe, colon, backslash, anyCharacter, dollar, digits, regularLiteral,
|
|
|
|
regularLiteralWithoutBar, regularLiteralWithoutSpace, escapedOrLiteralWithoutBar,
|
|
|
|
escapedOrRegularLiteral, templateContents, templateName, openTemplate,
|
|
|
|
closeTemplate, expression, paramExpression, result,
|
|
|
|
pos = 0;
|
|
|
|
|
|
|
|
// Try parsers until one works, if none work return null
|
|
|
|
function choice( parserSyntax ) {
|
|
|
|
return function () {
|
|
|
|
var i, result;
|
|
|
|
|
|
|
|
for ( i = 0; i < parserSyntax.length; i++ ) {
|
2021-05-01 18:05:45 +02:00
|
|
|
result = parserSyntax[ i ]();
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
if ( result !== null ) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try several parserSyntax-es in a row.
|
|
|
|
// All must succeed; otherwise, return null.
|
|
|
|
// This is the only eager one.
|
|
|
|
function sequence( parserSyntax ) {
|
|
|
|
var i, res,
|
|
|
|
originalPos = pos,
|
|
|
|
result = [];
|
|
|
|
|
|
|
|
for ( i = 0; i < parserSyntax.length; i++ ) {
|
2021-05-01 18:05:45 +02:00
|
|
|
res = parserSyntax[ i ]();
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
if ( res === null ) {
|
|
|
|
pos = originalPos;
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push( res );
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the same parser over and over until it fails.
|
|
|
|
// Must succeed a minimum of n times; otherwise, return null.
|
|
|
|
function nOrMore( n, p ) {
|
|
|
|
return function () {
|
|
|
|
var originalPos = pos,
|
|
|
|
result = [],
|
|
|
|
parsed = p();
|
|
|
|
|
|
|
|
while ( parsed !== null ) {
|
|
|
|
result.push( parsed );
|
|
|
|
parsed = p();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( result.length < n ) {
|
|
|
|
pos = originalPos;
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helpers -- just make parserSyntax out of simpler JS builtin types
|
|
|
|
|
|
|
|
function makeStringParser( s ) {
|
|
|
|
var len = s.length;
|
|
|
|
|
|
|
|
return function () {
|
|
|
|
var result = null;
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
if ( message.slice( pos, pos + len ) === s ) {
|
2016-12-04 19:32:23 +01:00
|
|
|
result = s;
|
|
|
|
pos += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeRegexParser( regex ) {
|
|
|
|
return function () {
|
2021-05-01 18:05:45 +02:00
|
|
|
var matches = message.slice( pos ).match( regex );
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
if ( matches === null ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
pos += matches[ 0 ].length;
|
2016-12-04 19:32:23 +01:00
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return matches[ 0 ];
|
2016-12-04 19:32:23 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pipe = makeStringParser( '|' );
|
|
|
|
colon = makeStringParser( ':' );
|
|
|
|
backslash = makeStringParser( '\\' );
|
|
|
|
anyCharacter = makeRegexParser( /^./ );
|
|
|
|
dollar = makeStringParser( '$' );
|
|
|
|
digits = makeRegexParser( /^\d+/ );
|
2021-05-01 18:05:45 +02:00
|
|
|
regularLiteral = makeRegexParser( /^[^{}[\]$\\]/ );
|
|
|
|
regularLiteralWithoutBar = makeRegexParser( /^[^{}[\]$\\|]/ );
|
|
|
|
regularLiteralWithoutSpace = makeRegexParser( /^[^{}[\]$\s]/ );
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
// There is a general pattern:
|
|
|
|
// parse a thing;
|
|
|
|
// if it worked, apply transform,
|
|
|
|
// otherwise return null.
|
|
|
|
// But using this as a combinator seems to cause problems
|
|
|
|
// when combined with nOrMore().
|
|
|
|
// May be some scoping issue.
|
|
|
|
function transform( p, fn ) {
|
|
|
|
return function () {
|
|
|
|
var result = p();
|
|
|
|
|
|
|
|
return result === null ? null : fn( result );
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to define "literals" within template parameters. The pipe
|
|
|
|
// character is the parameter delimeter, so by default
|
|
|
|
// it is not a literal in the parameter
|
|
|
|
function literalWithoutBar() {
|
|
|
|
var result = nOrMore( 1, escapedOrLiteralWithoutBar )();
|
|
|
|
|
|
|
|
return result === null ? null : result.join( '' );
|
|
|
|
}
|
|
|
|
|
|
|
|
function literal() {
|
|
|
|
var result = nOrMore( 1, escapedOrRegularLiteral )();
|
|
|
|
|
|
|
|
return result === null ? null : result.join( '' );
|
|
|
|
}
|
|
|
|
|
|
|
|
function escapedLiteral() {
|
|
|
|
var result = sequence( [ backslash, anyCharacter ] );
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return result === null ? null : result[ 1 ];
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
choice( [ escapedLiteral, regularLiteralWithoutSpace ] );
|
|
|
|
escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] );
|
|
|
|
escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] );
|
|
|
|
|
|
|
|
function replacement() {
|
|
|
|
var result = sequence( [ dollar, digits ] );
|
|
|
|
|
|
|
|
if ( result === null ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return [ 'REPLACE', parseInt( result[ 1 ], 10 ) - 1 ];
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
templateName = transform(
|
|
|
|
// see $wgLegalTitleChars
|
|
|
|
// not allowing : due to the need to catch "PLURAL:$1"
|
2021-05-01 18:05:45 +02:00
|
|
|
makeRegexParser( /^[ !"$&'()*,./0-9;=?@A-Z^_`a-z~\x80-\xFF+-]+/ ),
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
function ( result ) {
|
|
|
|
return result.toString();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
function templateParam() {
|
|
|
|
var expr,
|
|
|
|
result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] );
|
|
|
|
|
|
|
|
if ( result === null ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
expr = result[ 1 ];
|
2016-12-04 19:32:23 +01:00
|
|
|
|
|
|
|
// use a "CONCAT" operator if there are multiple nodes,
|
|
|
|
// otherwise return the first node, raw.
|
2021-05-01 18:05:45 +02:00
|
|
|
return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[ 0 ];
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function templateWithReplacement() {
|
|
|
|
var result = sequence( [ templateName, colon, replacement ] );
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return result === null ? null : [ result[ 0 ], result[ 2 ] ];
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function templateWithOutReplacement() {
|
|
|
|
var result = sequence( [ templateName, colon, paramExpression ] );
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return result === null ? null : [ result[ 0 ], result[ 2 ] ];
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
templateContents = choice( [
|
|
|
|
function () {
|
|
|
|
var res = sequence( [
|
|
|
|
// templates can have placeholders for dynamic
|
|
|
|
// replacement eg: {{PLURAL:$1|one car|$1 cars}}
|
|
|
|
// or no placeholders eg:
|
|
|
|
// {{GRAMMAR:genitive|{{SITENAME}}}
|
|
|
|
choice( [ templateWithReplacement, templateWithOutReplacement ] ),
|
|
|
|
nOrMore( 0, templateParam )
|
|
|
|
] );
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return res === null ? null : res[ 0 ].concat( res[ 1 ] );
|
2016-12-04 19:32:23 +01:00
|
|
|
},
|
|
|
|
function () {
|
|
|
|
var res = sequence( [ templateName, nOrMore( 0, templateParam ) ] );
|
|
|
|
|
|
|
|
if ( res === null ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return [ res[ 0 ] ].concat( res[ 1 ] );
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
] );
|
|
|
|
|
|
|
|
openTemplate = makeStringParser( '{{' );
|
|
|
|
closeTemplate = makeStringParser( '}}' );
|
|
|
|
|
|
|
|
function template() {
|
|
|
|
var result = sequence( [ openTemplate, templateContents, closeTemplate ] );
|
|
|
|
|
2021-05-01 18:05:45 +02:00
|
|
|
return result === null ? null : result[ 1 ];
|
2016-12-04 19:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
expression = choice( [ template, replacement, literal ] );
|
|
|
|
paramExpression = choice( [ template, replacement, literalWithoutBar ] );
|
|
|
|
|
|
|
|
function start() {
|
|
|
|
var result = nOrMore( 0, expression )();
|
|
|
|
|
|
|
|
if ( result === null ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [ 'CONCAT' ].concat( result );
|
|
|
|
}
|
|
|
|
|
|
|
|
result = start();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For success, the pos must have gotten to the end of the input
|
|
|
|
* and returned a non-null.
|
2021-05-01 18:05:45 +02:00
|
|
|
* n.b. This is part of language infrastructure, so we do not throw an
|
|
|
|
* internationalizable message.
|
2016-12-04 19:32:23 +01:00
|
|
|
*/
|
|
|
|
if ( result === null || pos !== message.length ) {
|
|
|
|
throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + message );
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
$.extend( $.i18n.parser, new MessageParser() );
|
|
|
|
}( jQuery ) );
|