From aa65ad9936c7c37ada7b5d8b7263adddf60dec8f Mon Sep 17 00:00:00 2001 From: christian Date: Sun, 13 Jun 2021 14:21:43 -0400 Subject: [PATCH] added jquery.i18n --- package.nw/lib/CLDRPluralRuleParser.js | 684 +++++++++++++++++++++ package.nw/lib/jquery.i18n.emitter.bidi.js | 102 +++ package.nw/lib/jquery.i18n.emitter.js | 185 ++++++ package.nw/lib/jquery.i18n.fallbacks.js | 187 ++++++ package.nw/lib/jquery.i18n.js | 332 ++++++++++ package.nw/lib/jquery.i18n.language.js | 529 ++++++++++++++++ package.nw/lib/jquery.i18n.messagestore.js | 139 +++++ package.nw/lib/jquery.i18n.parser.js | 353 +++++++++++ 8 files changed, 2511 insertions(+) create mode 100644 package.nw/lib/CLDRPluralRuleParser.js create mode 100644 package.nw/lib/jquery.i18n.emitter.bidi.js create mode 100644 package.nw/lib/jquery.i18n.emitter.js create mode 100644 package.nw/lib/jquery.i18n.fallbacks.js create mode 100644 package.nw/lib/jquery.i18n.js create mode 100644 package.nw/lib/jquery.i18n.language.js create mode 100644 package.nw/lib/jquery.i18n.messagestore.js create mode 100644 package.nw/lib/jquery.i18n.parser.js diff --git a/package.nw/lib/CLDRPluralRuleParser.js b/package.nw/lib/CLDRPluralRuleParser.js new file mode 100644 index 0000000..931a786 --- /dev/null +++ b/package.nw/lib/CLDRPluralRuleParser.js @@ -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 + * @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; +})); diff --git a/package.nw/lib/jquery.i18n.emitter.bidi.js b/package.nw/lib/jquery.i18n.emitter.bidi.js new file mode 100644 index 0000000..8dfe0f1 --- /dev/null +++ b/package.nw/lib/jquery.i18n.emitter.bidi.js @@ -0,0 +1,102 @@ +/*! + * BIDI embedding support for jQuery.i18n + * + * Copyright (C) 2015, David Chan + * + * This code 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 this code + * 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 strongDirRegExp; + + /** + * Matches the first strong directionality codepoint: + * - in group 1 if it is LTR + * - in group 2 if it is RTL + * Does not match if there is no strong directionality codepoint. + * + * Generated by UnicodeJS (see tools/strongDir) from the UCD; see + * https://phabricator.wikimedia.org/diffusion/GUJS/ . + */ + // eslint-disable-next-line no-misleading-character-class + strongDirRegExp = new RegExp( + "(?:" + + "(" + + "[\u0041-\u005a\u0061-\u007a\u00aa\u00b5\u00ba\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u02bb-\u02c1\u02d0\u02d1\u02e0-\u02e4\u02ee\u0370-\u0373\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0482\u048a-\u052f\u0531-\u0556\u0559-\u055f\u0561-\u0587\u0589\u0903-\u0939\u093b\u093d-\u0940\u0949-\u094c\u094e-\u0950\u0958-\u0961\u0964-\u0980\u0982\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd-\u09c0\u09c7\u09c8\u09cb\u09cc\u09ce\u09d7\u09dc\u09dd\u09df-\u09e1\u09e6-\u09f1\u09f4-\u09fa\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3e-\u0a40\u0a59-\u0a5c\u0a5e\u0a66-\u0a6f\u0a72-\u0a74\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd-\u0ac0\u0ac9\u0acb\u0acc\u0ad0\u0ae0\u0ae1\u0ae6-\u0af0\u0af9\u0b02\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0b5c\u0b5d\u0b5f-\u0b61\u0b66-\u0b77\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcc\u0bd0\u0bd7\u0be6-\u0bf2\u0c01-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c41-\u0c44\u0c58-\u0c5a\u0c60\u0c61\u0c66-\u0c6f\u0c7f\u0c82\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd-\u0cc4\u0cc6-\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0cde\u0ce0\u0ce1\u0ce6-\u0cef\u0cf1\u0cf2\u0d02\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d40\u0d46-\u0d48\u0d4a-\u0d4c\u0d4e\u0d57\u0d5f-\u0d61\u0d66-\u0d75\u0d79-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dcf-\u0dd1\u0dd8-\u0ddf\u0de6-\u0def\u0df2-\u0df4\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e4f-\u0e5b\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0ed0-\u0ed9\u0edc-\u0edf\u0f00-\u0f17\u0f1a-\u0f34\u0f36\u0f38\u0f3e-\u0f47\u0f49-\u0f6c\u0f7f\u0f85\u0f88-\u0f8c\u0fbe-\u0fc5\u0fc7-\u0fcc\u0fce-\u0fda\u1000-\u102c\u1031\u1038\u103b\u103c\u103f-\u1057\u105a-\u105d\u1061-\u1070\u1075-\u1081\u1083\u1084\u1087-\u108c\u108e-\u109c\u109e-\u10c5\u10c7\u10cd\u10d0-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1360-\u137c\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u167f\u1681-\u169a\u16a0-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1735\u1736\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17b6\u17be-\u17c5\u17c7\u17c8\u17d4-\u17da\u17dc\u17e0-\u17e9\u1810-\u1819\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1923-\u1926\u1929-\u192b\u1930\u1931\u1933-\u1938\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19da\u1a00-\u1a16\u1a19\u1a1a\u1a1e-\u1a55\u1a57\u1a61\u1a63\u1a64\u1a6d-\u1a72\u1a80-\u1a89\u1a90-\u1a99\u1aa0-\u1aad\u1b04-\u1b33\u1b35\u1b3b\u1b3d-\u1b41\u1b43-\u1b4b\u1b50-\u1b6a\u1b74-\u1b7c\u1b82-\u1ba1\u1ba6\u1ba7\u1baa\u1bae-\u1be5\u1be7\u1bea-\u1bec\u1bee\u1bf2\u1bf3\u1bfc-\u1c2b\u1c34\u1c35\u1c3b-\u1c49\u1c4d-\u1c7f\u1cc0-\u1cc7\u1cd3\u1ce1\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u200e\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u214f\u2160-\u2188\u2336-\u237a\u2395\u249c-\u24e9\u26ac\u2800-\u28ff\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d70\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u302e\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u3190-\u31ba\u31f0-\u321c\u3220-\u324f\u3260-\u327b\u327f-\u32b0\u32c0-\u32cb\u32d0-\u32fe\u3300-\u3376\u337b-\u33dd\u33e0-\u33fe\u3400-\u4db5\u4e00-\u9fd5\ua000-\ua48c\ua4d0-\ua60c\ua610-\ua62b\ua640-\ua66e\ua680-\ua69d\ua6a0-\ua6ef\ua6f2-\ua6f7\ua722-\ua787\ua789-\ua7ad\ua7b0-\ua7b7\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua824\ua827\ua830-\ua837\ua840-\ua873\ua880-\ua8c3\ua8ce-\ua8d9\ua8f2-\ua8fd\ua900-\ua925\ua92e-\ua946\ua952\ua953\ua95f-\ua97c\ua983-\ua9b2\ua9b4\ua9b5\ua9ba\ua9bb\ua9bd-\ua9cd\ua9cf-\ua9d9\ua9de-\ua9e4\ua9e6-\ua9fe\uaa00-\uaa28\uaa2f\uaa30\uaa33\uaa34\uaa40-\uaa42\uaa44-\uaa4b\uaa4d\uaa50-\uaa59\uaa5c-\uaa7b\uaa7d-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaaeb\uaaee-\uaaf5\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab65\uab70-\uabe4\uabe6\uabe7\uabe9-\uabec\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\ue000-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]|\ud800[\udc00-\udc0b]|\ud800[\udc0d-\udc26]|\ud800[\udc28-\udc3a]|\ud800\udc3c|\ud800\udc3d|\ud800[\udc3f-\udc4d]|\ud800[\udc50-\udc5d]|\ud800[\udc80-\udcfa]|\ud800\udd00|\ud800\udd02|\ud800[\udd07-\udd33]|\ud800[\udd37-\udd3f]|\ud800[\uddd0-\uddfc]|\ud800[\ude80-\ude9c]|\ud800[\udea0-\uded0]|\ud800[\udf00-\udf23]|\ud800[\udf30-\udf4a]|\ud800[\udf50-\udf75]|\ud800[\udf80-\udf9d]|\ud800[\udf9f-\udfc3]|\ud800[\udfc8-\udfd5]|\ud801[\udc00-\udc9d]|\ud801[\udca0-\udca9]|\ud801[\udd00-\udd27]|\ud801[\udd30-\udd63]|\ud801\udd6f|\ud801[\ude00-\udf36]|\ud801[\udf40-\udf55]|\ud801[\udf60-\udf67]|\ud804\udc00|\ud804[\udc02-\udc37]|\ud804[\udc47-\udc4d]|\ud804[\udc66-\udc6f]|\ud804[\udc82-\udcb2]|\ud804\udcb7|\ud804\udcb8|\ud804[\udcbb-\udcc1]|\ud804[\udcd0-\udce8]|\ud804[\udcf0-\udcf9]|\ud804[\udd03-\udd26]|\ud804\udd2c|\ud804[\udd36-\udd43]|\ud804[\udd50-\udd72]|\ud804[\udd74-\udd76]|\ud804[\udd82-\uddb5]|\ud804[\uddbf-\uddc9]|\ud804\uddcd|\ud804[\uddd0-\udddf]|\ud804[\udde1-\uddf4]|\ud804[\ude00-\ude11]|\ud804[\ude13-\ude2e]|\ud804\ude32|\ud804\ude33|\ud804\ude35|\ud804[\ude38-\ude3d]|\ud804[\ude80-\ude86]|\ud804\ude88|\ud804[\ude8a-\ude8d]|\ud804[\ude8f-\ude9d]|\ud804[\ude9f-\udea9]|\ud804[\udeb0-\udede]|\ud804[\udee0-\udee2]|\ud804[\udef0-\udef9]|\ud804\udf02|\ud804\udf03|\ud804[\udf05-\udf0c]|\ud804\udf0f|\ud804\udf10|\ud804[\udf13-\udf28]|\ud804[\udf2a-\udf30]|\ud804\udf32|\ud804\udf33|\ud804[\udf35-\udf39]|\ud804[\udf3d-\udf3f]|\ud804[\udf41-\udf44]|\ud804\udf47|\ud804\udf48|\ud804[\udf4b-\udf4d]|\ud804\udf50|\ud804\udf57|\ud804[\udf5d-\udf63]|\ud805[\udc80-\udcb2]|\ud805\udcb9|\ud805[\udcbb-\udcbe]|\ud805\udcc1|\ud805[\udcc4-\udcc7]|\ud805[\udcd0-\udcd9]|\ud805[\udd80-\uddb1]|\ud805[\uddb8-\uddbb]|\ud805\uddbe|\ud805[\uddc1-\udddb]|\ud805[\ude00-\ude32]|\ud805\ude3b|\ud805\ude3c|\ud805\ude3e|\ud805[\ude41-\ude44]|\ud805[\ude50-\ude59]|\ud805[\ude80-\udeaa]|\ud805\udeac|\ud805\udeae|\ud805\udeaf|\ud805\udeb6|\ud805[\udec0-\udec9]|\ud805[\udf00-\udf19]|\ud805\udf20|\ud805\udf21|\ud805\udf26|\ud805[\udf30-\udf3f]|\ud806[\udca0-\udcf2]|\ud806\udcff|\ud806[\udec0-\udef8]|\ud808[\udc00-\udf99]|\ud809[\udc00-\udc6e]|\ud809[\udc70-\udc74]|\ud809[\udc80-\udd43]|\ud80c[\udc00-\udfff]|\ud80d[\udc00-\udc2e]|\ud811[\udc00-\ude46]|\ud81a[\udc00-\ude38]|\ud81a[\ude40-\ude5e]|\ud81a[\ude60-\ude69]|\ud81a\ude6e|\ud81a\ude6f|\ud81a[\uded0-\udeed]|\ud81a\udef5|\ud81a[\udf00-\udf2f]|\ud81a[\udf37-\udf45]|\ud81a[\udf50-\udf59]|\ud81a[\udf5b-\udf61]|\ud81a[\udf63-\udf77]|\ud81a[\udf7d-\udf8f]|\ud81b[\udf00-\udf44]|\ud81b[\udf50-\udf7e]|\ud81b[\udf93-\udf9f]|\ud82c\udc00|\ud82c\udc01|\ud82f[\udc00-\udc6a]|\ud82f[\udc70-\udc7c]|\ud82f[\udc80-\udc88]|\ud82f[\udc90-\udc99]|\ud82f\udc9c|\ud82f\udc9f|\ud834[\udc00-\udcf5]|\ud834[\udd00-\udd26]|\ud834[\udd29-\udd66]|\ud834[\udd6a-\udd72]|\ud834\udd83|\ud834\udd84|\ud834[\udd8c-\udda9]|\ud834[\uddae-\udde8]|\ud834[\udf60-\udf71]|\ud835[\udc00-\udc54]|\ud835[\udc56-\udc9c]|\ud835\udc9e|\ud835\udc9f|\ud835\udca2|\ud835\udca5|\ud835\udca6|\ud835[\udca9-\udcac]|\ud835[\udcae-\udcb9]|\ud835\udcbb|\ud835[\udcbd-\udcc3]|\ud835[\udcc5-\udd05]|\ud835[\udd07-\udd0a]|\ud835[\udd0d-\udd14]|\ud835[\udd16-\udd1c]|\ud835[\udd1e-\udd39]|\ud835[\udd3b-\udd3e]|\ud835[\udd40-\udd44]|\ud835\udd46|\ud835[\udd4a-\udd50]|\ud835[\udd52-\udea5]|\ud835[\udea8-\udeda]|\ud835[\udedc-\udf14]|\ud835[\udf16-\udf4e]|\ud835[\udf50-\udf88]|\ud835[\udf8a-\udfc2]|\ud835[\udfc4-\udfcb]|\ud836[\udc00-\uddff]|\ud836[\ude37-\ude3a]|\ud836[\ude6d-\ude74]|\ud836[\ude76-\ude83]|\ud836[\ude85-\ude8b]|\ud83c[\udd10-\udd2e]|\ud83c[\udd30-\udd69]|\ud83c[\udd70-\udd9a]|\ud83c[\udde6-\ude02]|\ud83c[\ude10-\ude3a]|\ud83c[\ude40-\ude48]|\ud83c\ude50|\ud83c\ude51|[\ud840-\ud868][\udc00-\udfff]|\ud869[\udc00-\uded6]|\ud869[\udf00-\udfff]|[\ud86a-\ud86c][\udc00-\udfff]|\ud86d[\udc00-\udf34]|\ud86d[\udf40-\udfff]|\ud86e[\udc00-\udc1d]|\ud86e[\udc20-\udfff]|[\ud86f-\ud872][\udc00-\udfff]|\ud873[\udc00-\udea1]|\ud87e[\udc00-\ude1d]|[\udb80-\udbbe][\udc00-\udfff]|\udbbf[\udc00-\udffd]|[\udbc0-\udbfe][\udc00-\udfff]|\udbff[\udc00-\udffd]" + + ")|(" + + "[\u0590\u05be\u05c0\u05c3\u05c6\u05c8-\u05ff\u07c0-\u07ea\u07f4\u07f5\u07fa-\u0815\u081a\u0824\u0828\u082e-\u0858\u085c-\u089f\u200f\ufb1d\ufb1f-\ufb28\ufb2a-\ufb4f\u0608\u060b\u060d\u061b-\u064a\u066d-\u066f\u0671-\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u0710\u0712-\u072f\u074b-\u07a5\u07b1-\u07bf\u08a0-\u08e2\ufb50-\ufd3d\ufd40-\ufdcf\ufdf0-\ufdfc\ufdfe\ufdff\ufe70-\ufefe]|\ud802[\udc00-\udd1e]|\ud802[\udd20-\ude00]|\ud802\ude04|\ud802[\ude07-\ude0b]|\ud802[\ude10-\ude37]|\ud802[\ude3b-\ude3e]|\ud802[\ude40-\udee4]|\ud802[\udee7-\udf38]|\ud802[\udf40-\udfff]|\ud803[\udc00-\ude5f]|\ud803[\ude7f-\udfff]|\ud83a[\udc00-\udccf]|\ud83a[\udcd7-\udfff]|\ud83b[\udc00-\uddff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\ude00-\udeef]|\ud83b[\udef2-\udeff]" + + ")" + + ")" + ); + + /** + * Gets directionality of the first strongly directional codepoint + * + * This is the rule the BIDI algorithm uses to determine the directionality of + * paragraphs ( http://unicode.org/reports/tr9/#The_Paragraph_Level ) and + * FSI isolates ( http://unicode.org/reports/tr9/#Explicit_Directional_Isolates ). + * + * TODO: Does not handle BIDI control characters inside the text. + * TODO: Does not handle unallocated characters. + * + * @param {string} text The text from which to extract initial directionality. + * @return {string} Directionality (either 'ltr' or 'rtl') + */ + function strongDirFromContent(text) + { + var m = text.match(strongDirRegExp); + if (!m) + { + return null; + } + if (m[2] === undefined) + { + return "ltr"; + } + return "rtl"; + } + + $.extend($.i18n.parser.emitter, { + /** + * Wraps argument with unicode control characters for directionality safety + * + * This solves the problem where directionality-neutral characters at the edge of + * the argument string get interpreted with the wrong directionality from the + * enclosing context, giving renderings that look corrupted like "(Ben_(WMF". + * + * The wrapping is LRE...PDF or RLE...PDF, depending on the detected + * directionality of the argument string, using the BIDI algorithm's own "First + * strong directional codepoint" rule. Essentially, this works round the fact that + * there is no embedding equivalent of U+2068 FSI (isolation with heuristic + * direction inference). The latter is cleaner but still not widely supported. + * + * @param {string[]} nodes The text nodes from which to take the first item. + * @return {string} Wrapped String of content as needed. + */ + bidi: function (nodes) + { + var dir = strongDirFromContent(nodes[0]); + if (dir === "ltr") + { + // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING + return "\u202A" + nodes[0] + "\u202C"; + } + if (dir === "rtl") + { + // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING + return "\u202B" + nodes[0] + "\u202C"; + } + // No strong directionality: do not wrap + return nodes[0]; + } + }); +}(jQuery)); diff --git a/package.nw/lib/jquery.i18n.emitter.js b/package.nw/lib/jquery.i18n.emitter.js new file mode 100644 index 0000000..e5055ea --- /dev/null +++ b/package.nw/lib/jquery.i18n.emitter.js @@ -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)); diff --git a/package.nw/lib/jquery.i18n.fallbacks.js b/package.nw/lib/jquery.i18n.fallbacks.js new file mode 100644 index 0000000..c773f3c --- /dev/null +++ b/package.nw/lib/jquery.i18n.fallbacks.js @@ -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)); diff --git a/package.nw/lib/jquery.i18n.js b/package.nw/lib/jquery.i18n.js new file mode 100644 index 0000000..74f9772 --- /dev/null +++ b/package.nw/lib/jquery.i18n.js @@ -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: + * load('path/to/all_localizations.json'); + * + * To load a localization file for a locale: + * + * load('path/to/de-messages.json', 'de' ); + * + * + * To load a localization file from a directory: + * + * load('path/to/i18n/directory', 'de' ); + * + * 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: + * + * load( { 'hello' : 'Hello' }, optionalLocale ); + * + * + * A source map containing key-value pair of languagename and locations + * can also be passed. Example: + * + * load( { + * bn: 'i18n/bn.json', + * he: 'i18n/he.json', + * en: 'i18n/en.json' + * } ) + * + * + * 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)); diff --git a/package.nw/lib/jquery.i18n.language.js b/package.nw/lib/jquery.i18n.language.js new file mode 100644 index 0000000..058d5d1 --- /dev/null +++ b/package.nw/lib/jquery.i18n.language.js @@ -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)); diff --git a/package.nw/lib/jquery.i18n.messagestore.js b/package.nw/lib/jquery.i18n.messagestore.js new file mode 100644 index 0000000..3a2ad79 --- /dev/null +++ b/package.nw/lib/jquery.i18n.messagestore.js @@ -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. + * load('path/to/all_localizations.json'); + * + * This can also load a localization file for a locale + * load( 'path/to/de-messages.json', 'de' ); + * + * A data object containing message key- message translation mappings + * can also be passed Eg: + * + * load( { 'hello' : 'Hello' }, optionalLocale ); + * 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)); diff --git a/package.nw/lib/jquery.i18n.parser.js b/package.nw/lib/jquery.i18n.parser.js new file mode 100644 index 0000000..93da0d9 --- /dev/null +++ b/package.nw/lib/jquery.i18n.parser.js @@ -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));