From 28154206bf9fe635d8884b4204f83c7333fd3048 Mon Sep 17 00:00:00 2001 From: Santhosh Thottingal Date: Fri, 12 Jul 2013 14:34:31 +0530 Subject: [PATCH] Remove jquery.i18n code that the ULS extension overrides Instead of using the whole jquery.i18n code, use only required parts that are not customized in MW ULS. For this define a new RL module ext.uls.i18n. Also refactor MW message store for jquery.i18n. Change-Id: Idda9fe505428ade409d937314d7216ebb4232c36 --- Resources.php | 32 +- lib/jquery.i18n.js | 2570 ---------------------- lib/jquery.i18n/jquery.i18n.emitter.js | 169 ++ lib/jquery.i18n/jquery.i18n.fallbacks.js | 186 ++ lib/jquery.i18n/jquery.i18n.js | 265 +++ lib/jquery.i18n/jquery.i18n.language.js | 699 ++++++ lib/jquery.i18n/jquery.i18n.messages.js | 250 +++ lib/jquery.i18n/jquery.i18n.parser.js | 305 +++ lib/jquery.i18n/languages/bs.js | 22 + lib/jquery.i18n/languages/dsb.js | 22 + lib/jquery.i18n/languages/fi.js | 49 + lib/jquery.i18n/languages/ga.js | 38 + lib/jquery.i18n/languages/he.js | 31 + lib/jquery.i18n/languages/hsb.js | 21 + lib/jquery.i18n/languages/hu.js | 26 + lib/jquery.i18n/languages/hy.js | 25 + lib/jquery.i18n/languages/la.js | 54 + lib/jquery.i18n/languages/ml.js | 93 + lib/jquery.i18n/languages/os.js | 75 + lib/jquery.i18n/languages/ru.js | 33 + lib/jquery.i18n/languages/sl.js | 26 + lib/jquery.i18n/languages/uk.js | 47 + resources/js/ext.uls.i18n.js | 70 + resources/js/ext.uls.init.js | 50 +- 24 files changed, 2538 insertions(+), 2620 deletions(-) delete mode 100644 lib/jquery.i18n.js create mode 100644 lib/jquery.i18n/jquery.i18n.emitter.js create mode 100644 lib/jquery.i18n/jquery.i18n.fallbacks.js create mode 100644 lib/jquery.i18n/jquery.i18n.js create mode 100644 lib/jquery.i18n/jquery.i18n.language.js create mode 100644 lib/jquery.i18n/jquery.i18n.messages.js create mode 100644 lib/jquery.i18n/jquery.i18n.parser.js create mode 100644 lib/jquery.i18n/languages/bs.js create mode 100644 lib/jquery.i18n/languages/dsb.js create mode 100644 lib/jquery.i18n/languages/fi.js create mode 100644 lib/jquery.i18n/languages/ga.js create mode 100644 lib/jquery.i18n/languages/he.js create mode 100644 lib/jquery.i18n/languages/hsb.js create mode 100644 lib/jquery.i18n/languages/hu.js create mode 100644 lib/jquery.i18n/languages/hy.js create mode 100644 lib/jquery.i18n/languages/la.js create mode 100644 lib/jquery.i18n/languages/ml.js create mode 100644 lib/jquery.i18n/languages/os.js create mode 100644 lib/jquery.i18n/languages/ru.js create mode 100644 lib/jquery.i18n/languages/sl.js create mode 100644 lib/jquery.i18n/languages/uk.js create mode 100644 resources/js/ext.uls.i18n.js 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;