diff --git a/UniversalLanguageSelector.php b/UniversalLanguageSelector.php index a3bc1873..5a4cd143 100644 --- a/UniversalLanguageSelector.php +++ b/UniversalLanguageSelector.php @@ -92,6 +92,7 @@ $wgResourceModules['ext.uls.init'] = array( 'jquery.uls', 'jquery.i18n', 'ext.uls.displaysettings', + 'ext.uls.inputsettings', ), 'position' => 'top', ); @@ -118,7 +119,7 @@ $wgResourceModules['ext.uls.languagesettings'] = array( 'styles' => 'resources/css/ext.uls.languagesettings.css', 'localBasePath' => $dir, 'remoteExtPath' => 'UniversalLanguageSelector', - 'dependencies' => array( + 'dependencies' => array( 'ext.uls.preferences', ), ); @@ -130,8 +131,19 @@ $wgResourceModules['ext.uls.webfonts'] = array( 'dependencies' => array( 'jquery.webfonts', 'ext.uls.webfonts.repository', + 'ext.uls.preferences', ), ); +$wgResourceModules['ext.uls.ime'] = array( + 'scripts' => 'resources/js/ext.uls.ime.js', + 'localBasePath' => $dir, + 'remoteExtPath' => 'UniversalLanguageSelector', + 'dependencies' => array( + 'jquery.ime', + 'ext.uls.preferences', + ), +); + $wgResourceModules['ext.uls.displaysettings'] = array( 'scripts' => 'resources/js/ext.uls.displaysettings.js', @@ -145,6 +157,18 @@ $wgResourceModules['ext.uls.displaysettings'] = array( 'remoteExtPath' => 'UniversalLanguageSelector', ); +$wgResourceModules['ext.uls.inputsettings'] = array( + 'scripts' => 'resources/js/ext.uls.inputsettings.js', + 'styles' => 'resources/css/ext.uls.inputsettings.css', + 'localBasePath' => $dir, + 'dependencies' => array( + 'ext.uls.languagesettings', + 'ext.uls.ime', + 'jquery.i18n', + ), + 'remoteExtPath' => 'UniversalLanguageSelector', +); + $wgResourceModules['jquery.uls'] = array( 'scripts' => array( 'lib/jquery.uls/jquery.uls.js', @@ -176,4 +200,13 @@ $wgResourceModules['jquery.i18n'] = array( 'scripts' => 'lib/jquery.i18n.js', 'localBasePath' => $dir, 'remoteExtPath' => 'UniversalLanguageSelector', -); \ No newline at end of file +); + +$wgResourceModules['jquery.ime'] = array( + 'scripts' => 'lib/jquery.ime/jquery.ime.js', + 'styles' => array( + 'lib/jquery.ime/css/jquery.ime.css', + ), + 'localBasePath' => $dir, + 'remoteExtPath' => 'UniversalLanguageSelector', +); diff --git a/i18n/en.json b/i18n/en.json index 7bb627c3..e840c718 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -20,5 +20,13 @@ "ext-uls-webfonts-select-for": "Select font for $1", "ext-uls-webfonts-select-for-ui-info": "Font used for user interface", "ext-uls-webfonts-select-for-content-info": "Font used for content", -"ext-uls-back-to-display-settings": "Back to display settings" +"ext-uls-back-to-display-settings": "Back to display settings", +"ext-uls-input-settings-title": "Input settings", +"ext-uls-input-settings-title-short": "Input", +"ext-uls-input-settings-desc": "Writing in a different language?", +"ext-uls-input-settings-ime-settings": "Input methods for $1", +"ext-uls-input-settings-ui-language": "Language used for writing", +"ext-uls-back-to-input-settings": "Back to input settings", +"ext-uls-disable-input-method": "Disable input method", +"ext-uls-disable-input-method-desc": "Use a regular keyboard" } diff --git a/lib/jquery.ime/css/jquery.ime.css b/lib/jquery.ime/css/jquery.ime.css new file mode 100644 index 00000000..804fbb39 --- /dev/null +++ b/lib/jquery.ime/css/jquery.ime.css @@ -0,0 +1,186 @@ +div.imeselector { + position: absolute; + background: url('../images/ime-active.svg') no-repeat scroll left center + transparent; + background-size: 15px; + cursor: pointer; + height: 15px; + margin: 1px 0 0; + font-size: small; + padding-left: 20px; + margin-top: 0; + text-align: left; + font-family: 'sans'; + white-space: nowrap; +} + +div.imeselector a { + text-decoration: none; + outline: none; + color: #222222; + line-height: 1em; + padding-top: 4px; + padding-bottom: 4px; +} + +.ime-disable-link span { + text-align: right; + margin-left: 10px; + color: #888; + font-size: smaller; +} + +.ime-disable-link:hover span { + color: #eee; +} + +.ime-list-title, +.ime-lang-title { + color: #39d; + border-bottom: solid 1px #39d; + text-align: left; + font-size: larger; + padding-bottom: 5px; + padding-left: 20px; + padding-top: 9px; + margin-bottom: 1px; + cursor: default; +} + +.imeselector .caret { + margin-left: 2px; + margin-top: 8px; +} + +.caret { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #000000; + content: ""; + display: inline-block; + height: 0; + vertical-align: top; + width: 0; +} + +.imeselector-menu .sub-menu { + left: 100%; + position: absolute; + top: 0; + visibility: hidden; + margin-top: -1px; +} + +.imeselector-menu li:hover .sub-menu { + visibility: visible; + display: block; +} + +.imeselector-menu .checked a { + background: url(../images/tick.svg) 4px no-repeat; + background-size: 12px 12px; +} + +ul.ime-language-list { + overflow: auto; + max-height: 150px; + width: 300px; + list-style: none; + padding: 0 0; +} + +div.ime-language-list { + overflow: hidden; + width: 250px; + padding: 0 0; +} + +.ime-language-list { + margin: 0; +} + +.imeselector-menu { + position: absolute; + top: 14px; + right: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 0; + list-style: none; + border: 1px solid #888; + background-color: #FFFFFF; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + text-align: left; +} + +/* The triangle shaped callout */ +.imeselector-menu:before { + border-bottom: 7px solid #888; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + content: ""; + display: inline-block; + right: 9px; + position: absolute; + top: -7px; +} + +.imeselector-menu:after { + border-bottom: 6px solid #FFFFFF; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + content: ""; + display: inline-block; + right: 10px; + position: absolute; + top: -6px; +} + +.imeselector-menu .ime-help-link { + background-color: #f0f0f0; + border-radius: 0 0 5px 5px; + border-top: 1px solid #ddd; + margin-top: 6px; + padding: 2px 0 2px 0; + color: #444; +} + +.imeselector-menu .ime-help-link>a:hover { + background-color: #f0f0f0; + color: #000; +} + +.imeselector-menu a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + color: #333333; + outline: none; + white-space: nowrap; +} + +.imeselector-menu li>a:hover { + text-decoration: none; + color: #ffffff; + background-color: #5089e8; +} + +.open { + *z-index: 1000; + display: block; +} + +.imeselector-menu li { + position: relative; +} diff --git a/lib/jquery.ime/images/ime-active.svg b/lib/jquery.ime/images/ime-active.svg new file mode 100644 index 00000000..f4e790a3 --- /dev/null +++ b/lib/jquery.ime/images/ime-active.svg @@ -0,0 +1,51 @@ + + + +image/svg+xml + \ No newline at end of file diff --git a/lib/jquery.ime/images/tick.svg b/lib/jquery.ime/images/tick.svg new file mode 100644 index 00000000..f47f48b7 --- /dev/null +++ b/lib/jquery.ime/images/tick.svg @@ -0,0 +1,48 @@ + + + +image/svg+xml + \ No newline at end of file diff --git a/lib/jquery.ime/jquery.ime.js b/lib/jquery.ime/jquery.ime.js new file mode 100644 index 00000000..77ff2cdf --- /dev/null +++ b/lib/jquery.ime/jquery.ime.js @@ -0,0 +1,1187 @@ +( function ( $ ) { + 'use strict'; + + function IME ( element, options ) { + this.$element = $( element ); + // This needs to be delayed here since extending language list happens at DOM ready + $.ime.defaults.languages = Object.keys( $.ime.languages ); + this.options = $.extend( {}, $.ime.defaults, options ); + this.active = false; + this.inputmethod = null; + this.context = ''; + this.selector = this.$element.imeselector( this.options ); + this.listen(); + } + + IME.prototype = { + constructor: IME, + + listen: function () { + this.$element.on( 'keypress', $.proxy( this.keypress, this ) ); + }, + + /** + * Transliterate a given string input based on context and input method definition. + * If there are no matching rules defined, returns the original string. + * + * @param input + * @param context + * @param altGr bool whether altGr key is pressed or not + * @returns String transliterated string + */ + transliterate: function ( input, context, altGr ) { + var patterns, regex, rule, replacement, i; + + if ( altGr ) { + patterns = this.inputmethod.patterns_x || []; + } else { + patterns = this.inputmethod.patterns; + } + + if ( $.isFunction( patterns ) ) { + return patterns.call( this, input, context ); + } + + for ( i = 0; i < patterns.length; i++ ) { + rule = patterns[i]; + regex = new RegExp( rule[0] + '$' ); + + // Last item in the rules. + // It can also be a function, because the replace + // method can have a function as the second argument. + replacement = rule.slice( -1 )[0]; + + // Input string match test + if ( regex.test( input ) ) { + // Context test required? + if ( rule.length === 3 ) { + if ( new RegExp( rule[1] + '$' ).test( context ) ) { + return input.replace( regex, replacement ); + } + } else { + // No context test required. Just replace. + return input.replace( regex, replacement ); + } + } + } + + // No matches, return the input + return input; + }, + + keypress: function ( e ) { + var altGr = false, + c, startPos, pos, endPos, divergingPos, input, replacement; + + if ( !this.active ) { + return true; + } + + if ( !this.inputmethod ) { + return true; + } + + // handle backspace + if ( e.which === 8 ) { + // Blank the context + this.context = ''; + return true; + } + + if ( e.altKey || e.altGraphKey ) { + altGr = true; + } + + // Don't process ASCII control characters (except linefeed), + // as well as anything involving + // Alt (except for extended keymaps), Ctrl and Meta + if ( ( e.which < 32 && e.which !== 13 && !altGr ) || e.ctrlKey || e.metaKey ) { + // Blank the context + this.context = ''; + + return true; + } + + c = String.fromCharCode( e.which ); + + // Get the current caret position. The user may have selected text to overwrite, + // so get both the start and end position of the selection. If there is no selection, + // startPos and endPos will be equal. + pos = getCaretPosition( this.$element ); + startPos = pos[0]; + endPos = pos[1]; + + // Get the last few characters before the one the user just typed, + // to provide context for the transliteration regexes. + // We need to append c because it hasn't been added to $this.val() yet + input = lastNChars( this.$element.val() || this.$element.text(), startPos, + this.inputmethod.maxKeyLength ) + + c; + + replacement = this.transliterate( input, this.context, altGr ); + + // Update the context + this.context += c; + + if ( this.context.length > this.inputmethod.contextLength ) { + // The buffer is longer than needed, truncate it at the front + this.context = this.context.substring( this.context.length + - this.inputmethod.contextLength ); + } + + // it is a noop + if ( replacement === input ) { + return true; + } + + // Drop a common prefix, if any + divergingPos = firstDivergence( input, replacement ); + input = input.substring( divergingPos ); + replacement = replacement.substring( divergingPos ); + replaceText( this.$element, replacement, startPos - input.length + 1, endPos ); + + e.stopPropagation(); + return false; + }, + + isActive: function () { + return this.active; + }, + + disable: function () { + this.active = false; + $.ime.preferences.setIM( 'system' ); + }, + + enable: function () { + this.active = true; + }, + + toggle: function () { + this.active = !this.active; + }, + + setIM: function ( inputmethodId ) { + this.inputmethod = $.ime.inputmethods[inputmethodId]; + $.ime.preferences.setIM( inputmethodId ); + }, + + setLanguage: function( languageCode ) { + $.ime.preferences.setLanguage( languageCode ); + }, + + load: function ( name, callback ) { + var ime = this, dependency; + + if ( $.ime.inputmethods[name] ) { + if ( callback ) { + callback.call( ime ); + } + + return true; + } + + dependency = $.ime.sources[name].depends; + if ( dependency ) { + this.load( dependency ) ; + } + + $.ajax( { + url: ime.options.imePath + $.ime.sources[name].source, + dataType: 'script' + } ).done( function () { + debug( name + ' loaded' ); + + if ( callback ) { + callback.call( ime ); + } + } ).fail( function ( jqxhr, settings, exception ) { + debug( 'Error in loading inputmethod ' + name + ' Exception: ' + exception ); + } ); + } + }; + + $.fn.ime = function ( option ) { + return this.each( function () { + var $this = $( this ), + data = $this.data( 'ime' ), + options = typeof option === 'object' && option; + + if ( !data ) { + $this.data( 'ime', ( data = new IME( this, options ) ) ); + } + + if ( typeof option === 'string' ) { + data[option](); + } + } ); + }; + + $.ime = {}; + $.ime.inputmethods = {}; + $.ime.sources = {}; + $.ime.preferences = {}; + $.ime.languages = {}; + + var defaultInputMethod = { + contextLength: 0, + maxKeyLength: 1 + }; + + $.ime.register = function ( inputMethod ) { + $.ime.inputmethods[inputMethod.id] = $.extend( {}, defaultInputMethod, inputMethod ); + }; + + // default options + $.ime.defaults = { + imePath: '../', // Relative/Absolute path for the rules folder of jquery.ime + languages: [] // Languages to be used- by default all languages + }; + + // private function for debugging + function debug ( $obj ) { + if ( window.console && window.console.log ) { + window.console.log( $obj ); + } + } + + /** + * + */ + function getCaretPosition( $element ) { + var el = $element.get( 0 ), + start = 0, + end = 0, + normalizedValue, + range, + textInputRange, + len, + endRange; + + if ( typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number' ) { + start = el.selectionStart; + end = el.selectionEnd; + } else { + // IE + range = document.selection.createRange(); + + if ( range && range.parentElement() === el ) { + len = el.value.length; + normalizedValue = el.value.replace( /\r\n/g, '\n' ); + + // Create a working TextRange that lives only in the input + textInputRange = el.createTextRange(); + textInputRange.moveToBookmark( range.getBookmark() ); + + // Check if the start and end of the selection are at the very end + // of the input, since moveStart/moveEnd doesn't return what we want + // in those cases + endRange = el.createTextRange(); + endRange.collapse( false ); + + if ( textInputRange.compareEndPoints( 'StartToEnd', endRange ) > -1 ) { + start = end = len; + } else { + start = -textInputRange.moveStart( 'character', -len ); + start += normalizedValue.slice( 0, start ).split( '\n' ).length - 1; + + if ( textInputRange.compareEndPoints( 'EndToEnd', endRange ) > -1 ) { + end = len; + } else { + end = -textInputRange.moveEnd( 'character', -len ); + end += normalizedValue.slice( 0, end ).split( '\n' ).length - 1; + } + } + } + } + + return [ start, end ]; + } + + /** + * Helper function to get an IE TextRange object for an element + */ + function rangeForElementIE( e ) { + if ( e.nodeName.toLowerCase() === 'input' ) { + return e.createTextRange(); + } else { + var sel = document.body.createTextRange(); + + sel.moveToElementText( e ); + return sel; + } + } + + /** + * + */ + function replaceText( $element, replacement, start, end ) { + var element = $element.get( 0 ), + selection, + length, + newLines, + scrollTop; + + if ( document.body.createTextRange ) { + // IE + selection = rangeForElementIE(element); + length = element.value.length; + // IE doesn't count \n when computing the offset, so we won't either + newLines = element.value.match( /\n/g ); + + if ( newLines ) { + length = length - newLines.length; + } + + selection.moveStart( 'character', start ); + selection.moveEnd( 'character', end - length ); + + selection.text = replacement; + selection.collapse( false ); + selection.select(); + } else { + // All other browsers + scrollTop = element.scrollTop; + + // This could be made better if range selection worked on browsers. + // But for complex scripts, browsers place cursor in unexpected places + // and it's not possible to fix cursor programmatically. + // Ref Bug https://bugs.webkit.org/show_bug.cgi?id=66630 + element.value = element.value.substring( 0, start ) + replacement + + element.value.substring( end, element.value.length ); + // restore scroll + element.scrollTop = scrollTop; + // set selection + element.selectionStart = element.selectionEnd = start + replacement.length; + } + } + + /** + * Find the point at which a and b diverge, i.e. the first position + * at which they don't have matching characters. + * + * @param a String + * @param b String + * @return Position at which a and b diverge, or -1 if a === b + */ + function firstDivergence ( a, b ) { + var minLength, i; + + minLength = a.length < b.length ? a.length : b.length; + + for ( i = 0; i < minLength; i++ ) { + if ( a.charCodeAt( i ) !== b.charCodeAt( i ) ) { + return i; + } + } + + return -1; + } + + /** + * Get the n characters in str that immediately precede pos + * Example: lastNChars( 'foobarbaz', 5, 2 ) === 'ba' + * + * @param str String to search in + * @param pos Position in str + * @param n Number of characters to go back from pos + * @return Substring of str, at most n characters long, immediately preceding pos + */ + function lastNChars ( str, pos, n ) { + if ( n === 0 ) { + return ''; + } else if ( pos <= n ) { + return str.substr( 0, pos ); + } else { + return str.substr( pos - n, n ); + } + } + + function arrayKeys ( obj ) { + var rv = []; + $.each( obj, function ( key ) { + rv.push( key ); + } ); + return rv; + } + +}( jQuery ) ); + +( function ( $ ) { + 'use strict'; + + $.extend( $.ime.sources, { + 'en-capitalize': { + name: 'Capitalize', // XXX This can be the name written in its own script? + source: 'rules/en/capitalize.js' + }, + 'am-transliteration': { + name: 'Transliteration', + source: 'rules/am/am-transliteration.js' + }, + 'as-avro': { + name: 'Avro', + source: 'rules/as/as-avro.js' + }, + 'as-bornona': { + name: 'Bornona', + source: 'rules/as/as-bornona.js' + }, + 'as-inscript': { + name: 'InScript', + source: 'rules/as/as-inscript.js' + }, + 'as-transliteration': { + name: 'Transliteration', + source: 'rules/as/as-transliteration.js' + }, + 'ber-tfng': { + name: 'Tifinagh', + source: 'rules/ber/ber-tfng.js' + }, + 'bn-avro': { + name: 'Avro', + source: 'rules/bn/bn-avro.js' + }, + 'bn-inscript': { + name: 'InScript', + source: 'rules/bn/bn-inscript.js' + }, + 'bn-nkb': { + name: 'National Keyboard', + source: 'rules/bn/bn-nkb.js' + }, + 'bn-probhat': { + name: 'Probhat', + source: 'rules/bn/bn-probhat.js' + }, + 'brx-inscript': { + name: 'Inscript', + source: 'rules/brx/brx-inscript.js' + }, + 'cyrl-palochka': { + name: 'Palochka', + source: 'rules/cyrl/cyrl-palochka.js' + }, + 'eo-transliteration': { + name: 'Transliteration', + source: 'rules/eo/eo-transliteration.js' + }, + 'hi-transliteration': { + name: 'Transliteration', + source: 'rules/hi/hi-transliteration.js' + }, + 'hi-inscript': { + name: 'InScript', + source: 'rules/hi/hi-inscript.js' + }, + 'mai-inscript': { + name: 'InScript', + source: 'rules/mai/mai-inscript.js', + depends: 'hi-inscript' + }, + 'hi-bolnagri': { + name: 'BolNagri', + source: 'rules/hi/hi-bolnagri.js' + }, + 'ml-transliteration': { + name: 'Transliteration', + source: 'rules/ml/ml-transliteration.js' + }, + 'ml-inscript': { + name: 'InScript', + source: 'rules/ml/ml-inscript.js' + }, + 'ta-inscript': { + name: 'InScript', + source: 'rules/ta/ta-inscript.js' + }, + 'ta-transliteration': { + name: 'Transliteration', + source: 'rules/ta/ta-transliteration.js' + }, + 'ta-99': { + name: 'Tamil 99', + source: 'rules/ta/ta-99.js' + }, + 'ta-bamini': { + name: 'Bamini', + source: 'rules/ta/ta-bamini.js' + }, + 'de': { + name: 'Deutsch', + source: 'rules/de/de.js' + }, + 'he-standard-2012': { + name: 'Hebrew 2012 (from English)', + source: 'rules/he/he-standard-2012.js' + }, + 'he-standard-2012-extonly': { + name: 'Hebrew 2012', + source: 'rules/he/he-standard-2012-extonly.js' + }, + 'gu-inscript': { + name: 'Inscript', + source: 'rules/gu/gu-inscript.js' + }, + 'gu-transliteration': { + name: 'Transliteration', + source: 'rules/gu/gu-transliteration.js' + }, + 'ka-transliteration': { + name: 'Transliteration', + source: 'rules/ka/ka-transliteration.js' + }, + 'kn-inscript': { + name: 'Inscript', + source: 'rules/kn/kn-inscript.js' + }, + 'kn-transliteration': { + name: 'Transliteration', + source: 'rules/kn/kn-transliteration.js' + }, + 'mr-inscript': { + name: 'Inscript', + source: 'rules/mr/mr-inscript.js' + }, + 'mr-transliteration': { + name: 'Transliteration', + source: 'rules/mr/mr-transliteration.js' + }, + 'ne-inscript': { + name: 'Inscript', + source: 'rules/ne/ne-inscript.js' + }, + 'ne-transliteration': { + name: 'Transliteration', + source: 'rules/ne/ne-transliteration.js' + }, + 'or-transliteration': { + name: 'Transliteration', + source: 'rules/or/or-transliteration.js' + }, + 'or-inscript': { + name: 'Inscript', + source: 'rules/or/or-inscript.js' + }, + 'or-lekhani': { + name: 'Lekhani', + source: 'rules/or/or-lekhani.js' + }, + 'te-inscript': { + name: 'Inscript', + source: 'rules/te/te-inscript.js' + }, + 'te-transliteration': { + name: 'Transliteration', + source: 'rules/te/te-transliteration.js' + }, + 'pa-inscript': { + name: 'Inscript', + source: 'rules/pa/pa-inscript.js' + }, + 'pa-transliteration': { + name: 'Transliteration', + source: 'rules/pa/pa-transliteration.js' + }, + 'pa-phonetic': { + name: 'Phonetic', + source: 'rules/pa/pa-phonetic.js' + }, + 'ru-transliteration': { + name: 'Transliteration', + source: 'rules/ru/ru-transliteration.js' + }, + 'sa-inscript': { + name: 'Inscript', + source: 'rules/sa/sa-inscript.js' + }, + 'sa-transliteration': { + name: 'Transliteration', + source: 'rules/sa/sa-transliteration.js' + }, + 'sah-transliteration': { + name: 'Transliteration', + source: 'rules/sah/sah-transliteration.js' + }, + 'si-singlish': { + name: 'Singlish', + source: 'rules/si/si-singlish.js' + }, + 'si-wijesekara': { + name: 'Wijesekara', + source: 'rules/si/si-wijesekara.js' + }, + 'ur-transliteration': { + name: 'Transliteration', + source: 'rules/ur/ur-transliteration.js' + } + } ); + + $.extend( $.ime.languages, { + 'ady': { + autonym: 'адыгэбзэ', + inputmethods: [ 'cyrl-palochka' ] + }, + 'ahr': { + autonym: 'अहिराणी', + inputmethods: [ 'mr-transliteration', 'mr-inscript' ] + }, + 'am': { + autonym: 'አማርኛ', + inputmethods: [ 'am-transliteration' ] + }, + 'as': { + autonym: 'অসমীয়া', + inputmethods: [ 'as-transliteration', 'as-avro', 'as-bornona', 'as-inscript' ] + }, + 'av': { + autonym: 'авар', + inputmethods: [ 'cyrl-palochka' ] + }, + 'ber': { + autonym: 'ⵜⵉⴼⵉⵏⴰⵖ', + inputmethods: [ 'ber-tfng' ] + }, + 'bn': { + autonym: 'বাংলা', + inputmethods: [ 'bn-avro', 'bn-inscript', 'bn-nkb', 'bn-probhat' ] + }, + 'brx': { + autonym: 'बड़ो', + inputmethods: [ 'brx-inscript' ] + }, + 'ce': { + autonym: 'нохчийн', + inputmethods: [ 'cyrl-palochka' ] + }, + 'de': { + autonym: 'Deutsch', + inputmethods: [ 'de' ] + }, + 'en': { + autonym: 'English', + inputmethods: [ 'en-capitalize' ] + }, + 'eo': { + autonym: 'Esperanto', + inputmethods: [ 'eo-transliteration' ] + }, + 'gom': { + autonym: 'कोंकणी', + inputmethods: [ 'hi-transliteration', 'hi-inscript' ] + }, + 'gu': { + autonym: 'ગુજરાતી', + inputmethods: [ 'gu-transliteration', 'gu-inscript' ] + }, + 'he': { + autonym: 'עברית', + inputmethods: [ 'he-standard-2012-extonly', 'he-standard-2012' ] + }, + 'hi': { + autonym: 'हिन्दी', + inputmethods: [ 'hi-transliteration', 'hi-inscript', 'hi-bolnagri' ] + }, + 'hne': { + autonym: 'छत्तीसगढ़ी', + inputmethods: [ 'hi-transliteration' ] + }, + 'ka': { + autonym: 'ქართული ენა', + inputmethods: [ 'ka-transliteration' ] + }, + 'kbd': { + autonym: 'адыгэбзэ (къэбэрдеибзэ)', + inputmethods: [ 'cyrl-palochka' ] + }, + 'kn': { + autonym: 'ಕನ್ನಡ', + inputmethods: [ 'kn-transliteration', 'kn-inscript' ] + }, + 'lbe': { + autonym: 'лакку', + inputmethods: [ 'cyrl-palochka' ] + }, + 'lez': { + autonym: 'лезги', + inputmethods: [ 'cyrl-palochka' ] + }, + 'mai': { + autonym: 'मैथिली', + inputmethods: [ 'mai-inscript' ] + }, + 'ml': { + autonym: 'മലയാളം', + inputmethods: [ 'ml-transliteration', 'ml-inscript' ] + }, + 'mr': { + autonym: 'मराठी', + inputmethods: [ 'mr-transliteration', 'mr-inscript' ] + }, + 'ne': { + autonym: 'नेपाली', + inputmethods: [ 'ne-transliteration', 'ne-inscript' ] + }, + 'new': { + autonym: 'नेपाल भाषा', + inputmethods: [ 'hi-transliteration', 'hi-inscript' ] + }, + 'or': { + autonym: 'ଓଡ଼ିଆ', + inputmethods: [ 'or-transliteration', 'or-lekhani', 'or-inscript' ] + }, + 'pa': { + autonym: 'ਪੰਜਾਬੀ', + inputmethods: [ 'pa-transliteration', 'pa-inscript', 'pa-phonetic' ] + }, + 'rif': { + autonym: 'ⵜⵉⴼⵉⵏⴰⵖ', + inputmethods: [ 'ber-tfng' ] + }, + 'ru': { + autonym: 'русский', + inputmethods: [ 'ru-transliteration' ] + }, + 'sah': { + autonym: 'саха тыла', + inputmethods: [ 'sah-transliteration' ] + }, + 'sa': { + autonym: 'संस्कृत', + inputmethods: [ 'sa-transliteration', 'sa-inscript' ] + }, + 'shi': { + autonym: 'ⵜⵉⴼⵉⵏⴰⵖ', + inputmethods: [ 'ber-tfng' ] + }, + 'si': { + autonym: 'සිංහල', + inputmethods: [ 'si-singlish', 'si-wijesekara' ] + }, + 'ta': { + autonym: 'தமிழ்', + inputmethods: [ 'ta-transliteration', 'ta-99', 'ta-inscript', 'ta-bamini' ] + }, + 'tcy': { + autonym: 'ತುಳು', + inputmethods: [ 'kn-transliteration' ] + }, + 'te': { + autonym: 'తెలుగు', + inputmethods: [ 'te-transliteration', 'te-inscript' ] + }, + 'tkr': { + autonym: 'цӀаӀхна миз', + inputmethods: [ 'cyrl-palochka' ] + }, + 'ur': { + autonym: 'اردو', + inputmethods: [ 'ur-transliteration' ] + } + } ); + +}( jQuery ) ); + +( function ( $ ) { + 'use strict'; + + function IMESelector ( element, options ) { + this.$element = $( element ); + this.options = $.extend( {}, IMESelector.defaults, options ); + this.active = false; + this.$imeSetting = $( selectorTemplate ); + this.$menu = $( '