summaryrefslogtreecommitdiff
path: root/resources/lib/jquery.i18n/src
diff options
context:
space:
mode:
Diffstat (limited to 'resources/lib/jquery.i18n/src')
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.emitter.bidi.js88
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.emitter.js168
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.fallbacks.js186
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.js287
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.language.js472
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.messagestore.js125
-rw-r--r--resources/lib/jquery.i18n/src/jquery.i18n.parser.js309
-rw-r--r--resources/lib/jquery.i18n/src/languages/bs.js22
-rw-r--r--resources/lib/jquery.i18n/src/languages/dsb.js22
-rw-r--r--resources/lib/jquery.i18n/src/languages/fi.js49
-rw-r--r--resources/lib/jquery.i18n/src/languages/ga.js38
-rw-r--r--resources/lib/jquery.i18n/src/languages/he.js31
-rw-r--r--resources/lib/jquery.i18n/src/languages/hsb.js21
-rw-r--r--resources/lib/jquery.i18n/src/languages/hu.js26
-rw-r--r--resources/lib/jquery.i18n/src/languages/hy.js25
-rw-r--r--resources/lib/jquery.i18n/src/languages/la.js54
-rw-r--r--resources/lib/jquery.i18n/src/languages/ml.js98
-rw-r--r--resources/lib/jquery.i18n/src/languages/os.js76
-rw-r--r--resources/lib/jquery.i18n/src/languages/ru.js29
-rw-r--r--resources/lib/jquery.i18n/src/languages/sl.js26
-rw-r--r--resources/lib/jquery.i18n/src/languages/uk.js39
21 files changed, 2191 insertions, 0 deletions
diff --git a/resources/lib/jquery.i18n/src/jquery.i18n.emitter.bidi.js b/resources/lib/jquery.i18n/src/jquery.i18n.emitter.bidi.js
new file mode 100644
index 00000000..3a5b6251
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.emitter.bidi.js
@@ -0,0 +1,88 @@
+/**
+ * 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://git.wikimedia.org/summary/unicodejs.git .
+ */
+ 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.
+ */
+ 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.
+ */
+ 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/resources/lib/jquery.i18n/src/jquery.i18n.emitter.js b/resources/lib/jquery.i18n/src/jquery.i18n.emitter.js
new file mode 100644
index 00000000..b26f147d
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.emitter.js
@@ -0,0 +1,168 @@
+/**
+ * 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/resources/lib/jquery.i18n/src/jquery.i18n.fallbacks.js b/resources/lib/jquery.i18n/src/jquery.i18n.fallbacks.js
new file mode 100644
index 00000000..4584c5f2
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.fallbacks.js
@@ -0,0 +1,186 @@
+/**
+ * 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 ( $, undefined ) {
+ '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/resources/lib/jquery.i18n/src/jquery.i18n.js b/resources/lib/jquery.i18n/src/jquery.i18n.js
new file mode 100644
index 00000000..9236e4e2
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.js
@@ -0,0 +1,287 @@
+/**
+ * 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 nav, 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 = {};
+
+ this.init();
+ };
+
+ I18N.prototype = {
+ /**
+ * Initialize by loading locales and setting up
+ * String.prototype.toLocaleString and String.locale.
+ */
+ init: function () {
+ var i18n = this;
+
+ // Set locale of String environment
+ String.locale = i18n.locale;
+
+ // Override String.localeString method
+ String.prototype.toLocaleString = function () {
+ var localeParts, localePartIndex, value, locale, fallbackIndex,
+ tryingLocale, message;
+
+ value = this.valueOf();
+ locale = i18n.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 = i18n.messageStore.get( tryingLocale, value );
+
+ if ( message ) {
+ return message;
+ }
+
+ localePartIndex--;
+ } while ( localePartIndex );
+
+ if ( locale === 'en' ) {
+ break;
+ }
+
+ locale = ( $.i18n.fallbacks[i18n.locale] && $.i18n.fallbacks[i18n.locale][fallbackIndex] ) ||
+ i18n.options.fallbackLocale;
+ $.i18n.log( 'Trying fallback locale for ' + i18n.locale + ': ' + locale );
+
+ 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
+ * @returns {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.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 in fallbackLocales ) {
+ 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 = key.toLocaleString();
+ // 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 ) {
+ String.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 );
+ }
+ String.locale = i18n.locale;
+ return this.each( function () {
+ var $this = $( this ),
+ messageKey = $this.data( 'i18n' );
+
+ if ( messageKey ) {
+ $this.text( i18n.parse( messageKey ) );
+ } else {
+ $this.find( '[data-i18n]' ).i18n();
+ }
+ } );
+ };
+
+ String.locale = String.locale || $( 'html' ).attr( 'lang' );
+
+ if ( !String.locale ) {
+ if ( typeof window.navigator !== undefined ) {
+ nav = window.navigator;
+ String.locale = nav.language || nav.userLanguage || '';
+ } else {
+ String.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: String.locale,
+ fallbackLocale: 'en',
+ parser: $.i18n.parser,
+ messageStore: $.i18n.messageStore
+ };
+
+ // Expose constructor
+ $.i18n.constructor = I18N;
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/jquery.i18n.language.js b/resources/lib/jquery.i18n/src/jquery.i18n.language.js
new file mode 100644
index 00000000..e596fbc8
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.language.js
@@ -0,0 +1,472 @@
+/*global pluralRuleParser */
+( function ( $ ) {
+ 'use strict';
+
+ 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'
+ },
+ 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'
+ },
+ fa: {
+ one: 'i = 0 or n = 1'
+ },
+ ff: {
+ one: 'i = 0,1'
+ },
+ fil: {
+ one: 'i = 0..1 and v = 0'
+ },
+ 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: 'n % 10 = 1',
+ two: 'n % 10 = 2',
+ few: 'n % 100 = 0,20,40,60'
+ },
+ 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'
+ },
+ 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'
+ },
+ pt: {
+ one: 'i = 1 and v = 0 or i = 0 and t = 1'
+ },
+ // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
+ pt_PT: {
+ one: 'n = 1 and v = 0'
+ },
+ // jscs:enable requireCamelCaseOrUpperCaseIdentifiers
+ 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',
+ 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: 'i = 0..1 and v = 0'
+ },
+ 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'
+ }
+ },
+
+ /**
+ * Plural form transformations, needed for some languages.
+ *
+ * @param count
+ * integer Non-localized quantifier
+ * @param forms
+ * array List of plural forms
+ * @return string Correct form for quantifier in this language
+ */
+ convertPlural: function ( count, forms ) {
+ var pluralRules,
+ pluralFormIndex,
+ index,
+ explicitPluralPattern = new RegExp( '\\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.substring( 0, form.indexOf( '=' ) ), 10 );
+ if ( formCount === count ) {
+ return ( form.substr( 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 number
+ * @param pluralRules
+ * @return 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
+ */
+ 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 word {String}
+ * @param form {String}
+ * @return {String}
+ */
+ convertGrammar: function ( word, form ) { /*jshint unused: false */
+ 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 gender
+ * string male, female, or anything else for neutral.
+ * @param forms
+ * array 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 language
+ * @returns {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: '០១២៣៤៥៦៧៨៩',
+ 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/resources/lib/jquery.i18n/src/jquery.i18n.messagestore.js b/resources/lib/jquery.i18n/src/jquery.i18n.messagestore.js
new file mode 100644
index 00000000..759295ce
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.messagestore.js
@@ -0,0 +1,125 @@
+/**
+ * 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 ( $, window, undefined ) {
+ 'use strict';
+
+ var MessageStore = function () {
+ this.messages = {};
+ this.sources = {};
+ };
+
+ /**
+ * 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,
+ deferred = null,
+ deferreds = [],
+ messageStore = this;
+
+ if ( typeof source === 'string' ) {
+ // This is a URL to the messages file.
+ $.i18n.log( 'Loading messages from: ' + source );
+ deferred = jsonMessageLoader( source )
+ .done( function ( localization ) {
+ messageStore.set( locale, localization );
+ } );
+
+ return deferred.promise();
+ }
+
+ 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 locale
+ * @param messages
+ */
+ set: function ( locale, messages ) {
+ if ( !this.messages[locale] ) {
+ this.messages[locale] = messages;
+ } else {
+ this.messages[locale] = $.extend( this.messages[locale], messages );
+ }
+ },
+
+ /**
+ *
+ * @param locale
+ * @param messageKey
+ * @returns {Boolean}
+ */
+ get: function ( locale, messageKey ) {
+ return this.messages[locale] && this.messages[locale][messageKey];
+ }
+ };
+
+ 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();
+ }
+
+ $.extend( $.i18n.messageStore, new MessageStore() );
+}( jQuery, window ) );
diff --git a/resources/lib/jquery.i18n/src/jquery.i18n.parser.js b/resources/lib/jquery.i18n/src/jquery.i18n.parser.js
new file mode 100644
index 00000000..3dea2842
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/jquery.i18n.parser.js
@@ -0,0 +1,309 @@
+/**
+ * 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.substr( pos, len ) === s ) {
+ result = s;
+ pos += len;
+ }
+
+ return result;
+ };
+ }
+
+ function makeRegexParser( regex ) {
+ return function () {
+ var matches = message.substr( 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 ) );
diff --git a/resources/lib/jquery.i18n/src/languages/bs.js b/resources/lib/jquery.i18n/src/languages/bs.js
new file mode 100644
index 00000000..5370069e
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/bs.js
@@ -0,0 +1,22 @@
+/**
+ * Bosnian (bosanski) language functions
+ */
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.bs = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'instrumental': // instrumental
+ word = 's ' + word;
+ break;
+ case 'lokativ': // locative
+ word = 'o ' + word;
+ break;
+ }
+
+ return word;
+ }
+ } );
+
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/dsb.js b/resources/lib/jquery.i18n/src/languages/dsb.js
new file mode 100644
index 00000000..cc069ebf
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/dsb.js
@@ -0,0 +1,22 @@
+/**
+ * Lower Sorbian (Dolnoserbski) language functions
+ */
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.dsb = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'instrumental': // instrumental
+ word = 'z ' + word;
+ break;
+ case 'lokatiw': // lokatiw
+ word = 'wo ' + word;
+ break;
+ }
+
+ return word;
+ }
+ } );
+
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/fi.js b/resources/lib/jquery.i18n/src/languages/fi.js
new file mode 100644
index 00000000..d8e9578a
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/fi.js
@@ -0,0 +1,49 @@
+/**
+ * Finnish (Suomi) language functions
+ *
+ * @author Santhosh Thottingal
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.fi = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ // vowel harmony flag
+ var aou = word.match( /[aou][^äöy]*$/i ),
+ origWord = word;
+ if ( word.match( /wiki$/i ) ) {
+ aou = false;
+ }
+
+ // append i after final consonant
+ if ( word.match( /[bcdfghjklmnpqrstvwxz]$/i ) ) {
+ word += 'i';
+ }
+
+ switch ( form ) {
+ case 'genitive':
+ word += 'n';
+ break;
+ case 'elative':
+ word += ( aou ? 'sta' : 'stä' );
+ break;
+ case 'partitive':
+ word += ( aou ? 'a' : 'ä' );
+ break;
+ case 'illative':
+ // Double the last letter and add 'n'
+ word += word.substr( word.length - 1 ) + 'n';
+ break;
+ case 'inessive':
+ word += ( aou ? 'ssa' : 'ssä' );
+ break;
+ default:
+ word = origWord;
+ break;
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/ga.js b/resources/lib/jquery.i18n/src/languages/ga.js
new file mode 100644
index 00000000..1aceab75
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/ga.js
@@ -0,0 +1,38 @@
+/**
+ * Irish (Gaeilge) language functions
+ */
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.ga = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ if ( form === 'ainmlae' ) {
+ switch ( word ) {
+ case 'an Domhnach':
+ word = 'Dé Domhnaigh';
+ break;
+ case 'an Luan':
+ word = 'Dé Luain';
+ break;
+ case 'an Mháirt':
+ word = 'Dé Mháirt';
+ break;
+ case 'an Chéadaoin':
+ word = 'Dé Chéadaoin';
+ break;
+ case 'an Déardaoin':
+ word = 'Déardaoin';
+ break;
+ case 'an Aoine':
+ word = 'Dé hAoine';
+ break;
+ case 'an Satharn':
+ word = 'Dé Sathairn';
+ break;
+ }
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/he.js b/resources/lib/jquery.i18n/src/languages/he.js
new file mode 100644
index 00000000..cbbe90b9
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/he.js
@@ -0,0 +1,31 @@
+/**
+ * Hebrew (עברית) language functions
+ */
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.he = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'prefixed':
+ case 'תחילית': // the same word in Hebrew
+ // Duplicate prefixed "Waw", but only if it's not already double
+ if ( word.substr( 0, 1 ) === 'ו' && word.substr( 0, 2 ) !== 'וו' ) {
+ word = 'ו' + word;
+ }
+
+ // Remove the "He" if prefixed
+ if ( word.substr( 0, 1 ) === 'ה' ) {
+ word = word.substr( 1, word.length );
+ }
+
+ // Add a hyphen (maqaf) before numbers and non-Hebrew letters
+ if ( word.substr( 0, 1 ) < 'א' || word.substr( 0, 1 ) > 'ת' ) {
+ word = '־' + word;
+ }
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/hsb.js b/resources/lib/jquery.i18n/src/languages/hsb.js
new file mode 100644
index 00000000..957616f6
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/hsb.js
@@ -0,0 +1,21 @@
+/**
+ * Upper Sorbian (Hornjoserbsce) language functions
+ */
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.hsb = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'instrumental': // instrumental
+ word = 'z ' + word;
+ break;
+ case 'lokatiw': // lokatiw
+ word = 'wo ' + word;
+ break;
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/hu.js b/resources/lib/jquery.i18n/src/languages/hu.js
new file mode 100644
index 00000000..1177b850
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/hu.js
@@ -0,0 +1,26 @@
+/**
+ * Hungarian language functions
+ *
+ * @author Santhosh Thottingal
+ */
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.hu = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'rol':
+ word += 'ról';
+ break;
+ case 'ba':
+ word += 'ba';
+ break;
+ case 'k':
+ word += 'k';
+ break;
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/hy.js b/resources/lib/jquery.i18n/src/languages/hy.js
new file mode 100644
index 00000000..9c568992
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/hy.js
@@ -0,0 +1,25 @@
+/**
+ * Armenian (Հայերեն) language functions
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.hy = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ if ( form === 'genitive' ) { // սեռական հոլով
+ if ( word.substr( -1 ) === 'ա' ) {
+ word = word.substr( 0, word.length - 1 ) + 'այի';
+ } else if ( word.substr( -1 ) === 'ո' ) {
+ word = word.substr( 0, word.length - 1 ) + 'ոյի';
+ } else if ( word.substr( -4 ) === 'գիրք' ) {
+ word = word.substr( 0, word.length - 4 ) + 'գրքի';
+ } else {
+ word = word + 'ի';
+ }
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/la.js b/resources/lib/jquery.i18n/src/languages/la.js
new file mode 100644
index 00000000..11c1122d
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/la.js
@@ -0,0 +1,54 @@
+/**
+ * Latin (lingua Latina) language functions
+ *
+ * @author Santhosh Thottingal
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.la = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'genitive':
+ // only a few declensions, and even for those mostly the singular only
+ word = word.replace( /u[ms]$/i, 'i' ); // 2nd declension singular
+ word = word.replace( /ommunia$/i, 'ommunium' ); // 3rd declension neuter plural (partly)
+ word = word.replace( /a$/i, 'ae' ); // 1st declension singular
+ word = word.replace( /libri$/i, 'librorum' ); // 2nd declension plural (partly)
+ word = word.replace( /nuntii$/i, 'nuntiorum' ); // 2nd declension plural (partly)
+ word = word.replace( /tio$/i, 'tionis' ); // 3rd declension singular (partly)
+ word = word.replace( /ns$/i, 'ntis' );
+ word = word.replace( /as$/i, 'atis' );
+ word = word.replace( /es$/i, 'ei' ); // 5th declension singular
+ break;
+ case 'accusative':
+ // only a few declensions, and even for those mostly the singular only
+ word = word.replace( /u[ms]$/i, 'um' ); // 2nd declension singular
+ word = word.replace( /ommunia$/i, 'am' ); // 3rd declension neuter plural (partly)
+ word = word.replace( /a$/i, 'ommunia' ); // 1st declension singular
+ word = word.replace( /libri$/i, 'libros' ); // 2nd declension plural (partly)
+ word = word.replace( /nuntii$/i, 'nuntios' );// 2nd declension plural (partly)
+ word = word.replace( /tio$/i, 'tionem' ); // 3rd declension singular (partly)
+ word = word.replace( /ns$/i, 'ntem' );
+ word = word.replace( /as$/i, 'atem' );
+ word = word.replace( /es$/i, 'em' ); // 5th declension singular
+ break;
+ case 'ablative':
+ // only a few declensions, and even for those mostly the singular only
+ word = word.replace( /u[ms]$/i, 'o' ); // 2nd declension singular
+ word = word.replace( /ommunia$/i, 'ommunibus' ); // 3rd declension neuter plural (partly)
+ word = word.replace( /a$/i, 'a' ); // 1st declension singular
+ word = word.replace( /libri$/i, 'libris' ); // 2nd declension plural (partly)
+ word = word.replace( /nuntii$/i, 'nuntiis' ); // 2nd declension plural (partly)
+ word = word.replace( /tio$/i, 'tione' ); // 3rd declension singular (partly)
+ word = word.replace( /ns$/i, 'nte' );
+ word = word.replace( /as$/i, 'ate' );
+ word = word.replace( /es$/i, 'e' ); // 5th declension singular
+ break;
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/ml.js b/resources/lib/jquery.i18n/src/languages/ml.js
new file mode 100644
index 00000000..f724b7b2
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/ml.js
@@ -0,0 +1,98 @@
+/**
+ * Malayalam language functions
+ *
+ * @author Santhosh Thottingal
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.ml = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ form = form.toLowerCase();
+ switch ( form ) {
+ case 'ഉദ്ദേശിക':
+ case 'dative':
+ if ( word.substr( -1 ) === 'ു' ||
+ word.substr( -1 ) === 'ൂ' ||
+ word.substr( -1 ) === 'ൗ' ||
+ word.substr( -1 ) === 'ൌ'
+ ) {
+ word += 'വിന്';
+ } else if ( word.substr( -1 ) === 'ം' ) {
+ word = word.substr( 0, word.length - 1 ) + 'ത്തിന്';
+ } else if ( word.substr( -1 ) === 'ൻ' ) {
+ // Atomic chillu n. അവൻ -> അവന്
+ word = word.substr( 0, word.length - 1 ) + 'ന്';
+ } else if ( word.substr( -3 ) === 'ന്\u200d' ) {
+ // chillu n. അവൻ -> അവന്
+ word = word.substr( 0, word.length - 1 );
+ } else if ( word.substr( -1 ) === 'ൾ' || word.substr( -3 ) === 'ള്\u200d' ) {
+ word += 'ക്ക്';
+ } else if ( word.substr( -1 ) === 'ർ' || word.substr( -3 ) === 'ര്\u200d' ) {
+ word += 'ക്ക്';
+ } else if ( word.substr( -1 ) === 'ൽ' ) {
+ // Atomic chillu ൽ , ഫയൽ -> ഫയലിന്
+ word = word.substr( 0, word.length - 1 ) + 'ലിന്';
+ } else if ( word.substr( -3 ) === 'ല്\u200d' ) {
+ // chillu ല്\u200d , ഫയല്\u200d -> ഫയലിന്
+ word = word.substr( 0, word.length - 2 ) + 'ിന്';
+ } else if ( word.substr( -2 ) === 'ു്' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ിന്';
+ } else if ( word.substr( -1 ) === '്' ) {
+ word = word.substr( 0, word.length - 1 ) + 'ിന്';
+ } else {
+ // കാവ്യ -> കാവ്യയ്ക്ക്, ഹരി -> ഹരിയ്ക്ക്, മല -> മലയ്ക്ക്
+ word += 'യ്ക്ക്';
+ }
+
+ break;
+ case 'സംബന്ധിക':
+ case 'genitive':
+ if ( word.substr( -1 ) === 'ം' ) {
+ word = word.substr( 0, word.length - 1 ) + 'ത്തിന്റെ';
+ } else if ( word.substr( -2 ) === 'ു്' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ിന്റെ';
+ } else if ( word.substr( -1 ) === '്' ) {
+ word = word.substr( 0, word.length - 1 ) + 'ിന്റെ';
+ } else if ( word.substr( -1 ) === 'ു' ||
+ word.substr( -1 ) === 'ൂ' ||
+ word.substr( -1 ) === 'ൗ' ||
+ word.substr( -1 ) === 'ൌ'
+ ) {
+ word += 'വിന്റെ';
+ } else if ( word.substr( -1 ) === 'ൻ' ) {
+ // Atomic chillu n. അവൻ -> അവന്റെ
+ word = word.substr( 0, word.length - 1 ) + 'ന്റെ';
+ } else if ( word.substr( -3 ) === 'ന്\u200d' ) {
+ // chillu n. അവൻ -> അവന്റെ
+ word = word.substr( 0, word.length - 1 ) + 'റെ';
+ } else if ( word.substr( -3 ) === 'ള്\u200d' ) {
+ // chillu n. അവൾ -> അവളുടെ
+ word = word.substr( 0, word.length - 2 ) + 'ുടെ';
+ } else if ( word.substr( -1 ) === 'ൾ' ) {
+ // Atomic chillu n. അവള്\u200d -> അവളുടെ
+ word = word.substr( 0, word.length - 1 ) + 'ളുടെ';
+ } else if ( word.substr( -1 ) === 'ൽ' ) {
+ // Atomic l. മുയല്\u200d -> മുയലിന്റെ
+ word = word.substr( 0, word.length - 1 ) + 'ലിന്റെ';
+ } else if ( word.substr( -3 ) === 'ല്\u200d' ) {
+ // chillu l. മുയല്\u200d -> അവളുടെ
+ word = word.substr( 0, word.length - 2 ) + 'ിന്റെ';
+ } else if ( word.substr( -3 ) === 'ര്\u200d' ) {
+ // chillu r. അവര്\u200d -> അവരുടെ
+ word = word.substr( 0, word.length - 2 ) + 'ുടെ';
+ } else if ( word.substr( -1 ) === 'ർ' ) {
+ // Atomic chillu r. അവർ -> അവരുടെ
+ word = word.substr( 0, word.length - 1 ) + 'രുടെ';
+ } else {
+ word += 'യുടെ';
+ }
+
+ break;
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/os.js b/resources/lib/jquery.i18n/src/languages/os.js
new file mode 100644
index 00000000..47443671
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/os.js
@@ -0,0 +1,76 @@
+/**
+ * Ossetian (Ирон) language functions
+ *
+ * @author Santhosh Thottingal
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.os = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ var endAllative, jot, hyphen, ending;
+
+ // Ending for allative case
+ endAllative = 'мæ';
+ // Variable for 'j' beetwen vowels
+ jot = '';
+ // Variable for "-" for not Ossetic words
+ hyphen = '';
+ // Variable for ending
+ ending = '';
+
+ if ( word.match( /тæ$/i ) ) {
+ // Checking if the $word is in plural form
+ word = word.substring( 0, word.length - 1 );
+ endAllative = 'æм';
+ } else if ( word.match( /[аæеёиоыэюя]$/i ) ) {
+ // Works if word is in singular form.
+ // Checking if word ends on one of the vowels: е, ё, и, о, ы, э, ю,
+ // я.
+ jot = 'й';
+ } else if ( word.match( /у$/i ) ) {
+ // Checking if word ends on 'у'. 'У' can be either consonant 'W' or
+ // vowel 'U' in cyrillic Ossetic.
+ // Examples: {{grammar:genitive|аунеу}} = аунеуы,
+ // {{grammar:genitive|лæппу}} = лæппуйы.
+ if ( !word.substring( word.length - 2, word.length - 1 )
+ .match( /[аæеёиоыэюя]$/i ) ) {
+ jot = 'й';
+ }
+ } else if ( !word.match( /[бвгджзйклмнопрстфхцчшщьъ]$/i ) ) {
+ hyphen = '-';
+ }
+
+ switch ( form ) {
+ case 'genitive':
+ ending = hyphen + jot + 'ы';
+ break;
+ case 'dative':
+ ending = hyphen + jot + 'æн';
+ break;
+ case 'allative':
+ ending = hyphen + endAllative;
+ break;
+ case 'ablative':
+ if ( jot === 'й' ) {
+ ending = hyphen + jot + 'æ';
+ } else {
+ ending = hyphen + jot + 'æй';
+ }
+ break;
+ case 'superessive':
+ ending = hyphen + jot + 'ыл';
+ break;
+ case 'equative':
+ ending = hyphen + jot + 'ау';
+ break;
+ case 'comitative':
+ ending = hyphen + 'имæ';
+ break;
+ }
+
+ return word + ending;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/ru.js b/resources/lib/jquery.i18n/src/languages/ru.js
new file mode 100644
index 00000000..893b2386
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/ru.js
@@ -0,0 +1,29 @@
+/**
+ * Russian (Русский) language functions
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.ru = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ if ( form === 'genitive' ) { // родительный падеж
+ if ( word.substr( -1 ) === 'ь' ) {
+ word = word.substr( 0, word.length - 1 ) + 'я';
+ } else if ( word.substr( -2 ) === 'ия' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ии';
+ } else if ( word.substr( -2 ) === 'ка' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ки';
+ } else if ( word.substr( -2 ) === 'ти' ) {
+ word = word.substr( 0, word.length - 2 ) + 'тей';
+ } else if ( word.substr( -2 ) === 'ды' ) {
+ word = word.substr( 0, word.length - 2 ) + 'дов';
+ } else if ( word.substr( -3 ) === 'ник' ) {
+ word = word.substr( 0, word.length - 3 ) + 'ника';
+ }
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/sl.js b/resources/lib/jquery.i18n/src/languages/sl.js
new file mode 100644
index 00000000..a3aafc3b
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/sl.js
@@ -0,0 +1,26 @@
+/**
+ * Slovenian (Slovenščina) language functions
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.sl = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ // locative
+ case 'mestnik':
+ word = 'o ' + word;
+
+ break;
+ // instrumental
+ case 'orodnik':
+ word = 'z ' + word;
+
+ break;
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/resources/lib/jquery.i18n/src/languages/uk.js b/resources/lib/jquery.i18n/src/languages/uk.js
new file mode 100644
index 00000000..8e69efcd
--- /dev/null
+++ b/resources/lib/jquery.i18n/src/languages/uk.js
@@ -0,0 +1,39 @@
+/**
+ * Ukrainian (Українська) language functions
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.uk = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'genitive': // родовий відмінок
+ if ( word.substr( -1 ) === 'ь' ) {
+ word = word.substr( 0, word.length - 1 ) + 'я';
+ } else if ( word.substr( -2 ) === 'ія' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ії';
+ } else if ( word.substr( -2 ) === 'ка' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ки';
+ } else if ( word.substr( -2 ) === 'ти' ) {
+ word = word.substr( 0, word.length - 2 ) + 'тей';
+ } else if ( word.substr( -2 ) === 'ды' ) {
+ word = word.substr( 0, word.length - 2 ) + 'дов';
+ } else if ( word.substr( -3 ) === 'ник' ) {
+ word = word.substr( 0, word.length - 3 ) + 'ника';
+ }
+
+ break;
+ case 'accusative': // знахідний відмінок
+ if ( word.substr( -2 ) === 'ія' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ію';
+ }
+
+ break;
+ }
+
+ return word;
+ }
+ } );
+
+}( jQuery ) );