kopia lustrzana https://gitlab.com/gridtracker.org/gridtracker
added jquery.i18n
rodzic
9d31645823
commit
aa65ad9936
|
@ -0,0 +1,684 @@
|
|||
/**
|
||||
* cldrpluralparser.js
|
||||
* A parser engine for CLDR plural rules.
|
||||
*
|
||||
* Copyright 2012-2014 Santhosh Thottingal and other contributors
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
* @source https://github.com/santhoshtr/CLDRPluralRuleParser
|
||||
* @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
|
||||
* @author Timo Tijhof
|
||||
* @author Amir Aharoni
|
||||
*/
|
||||
|
||||
/**
|
||||
* Evaluates a plural rule in CLDR syntax for a number
|
||||
* @param {string} rule
|
||||
* @param {integer} number
|
||||
* @return {boolean} true if evaluation passed, false if evaluation failed.
|
||||
*/
|
||||
|
||||
// UMD returnExports https://github.com/umdjs/umd/blob/master/returnExports.js
|
||||
(function(root, factory)
|
||||
{
|
||||
if (typeof define === "function" && define.amd)
|
||||
{
|
||||
// AMD. Register as an anonymous module.
|
||||
define(factory);
|
||||
}
|
||||
else if (typeof exports === "object")
|
||||
{
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Browser globals (root is window)
|
||||
root.pluralRuleParser = factory();
|
||||
}
|
||||
}(this, function()
|
||||
{
|
||||
function pluralRuleParser(rule, number)
|
||||
{
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
|
||||
-----------------------------------------------------------------
|
||||
condition = and_condition ('or' and_condition)*
|
||||
('@integer' samples)?
|
||||
('@decimal' samples)?
|
||||
and_condition = relation ('and' relation)*
|
||||
relation = is_relation | in_relation | within_relation
|
||||
is_relation = expr 'is' ('not')? value
|
||||
in_relation = expr (('not')? 'in' | '=' | '!=') range_list
|
||||
within_relation = expr ('not')? 'within' range_list
|
||||
expr = operand (('mod' | '%') value)?
|
||||
operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
|
||||
range_list = (range | value) (',' range_list)*
|
||||
value = digit+
|
||||
digit = 0|1|2|3|4|5|6|7|8|9
|
||||
range = value'..'value
|
||||
samples = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
|
||||
sampleRange = decimalValue '~' decimalValue
|
||||
decimalValue = value ('.' value)?
|
||||
*/
|
||||
|
||||
// We don't evaluate the samples section of the rule. Ignore it.
|
||||
rule = rule.split("@")[0].replace(/^\s*/, "").replace(/\s*$/, "");
|
||||
|
||||
if (!rule.length)
|
||||
{
|
||||
// Empty rule or 'other' rule.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Indicates the current position in the rule as we parse through it.
|
||||
// Shared among all parsing functions below.
|
||||
var pos = 0,
|
||||
operand,
|
||||
expression,
|
||||
relation,
|
||||
result,
|
||||
whitespace = makeRegexParser(/^\s+/),
|
||||
value = makeRegexParser(/^\d+/),
|
||||
_n_ = makeStringParser("n"),
|
||||
_i_ = makeStringParser("i"),
|
||||
_f_ = makeStringParser("f"),
|
||||
_t_ = makeStringParser("t"),
|
||||
_v_ = makeStringParser("v"),
|
||||
_w_ = makeStringParser("w"),
|
||||
_is_ = makeStringParser("is"),
|
||||
_isnot_ = makeStringParser("is not"),
|
||||
_isnot_sign_ = makeStringParser("!="),
|
||||
_equal_ = makeStringParser("="),
|
||||
_mod_ = makeStringParser("mod"),
|
||||
_percent_ = makeStringParser("%"),
|
||||
_not_ = makeStringParser("not"),
|
||||
_in_ = makeStringParser("in"),
|
||||
_within_ = makeStringParser("within"),
|
||||
_range_ = makeStringParser(".."),
|
||||
_comma_ = makeStringParser(","),
|
||||
_or_ = makeStringParser("or"),
|
||||
_and_ = makeStringParser("and");
|
||||
|
||||
function debug()
|
||||
{
|
||||
// console.log.apply(console, arguments);
|
||||
}
|
||||
|
||||
debug("pluralRuleParser", rule, number);
|
||||
|
||||
// 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++)
|
||||
{
|
||||
result = parserSyntax[i]();
|
||||
|
||||
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, parserRes,
|
||||
originalPos = pos,
|
||||
result = [];
|
||||
|
||||
for (i = 0; i < parserSyntax.length; i++)
|
||||
{
|
||||
parserRes = parserSyntax[i]();
|
||||
|
||||
if (parserRes === null)
|
||||
{
|
||||
pos = originalPos;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
result.push(parserRes);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (rule.substr(pos, len) === s)
|
||||
{
|
||||
result = s;
|
||||
pos += len;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function makeRegexParser(regex)
|
||||
{
|
||||
return function()
|
||||
{
|
||||
var matches = rule.substr(pos).match(regex);
|
||||
|
||||
if (matches === null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
pos += matches[0].length;
|
||||
|
||||
return matches[0];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Integer digits of n.
|
||||
*/
|
||||
function i()
|
||||
{
|
||||
var result = _i_();
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed i", parseInt(number, 10));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = parseInt(number, 10);
|
||||
debug(" -- passed i ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of the source number (integer and decimals).
|
||||
*/
|
||||
function n()
|
||||
{
|
||||
var result = _n_();
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed n ", number);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = parseFloat(number, 10);
|
||||
debug(" -- passed n ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible fractional digits in n, with trailing zeros.
|
||||
*/
|
||||
function f()
|
||||
{
|
||||
var result = _f_();
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed f ", number);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = (number + ".").split(".")[1] || 0;
|
||||
debug(" -- passed f ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible fractional digits in n, without trailing zeros.
|
||||
*/
|
||||
function t()
|
||||
{
|
||||
var result = _t_();
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed t ", number);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = (number + ".").split(".")[1].replace(/0$/, "") || 0;
|
||||
debug(" -- passed t ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of visible fraction digits in n, with trailing zeros.
|
||||
*/
|
||||
function v()
|
||||
{
|
||||
var result = _v_();
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed v ", number);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = (number + ".").split(".")[1].length || 0;
|
||||
debug(" -- passed v ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of visible fraction digits in n, without trailing zeros.
|
||||
*/
|
||||
function w()
|
||||
{
|
||||
var result = _w_();
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed w ", number);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = (number + ".").split(".")[1].replace(/0$/, "").length || 0;
|
||||
debug(" -- passed w ", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
|
||||
operand = choice([n, i, f, t, v, w]);
|
||||
|
||||
// expr = operand (('mod' | '%') value)?
|
||||
expression = choice([mod, operand]);
|
||||
|
||||
function mod()
|
||||
{
|
||||
var result = sequence(
|
||||
[operand, whitespace, choice([_mod_, _percent_]), whitespace, value]
|
||||
);
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed mod");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
debug(" -- passed " + parseInt(result[0], 10) + " " + result[2] + " " + parseInt(result[4], 10));
|
||||
|
||||
return parseFloat(result[0]) % parseInt(result[4], 10);
|
||||
}
|
||||
|
||||
function not()
|
||||
{
|
||||
var result = sequence([whitespace, _not_]);
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
debug(" -- failed not");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return result[1];
|
||||
}
|
||||
|
||||
// is_relation = expr 'is' ('not')? value
|
||||
function is()
|
||||
{
|
||||
var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed is : " + result[0] + " == " + parseInt(result[4], 10));
|
||||
|
||||
return result[0] === parseInt(result[4], 10);
|
||||
}
|
||||
|
||||
debug(" -- failed is");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// is_relation = expr 'is' ('not')? value
|
||||
function isnot()
|
||||
{
|
||||
var result = sequence(
|
||||
[expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]
|
||||
);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed isnot: " + result[0] + " != " + parseInt(result[4], 10));
|
||||
|
||||
return result[0] !== parseInt(result[4], 10);
|
||||
}
|
||||
|
||||
debug(" -- failed isnot");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function not_in()
|
||||
{
|
||||
var i, range_list,
|
||||
result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed not_in: " + result[0] + " != " + result[4]);
|
||||
range_list = result[4];
|
||||
|
||||
for (i = 0; i < range_list.length; i++)
|
||||
{
|
||||
if (parseInt(range_list[i], 10) === parseInt(result[0], 10))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
debug(" -- failed not_in");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// range_list = (range | value) (',' range_list)*
|
||||
function rangeList()
|
||||
{
|
||||
var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]),
|
||||
resultList = [];
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
resultList = resultList.concat(result[0]);
|
||||
|
||||
if (result[1][0])
|
||||
{
|
||||
resultList = resultList.concat(result[1][0]);
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
debug(" -- failed rangeList");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function rangeTail()
|
||||
{
|
||||
// ',' range_list
|
||||
var result = sequence([_comma_, rangeList]);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
return result[1];
|
||||
}
|
||||
|
||||
debug(" -- failed rangeTail");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// range = value'..'value
|
||||
function range()
|
||||
{
|
||||
var i, array, left, right,
|
||||
result = sequence([value, _range_, value]);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed range");
|
||||
|
||||
array = [];
|
||||
left = parseInt(result[0], 10);
|
||||
right = parseInt(result[2], 10);
|
||||
|
||||
for (i = left; i <= right; i++)
|
||||
{
|
||||
array.push(i);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
debug(" -- failed range");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function _in()
|
||||
{
|
||||
var result, range_list, i;
|
||||
|
||||
// in_relation = expr ('not')? 'in' range_list
|
||||
result = sequence(
|
||||
[expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]
|
||||
);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed _in:" + result);
|
||||
|
||||
range_list = result[5];
|
||||
|
||||
for (i = 0; i < range_list.length; i++)
|
||||
{
|
||||
if (parseInt(range_list[i], 10) === parseFloat(result[0]))
|
||||
{
|
||||
return (result[1][0] !== "not");
|
||||
}
|
||||
}
|
||||
|
||||
return (result[1][0] === "not");
|
||||
}
|
||||
|
||||
debug(" -- failed _in ");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The difference between "in" and "within" is that
|
||||
* "in" only includes integers in the specified range,
|
||||
* while "within" includes all values.
|
||||
*/
|
||||
function within()
|
||||
{
|
||||
var range_list, result;
|
||||
|
||||
// within_relation = expr ('not')? 'within' range_list
|
||||
result = sequence(
|
||||
[expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]
|
||||
);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed within");
|
||||
|
||||
range_list = result[5];
|
||||
|
||||
if ((result[0] >= parseInt(range_list[0], 10)) &&
|
||||
(result[0] < parseInt(range_list[range_list.length - 1], 10)))
|
||||
{
|
||||
return (result[1][0] !== "not");
|
||||
}
|
||||
|
||||
return (result[1][0] === "not");
|
||||
}
|
||||
|
||||
debug(" -- failed within ");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// relation = is_relation | in_relation | within_relation
|
||||
relation = choice([is, not_in, isnot, _in, within]);
|
||||
|
||||
// and_condition = relation ('and' relation)*
|
||||
function and()
|
||||
{
|
||||
var i,
|
||||
result = sequence([relation, nOrMore(0, andTail)]);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (!result[0])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < result[1].length; i++)
|
||||
{
|
||||
if (!result[1][i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
debug(" -- failed and");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ('and' relation)*
|
||||
function andTail()
|
||||
{
|
||||
var result = sequence([whitespace, _and_, whitespace, relation]);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed andTail" + result);
|
||||
|
||||
return result[3];
|
||||
}
|
||||
|
||||
debug(" -- failed andTail");
|
||||
|
||||
return null;
|
||||
}
|
||||
// ('or' and_condition)*
|
||||
function orTail()
|
||||
{
|
||||
var result = sequence([whitespace, _or_, whitespace, and]);
|
||||
|
||||
if (result !== null)
|
||||
{
|
||||
debug(" -- passed orTail: " + result[3]);
|
||||
|
||||
return result[3];
|
||||
}
|
||||
|
||||
debug(" -- failed orTail");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// condition = and_condition ('or' and_condition)*
|
||||
function condition()
|
||||
{
|
||||
var i,
|
||||
result = sequence([and, nOrMore(0, orTail)]);
|
||||
|
||||
if (result)
|
||||
{
|
||||
for (i = 0; i < result[1].length; i++)
|
||||
{
|
||||
if (result[1][i])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
result = condition();
|
||||
|
||||
/**
|
||||
* For success, the pos must have gotten to the end of the rule
|
||||
* and returned a non-null.
|
||||
* n.b. This is part of language infrastructure,
|
||||
* so we do not throw an internationalizable message.
|
||||
*/
|
||||
if (result === null)
|
||||
{
|
||||
throw new Error("Parse error at position " + pos.toString() + " for rule: " + rule);
|
||||
}
|
||||
|
||||
if (pos !== rule.length)
|
||||
{
|
||||
debug("Warning: Rule not parsed completely. Parser stopped at " + rule.substr(0, pos) + " for rule: " + rule);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return pluralRuleParser;
|
||||
}));
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,185 @@
|
|||
/*!
|
||||
* 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 MessageParserEmitter = function ()
|
||||
{
|
||||
this.language = $.i18n.languages[String.locale] || $.i18n.languages.default;
|
||||
};
|
||||
|
||||
MessageParserEmitter.prototype = {
|
||||
constructor: MessageParserEmitter,
|
||||
|
||||
/**
|
||||
* (We put this method definition here, and not in prototype, to make
|
||||
* sure it's not overwritten by any magic.) Walk entire node structure,
|
||||
* applying replacements and template functions when appropriate
|
||||
*
|
||||
* @param {Mixed} node abstract syntax tree (top node or subnode)
|
||||
* @param {Array} replacements for $1, $2, ... $n
|
||||
* @return {Mixed} single-string node or array of nodes suitable for
|
||||
* jQuery appending.
|
||||
*/
|
||||
emit: function (node, replacements)
|
||||
{
|
||||
var ret, subnodes, operation,
|
||||
messageParserEmitter = this;
|
||||
|
||||
switch (typeof node)
|
||||
{
|
||||
case "string":
|
||||
case "number":
|
||||
ret = node;
|
||||
break;
|
||||
case "object":
|
||||
// node is an array of nodes
|
||||
subnodes = $.map(node.slice(1), function (n)
|
||||
{
|
||||
return messageParserEmitter.emit(n, replacements);
|
||||
});
|
||||
|
||||
operation = node[0].toLowerCase();
|
||||
|
||||
if (typeof messageParserEmitter[operation] === "function")
|
||||
{
|
||||
ret = messageParserEmitter[operation](subnodes, replacements);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error("unknown operation \"" + operation + "\"");
|
||||
}
|
||||
|
||||
break;
|
||||
case "undefined":
|
||||
// Parsing the empty string (as an entire expression, or as a
|
||||
// paramExpression in a template) results in undefined
|
||||
// Perhaps a more clever parser can detect this, and return the
|
||||
// empty string? Or is that useful information?
|
||||
// The logical thing is probably to return the empty string here
|
||||
// when we encounter undefined.
|
||||
ret = "";
|
||||
break;
|
||||
default:
|
||||
throw new Error("unexpected type in AST: " + typeof node);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parsing has been applied depth-first we can assume that all nodes
|
||||
* here are single nodes Must return a single node to parents -- a
|
||||
* jQuery with synthetic span However, unwrap any other synthetic spans
|
||||
* in our children and pass them upwards
|
||||
*
|
||||
* @param {Array} nodes Mixed, some single nodes, some arrays of nodes.
|
||||
* @return {string}
|
||||
*/
|
||||
concat: function (nodes)
|
||||
{
|
||||
var result = "";
|
||||
|
||||
$.each(nodes, function (i, node)
|
||||
{
|
||||
// strings, integers, anything else
|
||||
result += node;
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return escaped replacement of correct index, or string if
|
||||
* unavailable. Note that we expect the parsed parameter to be
|
||||
* zero-based. i.e. $1 should have become [ 0 ]. if the specified
|
||||
* parameter is not found return the same string (e.g. "$99" ->
|
||||
* parameter 98 -> not found -> return "$99" ) TODO throw error if
|
||||
* nodes.length > 1 ?
|
||||
*
|
||||
* @param {Array} nodes One element, integer, n >= 0
|
||||
* @param {Array} replacements for $1, $2, ... $n
|
||||
* @return {string} replacement
|
||||
*/
|
||||
replace: function (nodes, replacements)
|
||||
{
|
||||
var index = parseInt(nodes[0], 10);
|
||||
|
||||
if (index < replacements.length)
|
||||
{
|
||||
// replacement is not a string, don't touch!
|
||||
return replacements[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// index not found, fallback to displaying variable
|
||||
return "$" + (index + 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Transform parsed structure into pluralization n.b. The first node may
|
||||
* be a non-integer (for instance, a string representing an Arabic
|
||||
* number). So convert it back with the current language's
|
||||
* convertNumber.
|
||||
*
|
||||
* @param {Array} nodes List [ {String|Number}, {String}, {String} ... ]
|
||||
* @return {string} selected pluralized form according to current
|
||||
* language.
|
||||
*/
|
||||
plural: function (nodes)
|
||||
{
|
||||
var count = parseFloat(this.language.convertNumber(nodes[0], 10)),
|
||||
forms = nodes.slice(1);
|
||||
|
||||
return forms.length ? this.language.convertPlural(count, forms) : "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Transform parsed structure into gender Usage
|
||||
* {{gender:gender|masculine|feminine|neutral}}.
|
||||
*
|
||||
* @param {Array} nodes List [ {String}, {String}, {String} , {String} ]
|
||||
* @return {string} selected gender form according to current language
|
||||
*/
|
||||
gender: function (nodes)
|
||||
{
|
||||
var gender = nodes[0],
|
||||
forms = nodes.slice(1);
|
||||
|
||||
return this.language.gender(gender, forms);
|
||||
},
|
||||
|
||||
/**
|
||||
* Transform parsed structure into grammar conversion. Invoked by
|
||||
* putting {{grammar:form|word}} in a message
|
||||
*
|
||||
* @param {Array} nodes List [{Grammar case eg: genitive}, {String word}]
|
||||
* @return {string} selected grammatical form according to current
|
||||
* language.
|
||||
*/
|
||||
grammar: function (nodes)
|
||||
{
|
||||
var form = nodes[0],
|
||||
word = nodes[1];
|
||||
|
||||
return word && form && this.language.convertGrammar(word, form);
|
||||
}
|
||||
};
|
||||
|
||||
$.extend($.i18n.parser.emitter, new MessageParserEmitter());
|
||||
}(jQuery));
|
|
@ -0,0 +1,187 @@
|
|||
/*!
|
||||
* jQuery Internationalization library
|
||||
*
|
||||
* Copyright (C) 2012 Santhosh Thottingal
|
||||
*
|
||||
* 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";
|
||||
|
||||
$.i18n = $.i18n || {};
|
||||
$.extend($.i18n.fallbacks, {
|
||||
ab: ["ru"],
|
||||
ace: ["id"],
|
||||
aln: ["sq"],
|
||||
// Not so standard - als is supposed to be Tosk Albanian,
|
||||
// but in Wikipedia it's used for a Germanic language.
|
||||
als: ["gsw", "de"],
|
||||
an: ["es"],
|
||||
anp: ["hi"],
|
||||
arn: ["es"],
|
||||
arz: ["ar"],
|
||||
av: ["ru"],
|
||||
ay: ["es"],
|
||||
ba: ["ru"],
|
||||
bar: ["de"],
|
||||
"bat-smg": ["sgs", "lt"],
|
||||
bcc: ["fa"],
|
||||
"be-x-old": ["be-tarask"],
|
||||
bh: ["bho"],
|
||||
bjn: ["id"],
|
||||
bm: ["fr"],
|
||||
bpy: ["bn"],
|
||||
bqi: ["fa"],
|
||||
bug: ["id"],
|
||||
"cbk-zam": ["es"],
|
||||
ce: ["ru"],
|
||||
crh: ["crh-latn"],
|
||||
"crh-cyrl": ["ru"],
|
||||
csb: ["pl"],
|
||||
cv: ["ru"],
|
||||
"de-at": ["de"],
|
||||
"de-ch": ["de"],
|
||||
"de-formal": ["de"],
|
||||
dsb: ["de"],
|
||||
dtp: ["ms"],
|
||||
egl: ["it"],
|
||||
eml: ["it"],
|
||||
ff: ["fr"],
|
||||
fit: ["fi"],
|
||||
"fiu-vro": ["vro", "et"],
|
||||
frc: ["fr"],
|
||||
frp: ["fr"],
|
||||
frr: ["de"],
|
||||
fur: ["it"],
|
||||
gag: ["tr"],
|
||||
gan: ["gan-hant", "zh-hant", "zh-hans"],
|
||||
"gan-hans": ["zh-hans"],
|
||||
"gan-hant": ["zh-hant", "zh-hans"],
|
||||
gl: ["pt"],
|
||||
glk: ["fa"],
|
||||
gn: ["es"],
|
||||
gsw: ["de"],
|
||||
hif: ["hif-latn"],
|
||||
hsb: ["de"],
|
||||
ht: ["fr"],
|
||||
ii: ["zh-cn", "zh-hans"],
|
||||
inh: ["ru"],
|
||||
iu: ["ike-cans"],
|
||||
jut: ["da"],
|
||||
jv: ["id"],
|
||||
kaa: ["kk-latn", "kk-cyrl"],
|
||||
kbd: ["kbd-cyrl"],
|
||||
khw: ["ur"],
|
||||
kiu: ["tr"],
|
||||
kk: ["kk-cyrl"],
|
||||
"kk-arab": ["kk-cyrl"],
|
||||
"kk-latn": ["kk-cyrl"],
|
||||
"kk-cn": ["kk-arab", "kk-cyrl"],
|
||||
"kk-kz": ["kk-cyrl"],
|
||||
"kk-tr": ["kk-latn", "kk-cyrl"],
|
||||
kl: ["da"],
|
||||
"ko-kp": ["ko"],
|
||||
koi: ["ru"],
|
||||
krc: ["ru"],
|
||||
ks: ["ks-arab"],
|
||||
ksh: ["de"],
|
||||
ku: ["ku-latn"],
|
||||
"ku-arab": ["ckb"],
|
||||
kv: ["ru"],
|
||||
lad: ["es"],
|
||||
lb: ["de"],
|
||||
lbe: ["ru"],
|
||||
lez: ["ru"],
|
||||
li: ["nl"],
|
||||
lij: ["it"],
|
||||
liv: ["et"],
|
||||
lmo: ["it"],
|
||||
ln: ["fr"],
|
||||
ltg: ["lv"],
|
||||
lzz: ["tr"],
|
||||
mai: ["hi"],
|
||||
"map-bms": ["jv", "id"],
|
||||
mg: ["fr"],
|
||||
mhr: ["ru"],
|
||||
min: ["id"],
|
||||
mo: ["ro"],
|
||||
mrj: ["ru"],
|
||||
mwl: ["pt"],
|
||||
myv: ["ru"],
|
||||
mzn: ["fa"],
|
||||
nah: ["es"],
|
||||
nap: ["it"],
|
||||
nds: ["de"],
|
||||
"nds-nl": ["nl"],
|
||||
"nl-informal": ["nl"],
|
||||
no: ["nb"],
|
||||
os: ["ru"],
|
||||
pcd: ["fr"],
|
||||
pdc: ["de"],
|
||||
pdt: ["de"],
|
||||
pfl: ["de"],
|
||||
pms: ["it"],
|
||||
pt: ["pt-br"],
|
||||
"pt-br": ["pt"],
|
||||
qu: ["es"],
|
||||
qug: ["qu", "es"],
|
||||
rgn: ["it"],
|
||||
rmy: ["ro"],
|
||||
"roa-rup": ["rup"],
|
||||
rue: ["uk", "ru"],
|
||||
ruq: ["ruq-latn", "ro"],
|
||||
"ruq-cyrl": ["mk"],
|
||||
"ruq-latn": ["ro"],
|
||||
sa: ["hi"],
|
||||
sah: ["ru"],
|
||||
scn: ["it"],
|
||||
sg: ["fr"],
|
||||
sgs: ["lt"],
|
||||
sli: ["de"],
|
||||
sr: ["sr-ec"],
|
||||
srn: ["nl"],
|
||||
stq: ["de"],
|
||||
su: ["id"],
|
||||
szl: ["pl"],
|
||||
tcy: ["kn"],
|
||||
tg: ["tg-cyrl"],
|
||||
tt: ["tt-cyrl", "ru"],
|
||||
"tt-cyrl": ["ru"],
|
||||
ty: ["fr"],
|
||||
udm: ["ru"],
|
||||
ug: ["ug-arab"],
|
||||
uk: ["ru"],
|
||||
vec: ["it"],
|
||||
vep: ["et"],
|
||||
vls: ["nl"],
|
||||
vmf: ["de"],
|
||||
vot: ["fi"],
|
||||
vro: ["et"],
|
||||
wa: ["fr"],
|
||||
wo: ["fr"],
|
||||
wuu: ["zh-hans"],
|
||||
xal: ["ru"],
|
||||
xmf: ["ka"],
|
||||
yi: ["he"],
|
||||
za: ["zh-hans"],
|
||||
zea: ["nl"],
|
||||
zh: ["zh-hans"],
|
||||
"zh-classical": ["lzh"],
|
||||
"zh-cn": ["zh-hans"],
|
||||
"zh-hant": ["zh-hans"],
|
||||
"zh-hk": ["zh-hant", "zh-hans"],
|
||||
"zh-min-nan": ["nan"],
|
||||
"zh-mo": ["zh-hk", "zh-hant", "zh-hans"],
|
||||
"zh-my": ["zh-sg", "zh-hans"],
|
||||
"zh-sg": ["zh-hans"],
|
||||
"zh-tw": ["zh-hant", "zh-hans"],
|
||||
"zh-yue": ["yue"]
|
||||
});
|
||||
}(jQuery));
|
|
@ -0,0 +1,332 @@
|
|||
/*!
|
||||
* jQuery Internationalization library
|
||||
*
|
||||
* Copyright (C) 2012 Santhosh Thottingal
|
||||
*
|
||||
* 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 I18N,
|
||||
slice = Array.prototype.slice;
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
I18N = function (options)
|
||||
{
|
||||
// Load defaults
|
||||
this.options = $.extend({}, I18N.defaults, options);
|
||||
|
||||
this.parser = this.options.parser;
|
||||
this.locale = this.options.locale;
|
||||
this.messageStore = this.options.messageStore;
|
||||
this.languages = {};
|
||||
};
|
||||
|
||||
I18N.prototype = {
|
||||
/**
|
||||
* Localize a given messageKey to a locale.
|
||||
* @param {string} messageKey
|
||||
* @return {string} Localized message
|
||||
*/
|
||||
localize: function (messageKey)
|
||||
{
|
||||
var localeParts, localePartIndex, locale, fallbackIndex,
|
||||
tryingLocale, message;
|
||||
|
||||
locale = this.locale;
|
||||
fallbackIndex = 0;
|
||||
|
||||
while (locale)
|
||||
{
|
||||
// Iterate through locales starting at most-specific until
|
||||
// localization is found. As in fi-Latn-FI, fi-Latn and fi.
|
||||
localeParts = locale.split("-");
|
||||
localePartIndex = localeParts.length;
|
||||
|
||||
do
|
||||
{
|
||||
tryingLocale = localeParts.slice(0, localePartIndex).join("-");
|
||||
message = this.messageStore.get(tryingLocale, messageKey);
|
||||
|
||||
if (message)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
localePartIndex--;
|
||||
} while (localePartIndex);
|
||||
|
||||
if (locale === this.options.fallbackLocale)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
locale = ($.i18n.fallbacks[this.locale] &&
|
||||
$.i18n.fallbacks[this.locale][fallbackIndex]) ||
|
||||
this.options.fallbackLocale;
|
||||
$.i18n.log("Trying fallback locale for " + this.locale + ": " + locale + " (" + messageKey + ")");
|
||||
|
||||
fallbackIndex++;
|
||||
}
|
||||
|
||||
// key not found
|
||||
return "";
|
||||
},
|
||||
|
||||
/*
|
||||
* Destroy the i18n instance.
|
||||
*/
|
||||
destroy: function ()
|
||||
{
|
||||
$.removeData(document, "i18n");
|
||||
},
|
||||
|
||||
/**
|
||||
* General message loading API This can take a URL string for
|
||||
* the json formatted messages. Example:
|
||||
* <code>load('path/to/all_localizations.json');</code>
|
||||
*
|
||||
* To load a localization file for a locale:
|
||||
* <code>
|
||||
* load('path/to/de-messages.json', 'de' );
|
||||
* </code>
|
||||
*
|
||||
* To load a localization file from a directory:
|
||||
* <code>
|
||||
* load('path/to/i18n/directory', 'de' );
|
||||
* </code>
|
||||
* The above method has the advantage of fallback resolution.
|
||||
* ie, it will automatically load the fallback locales for de.
|
||||
* For most usecases, this is the recommended method.
|
||||
* It is optional to have trailing slash at end.
|
||||
*
|
||||
* A data object containing message key- message translation mappings
|
||||
* can also be passed. Example:
|
||||
* <code>
|
||||
* load( { 'hello' : 'Hello' }, optionalLocale );
|
||||
* </code>
|
||||
*
|
||||
* A source map containing key-value pair of languagename and locations
|
||||
* can also be passed. Example:
|
||||
* <code>
|
||||
* load( {
|
||||
* bn: 'i18n/bn.json',
|
||||
* he: 'i18n/he.json',
|
||||
* en: 'i18n/en.json'
|
||||
* } )
|
||||
* </code>
|
||||
*
|
||||
* If the data argument is null/undefined/false,
|
||||
* all cached messages for the i18n instance will get reset.
|
||||
*
|
||||
* @param {string|Object} source
|
||||
* @param {string} locale Language tag
|
||||
* @return {jQuery.Promise}
|
||||
*/
|
||||
load: function (source, locale)
|
||||
{
|
||||
var fallbackLocales, locIndex, fallbackLocale, sourceMap = {};
|
||||
if (!source && !locale)
|
||||
{
|
||||
source = "i18n/" + $.i18n().locale + ".json";
|
||||
locale = $.i18n().locale;
|
||||
}
|
||||
if (typeof source === "string" &&
|
||||
// source extension should be json, but can have query params after that.
|
||||
source.split("?")[0].split(".").pop() !== "json"
|
||||
)
|
||||
{
|
||||
// Load specified locale then check for fallbacks when directory is
|
||||
// specified in load()
|
||||
sourceMap[locale] = source + "/" + locale + ".json";
|
||||
fallbackLocales = ($.i18n.fallbacks[locale] || [])
|
||||
.concat(this.options.fallbackLocale);
|
||||
for (locIndex = 0; locIndex < fallbackLocales.length; locIndex++)
|
||||
{
|
||||
fallbackLocale = fallbackLocales[locIndex];
|
||||
sourceMap[fallbackLocale] = source + "/" + fallbackLocale + ".json";
|
||||
}
|
||||
return this.load(sourceMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.messageStore.load(source, locale);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Does parameter and magic word substitution.
|
||||
*
|
||||
* @param {string} key Message key
|
||||
* @param {Array} parameters Message parameters
|
||||
* @return {string}
|
||||
*/
|
||||
parse: function (key, parameters)
|
||||
{
|
||||
var message = this.localize(key);
|
||||
// FIXME: This changes the state of the I18N object,
|
||||
// should probably not change the 'this.parser' but just
|
||||
// pass it to the parser.
|
||||
this.parser.language = $.i18n.languages[$.i18n().locale] || $.i18n.languages.default;
|
||||
if (message === "")
|
||||
{
|
||||
message = key;
|
||||
}
|
||||
return this.parser.parse(message, parameters);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Process a message from the $.I18N instance
|
||||
* for the current document, stored in jQuery.data(document).
|
||||
*
|
||||
* @param {string} key Key of the message.
|
||||
* @param {string} param1 [param...] Variadic list of parameters for {key}.
|
||||
* @return {string|$.I18N} Parsed message, or if no key was given
|
||||
* the instance of $.I18N is returned.
|
||||
*/
|
||||
$.i18n = function (key, param1)
|
||||
{
|
||||
var parameters,
|
||||
i18n = $.data(document, "i18n"),
|
||||
options = typeof key === "object" && key;
|
||||
|
||||
// If the locale option for this call is different then the setup so far,
|
||||
// update it automatically. This doesn't just change the context for this
|
||||
// call but for all future call as well.
|
||||
// If there is no i18n setup yet, don't do this. It will be taken care of
|
||||
// by the `new I18N` construction below.
|
||||
// NOTE: It should only change language for this one call.
|
||||
// Then cache instances of I18N somewhere.
|
||||
if (options && options.locale && i18n && i18n.locale !== options.locale)
|
||||
{
|
||||
i18n.locale = options.locale;
|
||||
}
|
||||
|
||||
if (!i18n)
|
||||
{
|
||||
i18n = new I18N(options);
|
||||
$.data(document, "i18n", i18n);
|
||||
}
|
||||
|
||||
if (typeof key === "string")
|
||||
{
|
||||
if (param1 !== undefined)
|
||||
{
|
||||
parameters = slice.call(arguments, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters = [];
|
||||
}
|
||||
|
||||
return i18n.parse(key, parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: remove this feature/bug.
|
||||
return i18n;
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.i18n = function ()
|
||||
{
|
||||
var i18n = $.data(document, "i18n");
|
||||
|
||||
if (!i18n)
|
||||
{
|
||||
i18n = new I18N();
|
||||
$.data(document, "i18n", i18n);
|
||||
}
|
||||
|
||||
return this.each(function ()
|
||||
{
|
||||
var $this = $(this),
|
||||
messageKey = $this.data("i18n"),
|
||||
lBracket, rBracket, type, key;
|
||||
|
||||
if (messageKey)
|
||||
{
|
||||
lBracket = messageKey.indexOf("[");
|
||||
rBracket = messageKey.indexOf("]");
|
||||
if (lBracket !== -1 && rBracket !== -1 && lBracket < rBracket)
|
||||
{
|
||||
type = messageKey.slice(lBracket + 1, rBracket);
|
||||
key = messageKey.slice(rBracket + 1);
|
||||
if (type === "html")
|
||||
{
|
||||
$this.html(i18n.parse(key));
|
||||
}
|
||||
else
|
||||
{
|
||||
$this.attr(type, i18n.parse(key));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this.text(i18n.parse(messageKey));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this.find("[data-i18n]").i18n();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getDefaultLocale()
|
||||
{
|
||||
var locale = $("html").attr("lang");
|
||||
if (!locale)
|
||||
{
|
||||
locale = navigator.language || navigator.userLanguage || "";
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
$.i18n.languages = {};
|
||||
$.i18n.messageStore = $.i18n.messageStore || {};
|
||||
$.i18n.parser = {
|
||||
// The default parser only handles variable substitution
|
||||
parse: function (message, parameters)
|
||||
{
|
||||
return message.replace(/\$(\d+)/g, function (str, match)
|
||||
{
|
||||
var index = parseInt(match, 10) - 1;
|
||||
return parameters[index] !== undefined ? parameters[index] : "$" + match;
|
||||
});
|
||||
},
|
||||
emitter: {}
|
||||
};
|
||||
$.i18n.fallbacks = {};
|
||||
$.i18n.debug = false;
|
||||
$.i18n.log = function (/* arguments */)
|
||||
{
|
||||
if (window.console && $.i18n.debug)
|
||||
{
|
||||
window.console.log.apply(window.console, arguments);
|
||||
}
|
||||
};
|
||||
/* Static members */
|
||||
I18N.defaults = {
|
||||
locale: getDefaultLocale(),
|
||||
fallbackLocale: "en",
|
||||
parser: $.i18n.parser,
|
||||
messageStore: $.i18n.messageStore
|
||||
};
|
||||
|
||||
// Expose constructor
|
||||
$.i18n.constructor = I18N;
|
||||
}(jQuery));
|
|
@ -0,0 +1,529 @@
|
|||
/* global pluralRuleParser */
|
||||
(function ($)
|
||||
{
|
||||
"use strict";
|
||||
|
||||
// jscs:disable
|
||||
var language = {
|
||||
// CLDR plural rules generated using
|
||||
// libs/CLDRPluralRuleParser/tools/PluralXML2JSON.html
|
||||
pluralRules: {
|
||||
ak: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
am: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
ar: {
|
||||
zero: "n = 0",
|
||||
one: "n = 1",
|
||||
two: "n = 2",
|
||||
few: "n % 100 = 3..10",
|
||||
many: "n % 100 = 11..99"
|
||||
},
|
||||
ars: {
|
||||
zero: "n = 0",
|
||||
one: "n = 1",
|
||||
two: "n = 2",
|
||||
few: "n % 100 = 3..10",
|
||||
many: "n % 100 = 11..99"
|
||||
},
|
||||
as: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
be: {
|
||||
one: "n % 10 = 1 and n % 100 != 11",
|
||||
few: "n % 10 = 2..4 and n % 100 != 12..14",
|
||||
many: "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14"
|
||||
},
|
||||
bh: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
bn: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
br: {
|
||||
one: "n % 10 = 1 and n % 100 != 11,71,91",
|
||||
two: "n % 10 = 2 and n % 100 != 12,72,92",
|
||||
few: "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99",
|
||||
many: "n != 0 and n % 1000000 = 0"
|
||||
},
|
||||
bs: {
|
||||
one: "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14"
|
||||
},
|
||||
cs: {
|
||||
one: "i = 1 and v = 0",
|
||||
few: "i = 2..4 and v = 0",
|
||||
many: "v != 0"
|
||||
},
|
||||
cy: {
|
||||
zero: "n = 0",
|
||||
one: "n = 1",
|
||||
two: "n = 2",
|
||||
few: "n = 3",
|
||||
many: "n = 6"
|
||||
},
|
||||
da: {
|
||||
one: "n = 1 or t != 0 and i = 0,1"
|
||||
},
|
||||
dsb: {
|
||||
one: "v = 0 and i % 100 = 1 or f % 100 = 1",
|
||||
two: "v = 0 and i % 100 = 2 or f % 100 = 2",
|
||||
few: "v = 0 and i % 100 = 3..4 or f % 100 = 3..4"
|
||||
},
|
||||
fa: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
ff: {
|
||||
one: "i = 0,1"
|
||||
},
|
||||
fil: {
|
||||
one: "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9"
|
||||
},
|
||||
fr: {
|
||||
one: "i = 0,1"
|
||||
},
|
||||
ga: {
|
||||
one: "n = 1",
|
||||
two: "n = 2",
|
||||
few: "n = 3..6",
|
||||
many: "n = 7..10"
|
||||
},
|
||||
gd: {
|
||||
one: "n = 1,11",
|
||||
two: "n = 2,12",
|
||||
few: "n = 3..10,13..19"
|
||||
},
|
||||
gu: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
guw: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
gv: {
|
||||
one: "v = 0 and i % 10 = 1",
|
||||
two: "v = 0 and i % 10 = 2",
|
||||
few: "v = 0 and i % 100 = 0,20,40,60,80",
|
||||
many: "v != 0"
|
||||
},
|
||||
he: {
|
||||
one: "i = 1 and v = 0",
|
||||
two: "i = 2 and v = 0",
|
||||
many: "v = 0 and n != 0..10 and n % 10 = 0"
|
||||
},
|
||||
hi: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
hr: {
|
||||
one: "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14"
|
||||
},
|
||||
hsb: {
|
||||
one: "v = 0 and i % 100 = 1 or f % 100 = 1",
|
||||
two: "v = 0 and i % 100 = 2 or f % 100 = 2",
|
||||
few: "v = 0 and i % 100 = 3..4 or f % 100 = 3..4"
|
||||
},
|
||||
hy: {
|
||||
one: "i = 0,1"
|
||||
},
|
||||
is: {
|
||||
one: "t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0"
|
||||
},
|
||||
iu: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
iw: {
|
||||
one: "i = 1 and v = 0",
|
||||
two: "i = 2 and v = 0",
|
||||
many: "v = 0 and n != 0..10 and n % 10 = 0"
|
||||
},
|
||||
kab: {
|
||||
one: "i = 0,1"
|
||||
},
|
||||
kn: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
kw: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
lag: {
|
||||
zero: "n = 0",
|
||||
one: "i = 0,1 and n != 0"
|
||||
},
|
||||
ln: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
lt: {
|
||||
one: "n % 10 = 1 and n % 100 != 11..19",
|
||||
few: "n % 10 = 2..9 and n % 100 != 11..19",
|
||||
many: "f != 0"
|
||||
},
|
||||
lv: {
|
||||
zero: "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19",
|
||||
one: "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1"
|
||||
},
|
||||
mg: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
mk: {
|
||||
one: "v = 0 and i % 10 = 1 or f % 10 = 1"
|
||||
},
|
||||
mo: {
|
||||
one: "i = 1 and v = 0",
|
||||
few: "v != 0 or n = 0 or n != 1 and n % 100 = 1..19"
|
||||
},
|
||||
mr: {
|
||||
one: "i = 0 or n = 1"
|
||||
},
|
||||
mt: {
|
||||
one: "n = 1",
|
||||
few: "n = 0 or n % 100 = 2..10",
|
||||
many: "n % 100 = 11..19"
|
||||
},
|
||||
naq: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
nso: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
pa: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
pl: {
|
||||
one: "i = 1 and v = 0",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14",
|
||||
many: "v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14"
|
||||
},
|
||||
prg: {
|
||||
zero: "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19",
|
||||
one: "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1"
|
||||
},
|
||||
pt: {
|
||||
one: "i = 0..1"
|
||||
},
|
||||
ro: {
|
||||
one: "i = 1 and v = 0",
|
||||
few: "v != 0 or n = 0 or n != 1 and n % 100 = 1..19"
|
||||
},
|
||||
ru: {
|
||||
one: "v = 0 and i % 10 = 1 and i % 100 != 11",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14",
|
||||
many: "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14"
|
||||
},
|
||||
se: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
sh: {
|
||||
one: "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14"
|
||||
},
|
||||
shi: {
|
||||
one: "i = 0 or n = 1",
|
||||
few: "n = 2..10"
|
||||
},
|
||||
si: {
|
||||
one: "n = 0,1 or i = 0 and f = 1"
|
||||
},
|
||||
sk: {
|
||||
one: "i = 1 and v = 0",
|
||||
few: "i = 2..4 and v = 0",
|
||||
many: "v != 0"
|
||||
},
|
||||
sl: {
|
||||
one: "v = 0 and i % 100 = 1",
|
||||
two: "v = 0 and i % 100 = 2",
|
||||
few: "v = 0 and i % 100 = 3..4 or v != 0"
|
||||
},
|
||||
sma: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
smi: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
smj: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
smn: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
sms: {
|
||||
one: "n = 1",
|
||||
two: "n = 2"
|
||||
},
|
||||
sr: {
|
||||
one: "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14"
|
||||
},
|
||||
ti: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
tl: {
|
||||
one: "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9"
|
||||
},
|
||||
tzm: {
|
||||
one: "n = 0..1 or n = 11..99"
|
||||
},
|
||||
uk: {
|
||||
one: "v = 0 and i % 10 = 1 and i % 100 != 11",
|
||||
few: "v = 0 and i % 10 = 2..4 and i % 100 != 12..14",
|
||||
many: "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14"
|
||||
},
|
||||
wa: {
|
||||
one: "n = 0..1"
|
||||
},
|
||||
zu: {
|
||||
one: "i = 0 or n = 1"
|
||||
}
|
||||
},
|
||||
// jscs:enable
|
||||
|
||||
/**
|
||||
* Plural form transformations, needed for some languages.
|
||||
*
|
||||
* @param {integer} count
|
||||
* Non-localized quantifier
|
||||
* @param {Array} forms
|
||||
* List of plural forms
|
||||
* @return {string} Correct form for quantifier in this language
|
||||
*/
|
||||
convertPlural: function (count, forms)
|
||||
{
|
||||
var pluralRules,
|
||||
pluralFormIndex,
|
||||
index,
|
||||
explicitPluralPattern = /\d+=/i,
|
||||
formCount,
|
||||
form;
|
||||
|
||||
if (!forms || forms.length === 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// Handle for Explicit 0= & 1= values
|
||||
for (index = 0; index < forms.length; index++)
|
||||
{
|
||||
form = forms[index];
|
||||
if (explicitPluralPattern.test(form))
|
||||
{
|
||||
formCount = parseInt(form.slice(0, form.indexOf("=")), 10);
|
||||
if (formCount === count)
|
||||
{
|
||||
return (form.slice(form.indexOf("=") + 1));
|
||||
}
|
||||
forms[index] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
forms = $.map(forms, function (form)
|
||||
{
|
||||
if (form !== undefined)
|
||||
{
|
||||
return form;
|
||||
}
|
||||
});
|
||||
|
||||
pluralRules = this.pluralRules[$.i18n().locale];
|
||||
|
||||
if (!pluralRules)
|
||||
{
|
||||
// default fallback.
|
||||
return (count === 1) ? forms[0] : forms[1];
|
||||
}
|
||||
|
||||
pluralFormIndex = this.getPluralForm(count, pluralRules);
|
||||
pluralFormIndex = Math.min(pluralFormIndex, forms.length - 1);
|
||||
|
||||
return forms[pluralFormIndex];
|
||||
},
|
||||
|
||||
/**
|
||||
* For the number, get the plural for index
|
||||
*
|
||||
* @param {integer} number
|
||||
* @param {Object} pluralRules
|
||||
* @return {integer} plural form index
|
||||
*/
|
||||
getPluralForm: function (number, pluralRules)
|
||||
{
|
||||
var i,
|
||||
pluralForms = ["zero", "one", "two", "few", "many", "other"],
|
||||
pluralFormIndex = 0;
|
||||
|
||||
for (i = 0; i < pluralForms.length; i++)
|
||||
{
|
||||
if (pluralRules[pluralForms[i]])
|
||||
{
|
||||
if (pluralRuleParser(pluralRules[pluralForms[i]], number))
|
||||
{
|
||||
return pluralFormIndex;
|
||||
}
|
||||
|
||||
pluralFormIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return pluralFormIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a number using digitTransformTable.
|
||||
*
|
||||
* @param {number} num Value to be converted
|
||||
* @param {boolean} integer Convert the return value to an integer
|
||||
* @return {string} The number converted into a String.
|
||||
*/
|
||||
convertNumber: function (num, integer)
|
||||
{
|
||||
var tmp, item, i,
|
||||
transformTable, numberString, convertedNumber;
|
||||
|
||||
// Set the target Transform table:
|
||||
transformTable = this.digitTransformTable($.i18n().locale);
|
||||
numberString = String(num);
|
||||
convertedNumber = "";
|
||||
|
||||
if (!transformTable)
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
||||
// Check if the restore to Latin number flag is set:
|
||||
if (integer)
|
||||
{
|
||||
if (parseFloat(num, 10) === num)
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
||||
tmp = [];
|
||||
|
||||
for (item in transformTable)
|
||||
{
|
||||
tmp[transformTable[item]] = item;
|
||||
}
|
||||
|
||||
transformTable = tmp;
|
||||
}
|
||||
|
||||
for (i = 0; i < numberString.length; i++)
|
||||
{
|
||||
if (transformTable[numberString[i]])
|
||||
{
|
||||
convertedNumber += transformTable[numberString[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
convertedNumber += numberString[i];
|
||||
}
|
||||
}
|
||||
|
||||
return integer ? parseFloat(convertedNumber, 10) : convertedNumber;
|
||||
},
|
||||
|
||||
/**
|
||||
* Grammatical transformations, needed for inflected languages.
|
||||
* Invoked by putting {{grammar:form|word}} in a message.
|
||||
* Override this method for languages that need special grammar rules
|
||||
* applied dynamically.
|
||||
*
|
||||
* @param {string} word
|
||||
* @param {string} form
|
||||
* @return {string}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
convertGrammar: function (word, form)
|
||||
{
|
||||
return word;
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides an alternative text depending on specified gender. Usage
|
||||
* {{gender:[gender|user object]|masculine|feminine|neutral}}. If second
|
||||
* or third parameter are not specified, masculine is used.
|
||||
*
|
||||
* These details may be overriden per language.
|
||||
*
|
||||
* @param {string} gender
|
||||
* male, female, or anything else for neutral.
|
||||
* @param {Array} forms
|
||||
* List of gender forms
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
gender: function (gender, forms)
|
||||
{
|
||||
if (!forms || forms.length === 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
while (forms.length < 2)
|
||||
{
|
||||
forms.push(forms[forms.length - 1]);
|
||||
}
|
||||
|
||||
if (gender === "male")
|
||||
{
|
||||
return forms[0];
|
||||
}
|
||||
|
||||
if (gender === "female")
|
||||
{
|
||||
return forms[1];
|
||||
}
|
||||
|
||||
return (forms.length === 3) ? forms[2] : forms[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the digit transform table for the given language
|
||||
* See http://cldr.unicode.org/translation/numbering-systems
|
||||
*
|
||||
* @param {string} language
|
||||
* @return {Array|boolean} List of digits in the passed language or false
|
||||
* representation, or boolean false if there is no information.
|
||||
*/
|
||||
digitTransformTable: function (language)
|
||||
{
|
||||
var tables = {
|
||||
ar: "٠١٢٣٤٥٦٧٨٩",
|
||||
fa: "۰۱۲۳۴۵۶۷۸۹",
|
||||
ml: "൦൧൨൩൪൫൬൭൮൯",
|
||||
kn: "೦೧೨೩೪೫೬೭೮೯",
|
||||
lo: "໐໑໒໓໔໕໖໗໘໙",
|
||||
or: "୦୧୨୩୪୫୬୭୮୯",
|
||||
kh: "០១២៣៤៥៦៧៨៩",
|
||||
nqo: "߀߁߂߃߄߅߆߇߈߉", // Note that the digits go right to left
|
||||
pa: "੦੧੨੩੪੫੬੭੮੯",
|
||||
gu: "૦૧૨૩૪૫૬૭૮૯",
|
||||
hi: "०१२३४५६७८९",
|
||||
my: "၀၁၂၃၄၅၆၇၈၉",
|
||||
ta: "௦௧௨௩௪௫௬௭௮௯",
|
||||
te: "౦౧౨౩౪౫౬౭౮౯",
|
||||
th: "๐๑๒๓๔๕๖๗๘๙", // FIXME use iso 639 codes
|
||||
bo: "༠༡༢༣༤༥༦༧༨༩" // FIXME use iso 639 codes
|
||||
};
|
||||
|
||||
if (!tables[language])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return tables[language].split("");
|
||||
}
|
||||
};
|
||||
|
||||
$.extend($.i18n.languages, {
|
||||
default: language
|
||||
});
|
||||
}(jQuery));
|
|
@ -0,0 +1,139 @@
|
|||
/*!
|
||||
* jQuery Internationalization library - Message Store
|
||||
*
|
||||
* Copyright (C) 2012 Santhosh Thottingal
|
||||
*
|
||||
* 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 MessageStore = function ()
|
||||
{
|
||||
this.messages = {};
|
||||
this.sources = {};
|
||||
};
|
||||
|
||||
function jsonMessageLoader(url)
|
||||
{
|
||||
var deferred = $.Deferred();
|
||||
|
||||
$.getJSON(url)
|
||||
.done(deferred.resolve)
|
||||
.fail(function (jqxhr, settings, exception)
|
||||
{
|
||||
$.i18n.log("Error in loading messages from " + url + " Exception: " + exception);
|
||||
// Ignore 404 exception, because we are handling fallabacks explicitly
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://github.com/wikimedia/jquery.i18n/wiki/Specification#wiki-Message_File_Loading
|
||||
*/
|
||||
MessageStore.prototype = {
|
||||
|
||||
/**
|
||||
* General message loading API This can take a URL string for
|
||||
* the json formatted messages.
|
||||
* <code>load('path/to/all_localizations.json');</code>
|
||||
*
|
||||
* This can also load a localization file for a locale <code>
|
||||
* load( 'path/to/de-messages.json', 'de' );
|
||||
* </code>
|
||||
* A data object containing message key- message translation mappings
|
||||
* can also be passed Eg:
|
||||
* <code>
|
||||
* load( { 'hello' : 'Hello' }, optionalLocale );
|
||||
* </code> If the data argument is
|
||||
* null/undefined/false,
|
||||
* all cached messages for the i18n instance will get reset.
|
||||
*
|
||||
* @param {string|Object} source
|
||||
* @param {string} locale Language tag
|
||||
* @return {jQuery.Promise}
|
||||
*/
|
||||
load: function (source, locale)
|
||||
{
|
||||
var key = null,
|
||||
deferreds = [],
|
||||
messageStore = this;
|
||||
|
||||
if (typeof source === "string")
|
||||
{
|
||||
// This is a URL to the messages file.
|
||||
$.i18n.log("Loading messages from: " + source);
|
||||
return jsonMessageLoader(source)
|
||||
.then(function (localization)
|
||||
{
|
||||
return messageStore.load(localization, locale);
|
||||
});
|
||||
}
|
||||
|
||||
if (locale)
|
||||
{
|
||||
// source is an key-value pair of messages for given locale
|
||||
messageStore.set(locale, source);
|
||||
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
else
|
||||
{
|
||||
// source is a key-value pair of locales and their source
|
||||
for (key in source)
|
||||
{
|
||||
if (Object.prototype.hasOwnProperty.call(source, key))
|
||||
{
|
||||
locale = key;
|
||||
// No {locale} given, assume data is a group of languages,
|
||||
// call this function again for each language.
|
||||
deferreds.push(messageStore.load(source[key], locale));
|
||||
}
|
||||
}
|
||||
return $.when.apply($, deferreds);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set messages to the given locale.
|
||||
* If locale exists, add messages to the locale.
|
||||
*
|
||||
* @param {string} locale
|
||||
* @param {Object} messages
|
||||
*/
|
||||
set: function (locale, messages)
|
||||
{
|
||||
if (!this.messages[locale])
|
||||
{
|
||||
this.messages[locale] = messages;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.messages[locale] = $.extend(this.messages[locale], messages);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} locale
|
||||
* @param {string} messageKey
|
||||
* @return {boolean}
|
||||
*/
|
||||
get: function (locale, messageKey)
|
||||
{
|
||||
return this.messages[locale] && this.messages[locale][messageKey];
|
||||
}
|
||||
};
|
||||
|
||||
$.extend($.i18n.messageStore, new MessageStore());
|
||||
}(jQuery));
|
|
@ -0,0 +1,353 @@
|
|||
/*!
|
||||
* 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);
|
||||
this.language = $.i18n.languages[String.locale] || $.i18n.languages.default;
|
||||
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;
|
||||
|
||||
return parameters[index] !== undefined ? parameters[index] : "$" + match;
|
||||
});
|
||||
},
|
||||
|
||||
parse: function (message, replacements)
|
||||
{
|
||||
if (message.indexOf("{{") < 0)
|
||||
{
|
||||
return this.simpleParse(message, replacements);
|
||||
}
|
||||
|
||||
this.emitter.language = $.i18n.languages[$.i18n().locale] ||
|
||||
$.i18n.languages.default;
|
||||
|
||||
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++)
|
||||
{
|
||||
result = parserSyntax[i]();
|
||||
|
||||
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++)
|
||||
{
|
||||
res = parserSyntax[i]();
|
||||
|
||||
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;
|
||||
|
||||
if (message.slice(pos, pos + len) === s)
|
||||
{
|
||||
result = s;
|
||||
pos += len;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function makeRegexParser(regex)
|
||||
{
|
||||
return function ()
|
||||
{
|
||||
var matches = message.slice(pos).match(regex);
|
||||
|
||||
if (matches === null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
pos += matches[0].length;
|
||||
|
||||
return matches[0];
|
||||
};
|
||||
}
|
||||
|
||||
pipe = makeStringParser("|");
|
||||
colon = makeStringParser(":");
|
||||
backslash = makeStringParser("\\");
|
||||
anyCharacter = makeRegexParser(/^./);
|
||||
dollar = makeStringParser("$");
|
||||
digits = makeRegexParser(/^\d+/);
|
||||
regularLiteral = makeRegexParser(/^[^{}[\]$\\]/);
|
||||
regularLiteralWithoutBar = makeRegexParser(/^[^{}[\]$\\|]/);
|
||||
regularLiteralWithoutSpace = makeRegexParser(/^[^{}[\]$\s]/);
|
||||
|
||||
// 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]);
|
||||
|
||||
return result === null ? null : result[1];
|
||||
}
|
||||
|
||||
choice([escapedLiteral, regularLiteralWithoutSpace]);
|
||||
escapedOrLiteralWithoutBar = choice([escapedLiteral, regularLiteralWithoutBar]);
|
||||
escapedOrRegularLiteral = choice([escapedLiteral, regularLiteral]);
|
||||
|
||||
function replacement()
|
||||
{
|
||||
var result = sequence([dollar, digits]);
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ["REPLACE", parseInt(result[1], 10) - 1];
|
||||
}
|
||||
|
||||
templateName = transform(
|
||||
// see $wgLegalTitleChars
|
||||
// not allowing : due to the need to catch "PLURAL:$1"
|
||||
makeRegexParser(/^[ !"$&'()*,./0-9;=?@A-Z^_`a-z~\x80-\xFF+-]+/),
|
||||
|
||||
function (result)
|
||||
{
|
||||
return result.toString();
|
||||
}
|
||||
);
|
||||
|
||||
function templateParam()
|
||||
{
|
||||
var expr,
|
||||
result = sequence([pipe, nOrMore(0, paramExpression)]);
|
||||
|
||||
if (result === null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
expr = result[1];
|
||||
|
||||
// use a "CONCAT" operator if there are multiple nodes,
|
||||
// otherwise return the first node, raw.
|
||||
return expr.length > 1 ? ["CONCAT"].concat(expr) : expr[0];
|
||||
}
|
||||
|
||||
function templateWithReplacement()
|
||||
{
|
||||
var result = sequence([templateName, colon, replacement]);
|
||||
|
||||
return result === null ? null : [result[0], result[2]];
|
||||
}
|
||||
|
||||
function templateWithOutReplacement()
|
||||
{
|
||||
var result = sequence([templateName, colon, paramExpression]);
|
||||
|
||||
return result === null ? null : [result[0], result[2]];
|
||||
}
|
||||
|
||||
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)
|
||||
]);
|
||||
|
||||
return res === null ? null : res[0].concat(res[1]);
|
||||
},
|
||||
function ()
|
||||
{
|
||||
var res = sequence([templateName, nOrMore(0, templateParam)]);
|
||||
|
||||
if (res === null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return [res[0]].concat(res[1]);
|
||||
}
|
||||
]);
|
||||
|
||||
openTemplate = makeStringParser("{{");
|
||||
closeTemplate = makeStringParser("}}");
|
||||
|
||||
function template()
|
||||
{
|
||||
var result = sequence([openTemplate, templateContents, closeTemplate]);
|
||||
|
||||
return result === null ? null : result[1];
|
||||
}
|
||||
|
||||
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.
|
||||
* n.b. This is part of language infrastructure, so we do not throw an
|
||||
* internationalizable message.
|
||||
*/
|
||||
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));
|
Ładowanie…
Reference in New Issue