diff --git a/Resources.php b/Resources.php
index 1dc6cb12..ecc6036a 100644
--- a/Resources.php
+++ b/Resources.php
@@ -58,11 +58,16 @@ $wgResourceModules['ext.uls.init'] = array(
'mediawiki.util',
'jquery.json',
'jquery.uls',
- 'jquery.i18n',
+ 'ext.uls.i18n',
),
'position' => 'top',
) + $resourcePaths;
+$wgResourceModules['ext.uls.i18n'] = array(
+ 'scripts' => 'resources/js/ext.uls.i18n.js',
+ 'dependencies' => 'jquery.i18n',
+) + $resourcePaths;
+
$wgResourceModules['ext.uls.inputsettings'] = array(
'scripts' => 'resources/js/ext.uls.inputsettings.js',
'styles' => 'resources/css/ext.uls.inputsettings.css',
@@ -120,9 +125,30 @@ $wgResourceModules['ext.uls.webfonts.repository'] = array(
'scripts' => 'resources/js/ext.uls.webfonts.repository.js',
) + $resourcePaths;
-
$wgResourceModules['jquery.i18n'] = array(
- 'scripts' => 'lib/jquery.i18n.js',
+ 'scripts' => array(
+ 'lib/jquery.i18n/jquery.i18n.js',
+ 'lib/jquery.i18n/jquery.i18n.parser.js',
+ 'lib/jquery.i18n/jquery.i18n.emitter.js',
+ 'lib/jquery.i18n/jquery.i18n.language.js',
+ ),
+ 'dependencies' => 'mediawiki.libs.pluralruleparser',
+ 'languageScripts' => array(
+ 'bs' => 'lib/jquery.i18n/languages/bs.js',
+ 'dsb' => 'lib/jquery.i18n/languages/dsb.js',
+ 'fi' => 'lib/jquery.i18n/languages/fi.js',
+ 'ga' => 'lib/jquery.i18n/languages/ga.js',
+ 'he' => 'lib/jquery.i18n/languages/he.js',
+ 'hsb' => 'lib/jquery.i18n/languages/hsb.js',
+ 'hu' => 'lib/jquery.i18n/languages/hu.js',
+ 'hy' => 'lib/jquery.i18n/languages/hy.js',
+ 'la' => 'lib/jquery.i18n/languages/la.js',
+ 'ml' => 'lib/jquery.i18n/languages/ml.js',
+ 'os' => 'lib/jquery.i18n/languages/os.js',
+ 'ru' => 'lib/jquery.i18n/languages/ru.js',
+ 'sl' => 'lib/jquery.i18n/languages/sl.js',
+ 'uk' => 'lib/jquery.i18n/languages/uk.js',
+ ),
) + $resourcePaths;
$wgResourceModules['jquery.ime'] = array(
diff --git a/lib/jquery.i18n.js b/lib/jquery.i18n.js
deleted file mode 100644
index 6dea695c..00000000
--- a/lib/jquery.i18n.js
+++ /dev/null
@@ -1,2570 +0,0 @@
-/**
- * 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,
- slice = Array.prototype.slice;
- /**
- * @constructor
- * @param {Object} options
- */
- var 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;
-
- i18n = this;
- i18n.messageStore.init( i18n.locale );
- // Set locale of String environment
- String.locale = i18n.locale;
-
- // Override String.localeString method
- String.prototype.toLocaleString = function () {
- var localeParts, messageLocation, localePartIndex, value, locale, fallbackIndex;
-
- 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.toLowerCase().split( "-" );
- localePartIndex = localeParts.length;
-
- do {
- var _locale = localeParts.slice( 0, localePartIndex ).join( "-" );
-
- if ( i18n.options.messageLocationResolver ) {
- messageLocation = i18n.options.messageLocationResolver( _locale, value );
-
- if ( messageLocation
- && ( !i18n.messageStore.isLoaded(_locale ,messageLocation ) ) )
- {
- i18n.messageStore.load( messageLocation, _locale );
- }
- }
-
- var message = i18n.messageStore.get( _locale, 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++;
- }
-
- // fallback the original string value
- return value;
- };
- },
-
- /*
- * Destroy the i18n instance.
- */
- destroy: function () {
- $.removeData( document, 'i18n' );
- },
-
- /**
- * 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|null} data
- * @param {String} locale Language tag
- */
- load: function ( data, locale ) {
- this.messageStore.load( data, locale );
- },
-
- log: function (/* arguments */) {
- if ( window.console && $.i18n.debug ) {
- window.console.log.apply( window.console, arguments );
- }
- },
-
- /**
- * 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'];
- 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} [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 ( option ) {
- return this.each( function () {
- var $this = $( this );
- if ( $this.data( 'i18n' ) ) {
- var messageKey = $this.data( 'i18n' );
- var message = $.i18n( messageKey );
- if ( message !== messageKey ) {
- $this.text( message );
- }
- } 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.debug = false;
-
- /* Static members */
- I18N.defaults = {
- locale: String.locale,
- fallbackLocale: "en",
- parser: $.i18n.parser,
- messageStore: $.i18n.messageStore,
- /* messageLocationResolver - should be a function taking language code as argument and
- * returning absolute/ relative path to the localization file
- */
- messageLocationResolver: null
- };
-
- // Expose constructor
- $.I18N = I18N;
-}( jQuery ) );
-
-/**
- * jQuery Internationalization library Message loading , parsing, retrieving utilities
- *
- * 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 = {};
- this.locale = String.locale;
- };
-
- MessageStore.prototype = {
-
- /**
- * See https://github.com/wikimedia/jquery.i18n/wiki/Specification#wiki-Message_File_Loading
- *
- * @param locale
- */
- init: function ( locale ) {
- this.locale = locale;
- var messageStore = this;
- messageStore.log( "initializing for " + locale );
- var $links = $( "link" );
- var linksCount = $links.length;
- // Check for 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|null} data
- * @param {String} locale Language tag
- */
- load: function ( data, locale ) {
- var key = null,
- messageStore = this,
- hasOwn = Object.prototype.hasOwnProperty;
-
- if ( !data ) {
- // reset all localizations
- messageStore.log( "Resetting for locale" + locale );
- messageStore.messages = {};
- return;
- }
- /*
- // Only process this data load if the locale is our current
- // locale. Otherwise, put in the source queue for later.
- if ( locale && messageStore.locale !== locale ) {
- // queue loading locale if not needed
- if ( ! ( locale in messageStore.sources ) ) {
- messageStore.sources[locale] = [];
- }
- this.queue( locale, data );
- return;
- }*/
-
- if ( typeof data === 'string' ) {
- // This is a URL to the messages file.
- messageStore.log( "Loading messages from: " + data );
- messageStore.jsonMessageLoader( data ).done( function ( localization, textStatus ) {
- messageStore.load( localization, locale );
- messageStore.queue( locale, data );
- messageStore.markLoaded( locale, data );
- } );
- } else {
- // Data is either a group of messages for {locale},
- // or a group of languages with groups of messages inside.
- for ( key in data) {
- if ( hasOwn.call( data, key ) ) {
- if ( locale ) {
-
- // Lazy-init the object
- if ( !messageStore.messages[locale] ) {
- messageStore.messages[locale] = {};
- }
-
- // Update message object keys,
- // don't overwrite the entire object.
- messageStore.messages[locale][key] = data[key];
-
- messageStore.log( "[" + locale + "][" + key + "] : "
- + data[key] );
-
- // No {locale} given, assume data is a group of languages,
- // call this function again for each langauge.
- } else {
- messageStore.load( data[key], key );
- }
- }
- }
- }
- },
-
- log: function (/* arguments */) {
- if ( window.console && $.i18n.debug ) {
- window.console.log.apply( window.console, arguments );
- }
- },
-
- /**
- * Mark a message Location for a locale loaded
- *
- * @param locale
- * @param messageLocation
- */
- markLoaded: function ( locale, messageLocation ) {
- var i, queue = this.sources[locale];
-
- if ( !queue ) {
- this.queue( locale, messageLocation );
- queue = this.sources[locale];
- }
-
- this.sources[locale] = this.sources[locale] || [];
-
- for (i = 0; i < queue.length; i++) {
- if ( queue[i].source.url === messageLocation ) {
- queue[i].source.loaded = true;
- return;
- }
- }
- },
-
- /**
- * Register the message location for a locale, will be loaded when required
- *
- * @param locale
- * @param messageLocation
- */
- queue: function ( locale, messageLocation ) {
- var i, queue = this.sources[locale];
-
- this.sources[locale] = this.sources[locale] || [];
-
- if ( queue ) {
- for (i = 0; i < queue.length; i++) {
- if ( queue[i].source.url === messageLocation ) {
- return;
- }
- }
- }
-
- this.log( 'Source for: ' + locale + ' is ' + messageLocation + ' registered' );
- this.sources[locale].push( {
- source: {
- url: messageLocation,
- loaded: false
- }
- } );
- },
-
- /**
- * Load the messages from the source queue for the locale
- *
- * @param {String} locale
- */
- loadFromQueue: function ( locale ) {
- var i,
- queue = this.sources[locale];
-
- if ( queue ) {
- for (i = 0; i < queue.length; i++) {
- if ( !queue[i].source.loaded ) {
- this.load( queue[i].source.url, locale );
- this.sources[locale][i].source.loaded = true;
- }
- }
- }
- },
-
- isLoaded: function ( locale, messageLocation ) {
- var i, sources = this.sources[locale], result = false;
-
- if ( sources ) {
- for (i = 0; i < sources.length; i++) {
- if ( sources[i].source.url === messageLocation ) {
- result = true;
- }
- }
- }
-
- return result;
- },
-
- jsonMessageLoader: function ( url ) {
- var that = this;
- return $.ajax( {
- url: url,
- dataType: "json",
- async: false
- // that is unfortunate
- } ).fail( function ( jqxhr, settings, exception ) {
- that.log( "Error in loading messages from " + url + " Exception: " + exception );
- } );
- },
-
- /**
- *
- * @param locale
- * @param messageKey
- * @returns {Boolean}
- */
- get: function ( locale, messageKey ) {
- // load locale if not loaded
- if ( !this.messages[locale] ) {
- this.loadFromQueue( locale );
- }
- return this.messages[locale] && this.messages[locale][messageKey];
- }
- };
-
- $.extend( $.i18n.messageStore, new MessageStore() );
-
-}( jQuery, window ) );
-
-/**
- * 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 ) {
- $.i18n = $.i18n || {};
- $.i18n.fallbacks = {
- "ab": ["ru"],
- "ace": ["id"],
- "aln": ["sq"],
- "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 ) );
-
-/**
- * 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 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 pos = 0;
-
- // Try parsers until one works, if none work return null
- function choice ( parserSyntax ) {
- return function () {
- for ( var i = 0; i < parserSyntax.length; i++) {
- var 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;
- var result = [];
- var 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];
- };
- }
-
- var pipe = makeStringParser( '|' );
- var colon = makeStringParser( ':' );
- var backslash = makeStringParser( '\\' );
- var anyCharacter = makeRegexParser( /^./ );
- var dollar = makeStringParser( '$' );
- var digits = makeRegexParser( /^\d+/ );
- var regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ );
- var regularLiteralWithoutBar = makeRegexParser( /^[^{}\[\]$\\|]/ );
- var regularLiteralWithoutSpace = makeRegexParser( /^[^{}\[\]$\s]/ );
-
- // There is a general pattern -- parse a thing, if that 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 ] );
- var escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] );
- var escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] );
-
- function replacement () {
- var result = sequence( [ dollar, digits ] );
- if ( result === null ) {
- return null;
- }
- return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ];
- }
-
- var 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 result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] );
- if ( result === null ) {
- return null;
- }
- var 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] ];
- }
-
- var 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] );
- } ] );
-
- var openTemplate = makeStringParser( '{{' );
-
- var closeTemplate = makeStringParser( '}}' );
-
- function template () {
- var result = sequence( [ openTemplate, templateContents, closeTemplate ] );
- return result === null ? null : result[1];
- }
-
- var expression = choice( [ template, replacement, literal ] );
- var paramExpression = choice( [ template, replacement, literalWithoutBar ] );
-
- function start () {
- var result = nOrMore( 0, expression )();
-
- if ( result === null ) {
- return null;
- }
-
- return [ 'CONCAT' ].concat( result );
- }
-
- var result = start();
- return result;
- }
-
- };
-
- $.extend( $.i18n.parser, new MessageParser() );
-
-}( jQuery ) );
-
-/**
- * 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 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} 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,
- that = 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 that.emit( n, replacements );
- } );
- operation = node[0].toLowerCase();
- if ( typeof that[operation] === 'function' ) {
- ret = that[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
- * @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 ) );
-/*global pluralRuleParser */
-( function ( $ ) {
- 'use strict';
-
- var language = {
-
- // CLDR plural rules generated using
- // http://i18ndata.appspot.com/cldr/tags/unconfirmed/supplemental/plurals?action=browse&depth=-1
- // and compressed
- pluralRules: {
- gv: {
- one: 'n mod 10 in 1..2 or n mod 20 is 0'
- },
- gu: {
- one: 'n is 1'
- },
- rof: {
- one: 'n is 1'
- },
- ga: {
- few: 'n in 3..6',
- many: 'n in 7..10',
- two: 'n is 2',
- one: 'n is 1'
- },
- gl: {
- one: 'n is 1'
- },
- lg: {
- one: 'n is 1'
- },
- lb: {
- one: 'n is 1'
- },
- xog: {
- one: 'n is 1'
- },
- ln: {
- one: 'n in 0..1'
- },
- lo: '',
- brx: {
- one: 'n is 1'
- },
- tr: '',
- ts: {
- one: 'n is 1'
- },
- tn: {
- one: 'n is 1'
- },
- to: '',
- lt: {
- few: 'n mod 10 in 2..9 and n mod 100 not in 11..19',
- one: 'n mod 10 is 1 and n mod 100 not in 11..19'
- },
- tk: {
- one: 'n is 1'
- },
- th: '',
- ksb: {
- one: 'n is 1'
- },
- te: {
- one: 'n is 1'
- },
- ksh: {
- zero: 'n is 0',
- one: 'n is 1'
- },
- fil: {
- one: 'n in 0..1'
- },
- haw: {
- one: 'n is 1'
- },
- kcg: {
- one: 'n is 1'
- },
- ssy: {
- one: 'n is 1'
- },
- yo: '',
- de: {
- one: 'n is 1'
- },
- ko: '',
- da: {
- one: 'n is 1'
- },
- dz: '',
- dv: {
- one: 'n is 1'
- },
- guw: {
- one: 'n in 0..1'
- },
- shi: {
- few: 'n in 2..10',
- one: 'n within 0..1'
- },
- el: {
- one: 'n is 1'
- },
- eo: {
- one: 'n is 1'
- },
- en: {
- one: 'n is 1'
- },
- ses: '',
- teo: {
- one: 'n is 1'
- },
- ee: {
- one: 'n is 1'
- },
- kde: '',
- fr: {
- one: 'n within 0..2 and n is not 2'
- },
- eu: {
- one: 'n is 1'
- },
- et: {
- one: 'n is 1'
- },
- es: {
- one: 'n is 1'
- },
- seh: {
- one: 'n is 1'
- },
- ru: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- kl: {
- one: 'n is 1'
- },
- sms: {
- two: 'n is 2',
- one: 'n is 1'
- },
- smn: {
- two: 'n is 2',
- one: 'n is 1'
- },
- smj: {
- two: 'n is 2',
- one: 'n is 1'
- },
- smi: {
- two: 'n is 2',
- one: 'n is 1'
- },
- fy: {
- one: 'n is 1'
- },
- rm: {
- one: 'n is 1'
- },
- ro: {
- few: 'n is 0 OR n is not 1 AND n mod 100 in 1..19',
- one: 'n is 1'
- },
- bn: {
- one: 'n is 1'
- },
- sma: {
- two: 'n is 2',
- one: 'n is 1'
- },
- be: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- bg: {
- one: 'n is 1'
- },
- ms: '',
- wa: {
- one: 'n in 0..1'
- },
- ps: {
- one: 'n is 1'
- },
- wo: '',
- bm: '',
- jv: '',
- bo: '',
- bh: {
- one: 'n in 0..1'
- },
- kea: '',
- asa: {
- one: 'n is 1'
- },
- cgg: {
- one: 'n is 1'
- },
- br: {
- few: 'n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99',
- many: 'n mod 1000000 is 0 and n is not 0',
- two: 'n mod 10 is 2 and n mod 100 not in 12,72,92',
- one: 'n mod 10 is 1 and n mod 100 not in 11,71,91'
- },
- bs: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- ja: '',
- om: {
- one: 'n is 1'
- },
- fa: '',
- vun: {
- one: 'n is 1'
- },
- or: {
- one: 'n is 1'
- },
- xh: {
- one: 'n is 1'
- },
- nso: {
- one: 'n in 0..1'
- },
- ca: {
- one: 'n is 1'
- },
- cy: {
- few: 'n is 3',
- zero: 'n is 0',
- many: 'n is 6',
- two: 'n is 2',
- one: 'n is 1'
- },
- cs: {
- few: 'n in 2..4',
- one: 'n is 1'
- },
- zh: '',
- lv: {
- zero: 'n is 0',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- pt: {
- one: 'n is 1'
- },
- wae: {
- one: 'n is 1'
- },
- tl: {
- one: 'n in 0..1'
- },
- chr: {
- one: 'n is 1'
- },
- pa: {
- one: 'n is 1'
- },
- ak: {
- one: 'n in 0..1'
- },
- pl: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14',
- one: 'n is 1'
- },
- hr: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- am: {
- one: 'n in 0..1'
- },
- ti: {
- one: 'n in 0..1'
- },
- hu: '',
- hi: {
- one: 'n in 0..1'
- },
- jmc: {
- one: 'n is 1'
- },
- ha: {
- one: 'n is 1'
- },
- he: {
- one: 'n is 1'
- },
- mg: {
- one: 'n in 0..1'
- },
- fur: {
- one: 'n is 1'
- },
- bem: {
- one: 'n is 1'
- },
- ml: {
- one: 'n is 1'
- },
- mo: {
- few: 'n is 0 OR n is not 1 AND n mod 100 in 1..19',
- one: 'n is 1'
- },
- mn: {
- one: 'n is 1'
- },
- mk: {
- one: 'n mod 10 is 1 and n is not 11'
- },
- ur: {
- one: 'n is 1'
- },
- bez: {
- one: 'n is 1'
- },
- mt: {
- few: 'n is 0 or n mod 100 in 2..10',
- many: 'n mod 100 in 11..19',
- one: 'n is 1'
- },
- uk: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- mr: {
- one: 'n is 1'
- },
- ta: {
- one: 'n is 1'
- },
- my: '',
- sah: '',
- ve: {
- one: 'n is 1'
- },
- af: {
- one: 'n is 1'
- },
- vi: '',
- is: {
- one: 'n is 1'
- },
- iu: {
- two: 'n is 2',
- one: 'n is 1'
- },
- it: {
- one: 'n is 1'
- },
- kn: '',
- ii: '',
- ar: {
- few: 'n mod 100 in 3..10',
- zero: 'n is 0',
- many: 'n mod 100 in 11..99',
- two: 'n is 2',
- one: 'n is 1'
- },
- zu: {
- one: 'n is 1'
- },
- saq: {
- one: 'n is 1'
- },
- az: '',
- tzm: {
- one: 'n in 0..1 or n in 11..99'
- },
- id: '',
- ig: '',
- pap: {
- one: 'n is 1'
- },
- nl: {
- one: 'n is 1'
- },
- nn: {
- one: 'n is 1'
- },
- no: {
- one: 'n is 1'
- },
- nah: {
- one: 'n is 1'
- },
- nd: {
- one: 'n is 1'
- },
- ne: {
- one: 'n is 1'
- },
- ny: {
- one: 'n is 1'
- },
- naq: {
- two: 'n is 2',
- one: 'n is 1'
- },
- nyn: {
- one: 'n is 1'
- },
- kw: {
- two: 'n is 2',
- one: 'n is 1'
- },
- nr: {
- one: 'n is 1'
- },
- tig: {
- one: 'n is 1'
- },
- kab: {
- one: 'n within 0..2 and n is not 2'
- },
- mas: {
- one: 'n is 1'
- },
- rwk: {
- one: 'n is 1'
- },
- kaj: {
- one: 'n is 1'
- },
- lag: {
- zero: 'n is 0',
- one: 'n within 0..2 and n is not 0 and n is not 2'
- },
- syr: {
- one: 'n is 1'
- },
- kk: {
- one: 'n is 1'
- },
- ff: {
- one: 'n within 0..2 and n is not 2'
- },
- fi: {
- one: 'n is 1'
- },
- fo: {
- one: 'n is 1'
- },
- ka: '',
- gsw: {
- one: 'n is 1'
- },
- ckb: {
- one: 'n is 1'
- },
- ss: {
- one: 'n is 1'
- },
- sr: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- sq: {
- one: 'n is 1'
- },
- sw: {
- one: 'n is 1'
- },
- sv: {
- one: 'n is 1'
- },
- km: '',
- st: {
- one: 'n is 1'
- },
- sk: {
- few: 'n in 2..4',
- one: 'n is 1'
- },
- sh: {
- few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
- many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
- one: 'n mod 10 is 1 and n mod 100 is not 11'
- },
- so: {
- one: 'n is 1'
- },
- sn: {
- one: 'n is 1'
- },
- ku: {
- one: 'n is 1'
- },
- sl: {
- few: 'n mod 100 in 3..4',
- two: 'n mod 100 is 2',
- one: 'n mod 100 is 1'
- },
- sg: '',
- nb: {
- one: 'n is 1'
- },
- se: {
- two: 'n is 2',
- one: 'n is 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 = 0;
- if ( !forms || forms.length === 0 ) {
- return '';
- }
- 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 {num}
- * number 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 = '' + 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.
- * forms can be computed dynamically by overriding this method per language
- *
- * @param word {String}
- * @param form {String}
- * @return {String}
- */
- 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 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|false} List of digits in the passed language
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * 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 ) );
-
-/**
- * Ossetian (Ирон) language functions
- *
- * @author Santhosh Thottingal
- */
-
-( function ( $ ) {
- 'use strict';
-
- $.i18n.languages.os = $.extend( {}, $.i18n.languages['default'], {
- convertGrammar: function ( word, form ) {
- // Ending for allative case
- var endAllative = 'мæ';
- // Variable for 'j' beetwen vowels
- var jot = '';
- // Variable for "-" for not Ossetic words
- var hyphen = '';
- // Variable for ending
- var ending = '';
- // Checking if the $word is in plural form
- if ( word.match( /тæ$/i ) ) {
- word = word.substring( 0, word.length - 1 );
- endAllative = 'æм';
- }
- // Works if word is in singular form.
- // Checking if word ends on one of the vowels: е, ё, и, о, ы, э, ю,
- // я.
- else if ( word.match( /[аæеёиоыэюя]$/i ) ) {
- jot = 'й';
- }
- // Checking if word ends on 'у'. 'У' can be either consonant 'W' or
- // vowel 'U' in cyrillic Ossetic.
- // Examples: {{grammar:genitive|аунеу}} = аунеуы,
- // {{grammar:genitive|лæппу}} = лæппуйы.
- else if ( word.match( /у$/i ) ) {
- 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 ) );
-
-/**
- * Russian (Русский) language functions
- */
-
-( function ( $ ) {
- 'use strict';
-
- $.i18n.languages.ru = $.extend( {}, $.i18n.languages['default'], {
- convertGrammar: function ( word, form ) {
- if ( form === 'genitive' ) { // родительный падеж
- if ( ( word.substr( word.length - 4 ) === 'вики' ) ||
- ( word.substr( word.length - 4 ) === 'Вики' )
- ) {
- // ...
- } else if ( word.substr( word.length - 1 ) === 'ь' ) {
- word = word.substr( 0, word.length - 1 ) + 'я';
- } else if ( word.substr( word.length - 2 ) === 'ия' ) {
- word = word.substr( 0, word.length - 2 ) + 'ии';
- } else if ( word.substr( word.length - 2 ) === 'ка' ) {
- word = word.substr( 0, word.length - 2 ) + 'ки';
- } else if ( word.substr( word.length - 2 ) === 'ти' ) {
- word = word.substr( 0, word.length - 2 ) + 'тей';
- } else if ( word.substr( word.length - 2 ) === 'ды' ) {
- word = word.substr( 0, word.length - 2 ) + 'дов';
- } else if ( word.substr( word.length - 3 ) === 'ник' ) {
- word = word.substr( 0, word.length - 3 ) + 'ника';
- }
- }
- return word;
- }
- } );
-
-}( jQuery ) );
-
-/**
- * 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 ) );
-
-/**
- * Ukrainian (Українська) language functions
- */
-
-( function ( $ ) {
- 'use strict';
-
- $.i18n.languages.uk = $.extend( {}, $.i18n.languages['default'], {
- convertGrammar: function ( word, form ) {
- switch (form) {
- case 'genitive': // родовий відмінок
- if ( ( word.substr( word.length - 4 ) === 'вікі' ) ||
- ( word.substr( word.length - 4 ) === 'Вікі' )
- ) {
- // ...
- } else if ( word.substr( word.length - 1 ) === 'ь' ) {
- word = word.substr( 0, word.length - 1 ) + 'я';
- } else if ( word.substr( word.length - 2 ) === 'ія' ) {
- word = word.substr( 0, word.length - 2 ) + 'ії';
- } else if ( word.substr( word.length - 2 ) === 'ка' ) {
- word = word.substr( 0, word.length - 2 ) + 'ки';
- } else if ( word.substr( word.length - 2 ) === 'ти' ) {
- word = word.substr( 0, word.length - 2 ) + 'тей';
- } else if ( word.substr( word.length - 2 ) === 'ды' ) {
- word = word.substr( 0, word.length - 2 ) + 'дов';
- } else if ( word.substr( word.length - 3 ) === 'ник' ) {
- word = word.substr( 0, word.length - 3 ) + 'ника';
- }
- break;
- case 'accusative': // знахідний відмінок
- if ( ( word.substr( word.length - 4 ) === 'вікі' ) ||
- ( word.substr( word.length - 4 ) === 'Вікі' )
- ) {
- // ...
- } else if ( word.substr( word.length - 2 ) === 'ія' ) {
- word = word.substr( 0, word.length - 2 ) + 'ію';
- }
- break;
- }
- return word;
- }
- } );
-
-}( jQuery ) );
-
-/**
- * cldrpluralparser.js
- * A parser engine for CLDR plural rules.
- *
- * Copyright 2012 GPLV3+, Santhosh Thottingal
- *
- * @version 0.1.0-alpha
- * @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 rule
- * @param number
- * @return true|false|null
- */
-function pluralRuleParser(rule, number) {
- /*
- Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
- -----------------------------------------------------------------
-
- condition = and_condition ('or' and_condition)*
- and_condition = relation ('and' relation)*
- relation = is_relation | in_relation | within_relation | 'n'
- is_relation = expr 'is' ('not')? value
- in_relation = expr ('not')? 'in' range_list
- within_relation = expr ('not')? 'within' range_list
- expr = 'n' ('mod' value)?
- range_list = (range | value) (',' range_list)*
- value = digit+
- digit = 0|1|2|3|4|5|6|7|8|9
- range = value'..'value
-
- */
- // Indicates current position in the rule as we parse through it.
- // Shared among all parsing functions below.
- var pos = 0;
-
- var whitespace = makeRegexParser(/^\s+/);
- var digits = makeRegexParser(/^\d+/);
-
- var _n_ = makeStringParser('n');
- var _is_ = makeStringParser('is');
- var _mod_ = makeStringParser('mod');
- var _not_ = makeStringParser('not');
- var _in_ = makeStringParser('in');
- var _within_ = makeStringParser('within');
- var _range_ = makeStringParser('..');
- var _comma_ = makeStringParser(',');
- var _or_ = makeStringParser('or');
- var _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 () {
- for (var i = 0; i < parserSyntax.length; i++) {
- var 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 originalPos = pos;
- var result = [];
- for (var i = 0; i < parserSyntax.length; i++) {
- var 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;
- var result = [];
- var 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];
- };
- }
-
- function n() {
- var result = _n_();
- if (result === null) {
- debug(" -- failed n");
- return result;
- }
- result = parseInt(number, 10);
- debug(" -- passed n ", result);
- return result;
- }
-
- var expression = choice([mod, n]);
-
- function mod() {
- var result = sequence([n, whitespace, _mod_, whitespace, digits]);
- if (result === null) {
- debug(" -- failed mod");
- return null;
- }
- debug(" -- passed mod");
- return parseInt(result[0], 10) % parseInt(result[4], 10);
- }
-
- function not() {
- var result = sequence([whitespace, _not_]);
- if (result === null) {
- debug(" -- failed not");
- return null;
- } else {
- return result[1];
- }
- }
-
- function is() {
- var result = sequence([expression, whitespace, _is_, nOrMore(0, not), whitespace, digits]);
- if (result !== null) {
- debug(" -- passed is");
- if (result[3][0] === 'not') {
- return result[0] !== parseInt(result[5], 10);
- } else {
- return result[0] === parseInt(result[5], 10);
- }
- }
- debug(" -- failed is");
- return null;
- }
-
- function rangeList() {
- // range_list = (range | value) (',' range_list)*
- var result = sequence([choice([range, digits]), nOrMore(0, rangeTail)]);
- var resultList = [];
- if (result !== null) {
- resultList = resultList.concat(result[0], 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;
- }
-
- function range() {
- var result = sequence([digits, _range_, digits]);
- if (result !== null) {
- debug(" -- passed range");
- var array = [];
- var left = parseInt(result[0], 10);
- var right = parseInt(result[2], 10);
- for ( i = left; i <= right; i++) {
- array.push(i);
- }
- return array;
- }
- debug(" -- failed range");
- return null;
- }
-
- function _in() {
- // in_relation = expr ('not')? 'in' range_list
- var result = sequence([expression, nOrMore(0, not), whitespace, _in_, whitespace, rangeList]);
- if (result !== null) {
- debug(" -- passed _in");
- var range_list = result[5];
- for (var i = 0; i < range_list.length; i++) {
- if (parseInt(range_list[i], 10) === result[0]) {
- return (result[1][0] !== 'not');
- }
- }
- return (result[1][0] === 'not');
- }
- debug(" -- failed _in ");
- return null;
- }
-
- function within() {
- var result = sequence([expression, whitespace, _within_, whitespace, rangeList]);
- if (result !== null) {
- debug(" -- passed within ");
- var range_list = result[4];
- return (parseInt( range_list[0],10 )<= result[0] && result[0] <= parseInt( range_list[1], 10));
- }
- debug(" -- failed within ");
- return null;
- }
-
-
- var relation = choice([is, _in, within]);
-
- function and() {
- var result = sequence([relation, whitespace, _and_, whitespace, condition]);
- if (result) {
- debug(" -- passed and");
- return result[0] && result[4];
- }
- debug(" -- failed and");
- return null;
- }
-
- function or() {
- var result = sequence([relation, whitespace, _or_, whitespace, condition]);
- if (result) {
- debug(" -- passed or");
- return result[0] || result[4];
- }
- debug(" -- failed or");
- return null;
- }
-
- var condition = choice([and, or, relation]);
-
- function isInt(n) {
- return parseFloat(n) % 1 === 0;
- }
-
-
- function start() {
- if (!isInt(number)) {
- return false;
- }
- var result = condition();
- return result;
- }
-
-
- var result = start();
-
- /*
- * 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 || pos !== rule.length) {
- // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result);
- }
-
- return result;
-}
-
-/* For module loaders, e.g. NodeJS, NPM */
-if (typeof module !== 'undefined' && module.exports) {
- module.exports = pluralRuleParser;
-}
-
diff --git a/lib/jquery.i18n/jquery.i18n.emitter.js b/lib/jquery.i18n/jquery.i18n.emitter.js
new file mode 100644
index 00000000..590ef38b
--- /dev/null
+++ b/lib/jquery.i18n/jquery.i18n.emitter.js
@@ -0,0 +1,169 @@
+/**
+ * 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/lib/jquery.i18n/jquery.i18n.fallbacks.js b/lib/jquery.i18n/jquery.i18n.fallbacks.js
new file mode 100644
index 00000000..db4018b0
--- /dev/null
+++ b/lib/jquery.i18n/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 || {};
+ $.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/lib/jquery.i18n/jquery.i18n.js b/lib/jquery.i18n/jquery.i18n.js
new file mode 100644
index 00000000..7c20f119
--- /dev/null
+++ b/lib/jquery.i18n/jquery.i18n.js
@@ -0,0 +1,265 @@
+/**
+ * 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,
+ slice = Array.prototype.slice;
+ /**
+ * @constructor
+ * @param {Object} options
+ */
+ var 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;
+
+ i18n = this;
+ i18n.messageStore.init( i18n.locale );
+ // Set locale of String environment
+ String.locale = i18n.locale;
+
+ // Override String.localeString method
+ String.prototype.toLocaleString = function () {
+ var localeParts, messageLocation, localePartIndex, value, locale, fallbackIndex;
+
+ 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.toLowerCase().split( '-' );
+ localePartIndex = localeParts.length;
+
+ do {
+ var _locale = localeParts.slice( 0, localePartIndex ).join( '-' );
+
+ if ( i18n.options.messageLocationResolver ) {
+ messageLocation = i18n.options.messageLocationResolver( _locale, value );
+
+ if ( messageLocation &&
+ ( !i18n.messageStore.isLoaded( _locale ,messageLocation ) )
+ ) {
+ i18n.messageStore.load( messageLocation, _locale );
+ }
+ }
+
+ var message = i18n.messageStore.get( _locale, 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.
+ * 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|null} data
+ * @param {String} locale Language tag
+ */
+ load: function ( data, locale ) {
+ this.messageStore.load( data, locale );
+ },
+
+ log: function ( /* arguments */ ) {
+ if ( window.console && $.i18n.debug ) {
+ window.console.log.apply( window.console, arguments );
+ }
+ },
+
+ /**
+ * 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' );
+ String.locale = i18n.locale;
+ if ( !i18n ) {
+ i18n = new I18N( );
+ $.data( document, 'i18n', i18n );
+ }
+ return this.each( function () {
+ var $this = $( this );
+
+ if ( $this.data( 'i18n' ) ) {
+ var messageKey = $this.data( 'i18n' ),
+ message = messageKey.toLocaleString();
+ if ( message !== '' ) {
+ $this.text( message );
+ }
+ } 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.debug = false;
+
+ /* Static members */
+ I18N.defaults = {
+ locale: String.locale,
+ fallbackLocale: 'en',
+ parser: $.i18n.parser,
+ messageStore: $.i18n.messageStore,
+ /* messageLocationResolver - should be a function taking language code as argument and
+ * returning absolute or relative path to the localization file
+ */
+ messageLocationResolver: null
+ };
+
+ // Expose constructor
+ $.I18N = I18N;
+}( jQuery ) );
diff --git a/lib/jquery.i18n/jquery.i18n.language.js b/lib/jquery.i18n/jquery.i18n.language.js
new file mode 100644
index 00000000..ae252865
--- /dev/null
+++ b/lib/jquery.i18n/jquery.i18n.language.js
@@ -0,0 +1,699 @@
+/*global pluralRuleParser */
+( function ( $ ) {
+ 'use strict';
+
+ var language = {
+
+ // CLDR plural rules generated using
+ // http://i18ndata.appspot.com/cldr/tags/unconfirmed/supplemental/plurals?action=browse&depth=-1
+ // and compressed
+ pluralRules: {
+ gv: {
+ one: 'n mod 10 in 1..2 or n mod 20 is 0'
+ },
+ gu: {
+ one: 'n is 1'
+ },
+ rof: {
+ one: 'n is 1'
+ },
+ ga: {
+ few: 'n in 3..6',
+ many: 'n in 7..10',
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ gl: {
+ one: 'n is 1'
+ },
+ lg: {
+ one: 'n is 1'
+ },
+ lb: {
+ one: 'n is 1'
+ },
+ xog: {
+ one: 'n is 1'
+ },
+ ln: {
+ one: 'n in 0..1'
+ },
+ lo: '',
+ brx: {
+ one: 'n is 1'
+ },
+ tr: '',
+ ts: {
+ one: 'n is 1'
+ },
+ tn: {
+ one: 'n is 1'
+ },
+ to: '',
+ lt: {
+ few: 'n mod 10 in 2..9 and n mod 100 not in 11..19',
+ one: 'n mod 10 is 1 and n mod 100 not in 11..19'
+ },
+ tk: {
+ one: 'n is 1'
+ },
+ th: '',
+ ksb: {
+ one: 'n is 1'
+ },
+ te: {
+ one: 'n is 1'
+ },
+ ksh: {
+ zero: 'n is 0',
+ one: 'n is 1'
+ },
+ fil: {
+ one: 'n in 0..1'
+ },
+ haw: {
+ one: 'n is 1'
+ },
+ kcg: {
+ one: 'n is 1'
+ },
+ ssy: {
+ one: 'n is 1'
+ },
+ yo: '',
+ de: {
+ one: 'n is 1'
+ },
+ ko: '',
+ da: {
+ one: 'n is 1'
+ },
+ dz: '',
+ dv: {
+ one: 'n is 1'
+ },
+ guw: {
+ one: 'n in 0..1'
+ },
+ shi: {
+ few: 'n in 2..10',
+ one: 'n within 0..1'
+ },
+ el: {
+ one: 'n is 1'
+ },
+ eo: {
+ one: 'n is 1'
+ },
+ en: {
+ one: 'n is 1'
+ },
+ ses: '',
+ teo: {
+ one: 'n is 1'
+ },
+ ee: {
+ one: 'n is 1'
+ },
+ kde: '',
+ fr: {
+ one: 'n within 0..2 and n is not 2'
+ },
+ eu: {
+ one: 'n is 1'
+ },
+ et: {
+ one: 'n is 1'
+ },
+ es: {
+ one: 'n is 1'
+ },
+ seh: {
+ one: 'n is 1'
+ },
+ ru: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ kl: {
+ one: 'n is 1'
+ },
+ sms: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ smn: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ smj: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ smi: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ fy: {
+ one: 'n is 1'
+ },
+ rm: {
+ one: 'n is 1'
+ },
+ ro: {
+ few: 'n is 0 OR n is not 1 AND n mod 100 in 1..19',
+ one: 'n is 1'
+ },
+ bn: {
+ one: 'n is 1'
+ },
+ sma: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ be: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ bg: {
+ one: 'n is 1'
+ },
+ ms: '',
+ wa: {
+ one: 'n in 0..1'
+ },
+ ps: {
+ one: 'n is 1'
+ },
+ wo: '',
+ bm: '',
+ jv: '',
+ bo: '',
+ bh: {
+ one: 'n in 0..1'
+ },
+ kea: '',
+ asa: {
+ one: 'n is 1'
+ },
+ cgg: {
+ one: 'n is 1'
+ },
+ br: {
+ few: 'n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99',
+ many: 'n mod 1000000 is 0 and n is not 0',
+ two: 'n mod 10 is 2 and n mod 100 not in 12,72,92',
+ one: 'n mod 10 is 1 and n mod 100 not in 11,71,91'
+ },
+ bs: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ ja: '',
+ om: {
+ one: 'n is 1'
+ },
+ fa: '',
+ vun: {
+ one: 'n is 1'
+ },
+ or: {
+ one: 'n is 1'
+ },
+ xh: {
+ one: 'n is 1'
+ },
+ nso: {
+ one: 'n in 0..1'
+ },
+ ca: {
+ one: 'n is 1'
+ },
+ cy: {
+ few: 'n is 3',
+ zero: 'n is 0',
+ many: 'n is 6',
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ cs: {
+ few: 'n in 2..4',
+ one: 'n is 1'
+ },
+ zh: '',
+ lv: {
+ zero: 'n is 0',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ pt: {
+ one: 'n is 1'
+ },
+ wae: {
+ one: 'n is 1'
+ },
+ tl: {
+ one: 'n in 0..1'
+ },
+ chr: {
+ one: 'n is 1'
+ },
+ pa: {
+ one: 'n is 1'
+ },
+ ak: {
+ one: 'n in 0..1'
+ },
+ pl: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14',
+ one: 'n is 1'
+ },
+ hr: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ am: {
+ one: 'n in 0..1'
+ },
+ ti: {
+ one: 'n in 0..1'
+ },
+ hu: '',
+ hi: {
+ one: 'n in 0..1'
+ },
+ jmc: {
+ one: 'n is 1'
+ },
+ ha: {
+ one: 'n is 1'
+ },
+ he: {
+ one: 'n is 1'
+ },
+ mg: {
+ one: 'n in 0..1'
+ },
+ fur: {
+ one: 'n is 1'
+ },
+ bem: {
+ one: 'n is 1'
+ },
+ ml: {
+ one: 'n is 1'
+ },
+ mo: {
+ few: 'n is 0 OR n is not 1 AND n mod 100 in 1..19',
+ one: 'n is 1'
+ },
+ mn: {
+ one: 'n is 1'
+ },
+ mk: {
+ one: 'n mod 10 is 1 and n is not 11'
+ },
+ ur: {
+ one: 'n is 1'
+ },
+ bez: {
+ one: 'n is 1'
+ },
+ mt: {
+ few: 'n is 0 or n mod 100 in 2..10',
+ many: 'n mod 100 in 11..19',
+ one: 'n is 1'
+ },
+ uk: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ mr: {
+ one: 'n is 1'
+ },
+ ta: {
+ one: 'n is 1'
+ },
+ my: '',
+ sah: '',
+ ve: {
+ one: 'n is 1'
+ },
+ af: {
+ one: 'n is 1'
+ },
+ vi: '',
+ is: {
+ one: 'n is 1'
+ },
+ iu: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ it: {
+ one: 'n is 1'
+ },
+ kn: '',
+ ii: '',
+ ar: {
+ few: 'n mod 100 in 3..10',
+ zero: 'n is 0',
+ many: 'n mod 100 in 11..99',
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ zu: {
+ one: 'n is 1'
+ },
+ saq: {
+ one: 'n is 1'
+ },
+ az: '',
+ tzm: {
+ one: 'n in 0..1 or n in 11..99'
+ },
+ id: '',
+ ig: '',
+ pap: {
+ one: 'n is 1'
+ },
+ nl: {
+ one: 'n is 1'
+ },
+ nn: {
+ one: 'n is 1'
+ },
+ no: {
+ one: 'n is 1'
+ },
+ nah: {
+ one: 'n is 1'
+ },
+ nd: {
+ one: 'n is 1'
+ },
+ ne: {
+ one: 'n is 1'
+ },
+ ny: {
+ one: 'n is 1'
+ },
+ naq: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ nyn: {
+ one: 'n is 1'
+ },
+ kw: {
+ two: 'n is 2',
+ one: 'n is 1'
+ },
+ nr: {
+ one: 'n is 1'
+ },
+ tig: {
+ one: 'n is 1'
+ },
+ kab: {
+ one: 'n within 0..2 and n is not 2'
+ },
+ mas: {
+ one: 'n is 1'
+ },
+ rwk: {
+ one: 'n is 1'
+ },
+ kaj: {
+ one: 'n is 1'
+ },
+ lag: {
+ zero: 'n is 0',
+ one: 'n within 0..2 and n is not 0 and n is not 2'
+ },
+ syr: {
+ one: 'n is 1'
+ },
+ kk: {
+ one: 'n is 1'
+ },
+ ff: {
+ one: 'n within 0..2 and n is not 2'
+ },
+ fi: {
+ one: 'n is 1'
+ },
+ fo: {
+ one: 'n is 1'
+ },
+ ka: '',
+ gsw: {
+ one: 'n is 1'
+ },
+ ckb: {
+ one: 'n is 1'
+ },
+ ss: {
+ one: 'n is 1'
+ },
+ sr: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ sq: {
+ one: 'n is 1'
+ },
+ sw: {
+ one: 'n is 1'
+ },
+ sv: {
+ one: 'n is 1'
+ },
+ km: '',
+ st: {
+ one: 'n is 1'
+ },
+ sk: {
+ few: 'n in 2..4',
+ one: 'n is 1'
+ },
+ sh: {
+ few: 'n mod 10 in 2..4 and n mod 100 not in 12..14',
+ many: 'n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14',
+ one: 'n mod 10 is 1 and n mod 100 is not 11'
+ },
+ so: {
+ one: 'n is 1'
+ },
+ sn: {
+ one: 'n is 1'
+ },
+ ku: {
+ one: 'n is 1'
+ },
+ sl: {
+ few: 'n mod 100 in 3..4',
+ two: 'n mod 100 is 2',
+ one: 'n mod 100 is 1'
+ },
+ sg: '',
+ nb: {
+ one: 'n is 1'
+ },
+ se: {
+ two: 'n is 2',
+ one: 'n is 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;
+
+ if ( !forms || forms.length === 0 ) {
+ return '';
+ }
+
+ 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 = '' + 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 ) {
+ 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/lib/jquery.i18n/jquery.i18n.messages.js b/lib/jquery.i18n/jquery.i18n.messages.js
new file mode 100644
index 00000000..f14602da
--- /dev/null
+++ b/lib/jquery.i18n/jquery.i18n.messages.js
@@ -0,0 +1,250 @@
+/**
+ * jQuery Internationalization library Message loading , parsing, retrieving utilities
+ *
+ * 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 = {};
+ this.locale = String.locale;
+ };
+
+ MessageStore.prototype = {
+
+ /**
+ * See https://github.com/wikimedia/jquery.i18n/wiki/Specification#wiki-Message_File_Loading
+ *
+ * @param locale
+ */
+ init: function ( locale ) {
+ var messageStore = this;
+
+ messageStore.locale = locale;
+ messageStore.log( 'initializing for ' + locale );
+
+ $( 'link' ).each( function ( index, element ) {
+ var $link = $( element ),
+ rel = ( $link.attr( 'rel' ) || '' ).toLowerCase().split( /\s+/ );
+
+ if ( $.inArray( 'localizations', rel ) !== -1 ) {
+ // multiple localizations
+ messageStore.load( $link.attr( 'href' ) );
+ } else if ( $.inArray( 'localization', rel ) !== -1 ) {
+ // single localization
+ messageStore.queue( ( $link.attr( 'hreflang' ) || '' ).toLowerCase(),
+ $link.attr( 'href' ) );
+ }
+ } );
+ },
+
+ /**
+ * 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|null} data
+ * @param {String} locale Language tag
+ */
+ load: function ( data, locale ) {
+ var key = null,
+ messageStore = this,
+ hasOwn = Object.prototype.hasOwnProperty;
+
+ if ( !data ) {
+ // reset all localizations
+ messageStore.log( 'Resetting for locale ' + locale );
+ messageStore.messages = {};
+
+ return;
+ }
+
+ if ( typeof data === 'string' ) {
+ // This is a URL to the messages file.
+ messageStore.log( 'Loading messages from: ' + data );
+
+ messageStore.jsonMessageLoader( data ).done( function ( localization, textStatus ) {
+ messageStore.load( localization, locale );
+ messageStore.queue( locale, data );
+ messageStore.markLoaded( locale, data );
+ } );
+ } else {
+ // Data is either a group of messages for {locale},
+ // or a group of languages with groups of messages inside.
+ for ( key in data ) {
+ if ( hasOwn.call( data, key ) ) {
+ if ( locale ) {
+
+ // Lazy-init the object
+ if ( !messageStore.messages[locale] ) {
+ messageStore.messages[locale] = {};
+ }
+
+ // Update message object keys,
+ // don't overwrite the entire object.
+ messageStore.messages[locale][key] = data[key];
+
+ messageStore.log(
+ '[' + locale + '][' + key + '] : ' + data[key]
+ );
+
+ // No {locale} given, assume data is a group of languages,
+ // call this function again for each langauge.
+ } else {
+ messageStore.load( data[key], key );
+ }
+ }
+ }
+ }
+ },
+
+ log: function ( /* arguments */ ) {
+ if ( window.console && $.i18n.debug ) {
+ window.console.log.apply( window.console, arguments );
+ }
+ },
+
+ /**
+ * Mark a message Location for a locale loaded
+ *
+ * @param locale
+ * @param messageLocation
+ */
+ markLoaded: function ( locale, messageLocation ) {
+ var i,
+ queue = this.sources[locale];
+
+ if ( !queue ) {
+ this.queue( locale, messageLocation );
+ queue = this.sources[locale];
+ }
+
+ this.sources[locale] = this.sources[locale] || [];
+
+ for ( i = 0; i < queue.length; i++ ) {
+ if ( queue[i].source.url === messageLocation ) {
+ queue[i].source.loaded = true;
+
+ return;
+ }
+ }
+ },
+
+ /**
+ * Register the message location for a locale, will be loaded when required
+ *
+ * @param locale
+ * @param messageLocation
+ */
+ queue: function ( locale, messageLocation ) {
+ var i,
+ queue = this.sources[locale];
+
+ this.sources[locale] = this.sources[locale] || [];
+
+ if ( queue ) {
+ for ( i = 0; i < queue.length; i++ ) {
+ if ( queue[i].source.url === messageLocation ) {
+ return;
+ }
+ }
+ }
+
+ this.log( 'Source for: ' + locale + ' is ' + messageLocation + ' registered' );
+ this.sources[locale].push( {
+ source: {
+ url: messageLocation,
+ loaded: false
+ }
+ } );
+ },
+
+ /**
+ * Load the messages from the source queue for the locale
+ *
+ * @param {String} locale
+ */
+ loadFromQueue: function ( locale ) {
+ var i,
+ queue = this.sources[locale];
+
+ if ( queue ) {
+ for ( i = 0; i < queue.length; i++ ) {
+ if ( !queue[i].source.loaded ) {
+ this.load( queue[i].source.url, locale );
+ this.sources[locale][i].source.loaded = true;
+ }
+ }
+ }
+ },
+
+ isLoaded: function ( locale, messageLocation ) {
+ var i,
+ sources = this.sources[locale],
+ result = false;
+
+ if ( sources ) {
+ for ( i = 0; i < sources.length; i++ ) {
+ if ( sources[i].source.url === messageLocation ) {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+ },
+
+ jsonMessageLoader: function ( url ) {
+ var messageStore = this;
+
+ return $.ajax( {
+ url: url,
+ dataType: 'json',
+ //async: false
+ // This is unfortunate.
+ } ).fail( function ( jqxhr, settings, exception ) {
+ messageStore.log( 'Error in loading messages from ' + url + ' Exception: ' + exception );
+ } );
+ },
+
+ /**
+ *
+ * @param locale
+ * @param messageKey
+ * @returns {Boolean}
+ */
+ get: function ( locale, messageKey ) {
+ // load locale if not loaded
+ if ( !this.messages[locale] ) {
+ this.loadFromQueue( locale );
+ }
+
+ return this.messages[locale] && this.messages[locale][messageKey];
+ }
+ };
+
+ $.extend( $.i18n.messageStore, new MessageStore() );
+
+}( jQuery, window ) );
diff --git a/lib/jquery.i18n/jquery.i18n.parser.js b/lib/jquery.i18n/jquery.i18n.parser.js
new file mode 100644
index 00000000..f26109bd
--- /dev/null
+++ b/lib/jquery.i18n/jquery.i18n.parser.js
@@ -0,0 +1,305 @@
+/**
+ * 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 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];
+ };
+ }
+
+ var pipe = makeStringParser( '|' );
+ var colon = makeStringParser( ':' );
+ var backslash = makeStringParser( '\\' );
+ var anyCharacter = makeRegexParser( /^./ );
+ var dollar = makeStringParser( '$' );
+ var digits = makeRegexParser( /^\d+/ );
+ var regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ );
+ var regularLiteralWithoutBar = makeRegexParser( /^[^{}\[\]$\\|]/ );
+ var 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 ] );
+ var escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] );
+ var escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] );
+
+ function replacement () {
+ var result = sequence( [ dollar, digits ] );
+
+ if ( result === null ) {
+ return null;
+ }
+
+ return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ];
+ }
+
+ var 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 result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] );
+
+ if ( result === null ) {
+ return null;
+ }
+
+ var 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] ];
+ }
+
+ var 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] );
+ }
+ ] );
+
+ var openTemplate = makeStringParser( '{{' );
+ var closeTemplate = makeStringParser( '}}' );
+
+ function template () {
+ var result = sequence( [ openTemplate, templateContents, closeTemplate ] );
+
+ return result === null ? null : result[1];
+ }
+
+ var expression = choice( [ template, replacement, literal ] );
+ var paramExpression = choice( [ template, replacement, literalWithoutBar ] );
+
+ function start () {
+ var result = nOrMore( 0, expression )();
+
+ if ( result === null ) {
+ return null;
+ }
+
+ return [ 'CONCAT' ].concat( result );
+ }
+
+ var 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/lib/jquery.i18n/languages/bs.js b/lib/jquery.i18n/languages/bs.js
new file mode 100644
index 00000000..5370069e
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/dsb.js b/lib/jquery.i18n/languages/dsb.js
new file mode 100644
index 00000000..cc069ebf
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/fi.js b/lib/jquery.i18n/languages/fi.js
new file mode 100644
index 00000000..d8e9578a
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/ga.js b/lib/jquery.i18n/languages/ga.js
new file mode 100644
index 00000000..1aceab75
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/he.js b/lib/jquery.i18n/languages/he.js
new file mode 100644
index 00000000..cbbe90b9
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/hsb.js b/lib/jquery.i18n/languages/hsb.js
new file mode 100644
index 00000000..957616f6
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/hu.js b/lib/jquery.i18n/languages/hu.js
new file mode 100644
index 00000000..1177b850
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/hy.js b/lib/jquery.i18n/languages/hy.js
new file mode 100644
index 00000000..9c568992
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/la.js b/lib/jquery.i18n/languages/la.js
new file mode 100644
index 00000000..11c1122d
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/ml.js b/lib/jquery.i18n/languages/ml.js
new file mode 100644
index 00000000..02a5159c
--- /dev/null
+++ b/lib/jquery.i18n/languages/ml.js
@@ -0,0 +1,93 @@
+/**
+ * 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 ) === 'ന്' ) {
+ // chillu n. അവൻ -> അവന്
+ word = word.substr( 0, word.length - 1 );
+ } else if ( word.substr( -1 ) === 'ൾ' || word.substr( -3 ) === 'ള്' ) {
+ word += 'ക്ക്';
+ } else if ( word.substr( -1 ) === 'ർ' || word.substr( -3 ) === 'ര്' ) {
+ word += 'ക്ക്';
+ } else if ( word.substr( -1 ) === 'ൽ' ) {
+ // Atomic chillu ൽ , ഫയൽ -> ഫയലിന്
+ word = word.substr( 0, word.length - 1 ) + 'ലിന്';
+ } else if ( word.substr( -3 ) === 'ല്' ) {
+ // chillu ല് , ഫയല് -> ഫയലിന്
+ 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 ) === 'ന്' ) {
+ // chillu n. അവൻ -> അവന്റെ
+ word = word.substr( 0, word.length -1 ) + 'റെ';
+ } else if ( word.substr( -3 ) === 'ള്' ) {
+ // chillu n. അവൾ -> അവളുടെ
+ word = word.substr( 0, word.length - 2 ) + 'ുടെ';
+ } else if ( word.substr( -1 ) === 'ൾ' ) {
+ // Atomic chillu n. അവള് -> അവളുടെ
+ word = word.substr( 0, word.length - 1 ) + 'ളുടെ';
+ } else if ( word.substr( -1 ) === 'ൽ' ) {
+ // Atomic l. മുയല് -> മുയലിന്റെ
+ word = word.substr( 0, word.length - 1 ) + 'ലിന്റെ';
+ } else if ( word.substr( -3 ) === 'ല്' ) {
+ // chillu l. മുയല് -> അവളുടെ
+ word = word.substr( 0, word.length - 2 ) + 'ിന്റെ';
+ } else if ( word.substr( -3 ) === 'ര്' ) {
+ // chillu r. അവര് -> അവരുടെ
+ 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/lib/jquery.i18n/languages/os.js b/lib/jquery.i18n/languages/os.js
new file mode 100644
index 00000000..7b14d6ea
--- /dev/null
+++ b/lib/jquery.i18n/languages/os.js
@@ -0,0 +1,75 @@
+/**
+ * Ossetian (Ирон) language functions
+ *
+ * @author Santhosh Thottingal
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.os = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ // Ending for allative case
+ var endAllative = 'мæ';
+ // Variable for 'j' beetwen vowels
+ var jot = '';
+ // Variable for "-" for not Ossetic words
+ var hyphen = '';
+ // Variable for ending
+ var ending = '';
+ // Checking if the $word is in plural form
+ if ( word.match( /тæ$/i ) ) {
+ word = word.substring( 0, word.length - 1 );
+ endAllative = 'æм';
+ }
+ // Works if word is in singular form.
+ // Checking if word ends on one of the vowels: е, ё, и, о, ы, э, ю,
+ // я.
+ else if ( word.match( /[аæеёиоыэюя]$/i ) ) {
+ jot = 'й';
+ }
+ // Checking if word ends on 'у'. 'У' can be either consonant 'W' or
+ // vowel 'U' in cyrillic Ossetic.
+ // Examples: {{grammar:genitive|аунеу}} = аунеуы,
+ // {{grammar:genitive|лæппу}} = лæппуйы.
+ else if ( word.match( /у$/i ) ) {
+ 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/lib/jquery.i18n/languages/ru.js b/lib/jquery.i18n/languages/ru.js
new file mode 100644
index 00000000..9452036e
--- /dev/null
+++ b/lib/jquery.i18n/languages/ru.js
@@ -0,0 +1,33 @@
+/**
+ * Russian (Русский) language functions
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.ru = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ if ( form === 'genitive' ) { // родительный падеж
+ if ( ( word.substr( word.length - 4 ) === 'вики' ) ||
+ ( word.substr( word.length - 4 ) === 'Вики' )
+ ) {
+ // ...
+ } else if ( word.substr( word.length - 1 ) === 'ь' ) {
+ word = word.substr( 0, word.length - 1 ) + 'я';
+ } else if ( word.substr( word.length - 2 ) === 'ия' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ии';
+ } else if ( word.substr( word.length - 2 ) === 'ка' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ки';
+ } else if ( word.substr( word.length - 2 ) === 'ти' ) {
+ word = word.substr( 0, word.length - 2 ) + 'тей';
+ } else if ( word.substr( word.length - 2 ) === 'ды' ) {
+ word = word.substr( 0, word.length - 2 ) + 'дов';
+ } else if ( word.substr( word.length - 3 ) === 'ник' ) {
+ word = word.substr( 0, word.length - 3 ) + 'ника';
+ }
+ }
+
+ return word;
+ }
+ } );
+}( jQuery ) );
diff --git a/lib/jquery.i18n/languages/sl.js b/lib/jquery.i18n/languages/sl.js
new file mode 100644
index 00000000..a3aafc3b
--- /dev/null
+++ b/lib/jquery.i18n/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/lib/jquery.i18n/languages/uk.js b/lib/jquery.i18n/languages/uk.js
new file mode 100644
index 00000000..bd92bc01
--- /dev/null
+++ b/lib/jquery.i18n/languages/uk.js
@@ -0,0 +1,47 @@
+/**
+ * Ukrainian (Українська) language functions
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ $.i18n.languages.uk = $.extend( {}, $.i18n.languages['default'], {
+ convertGrammar: function ( word, form ) {
+ switch ( form ) {
+ case 'genitive': // родовий відмінок
+ if ( ( word.substr( word.length - 4 ) === 'вікі' ) ||
+ ( word.substr( word.length - 4 ) === 'Вікі' )
+ ) {
+ // ...
+ } else if ( word.substr( word.length - 1 ) === 'ь' ) {
+ word = word.substr( 0, word.length - 1 ) + 'я';
+ } else if ( word.substr( word.length - 2 ) === 'ія' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ії';
+ } else if ( word.substr( word.length - 2 ) === 'ка' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ки';
+ } else if ( word.substr( word.length - 2 ) === 'ти' ) {
+ word = word.substr( 0, word.length - 2 ) + 'тей';
+ } else if ( word.substr( word.length - 2 ) === 'ды' ) {
+ word = word.substr( 0, word.length - 2 ) + 'дов';
+ } else if ( word.substr( word.length - 3 ) === 'ник' ) {
+ word = word.substr( 0, word.length - 3 ) + 'ника';
+ }
+
+ break;
+ case 'accusative': // знахідний відмінок
+ if ( ( word.substr( word.length - 4 ) === 'вікі' ) ||
+ ( word.substr( word.length - 4 ) === 'Вікі' )
+ ) {
+ // ...
+ } else if ( word.substr( word.length - 2 ) === 'ія' ) {
+ word = word.substr( 0, word.length - 2 ) + 'ію';
+ }
+
+ break;
+ }
+
+ return word;
+ }
+ } );
+
+}( jQuery ) );
diff --git a/resources/js/ext.uls.i18n.js b/resources/js/ext.uls.i18n.js
new file mode 100644
index 00000000..5f890c38
--- /dev/null
+++ b/resources/js/ext.uls.i18n.js
@@ -0,0 +1,70 @@
+/**
+ * ULS i18n preparation using jquery.i18n library
+ *
+ * Copyright (C) 2012-2013 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris,
+ * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland and other
+ * contributors. See CREDITS for a list.
+ *
+ * UniversalLanguageSelector 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.
+ *
+ * @file
+ * @ingroup Extensions
+ * @licence GNU General Public Licence 2.0 or later
+ * @licence MIT License
+ */
+
+( function ( $, mw, undefined ) {
+ 'use strict';
+
+ // jquery.i18n has CLDRPluralRuleParser but MediaWiki also has the same
+ // parser. Reuse it by aliasing it to window.pluralRuleParser
+ window.pluralRuleParser = mw.libs.pluralRuleParser;
+
+ /**
+ * jquery.i18n message store for MediaWiki
+ *
+ */
+ var MWMessageStore = function () {
+ this.messages = {};
+ };
+
+ MWMessageStore.prototype = {
+ init: function () {},
+
+ get: function ( locale, messageKey ) {
+ return ( this.isLoaded( locale ) && this.messages[locale][messageKey] ) ||
+ '<' + messageKey + '>';
+ },
+
+ set: function( locale, messages ) {
+ this.messages[locale] = messages;
+ },
+
+ isLoaded: function ( locale ) {
+ return this.messages[locale];
+ },
+
+ load: function ( locale ) {
+ var store = this,
+ deferred = $.Deferred(),
+ url = mw.util.wikiScript( 'api' ) + '?action=ulslocalization&language=';
+
+ if ( store.isLoaded( locale ) ) {
+ return deferred.resolve();
+ }
+
+ deferred = $.getJSON( url + locale ).done( function ( data ) {
+ store.set( locale, data );
+ } ).fail( function ( jqxhr, settings, exception ) {
+ mw.log( 'Error in loading messages from ' + url + ' Exception: ' + exception );
+ } );
+ return deferred.promise();
+ }
+ };
+ mw.uls = mw.uls || {};
+ mw.uls.messageStore = new MWMessageStore();
+}( jQuery, mediaWiki ) );
diff --git a/resources/js/ext.uls.init.js b/resources/js/ext.uls.init.js
index 70a14f9b..1fc9649b 100644
--- a/resources/js/ext.uls.init.js
+++ b/resources/js/ext.uls.init.js
@@ -31,8 +31,7 @@
this.$languageFilter.addClass( 'noime' );
};
- var MWMessageStore,
- jsonLoader,
+ var jsonLoader,
initialized = false,
currentLang = mw.config.get( 'wgUserLanguage' ),
logEventQueue = $.Callbacks( 'memory once' );
@@ -164,55 +163,12 @@
} );
};
- /**
- * jquery.i18n message store for MediaWiki
- *
- */
- MWMessageStore = function () {
- this.messages = {};
- };
-
- MWMessageStore.prototype = {
- init: function () {},
-
- get: function ( locale, messageKey ) {
- return ( this.isLoaded( locale ) && this.messages[locale][messageKey] ) ||
- '<' + messageKey + '>';
- },
-
- set: function( locale, messages ) {
- this.messages[locale] = messages;
- },
-
- isLoaded: function ( locale ) {
- return this.messages[locale];
- },
-
- load: function ( locale ) {
- var store = this,
- deferred = $.Deferred(),
- url = mw.util.wikiScript( 'api' ) + '?action=ulslocalization&language=';
-
- if ( store.isLoaded( locale ) ) {
- return deferred.resolve();
- }
-
- deferred = $.getJSON( url + locale ).done( function ( data ) {
- store.set( locale, data );
- } ).fail( function ( jqxhr, settings, exception ) {
- mw.log( 'Error in loading messages from ' + url + ' Exception: ' + exception );
- } );
- return deferred.promise();
- }
- };
-
/**
* Initialize ULS front-end and its i18n.
*
* @param {Function} callback callback function to be called after initialization.
*/
mw.uls.init = function ( callback ) {
- var messageStore = new MWMessageStore();
callback = callback || $.noop;
@@ -255,11 +211,11 @@
// JavaScript side i18n initialization
$.i18n( {
locale: currentLang,
- messageStore: messageStore
+ messageStore: mw.uls.messageStore
} );
if ( !jsonLoader ) {
- jsonLoader = messageStore.load( currentLang );
+ jsonLoader = mw.uls.messageStore.load( currentLang );
} else {
jsonLoader.done( function () {
initialized = true;