diff options
Diffstat (limited to 'www/wiki/extensions/UniversalLanguageSelector/lib/jquery.ime/jquery.ime.js')
-rw-r--r-- | www/wiki/extensions/UniversalLanguageSelector/lib/jquery.ime/jquery.ime.js | 3008 |
1 files changed, 3008 insertions, 0 deletions
diff --git a/www/wiki/extensions/UniversalLanguageSelector/lib/jquery.ime/jquery.ime.js b/www/wiki/extensions/UniversalLanguageSelector/lib/jquery.ime/jquery.ime.js new file mode 100644 index 00000000..04b1d47b --- /dev/null +++ b/www/wiki/extensions/UniversalLanguageSelector/lib/jquery.ime/jquery.ime.js @@ -0,0 +1,3008 @@ +/*! jquery.ime - v0.2.0+20190329 +* https://github.com/wikimedia/jquery.ime +* Copyright (c) 2019 Santhosh Thottingal; License: (GPL-2.0+ OR MIT) */ +( function ( $ ) { + 'use strict'; + + var TextEntryFactory, TextEntry, FormWidgetEntry, ContentEditableEntry, + defaultInputMethod; + + // rangy is defined in the rangy library + /* global rangy */ + + function arrayKeys( obj ) { + return $.map( obj, function ( element, index ) { + return index; + } ); + } + + /** + * private function for debugging + * @param {jQuery} [$obj] + */ + function debug( $obj ) { + if ( window.console && window.console.log ) { + window.console.log( $obj ); + } + } + + /** + * Just initializes an empty static object. + * Similar to initClass in https://www.mediawiki.org/wiki/OOjs + * + * @param {Function} fn + */ + function initClass( fn ) { + fn.static = fn.static || {}; + } + + /** + * Inheritance. Uses pattern similar to OOjs (https://www.mediawiki.org/wiki/OOjs). + * Extend prototype and static methods and properties of child constructor from + * a parent constructor. + * + * @param {Function} targetFn + * @param {Function} originFn + */ + function inheritClass( targetFn, originFn ) { + targetFn.parent = originFn; + targetFn.prototype = $.extend( {}, originFn.prototype ); + targetFn.prototype.constructor = originFn.constructor; + targetFn.static = $.extend( {}, originFn.static ); + } + + /** + * IME Class + * + * @class + * @constructor + * @param {HTMLElement} element Element on which to listen for events + * @param {TextEntry} textEntry Text entry object to use to get/set text + * @param {Object} [options] + * @param {Function} [options.helpHandler] Called for each input method row in the selector + * @param {Object} [options.helpHandler.imeSelector] + * @param {string} [options.helpHandler.ime] Id of the input method + */ + function IME( element, textEntry, options ) { + this.$element = $( element ); + this.textEntry = textEntry; + // This needs to be delayed here since extending language list happens at DOM ready + $.ime.defaults.languages = arrayKeys( $.ime.languages ); + this.options = $.extend( {}, $.ime.defaults, options ); + if ( this.options.imePath ) { + // Set the global IME path from the one specified to the instance + // TODO: remove this functionality and force clients to set the global + // IME path + $.ime.path = this.options.imePath; + } + this.active = false; + this.shifted = false; + this.inputmethod = null; + this.language = null; + this.context = ''; + if ( this.options.showSelector ) { + this.selector = this.$element.imeselector( this.options ); + } + this.listen(); + } + + IME.prototype = { + constructor: IME, + + /** + * Listen for events and bind to handlers + */ + listen: function () { + this.$element.on( { + 'keypress.ime': this.keypress.bind( this ), + 'keyup.ime': this.keyup.bind( this ), + 'keydown.ime': this.keydown.bind( this ), + 'destroy.ime': this.destroy.bind( this ), + 'enable.ime': this.enable.bind( this ), + 'disable.ime': this.disable.bind( this ) + } ); + }, + + /** + * Return a list of available language codes + * + * @return {string[]} Available language codes + */ + getLanguageCodes: function () { + return $.ime.defaults.languages; + }, + + /** + * Return the autonym for an available language code + * + * @param {string} languageCode The language code + * @return {string} The autonym + */ + getAutonym: function ( languageCode ) { + return $.ime.languages[ languageCode ].autonym; + }, + + /** + * Return a list of available input method ids for a language + * + * @param {string} languageCode An available language code + * @return {string[]} Available input method ids for that language + */ + getInputMethodIds: function ( languageCode ) { + return $.ime.languages[ languageCode ].inputmethods; + }, + + /** + * Return the name of an input method + * + * @param {string} inputMethodId The id of an input method + * @return {string} The input method's name + * @see IME#load + */ + getInputMethodName: function ( inputMethodId ) { + return $.ime.sources[ inputMethodId ].name; + }, + + /** + * Return a list of input method info { id: ..., name: ... } for a language. + * + * @param {string} languageCode An available language code + * @return {Object[]} Info object for each available input method + */ + getInputMethods: function ( languageCode ) { + return this.getInputMethodIds( languageCode ).map( function ( inputMethodId ) { + return { + id: inputMethodId, + name: $.ime.sources[ inputMethodId ].name + }; + } ); + }, + + /** + * Transliterate a given string input based on context and input method definition. + * If there are no matching rules defined, returns the original string. + * + * @param {string} input + * @param {string} context + * @param {boolean} altGr whether altGr key is pressed or not + * @return {Object} Transliteration object + * @return {boolean} return.noop Whether to consider input processed or passed through. + * @return {string} return.output The transliterated input or input unmodified. + */ + transliterate: function ( input, context, altGr ) { + var patterns, regex, rule, replacement, i, retval; + + if ( altGr ) { + patterns = this.inputmethod.patterns_x || []; + } else { + patterns = this.inputmethod.patterns || []; + } + + if ( this.shifted ) { + // if shift is pressed give priority for the patterns_shift + // if exists. + // Example: Shift+space where shift does not alter the keycode + patterns = ( this.inputmethod.patterns_shift || [] ) + .concat( patterns ); + } + + if ( $.isFunction( patterns ) ) { + // For backwards compatibility, allow the rule functions to return plain + // string. Determine noop by checking whether input is different from + // output. If the rule function returns object, just return it as-is. + retval = patterns.call( this, input, context ); + if ( typeof retval === 'string' ) { + return { noop: input === retval, output: retval }; + } + + return retval; + } + + 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 { noop: false, output: input.replace( regex, replacement ) }; + } + } else { + return { noop: false, output: input.replace( regex, replacement ) }; + } + } + } + + // No matches, return the input + return { noop: true, output: input }; + }, + + keyup: function ( e ) { + if ( e.which === 16 ) { // shift key + this.shifted = false; + } + }, + + keydown: function ( e ) { + if ( e.which === 16 ) { // shift key + this.shifted = true; + } + }, + + /** + * Keypress handler + * + * @param {jQuery.Event} e Event + * @return {boolean} + */ + keypress: function ( e ) { + var altGr = false, + c, 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 Ctrl, Meta and Alt, + // but do process extended keymaps + if ( ( e.which < 32 && e.which !== 13 && !altGr ) || e.ctrlKey || e.metaKey ) { + // Blank the context + this.context = ''; + + return true; + } + + c = String.fromCharCode( e.which ); + + // Append the character being typed to the preceding few characters, + // to provide context for the transliteration regexes. + input = this.textEntry.getTextBeforeSelection( this.inputmethod.maxKeyLength ); + replacement = this.transliterate( input + c, 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 + ); + } + + // Allow rules to explicitly define whether we match something. + // Otherwise we cannot distinguish between no matching rule and + // rule that provides identical output but consumes the event + // to prevent normal behavior. See Udmurt layout which uses + // altgr rules to allow typing the original character. + if ( replacement.noop ) { + return true; + } + + this.textEntry.replaceTextAtSelection( input.length, replacement.output ); + + e.stopPropagation(); + + return false; + }, + + /** + * Check whether the input method is active or not + * + * @return {boolean} + */ + isActive: function () { + return this.active; + }, + + /** + * Disable the input method + */ + disable: function () { + this.active = false; + $.ime.preferences.setIM( 'system' ); + }, + + /** + * Enable the input method + */ + enable: function () { + this.active = true; + }, + + /** + * Toggle the active state of input method + */ + toggle: function () { + this.active = !this.active; + }, + + /** + * Destroy the binding of ime to the editable element + */ + destroy: function () { + $( 'body' ).off( '.ime' ); + this.$element.off( '.ime' ).removeData( 'ime' ).removeData( 'imeselector' ); + }, + + /** + * Get the current input method + * + * @return {string} Current input method id + */ + getIM: function () { + return this.inputmethod; + }, + + /** + * Set the current input method + * + * @param {string} inputmethodId + * @fires imeLanguageChange + */ + setIM: function ( inputmethodId ) { + this.inputmethod = $.ime.inputmethods[ inputmethodId ]; + $.ime.preferences.setIM( inputmethodId ); + this.$element.trigger( 'imeMethodChange' ); + }, + + /** + * Set the current Language + * + * @param {string} languageCode + * @fires imeLanguageChange + * @return {boolean} + */ + setLanguage: function ( languageCode ) { + if ( !$.ime.languages[ languageCode ] ) { + debug( 'Language ' + languageCode + ' is not known to jquery.ime.' ); + + return false; + } + + this.language = languageCode; + $.ime.preferences.setLanguage( languageCode ); + this.$element.trigger( 'imeLanguageChange' ); + return true; + }, + + /** + * Get current language + * + * @return {string} + */ + getLanguage: function () { + return this.language; + }, + + /** + * load an input method by given id + * + * @param {string} inputmethodId + * @return {jQuery.Promise} + */ + load: function ( inputmethodId ) { + return $.ime.load( inputmethodId ); + } + }; + + /** + * TextEntry factory + * + * @class + * @constructor + */ + TextEntryFactory = function IMETextEntryFactory() { + this.TextEntryClasses = []; + }; + + /* Inheritance */ + + initClass( TextEntryFactory ); + + /* Methods */ + + /** + * Register a TextEntry class, with priority over previous registrations + * + * @param {TextEntry} TextEntryClass Class to register + */ + TextEntryFactory.prototype.register = function ( TextEntryClass ) { + this.TextEntryClasses.unshift( TextEntryClass ); + }; + + /** + * Wrap an editable element with the appropriate TextEntry class + * + * @param {jQuery} $element The element to wrap + * @return {TextEntry|undefined} A TextEntry, or undefined if no match + */ + TextEntryFactory.prototype.wrap = function ( $element ) { + var i, len, TextEntryClass; + for ( i = 0, len = this.TextEntryClasses.length; i < len; i++ ) { + TextEntryClass = this.TextEntryClasses[ i ]; + if ( TextEntryClass.static.canWrap( $element ) ) { + return new TextEntryClass( $element ); + } + } + return undefined; + }; + + /* Initialization */ + + TextEntryFactory.static.singleton = new TextEntryFactory(); + + /** + * Generic text entry + * + * @class + * @abstract + */ + TextEntry = function IMETextEntry() { + }; + + /* Inheritance */ + + initClass( TextEntry ); + + /* Static methods */ + + /** + * Test whether can wrap this type of element + * + * @param {jQuery} $element The element to wrap + * @return {boolean} Whether the element can be wrapped + */ + TextEntry.static.canWrap = function () { + return false; + }; + + /* Abstract methods */ + + /** + * Get text immediately before the current selection start. + * + * This SHOULD return the empty string for non-collapsed selections. + * + * @param {number} maxLength Maximum number of chars (code units) to return + * @return {string} Up to maxLength of text + */ + TextEntry.prototype.getTextBeforeSelection = null; + + /** + * Replace the currently selected text and/or text before the selection + * + * @param {number} precedingCharCount Number of chars before selection to replace + * @param {string} newText Replacement text + */ + TextEntry.prototype.replaceTextAtSelection = null; + + /** + * TextEntry class for input/textarea widgets + * + * @class + * @constructor + * @param {jQuery} $element The element to wrap + */ + FormWidgetEntry = function IMEFormWidgetEntry( $element ) { + this.$element = $element; + }; + + /* Inheritance */ + + inheritClass( FormWidgetEntry, TextEntry ); + + /* Static methods */ + + /** + * @inheritdoc TextEntry + */ + FormWidgetEntry.static.canWrap = function ( $element ) { + return $element.is( 'input:not([type]), input[type=text], input[type=search], textarea' ) && + !$element.prop( 'readonly' ) && + !$element.prop( 'disabled' ) && + !$element.hasClass( 'noime' ); + }; + + /* Instance methods */ + + /** + * @inheritdoc TextEntry + */ + FormWidgetEntry.prototype.getTextBeforeSelection = function ( maxLength ) { + var pos = this.getCaretPosition(); + return this.$element.val().substring( + Math.max( 0, pos.start - maxLength ), + pos.start + ); + }; + + /** + * @inheritdoc TextEntry + */ + FormWidgetEntry.prototype.replaceTextAtSelection = function ( precedingCharCount, newText ) { + var selection, + length, + newLines, + start, + scrollTop, + pos, + element = this.$element.get( 0 ); + + if ( typeof element.selectionStart === 'number' && typeof element.selectionEnd === 'number' ) { + // IE9+ and all other browsers + start = element.selectionStart; + scrollTop = element.scrollTop; + + // Replace the whole text of the text area: + // text before + newText + text after. + // 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 - precedingCharCount ) + + newText + + element.value.substring( element.selectionEnd, element.value.length ); + + // restore scroll + element.scrollTop = scrollTop; + // set selection + element.selectionStart = element.selectionEnd = start - precedingCharCount + newText.length; + } else { + // IE8 and lower + pos = this.getCaretPosition(); + selection = element.createTextRange(); + 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', pos.start - precedingCharCount ); + selection.moveEnd( 'character', pos.end - length ); + + selection.text = newText; + selection.collapse( false ); + selection.select(); + } + }; + + /** + * Get the current selection offsets inside the widget + * + * @return {Object} return Offsets in chars (0 means first offset *or* no selection in widget) + * @return {number} return.start Selection start + * @return {number} return.end Selection end + */ + FormWidgetEntry.prototype.getCaretPosition = function () { + var el = this.$element.get( 0 ), + start = 0, + end = 0, + normalizedValue, + range, + textInputRange, + len, + newLines, + 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' ); + newLines = normalizedValue.match( /\n/g ); + + // 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 ) { + if ( newLines ) { + start = end = len - newLines.length; + } else { + start = end = len; + } + } else { + start = -textInputRange.moveStart( 'character', -len ); + + if ( textInputRange.compareEndPoints( 'EndToEnd', endRange ) > -1 ) { + end = len; + } else { + end = -textInputRange.moveEnd( 'character', -len ); + } + } + } + } + return { start: start, end: end }; + }; + + TextEntryFactory.static.singleton.register( FormWidgetEntry ); + + /** + * TextEntry class for ContentEditable + * + * @class + * @constructor + * @param {jQuery} $element The element to wrap + */ + ContentEditableEntry = function IMEContentEditableEntry( $element ) { + this.$element = $element; + }; + + /* Inheritance */ + + inheritClass( ContentEditableEntry, TextEntry ); + + /* Static methods */ + + /** + * @inheritdoc TextEntry + */ + ContentEditableEntry.static.canWrap = function ( $element ) { + return $element.is( '[contenteditable]' ) && !$element.hasClass( 'noime' ); + }; + + /* Instance methods */ + + /** + * @inheritdoc TextEntry + */ + ContentEditableEntry.prototype.getTextBeforeSelection = function ( maxLength ) { + var range = this.getSelectedRange(); + if ( !range || !range.collapsed || range.startContainer.nodeType !== Node.TEXT_NODE ) { + return ''; + } + return range.startContainer.nodeValue.substring( + Math.max( 0, range.startOffset - maxLength ), + range.startOffset + ); + }; + + /** + * @inheritdoc SelectionWrapper + */ + ContentEditableEntry.prototype.replaceTextAtSelection = function ( precedingCharCount, newText ) { + var range, textNode, textOffset, newOffset, newRange; + + if ( !this.getSelectedRange() ) { + return; + } + + // Trigger any externally registered jQuery compositionstart event listeners. + // TODO: Try node.dispatchEvent( new CompositionEvent(...) ) so listeners not + // registered using jQuery will also get triggered, then fallback gracefully for + // browsers that do not support it. + this.$element.trigger( 'compositionstart' ); + + range = this.getSelectedRange(); + + if ( !range.collapsed ) { + range.deleteContents(); + } + + if ( range.startContainer.nodeType === Node.TEXT_NODE ) { + // Alter this text node's content and move the cursor + textNode = range.startContainer; + textOffset = range.startOffset; + textNode.nodeValue = + textNode.nodeValue.substr( 0, textOffset - precedingCharCount ) + + newText + + textNode.nodeValue.substr( textOffset ); + newOffset = textOffset - precedingCharCount + newText.length; + newRange = rangy.createRange(); + newRange.setStart( range.startContainer, newOffset ); + newRange.setEnd( range.startContainer, newOffset ); + rangy.getSelection().setSingleRange( newRange ); + } else { + // XXX assert precedingCharCount === 0 + // Insert a new text node with the new text + textNode = document.createTextNode( newText ); + range.startContainer.insertBefore( + textNode, + range.startContainer.childNodes[ range.startOffset ] + ); + newRange = rangy.createRange(); + newRange.setStart( textNode, textNode.length ); + newRange.setEnd( textNode, textNode.length ); + rangy.getSelection().setSingleRange( newRange ); + } + + // Trigger any externally registered jQuery compositionend / input event listeners. + // TODO: Try node.dispatchEvent( new CompositionEvent(...) ) so listeners not + // registered using jQuery will also get triggered, then fallback gracefully for + // browsers that do not support it. + this.$element.trigger( 'compositionend' ); + this.$element.trigger( 'input' ); + }; + + /** + * Get the selection range inside the wrapped element, or null + * + * @return {Range|null} The selection range + */ + ContentEditableEntry.prototype.getSelectedRange = function () { + var sel, range; + rangy.init(); + sel = rangy.getSelection(); + if ( sel.rangeCount === 0 ) { + return null; + } + range = sel.getRangeAt( 0 ); + if ( !this.$element[ 0 ].contains( range.commonAncestorContainer ) ) { + return null; + } + return range; + }; + + TextEntryFactory.static.singleton.register( ContentEditableEntry ); + + /* Exports */ + + /** + * jQuery plugin ime + * + * @param {Object} option + * @return {jQuery} + */ + $.fn.ime = function ( option ) { + return this.each( function () { + var data, textEntry, + $this = $( this ), + options = typeof option === 'object' && option; + + data = $this.data( 'ime' ); + if ( !data ) { + textEntry = TextEntryFactory.static.singleton.wrap( $this ); + if ( textEntry === undefined ) { + return; + } + data = new IME( this, textEntry, options ); + $this.data( 'ime', data ); + } + + if ( typeof option === 'string' ) { + data[ option ](); + } + } ); + }; + + $.ime = {}; + $.ime.inputmethods = {}; + $.ime.sources = {}; + $.ime.preferences = {}; + $.ime.languages = {}; + + /** + * @property {string} Relative/absolute path for the rules folder of jquery.ime + */ + $.ime.path = '../'; + $.ime.textEntryFactory = TextEntryFactory.static.singleton; + $.ime.TextEntry = TextEntry; + $.ime.inheritClass = inheritClass; + + defaultInputMethod = { + contextLength: 0, + maxKeyLength: 1 + }; + + /** + * load an input method by given id + * + * @param {string} inputmethodId + * @return {jQuery.Promise} + */ + $.ime.load = function ( inputmethodId ) { + var dependency, + deferred = $.Deferred(); + + if ( $.ime.inputmethods[ inputmethodId ] ) { + return deferred.resolve(); + } + + // Validate the input method id. + if ( !$.ime.sources[ inputmethodId ] ) { + return deferred.reject(); + } + + dependency = $.ime.sources[ inputmethodId ].depends; + if ( dependency && !$.ime.inputmethods[ dependency ] ) { + $.ime.load( dependency ).done( function () { + $.ime.load( inputmethodId ).done( function () { + deferred.resolve(); + } ); + } ); + + return deferred; + } + + debug( 'Loading ' + inputmethodId ); + deferred = $.ajax( { + url: $.ime.path + $.ime.sources[ inputmethodId ].source, + dataType: 'script', + cache: true + } ).done( function () { + debug( inputmethodId + ' loaded' ); + } ).fail( function ( jqxhr, settings, exception ) { + debug( 'Error in loading inputmethod ' + inputmethodId + ' Exception: ' + exception ); + } ); + + return deferred.promise(); + }; + + $.ime.register = function ( inputMethod ) { + $.ime.inputmethods[ inputMethod.id ] = $.extend( {}, defaultInputMethod, inputMethod ); + }; + + /** + * Set the relative/absolute path to rules/ (for loading input methods) + * + * @param {string} path The relative/absolute path in which rules/ lies + */ + $.ime.setPath = function ( path ) { + $.ime.path = path; + }; + + // default options + $.ime.defaults = { + languages: [], // Languages to be used- by default all languages + helpHandler: null, // Called for each ime option in the menu + showSelector: true + }; +}( jQuery ) ); + +( function ( $ ) { + 'use strict'; + + var selectorTemplate, MutationObserver; + + function IMESelector( element, options ) { + this.$element = $( element ); + this.options = $.extend( {}, IMESelector.defaults, options ); + this.active = false; + this.$imeSetting = null; + this.$menu = null; + this.inputmethod = null; + this.timer = null; + this.init(); + this.listen(); + } + + function languageListTitle() { + return $( '<h3>' ) + .addClass( 'ime-lang-title' ) + .attr( 'data-i18n', 'jquery-ime-other-languages' ) + .text( 'Other languages' ); + } + + function imeList() { + return $( '<ul>' ).addClass( 'ime-list' ); + } + + function imeListTitle() { + return $( '<h3>' ).addClass( 'ime-list-title autonym' ); + } + + function toggleMenuItem() { + return $( '<div class="ime-disable selectable-row">' ).append( + $( '<span>' ) + .attr( { + 'class': 'ime-disable-link', + 'data-i18n': 'jquery-ime-disable-text' + } ) + .addClass( 'ime-checked' ) + .text( 'System input method' ), + $( '<span>' ) + .addClass( 'ime-disable-shortcut' ) + .text( 'CTRL+M' ) + ); + } + + /** + * Check whether a keypress event corresponds to the shortcut key + * + * @param {event} event + * @return {boolean} true if the key is a shortcut key + */ + function isShortcutKey( event ) { + // 77 - The letter M, for Ctrl-M + return event.ctrlKey && !event.altKey && ( event.which === 77 ); + } + + IMESelector.prototype = { + constructor: IMESelector, + + init: function () { + this.prepareSelectorMenu(); + this.position(); + this.$imeSetting.hide(); + }, + + prepareSelectorMenu: function () { + // TODO: In this approach there is a menu for each editable area. + // With correct event mapping we can probably reduce it to one menu. + this.$imeSetting = $( selectorTemplate ); + this.$menu = $( '<div class="imeselector-menu" role="menu">' ); + this.$menu.append( + imeListTitle(), + imeList(), + toggleMenuItem(), + languageListTitle() + ); + + this.prepareLanguageList(); + this.$menu.append( this.helpLink() ); + + if ( $.i18n ) { + this.$menu.i18n(); + } + + this.$imeSetting.append( this.$menu ); + $( 'body' ).append( this.$imeSetting ); + }, + + stopTimer: function () { + if ( this.timer ) { + clearTimeout( this.timer ); + this.timer = null; + } + + this.$imeSetting.stop( true, true ); + }, + + resetTimer: function () { + var imeselector = this; + + this.stopTimer(); + + this.timer = setTimeout( + function () { + imeselector.$imeSetting.animate( { + opacity: 0, + marginTop: '-20px' + }, 500, function () { + imeselector.$imeSetting.hide(); + // Restore properties for the next time it becomes visible: + imeselector.$imeSetting.css( 'opacity', 1 ); + imeselector.$imeSetting.css( 'margin-top', 0 ); + } ); + }, this.options.timeout + ); + }, + + focus: function () { + // Hide all other IME settings and collapse open menus + $( 'div.imeselector' ).hide(); + $( 'div.imeselector-menu' ).removeClass( 'ime-open' ); + this.afterKeydown(); + }, + + afterKeydown: function () { + this.$imeSetting.show(); + this.resetTimer(); + }, + + show: function () { + this.$menu.addClass( 'ime-open' ); + this.stopTimer(); + this.$imeSetting.show(); + + return false; + }, + + hide: function () { + this.$menu.removeClass( 'ime-open' ); + this.resetTimer(); + + return false; + }, + + toggle: function () { + if ( this.$menu.hasClass( 'ime-open' ) ) { + this.hide(); + } else { + this.show(); + } + }, + + /** + * Bind the events and listen + */ + listen: function () { + var imeselector = this; + + imeselector.$imeSetting.on( 'click.ime', function ( e ) { + var t = $( e.target ); + + if ( t.hasClass( 'imeselector-toggle' ) ) { + imeselector.toggle(); + } + + return false; + } ); + + imeselector.$element.on( 'blur.ime', function () { + if ( !imeselector.$imeSetting.hasClass( 'ime-onfocus' ) ) { + imeselector.$imeSetting.hide(); + imeselector.hide(); + } + } ); + + // Hide the menu when clicked outside + $( 'html' ).click( function () { + imeselector.hide(); + } ); + + // ... but when clicked on window do not propagate it. + this.$menu.on( 'click', function ( event ) { + event.stopPropagation(); + } ); + + imeselector.$imeSetting.mouseenter( function () { + // We don't want the selector to disappear + // while the user is trying to click it + imeselector.stopTimer(); + imeselector.$imeSetting.addClass( 'ime-onfocus' ); + } ).mouseleave( function () { + imeselector.resetTimer(); + imeselector.$imeSetting.removeClass( 'ime-onfocus' ); + } ); + + imeselector.$menu.on( 'click.ime', 'li', function () { + imeselector.$element.focus(); + + return false; + } ); + + imeselector.$menu.on( 'click.ime', 'li.ime-im', function () { + imeselector.selectIM( $( this ).data( 'ime-inputmethod' ) ); + imeselector.$element.trigger( 'setim.ime', $( this ).data( 'ime-inputmethod' ) ); + + return false; + } ); + + imeselector.$menu.on( 'click.ime', 'li.ime-lang', function () { + var im = imeselector.selectLanguage( $( this ).attr( 'lang' ) ); + + imeselector.$element.trigger( 'setim.ime', im ); + + return false; + } ); + + imeselector.$menu.on( 'click.ime', 'div.ime-disable', function () { + imeselector.disableIM(); + + return false; + } ); + + // Just make it work as a regular link + imeselector.$menu.on( 'click.ime', '.ime-help-link', function ( e ) { + e.stopPropagation(); + } ); + + imeselector.$element.on( 'focus.ime', function ( e ) { + imeselector.selectLanguage( imeselector.decideLanguage() ); + imeselector.focus(); + e.stopPropagation(); + } ); + + imeselector.$element.attrchange( function () { + if ( imeselector.$element.is( ':hidden' ) ) { + imeselector.$imeSetting.hide(); + } + } ); + + // Possible resize of textarea + imeselector.$element.on( { + 'mouseup.ime': this.position.bind( this ), + 'keydown.ime': this.keydown.bind( this ) + } ); + + // Update IM selector position when the window is resized + // or the browser window is zoomed in or zoomed out + $( window ).resize( function () { + imeselector.position(); + } ); + }, + + /** + * Keydown event handler. Handles shortcut key presses + * + * @context {HTMLElement} + * @param {jQuery.Event} e + * @return {boolean} + */ + keydown: function ( e ) { + var ime = $( e.target ).data( 'ime' ), + firstInputmethod, + previousInputMethods, + languageCode; + + this.afterKeydown(); // shows the trigger in case it is hidden + + if ( isShortcutKey( e ) ) { + if ( ime.isActive() ) { + this.disableIM(); + this.$element.trigger( 'setim.ime', 'system' ); + } else { + if ( this.inputmethod !== null ) { + this.selectIM( this.inputmethod.id ); + this.$element.trigger( 'setim.ime', this.inputmethod.id ); + } else { + languageCode = this.decideLanguage(); + this.selectLanguage( languageCode ); + + if ( !ime.isActive() && $.ime.languages[ languageCode ] ) { + // Even after pressing toggle shortcut again, it is still disabled + // Check if there is a previously used input method. + previousInputMethods = $.ime.preferences.getPreviousInputMethods(); + + if ( previousInputMethods[ 0 ] ) { + this.selectIM( previousInputMethods[ 0 ] ); + } else { + // Provide the default input method in this case. + firstInputmethod = $.ime.languages[ languageCode ].inputmethods[ 0 ]; + this.selectIM( firstInputmethod ); + } + } + } + } + + e.preventDefault(); + e.stopPropagation(); + + return false; + } + + return true; + }, + + /** + * Position the im selector relative to the edit area + */ + position: function () { + var menuWidth, menuTop, menuLeft, elementPosition, + top, left, cssTop, cssLeft, verticalRoom, overflowsOnRight, + imeSelector = this, + rtlElement = this.$element.css( 'direction' ) === 'rtl', + $window = $( window ); + + this.focus(); // shows the trigger in case it is hidden + + elementPosition = this.$element.offset(); + top = elementPosition.top + this.$element.outerHeight(); + left = elementPosition.left; + + // RTL element position fix + if ( !rtlElement ) { + left = elementPosition.left + this.$element.outerWidth() - + this.$imeSetting.outerWidth(); + } + + // While determining whether to place the selector above or below the input box, + // take into account the value of scrollTop, to avoid the selector from always + // getting placed above the input box since window.height would be less than top + // if the page has been scrolled. + verticalRoom = $window.height() + $( document ).scrollTop() - top; + + if ( verticalRoom < this.$imeSetting.outerHeight() ) { + top = elementPosition.top - this.$imeSetting.outerHeight(); + menuTop = this.$menu.outerHeight() + + this.$imeSetting.outerHeight(); + + // Flip the menu to the top only if it can fit in the space there + if ( menuTop < top ) { + this.$menu + .addClass( 'ime-position-top' ) + .css( 'top', -menuTop ); + } + } + + cssTop = top; + cssLeft = left; + this.$element.parents().each( function () { + if ( $( this ).css( 'position' ) === 'fixed' ) { + imeSelector.$imeSetting.css( 'position', 'fixed' ); + cssTop -= $( document ).scrollTop(); + cssLeft -= $( document ).scrollLeft(); + return false; + } + } ); + + this.$imeSetting.css( { + top: cssTop, + left: cssLeft + } ); + + menuWidth = this.$menu.width(); + overflowsOnRight = ( left - $( document ).scrollLeft() + menuWidth ) > $window.width(); + + // Adjust horizontal position if there's + // not enough space on any side + if ( menuWidth > left || + rtlElement && overflowsOnRight + ) { + if ( rtlElement ) { + if ( overflowsOnRight ) { + this.$menu.addClass( 'ime-right' ); + menuLeft = this.$imeSetting.outerWidth() - menuWidth; + } else { + menuLeft = 0; + } + } else { + this.$menu.addClass( 'ime-right' ); + menuLeft = elementPosition.left; + } + + this.$menu.css( 'left', menuLeft ); + } + }, + + /** + * Select a language + * + * @param {string} languageCode + * @return {string|bool} Selected input method id or false + */ + selectLanguage: function ( languageCode ) { + var ime, imePref, language; + + // consider language codes case insensitive + languageCode = languageCode && languageCode.toLowerCase(); + + ime = this.$element.data( 'ime' ); + imePref = $.ime.preferences.getIM( languageCode ); + language = $.ime.languages[ languageCode ]; + + this.setMenuTitle( this.getAutonym( languageCode ) ); + + if ( !language ) { + return false; + } + + if ( ime.getLanguage() === languageCode ) { + // Nothing to do. It is same as the current language, + // but check whether the input method changed. + if ( ime.inputmethod && ime.inputmethod.id !== imePref ) { + this.selectIM( $.ime.preferences.getIM( languageCode ) ); + } + + return $.ime.preferences.getIM( languageCode ); + } + + this.$menu.find( 'li.ime-lang' ).show(); + this.$menu.find( 'li[lang=' + languageCode + ']' ).hide(); + + this.prepareInputMethods( languageCode ); + this.hide(); + // And select the default inputmethod + ime.setLanguage( languageCode ); + this.inputmethod = null; + this.selectIM( $.ime.preferences.getIM( languageCode ) ); + + return $.ime.preferences.getIM( languageCode ); + }, + + /** + * Get the autonym by language code. + * + * @param {string} languageCode + * @return {string} The autonym + */ + getAutonym: function ( languageCode ) { + return $.ime.languages[ languageCode ] && + $.ime.languages[ languageCode ].autonym; + }, + + /** + * Set the title of the selector menu. + * + * @param {string} title + */ + setMenuTitle: function ( title ) { + this.$menu.find( '.ime-list-title' ).text( title ); + }, + + /** + * Decide on initial language to select + * @return {string} + */ + decideLanguage: function () { + if ( $.ime.preferences.getLanguage() ) { + // There has been an override by the user, + // so return the language selected by user + return $.ime.preferences.getLanguage(); + } + + if ( this.$element.attr( 'lang' ) && + $.ime.languages[ this.$element.attr( 'lang' ) ] + ) { + return this.$element.attr( 'lang' ); + } + + // There is either no IMs for the given language attr + // or there is no lang attr at all. + return $.ime.preferences.getDefaultLanguage(); + }, + + /** + * Select an input method + * + * @param {string} inputmethodId + */ + selectIM: function ( inputmethodId ) { + var imeselector = this, + ime; + + if ( !inputmethodId ) { + return; + } + + this.$menu.find( '.ime-checked' ).removeClass( 'ime-checked' ); + this.$menu.find( 'li[data-ime-inputmethod=' + inputmethodId + ']' ) + .addClass( 'ime-checked' ); + ime = this.$element.data( 'ime' ); + + if ( inputmethodId === 'system' ) { + this.disableIM(); + + return; + } + + ime.load( inputmethodId ).done( function () { + imeselector.inputmethod = $.ime.inputmethods[ inputmethodId ]; + imeselector.hide(); + ime.enable(); + ime.setIM( inputmethodId ); + imeselector.$imeSetting.find( 'a.ime-name' ).text( + $.ime.sources[ inputmethodId ].name + ); + + imeselector.position(); + + // Save this preference + $.ime.preferences.save(); + } ); + }, + + /** + * Disable the inputmethods (Use the system input method) + */ + disableIM: function () { + this.$menu.find( '.ime-checked' ).removeClass( 'ime-checked' ); + this.$menu.find( 'div.ime-disable' ).addClass( 'ime-checked' ); + this.$element.data( 'ime' ).disable(); + this.$imeSetting.find( 'a.ime-name' ).text( '' ); + this.hide(); + this.position(); + + // Save this preference + $.ime.preferences.save(); + }, + + /** + * Prepare language list + */ + prepareLanguageList: function () { + var languageCodeIndex, + $languageListWrapper, + $languageList, + languageList, + $languageItem, + $language, + languageCode, + language; + + // Language list can be very long, so we use a container with + // overflow auto + $languageListWrapper = $( '<div class="ime-language-list-wrapper">' ); + $languageList = $( '<ul class="ime-language-list">' ); + + if ( $.isFunction( this.options.languages ) ) { + languageList = this.options.languages(); + } else { + languageList = this.options.languages; + } + + for ( languageCodeIndex in languageList ) { + languageCode = languageList[ languageCodeIndex ]; + language = $.ime.languages[ languageCode ]; + + if ( !language ) { + continue; + } + + $languageItem = $( '<a>' ) + .attr( 'href', '#' ) + .text( this.getAutonym( languageCode ) ) + .addClass( 'selectable-row-item autonym' ); + $language = $( '<li class="ime-lang selectable-row">' ).attr( 'lang', languageCode ); + $language.append( $languageItem ); + $languageList.append( $language ); + } + + $languageListWrapper.append( $languageList ); + this.$menu.append( $languageListWrapper ); + + if ( this.options.languageSelector ) { + this.$menu.append( this.options.languageSelector() ); + } + }, + + /** + * Prepare input methods in menu for the given language code + * + * @param {string} languageCode + */ + prepareInputMethods: function ( languageCode ) { + var language = $.ime.languages[ languageCode ], + $imeList = this.$menu.find( '.ime-list' ), + imeSelector = this; + + $imeList.empty(); + + $.each( language.inputmethods, function ( index, inputmethod ) { + var $imeItem, $inputMethod, source, name; + + source = $.ime.sources[ inputmethod ]; + if ( !source ) { + return; + } + name = source.name; + + $imeItem = $( '<a>' ) + .attr( 'href', '#' ) + .text( name ) + .addClass( 'selectable-row-item' ); + + $inputMethod = $( '<li>' ) + .attr( 'data-ime-inputmethod', inputmethod ) + .addClass( 'ime-im selectable-row' ) + .append( '<span class="ime-im-check"></span>', $imeItem ); + + if ( imeSelector.options.helpHandler ) { + $inputMethod.append( imeSelector.options.helpHandler.call( imeSelector, inputmethod ) ); + } + + $imeList.append( $inputMethod ); + } ); + }, + + /** + * Create a help link element. + * + * @return {jQuery} + */ + helpLink: function () { + return $( '<div class="ime-help-link selectable-row">' ) + .append( $( '<a>' ).text( 'Help' ) + .addClass( 'selectable-row-item' ) + .attr( { + href: 'http://github.com/wikimedia/jquery.ime', + target: '_blank', + 'data-i18n': 'jquery-ime-help' + } ) + ); + } + }; + + IMESelector.defaults = { + defaultLanguage: 'en', + timeout: 2500 // Milliseconds after which IME widget hides itself. + }; + + /* + * imeselector PLUGIN DEFINITION + */ + + $.fn.imeselector = function ( options ) { + return this.each( function () { + var $this = $( this ), + data = $this.data( 'imeselector' ); + + if ( !data ) { + $this.data( 'imeselector', ( data = new IMESelector( this, options ) ) ); + } + + if ( typeof options === 'string' ) { + data[ options ].call( $this ); + } + } ); + }; + + $.fn.imeselector.Constructor = IMESelector; + + selectorTemplate = '<div class="imeselector imeselector-toggle">' + + '<a class="ime-name imeselector-toggle" href="#"></a>' + + '<b class="ime-setting-caret imeselector-toggle"></b></div>'; + + MutationObserver = window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver; + + function isDOMAttrModifiedSupported() { + var p = document.createElement( 'p' ), + flag = false; + + if ( p.addEventListener ) { + p.addEventListener( 'DOMAttrModified', function () { + flag = true; + }, false ); + } else if ( p.attachEvent ) { + p.attachEvent( 'onDOMAttrModified', function () { + flag = true; + } ); + } else { + return false; + } + + p.setAttribute( 'id', 'target' ); + + return flag; + } + + $.fn.attrchange = function ( callback ) { + var observer; + + if ( MutationObserver ) { + observer = new MutationObserver( function ( mutations ) { + mutations.forEach( function ( e ) { + callback.call( e.target, e.attributeName ); + } ); + } ); + + return this.each( function () { + observer.observe( this, { + subtree: false, + attributes: true + } ); + } ); + } else if ( isDOMAttrModifiedSupported() ) { + return this.on( 'DOMAttrModified', function ( e ) { + callback.call( this, e.originalEvent.attrName ); + } ); + } else if ( 'onpropertychange' in document.body ) { + return this.on( 'propertychange', function () { + callback.call( this, window.event.propertyName ); + } ); + } + }; +}( jQuery ) ); + +( function ( $ ) { + 'use strict'; + + $.extend( $.ime.preferences, { + registry: { + isDirty: false, + language: null, + previousLanguages: [], // array of previous languages + previousInputMethods: [], // array of previous inputmethods + imes: { + en: 'system' + } + }, + + setLanguage: function ( language ) { + // Do nothing if there's no actual change + if ( language === this.registry.language ) { + return; + } + + this.registry.language = language; + this.registry.isDirty = true; + if ( !this.registry.previousLanguages ) { + this.registry.previousLanguages = []; + } + + // Add to the previous languages, but avoid duplicates. + if ( $.inArray( language, this.registry.previousLanguages ) === -1 ) { + this.registry.previousLanguages.unshift( language ); + this.registry.previousLanguages = this.registry.previousLanguages.slice( 0, 5 ); + } + }, + + getLanguage: function () { + return this.registry.language; + }, + + getDefaultLanguage: function () { + return 'en'; + }, + + getPreviousLanguages: function () { + return this.registry.previousLanguages; + }, + + getPreviousInputMethods: function () { + return this.registry.previousInputMethods; + }, + + // Set the given IM as the last used for the language + setIM: function ( inputMethod ) { + if ( !this.registry.imes ) { + this.registry.imes = {}; + } + + // Do nothing if there's no actual change + if ( inputMethod === this.registry.imes[ this.registry.language ] ) { + return; + } + + this.registry.imes[ this.getLanguage() ] = inputMethod; + this.registry.isDirty = true; + if ( !this.registry.previousInputMethods ) { + this.registry.previousInputMethods = []; + } + + // Add to the previous languages, + if ( inputMethod !== 'system' ) { + this.registry.previousInputMethods.unshift( inputMethod ); + this.registry.previousInputMethods = this.registry.previousInputMethods.slice( 0, 5 ); + } + }, + + // Return the last used or the default IM for language + getIM: function ( language ) { + if ( !this.registry.imes ) { + this.registry.imes = {}; + } + + return this.registry.imes[ language ] || 'system'; + }, + + save: function () { + // save registry in cookies or localstorage + }, + + load: function () { + // load registry from cookies or localstorage + } + } ); +}( jQuery ) ); + +( function ( $ ) { + 'use strict'; + + // All keys have quotes for consistency + /* eslint-disable quote-props */ + $.extend( $.ime.sources, { + 'ak-qx': { + name: 'QX replacement', + source: 'rules/ak/ak-qx.js' + }, + 'am-transliteration': { + name: 'ትራንስልተራትዖን', + source: 'rules/am/am-transliteration.js' + }, + 'ar-kbd': { + name: 'لوحة المفاتيح العربية', + source: 'rules/ar/ar-kbd.js' + }, + 'as-avro': { + name: 'অভ্ৰ', + source: 'rules/as/as-avro.js' + }, + 'as-bornona': { + name: 'বৰ্ণনা', + source: 'rules/as/as-bornona.js' + }, + 'as-inscript': { + name: 'ইনস্ক্ৰিপ্ট', + source: 'rules/as/as-inscript.js' + }, + 'as-inscript2': { + name: 'ইনস্ক্ৰিপ্ট ২', + source: 'rules/as/as-inscript2.js' + }, + 'as-phonetic': { + name: 'ফনেটিক', + source: 'rules/as/as-phonetic.js' + }, + 'as-rodali': { + name: 'ৰ\'দালি', + source: 'rules/as/as-rodali.js' + }, + 'as-transliteration': { + name: 'প্ৰতিৰূপান্তৰণ', + source: 'rules/as/as-transliteration.js' + }, + 'azb-kbd': { + name: 'تۆرکجه', + source: 'rules/azb/azb-kbd.js' + }, + 'batak-qwerty': { + name: 'Batak QWERTY', + source: 'rules/bbc/batak-qwerty.js' + }, + 'be-kbd': { + name: 'Стандартная', + source: 'rules/be/be-kbd.js' + }, + 'be-latin': { + name: 'Łacinka', + source: 'rules/be/be-latin.js' + }, + 'be-transliteration': { + name: 'Транслітэрацыя', + source: 'rules/be/be-transliteration.js' + }, + 'ber-tfng': { + name: 'Tifinagh', + source: 'rules/ber/ber-tfng.js' + }, + 'bgn-kbd': { + name: 'روچ کپتین بلوچی', + source: 'rules/bgn/bgn-kbd.js' + }, + 'bm-alt': { + name: 'Bamanankan Alt', + source: 'rules/bm/bm-alt.js' + }, + 'bn-avro': { + name: 'অভ্র', + source: 'rules/bn/bn-avro.js' + }, + 'bn-inscript': { + name: 'ইনস্ক্ৰিপ্ট', + source: 'rules/bn/bn-inscript.js' + }, + 'bn-inscript2': { + name: 'ইনস্ক্ৰিপ্ট ২', + source: 'rules/bn/bn-inscript2.js' + }, + 'bn-nkb': { + name: 'ন্যাশনাল কিবোর্ড', + source: 'rules/bn/bn-nkb.js' + }, + 'bn-probhat': { + name: 'প্রভাত', + source: 'rules/bn/bn-probhat.js' + }, + 'bo-ewts': { + name: 'Tibetan EWTS', + source: 'rules/bo/bo-ewts.js' + }, + 'bo-sambhota': { + name: 'Tibetan Sambhota', + source: 'rules/bo/bo-sambhota.js' + }, + 'brx-inscript': { + name: 'इनस्क्रिप्ट', + source: 'rules/brx/brx-inscript.js' + }, + 'brx-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/brx/brx-inscript2.js' + }, + 'byn-geezim': { + name: 'ብሊን', + source: 'rules/byn/byn-geezim.js' + }, + 'ckb-transliteration-arkbd': { + name: 'باشووری', + source: 'rules/ckb/ckb-transliteration-arkbd.js' + }, + 'ckb-transliteration-fakbd': { + name: 'ڕۆژھەڵاتی', + source: 'rules/ckb/ckb-transliteration-fakbd.js' + }, + 'ckb-transliteration-lakbd': { + name: 'لاتینی', + source: 'rules/ckb/ckb-transliteration-lakbd.js' + }, + 'cv-cyr-altgr': { + name: 'Чăвашла (AltGr)', + source: 'rules/cv/cv-cyr-altgr.js' + }, + 'cv-cyr-numbers': { + name: 'Чăвашла (цифрилисем)', + source: 'rules/cv/cv-cyr-numbers.js' + }, + 'cv-lat-altgr': { + name: 'Căvašla (AltGr)', + source: 'rules/cv/cv-lat-altgr.js' + }, + 'cyrl-palochka': { + name: 'Palochka', + source: 'rules/cyrl/cyrl-palochka.js' + }, + 'da-normforms': { + name: 'Brug translitteration', + source: 'rules/da/da-normforms.js' + }, + 'dag-alt': { + name: 'Dagbani Alt', + source: 'rules/dag/dag-alt.js' + }, + 'de-transliteration': { + name: 'Deutsch Tilde', + source: 'rules/de/de-transliteration.js' + }, + 'din-fqsx': { + name: 'Dinka FQSX', + source: 'rules/din/din-fqsx.js' + }, + 'doi-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/doi/doi-inscript2.js' + }, + 'ee-tilde': { + name: 'Ewe Tilde', + source: 'rules/ee/ee-tilde.js' + }, + 'el-kbd': { + name: 'Τυπική πληκτρολόγιο', + source: 'rules/el/el-kbd.js' + }, + 'eo-h': { + name: 'Esperanto h', + source: 'rules/eo/eo-h.js' + }, + 'eo-h-f': { + name: 'Esperanto h fundamente', + source: 'rules/eo/eo-h-f.js' + }, + 'eo-plena': { + name: 'Esperanto plena', + source: 'rules/eo/eo-plena.js' + }, + 'eo-q': { + name: 'Esperanto q sistemo', + source: 'rules/eo/eo-q.js' + }, + 'eo-transliteration': { + name: 'transliterigo', + source: 'rules/eo/eo-transliteration.js' + }, + 'eo-vi': { + name: 'Esperanto vi sistemo', + source: 'rules/eo/eo-vi.js' + }, + 'eo-x': { + name: 'Esperanto x sistemo', + source: 'rules/eo/eo-x.js' + }, + 'fa-kbd': { + name: 'فارسی', + source: 'rules/fa/fa-kbd.js' + }, + 'ff-alt': { + name: 'Fulfulde', + source: 'rules/ff/ff-alt.js' + }, + 'fi-transliteration': { + name: 'translitterointi', + source: 'rules/fi/fi-transliteration.js' + }, + 'fo-normforms': { + name: 'Føroyskt', + source: 'rules/fo/fo-normforms.js' + }, + 'fon-tilde': { + name: 'Fon Tilde', + source: 'rules/fon/fon-tilde.js' + }, + 'gaa-cqx': { + name: 'Ga - CQX replacement', + source: 'rules/gaa/gaa-cqx.js' + }, + 'gom-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/gom/gom-inscript2.js' + }, + 'got-standard': { + name: '𐌲𐌿𐍄𐌹𐍃𐌺𐌰 𐍂𐌰𐌶𐌳𐌰', + source: 'rules/got/got-standard.js' + }, + 'gu-inscript': { + name: 'ઇનસ્ક્રિપ્ટ', + source: 'rules/gu/gu-inscript.js' + }, + 'gu-inscript2': { + name: 'ઇનસ્ક્રિપ્ટ ૨', + source: 'rules/gu/gu-inscript2.js' + }, + 'gu-phonetic': { + name: 'ફોનૅટિક', + source: 'rules/gu/gu-phonetic.js' + }, + 'gu-transliteration': { + name: 'લિપ્યંતરણ', + source: 'rules/gu/gu-transliteration.js' + }, + 'ha-tilde': { + name: 'Hausa - tilde', + source: 'rules/ha/ha-tilde.js' + }, + 'he-standard-2012': { + name: 'עברית עם ניקוד על בסיס אנגלית', + source: 'rules/he/he-standard-2012.js' + }, + 'he-standard-2012-extonly': { + name: 'עברית עם ניקוד', + source: 'rules/he/he-standard-2012-extonly.js' + }, + 'hi-bolnagri': { + name: 'बोलनागरी', + source: 'rules/hi/hi-bolnagri.js' + }, + 'hi-inscript': { + name: 'इनस्क्रिप्ट', + source: 'rules/hi/hi-inscript.js' + }, + 'hi-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/hi/hi-inscript2.js' + }, + 'hi-phonetic': { + name: 'फोनेटिक', + source: 'rules/hi/hi-phonetic.js' + }, + 'hi-transliteration': { + name: 'लिप्यंतरण', + source: 'rules/hi/hi-transliteration.js' + }, + 'hoc-transliteration': { + name: 'Ho transliteration', + source: 'rules/hoc/hoc-transliteration.js' + }, + 'hr-kbd': { + name: 'Croatian kbd', + source: 'rules/hr/hr-kbd.js' + }, + 'hy-emslegacy': { + name: 'Մայքրոսոֆթի հին արևելահայերեն', + source: 'rules/hy/hy-emslegacy.js' + }, + 'hy-ephonetic': { + name: 'Հնչյունային դասավորություն', + source: 'rules/hy/hy-ephonetic.js' + }, + 'hy-ephoneticalt': { + name: 'Հնչյունային նոր (R→Ր, F→Թ)', + source: 'rules/hy/hy-ephoneticalt.js' + }, + 'hy-typewriter': { + name: 'Գրամեքենայի դասավորություն', + source: 'rules/hy/hy-typewriter.js' + }, + 'hy-wmslegacy': { + name: 'Մայքրոսոֆթի հին արևմտահայերեն', + source: 'rules/hy/hy-wmslegacy.js' + }, + 'ig-tilde': { + name: 'Igbo - tilde', + source: 'rules/ig/ig-tilde.js' + }, + 'ipa-sil': { + name: 'International Phonetic Alphabet - SIL', + source: 'rules/fonipa/ipa-sil.js' + }, + 'ipa-x-sampa': { + name: 'International Phonetic Alphabet - X-SAMPA', + source: 'rules/fonipa/ipa-x-sampa.js' + }, + 'is-normforms': { + name: 'Venjuleg eyðublöð', + source: 'rules/is/is-normforms.js' + }, + 'jv-transliteration': { + name: 'Transliteration', + source: 'rules/jv/jv-transliteration.js' + }, + 'ka-kbd': { + name: 'სტანდარტული კლავიატურის', + source: 'rules/ka/ka-kbd.js' + }, + 'ka-transliteration': { + name: 'ტრანსლიტერაცია', + source: 'rules/ka/ka-transliteration.js' + }, + 'kab-tilde': { + name: 'Taqbaylit Alatin tilde', + source: 'rules/kab/kab-tilde.js' + }, + 'kbp-tilde': { + name: 'Kabɩyɛ tilde', + source: 'rules/kbp/kbp-tilde.js' + }, + 'ki-tilde': { + name: 'Gĩkũyũ', + source: 'rules/ki/ki-tilde.js' + }, + 'kk-arabic': { + name: 'Kazakh Arabic transliteration', + source: 'rules/kk/kk-arabic.js' + }, + 'kk-kbd': { + name: 'Кирил', + source: 'rules/kk/kk-kbd.js' + }, + 'km-nidakyk': { + name: 'ក្តារចុចយូនីកូដខ្មែរ (NiDA)', + source: 'rules/km/km-nidakyk.js' + }, + 'kn-inscript': { + name: 'ಇನ್ಸ್ಕ್ರಿಪ್ಟ್', + source: 'rules/kn/kn-inscript.js' + }, + 'kn-inscript2': { + name: 'ಇನ್\u200cಸ್ಕ್ರಿಪ್ಟ್ ೨', + source: 'rules/kn/kn-inscript2.js' + }, + 'kn-kgp': { + name: 'KGP/Nudi/KP Rao', + source: 'rules/kn/kn-kgp.js' + }, + 'kn-transliteration': { + name: 'ಲಿಪ್ಯಂತರಣ', + source: 'rules/kn/kn-transliteration.js' + }, + 'ky-cyrl-alt': { + name: 'Кыргыз Alt', + source: 'rules/ky/ky-cyrl-alt.js' + }, + 'ks-inscript': { + name: 'इनस्क्रिप्ट', + source: 'rules/ks/ks-inscript.js' + }, + 'ks-kbd': { + name: 'Kashmiri Arabic', + source: 'rules/ks/ks-kbd.js' + }, + 'ku-h': { + name: 'Kurdî-h', + source: 'rules/ku/ku-h.js' + }, + 'ku-tr': { + name: 'Kurdî-tr', + source: 'rules/ku/ku-tr.js' + }, + 'lg-tilde': { + name: 'Luganda tilde', + source: 'rules/lg/lg-tilde.js' + }, + 'ln-tilde': { + name: 'Lingála tilde', + source: 'rules/ln/ln-tilde.js' + }, + 'lo-kbd': { + name: 'າຶກ', + source: 'rules/lo/lo-kbd.js' + }, + 'lrc-kbd': { + name: 'لۊری شومالی', + source: 'rules/lrc/lrc-kbd.js' + }, + 'lud-transliteration': { + name: 'lud', + source: 'rules/lud/lud-transliteration.js' + }, + 'lut-tulalip': { + name: 'Lushootseed Tulalip', + source: 'rules/lut/lut-tulalip.js' + }, + 'mai-inscript': { + name: 'इनस्क्रिप्ट', + source: 'rules/mai/mai-inscript.js', + depends: 'hi-inscript' + }, + 'mai-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/mai/mai-inscript2.js', + depends: 'hi-inscript2' + }, + 'mg-tilde': { + name: 'Malagasy tilde', + source: 'rules/mg/mg-tilde.js' + }, + 'mh': { + name: 'Kajin M̧ajeļ', + source: 'rules/mh/mh.js' + }, + 'ml-inscript': { + name: 'ഇൻസ്ക്രിപ്റ്റ്', + source: 'rules/ml/ml-inscript.js' + }, + 'ml-inscript2': { + name: 'ഇൻസ്ക്രിപ്റ്റ് 2', + source: 'rules/ml/ml-inscript2.js' + }, + 'ml-transliteration': { + name: 'ലിപ്യന്തരണം', + source: 'rules/ml/ml-transliteration.js' + }, + 'mn-cyrl': { + name: 'Кирилл', + source: 'rules/mn/mn-cyrl.js' + }, + 'mn-todo': { + name: 'ᡐᡆᡑᡆ ᡋᡅᡔᡅᡎ᠌', + source: 'rules/mn/mn-todo.js' + }, + 'mn-todoali': { + name: 'Todo Mongolian Ali-gali', + source: 'rules/mn/mn-todoali.js' + }, + 'mn-trad': { + name: 'ᠮᠣᠩᠭᠣᠯ ᠪᠢᠴᠢᠭ᠌', + source: 'rules/mn/mn-trad.js' + }, + 'mn-tradali': { + name: 'Traditional Mongolian Ali-gali', + source: 'rules/mn/mn-tradali.js' + }, + 'mnc': { + name: 'ᠮᠠᠨᠵᡠ', + source: 'rules/mnc/mnc.js' + }, + 'mnc-ali': { + name: 'Manchu Ali-gali', + source: 'rules/mnc/mnc-ali.js' + }, + 'mni-inscript2': { + name: 'ইনস্ক্ৰিপ্ট ২', + source: 'rules/mni/mni-inscript2.js' + }, + 'mr-inscript': { + name: 'मराठी लिपी', + source: 'rules/mr/mr-inscript.js' + }, + 'mr-inscript2': { + name: 'मराठी इनस्क्रिप्ट २', + source: 'rules/mr/mr-inscript2.js' + }, + 'mr-phonetic': { + name: 'फोनेटिक', + source: 'rules/mr/mr-phonetic.js' + }, + 'mr-transliteration': { + name: 'अक्षरांतरण', + source: 'rules/mr/mr-transliteration.js' + }, + 'my-mm3': { + name: 'မြန်မာ၃ လက်ကွက်', + source: 'rules/my/my-mm3.js' + }, + 'my-xkb': { + name: 'မြန်မာဘာသာ xkb', + source: 'rules/my/my-xkb.js' + }, + 'nb-normforms': { + name: 'Normal transliterasjon', + source: 'rules/nb/nb-normforms.js' + }, + 'nb-tildeforms': { + name: 'Tildemerket transliterasjon', + source: 'rules/nb/nb-tildeforms.js' + }, + 'ne-inscript': { + name: 'इनस्क्रिप्ट', + source: 'rules/ne/ne-inscript.js' + }, + 'ne-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/ne/ne-inscript2.js' + }, + 'ne-rom': { + name: 'Romanized', + source: 'rules/ne/ne-rom.js' + }, + 'ne-trad': { + name: 'Traditional', + source: 'rules/ne/ne-trad.js' + }, + 'ne-transliteration': { + name: 'ट्रांस्लितेरेशन', + source: 'rules/ne/ne-transliteration.js' + }, + 'nn-tildeforms': { + name: 'Tildemerkt transliterasjon', + source: 'rules/nb/nb-tildeforms.js' + }, + 'nso-tilde': { + name: 'Sesotho sa Leboa tilde', + source: 'rules/nso/nso-tilde.js' + }, + 'or-inscript': { + name: 'ଇନସ୍କ୍ରିପ୍ଟ', + source: 'rules/or/or-inscript.js' + }, + 'or-inscript2': { + name: 'ଇନସ୍କ୍ରିପ୍ଟ2', + source: 'rules/or/or-inscript2.js' + }, + 'or-lekhani': { + name: 'ଲେଖନୀ', + source: 'rules/or/or-lekhani.js' + }, + 'or-OdiScript': { + name: 'ଓଡ଼ିସ୍କ୍ରିପ୍ଟ', + source: 'rules/or/or-OdiScript.js' + }, + 'or-phonetic': { + name: 'ଫୋନେଟିକ', + source: 'rules/or/or-phonetic.js' + }, + 'or-transliteration': { + name: 'ଟ୍ରାନ୍ସଲିଟରେସନ', + source: 'rules/or/or-transliteration.js' + }, + 'pa-inscript': { + name: 'ਇਨਸਕ੍ਰਿਪਟ', + source: 'rules/pa/pa-inscript.js' + }, + 'pa-inscript2': { + name: 'ਇਨਸਕ੍ਰਿਪਟ2', + source: 'rules/pa/pa-inscript2.js' + }, + 'pa-jhelum': { + name: 'ਜੇਹਲਮ', + source: 'rules/pa/pa-jhelum.js' + }, + 'pa-transliteration': { + name: 'ਲਿਪਾਂਤਰਨ', + source: 'rules/pa/pa-transliteration.js' + }, + 'pa-phonetic': { + name: 'ਫੋਨੇਟਿਕ', + source: 'rules/pa/pa-phonetic.js' + }, + 'phagspa': { + name: 'PhagsPa', + source: 'rules/mn/phagspa.js' + }, + 'pms': { + name: 'Piemontèis', + source: 'rules/pms/pms.js' + }, + 'roa-tara-GVU': { + name: 'Tarandine', + source: 'rules/roa-tara/roa-tara.js' + }, + 'ru-jcuken': { + name: 'ЙЦУКЕН', + source: 'rules/ru/ru-jcuken.js' + }, + 'ru-kbd': { + name: 'кбд', + source: 'rules/ru/ru-kbd.js' + }, + 'ru-phonetic': { + name: 'фонетический', + source: 'rules/ru/ru-phonetic.js' + }, + 'ru-yawerty': { + name: 'yawerty', + source: 'rules/ru/ru-yawerty.js' + }, + 'sa-iast': { + name: 'Romanized', + source: 'rules/sa/sa-iast.js' + }, + 'sa-inscript': { + name: 'इनस्क्रिप्ट', + source: 'rules/sa/sa-inscript.js' + }, + 'sa-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/sa/sa-inscript2.js' + }, + 'sa-transliteration': { + name: 'लिप्यन्तरणम्', + source: 'rules/sa/sa-transliteration.js' + }, + 'sah-transliteration': { + name: 'Transliteration', + source: 'rules/sah/sah-transliteration.js' + }, + 'sat-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/sat/sat-inscript2.js' + }, + 'sat-inscript2-ol-chiki': { + name: 'inscript2 ᱚᱞ ᱪᱤᱠᱤ', + source: 'rules/sat/sat-inscript2-ol-chiki.js' + }, + 'sat-sarjom-baha': { + name: 'sarjom baha', + source: 'rules/sat/sat-sarjom-baha.js' + }, + 'sd-inscript2': { + name: 'इनस्क्रिप्ट २', + source: 'rules/sd/sd-inscript2.js' + }, + 'sdh-kbd': { + name: 'کوردی خوارگ', + source: 'rules/sdh/sdh-kbd.js' + }, + 'se-normforms': { + name: 'Normal forms', + source: 'rules/se/se-normforms.js' + }, + 'ses-tilde': { + name: 'Koyraboro Senni tilde', + source: 'rules/ses/ses-tilde.js' + }, + 'sg-tilde': { + name: 'Sängö', + source: 'rules/sg/sg-tilde.js' + }, + 'si-singlish': { + name: 'සිංග්ලිෂ්', + source: 'rules/si/si-singlish.js' + }, + 'si-wijesekara': { + name: 'විජේසේකර', + source: 'rules/si/si-wijesekara.js' + }, + 'sjo': { + name: 'ᠰᡞᠪᡝ', + source: 'rules/sjo/sjo.js' + }, + 'sk-kbd': { + name: 'Štandardná', + source: 'rules/sk/sk-kbd.js' + }, + 'sr-kbd': { + name: 'Стандардна', + source: 'rules/sr/sr-kbd.js' + }, + 'st-tilde': { + name: 'Sesotho tilde', + source: 'rules/st/st-tilde.js' + }, + 'sv-normforms': { + name: 'Normal forms', + source: 'rules/sv/sv-normforms.js' + }, + 'ta-99': { + name: 'தமிழ்99', + source: 'rules/ta/ta-99.js' + }, + 'ta-bamini': { + name: 'பாமினி', + source: 'rules/ta/ta-bamini.js' + }, + 'ta-inscript': { + name: 'இன்ஸ்கிரிப்ட்', + source: 'rules/ta/ta-inscript.js' + }, + 'ta-inscript2': { + name: 'இன்ஸ்கிரிப்ட் 2', + source: 'rules/ta/ta-inscript2.js' + }, + 'ta-transliteration': { + name: 'எழுத்துப்பெயர்ப்பு', + source: 'rules/ta/ta-transliteration.js' + }, + 'te-apple': { + name: 'ఆపిల్', + source: 'rules/te/te-apple.js' + }, + 'te-inscript': { + name: 'ఇన్\u200dస్క్రిప్ట్', + source: 'rules/te/te-inscript.js' + }, + 'te-inscript2': { + name: 'ఇన్\u200dస్క్రిప్ట్ 2', + source: 'rules/te/te-inscript2.js' + }, + 'te-modular': { + name: 'మాడ్యులర్', + source: 'rules/te/te-modular.js' + }, + 'te-transliteration': { + name: 'లిప్యంతరీకరణ', + source: 'rules/te/te-transliteration.js' + }, + 'th-kedmanee': { + name: 'เกษมณี', + source: 'rules/th/th-kedmanee.js' + }, + 'th-pattachote': { + name: 'ปัตตะโชติ', + source: 'rules/th/th-pattachote.js' + }, + 'ti-geezim': { + name: 'ትግርኛ', + source: 'rules/ti/ti-geezim.js' + }, + 'tig-geezim': { + name: 'ትግረ', + source: 'rules/tig/tig-geezim.js' + }, + 'udm-alt': { + name: 'Удмурт ALT', + source: 'rules/udm/udm-alt.js' + }, + 'ug-kbd': { + name: 'Uyghur kbd', + source: 'rules/ug/ug-kbd.js' + }, + 'uk-kbd': { + name: 'кбд', + source: 'rules/uk/uk-kbd.js' + }, + 'ur-phonetic': { + name: 'صوتی', + source: 'rules/ur/ur-phonetic.js' + }, + 'ur-transliteration': { + name: 'ٹرانسلٹریشن', + source: 'rules/ur/ur-transliteration.js' + }, + 'uz-kbd': { + name: 'Uzbek kbd', + source: 'rules/uz/uz-kbd.js' + }, + 've-tilde': { + name: 'TshiVenḓa tilde', + source: 'rules/ve/ve-tilde.js' + }, + 'vec-GVU': { + name: 'Vèneto', + source: 'rules/vec/vec-GVU.js' + }, + 'wo-alt': { + name: 'Wolof Alt', + source: 'rules/wo/wo-alt.js' + }, + 'yo-alt': { + name: 'Yorùbá Alt', + source: 'rules/yo/yo-alt.js' + }, + 'yo-tilde': { + name: 'Yorùbá tilde', + source: 'rules/yo/yo-tilde.js' + }, + 'zh-pinyin-transliteration': { + name: '拼音符号输入法', + source: 'rules/zh/zh-pinyin-transliteration.js' + } + } ); + /* eslint-disable quote-props */ + + $.extend( $.ime.languages, { + ady: { + autonym: 'адыгэбзэ', + inputmethods: [ 'cyrl-palochka' ] + }, + ahr: { + autonym: 'अहिराणी', + inputmethods: [ 'mr-transliteration', 'mr-inscript' ] + }, + ak: { + autonym: 'Akan', + inputmethods: [ 'ak-qx' ] + }, + am: { + autonym: 'አማርኛ', + inputmethods: [ 'am-transliteration' ] + }, + ar: { + autonym: 'العربية', + inputmethods: [ 'ar-kbd' ] + }, + as: { + autonym: 'অসমীয়া', + inputmethods: [ 'as-transliteration', 'as-avro', 'as-bornona', 'as-inscript', 'as-phonetic', 'as-inscript2', 'as-rodali' ] + }, + av: { + autonym: 'авар', + inputmethods: [ 'cyrl-palochka' ] + }, + azb: { + autonym: 'تۆرکجه', + inputmethods: [ 'azb-kbd' ] + }, + bbc: { + autonym: 'Batak', + inputmethods: [ 'batak-qwerty' ] + }, + be: { + autonym: 'беларуская', + inputmethods: [ 'be-transliteration', 'be-latin', 'be-kbd' ] + }, + 'be-tarask': { + autonym: 'беларуская (тарашкевіца)', + inputmethods: [ 'be-transliteration', 'be-latin' ] + }, + bh: { + autonym: 'भोजपुरी', + inputmethods: [ 'hi-transliteration' ] + }, + bgn: { + autonym: 'روچ کپتین بلوچی', + inputmethods: [ 'bgn-kbd' ] + }, + bho: { + autonym: 'भोजपुरी', + inputmethods: [ 'hi-transliteration' ] + }, + bm: { + autonym: 'Bamanankan', + inputmethods: [ 'bm-alt' ] + }, + bn: { + autonym: 'বাংলা', + inputmethods: [ 'bn-avro', 'bn-inscript', 'bn-nkb', 'bn-probhat', 'bn-inscript2' ] + }, + bo: { + autonym: 'བོད་ཡིག།', + inputmethods: [ 'bo-ewts', 'bo-sambhota' ] + }, + brx: { + autonym: 'बोड़ो', + inputmethods: [ 'brx-inscript', 'brx-inscript2' ] + }, + byn: { + autonym: 'ብሊን', + inputmethods: [ 'byn-geezim' ] + }, + ce: { + autonym: 'нохчийн', + inputmethods: [ 'cyrl-palochka' ] + }, + ckb: { + autonym: 'کوردی', + inputmethods: [ 'ckb-transliteration-arkbd', 'ckb-transliteration-fakbd', 'ckb-transliteration-lakbd' ] + }, + cv: { + autonym: 'Чăвашла', + inputmethods: [ 'cv-cyr-altgr', 'cv-lat-altgr', 'cv-cyr-numbers' ] + }, + da: { + autonym: 'Dansk', + inputmethods: [ 'da-normforms' ] + }, + dag: { + autonym: 'Dagbani', + inputmethods: [ 'dag-alt' ] + }, + de: { + autonym: 'Deutsch', + inputmethods: [ 'de-transliteration' ] + }, + din: { + autonym: 'Thuɔŋjäŋ', + inputmethods: [ 'din-fqsx' ] + }, + diq: { + autonym: 'Kirdkî', + inputmethods: [ 'ku-h', 'ku-tr' ] + }, + doi: { + autonym: 'डोगरी', + inputmethods: [ 'doi-inscript2' ] + }, + en: { + autonym: 'English', + inputmethods: [ 'ipa-sil', 'ipa-x-sampa' ] + }, + ee: { + autonym: 'Èʋegbe', + inputmethods: [ 'ee-tilde' ] + }, + el: { + autonym: 'Ελληνικά', + inputmethods: [ 'el-kbd' ] + }, + eo: { + autonym: 'Esperanto', + inputmethods: [ 'eo-transliteration', 'eo-h', 'eo-h-f', 'eo-plena', 'eo-q', 'eo-vi', 'eo-x' ] + }, + fa: { + autonym: 'فارسی', + inputmethods: [ 'fa-kbd' ] + }, + ff: { + autonym: 'Fulfulde', + inputmethods: [ 'ff-alt' ] + }, + fi: { + autonym: 'Suomi', + inputmethods: [ 'fi-transliteration' ] + }, + fo: { + autonym: 'Føroyskt', + inputmethods: [ 'fo-normforms' ] + }, + fon: { + autonym: 'Fon', + inputmethods: [ 'fon-tilde' ] + }, + fonipa: { + autonym: 'International Phonetic Alphabet', + inputmethods: [ 'ipa-sil', 'ipa-x-sampa' ] + }, + gaa: { + autonym: 'Ga', + inputmethods: [ 'gaa-cqx' ] + }, + got: { + autonym: '𐌲𐌿𐍄𐌹𐍃𐌺𐌰 𐍂𐌰𐌶𐌳𐌰', + inputmethods: [ 'got-standard' ] + }, + ha: { + autonym: 'Hausa', + inputmethods: [ 'ha-tilde' ] + }, + ig: { + autonym: 'Igbo', + inputmethods: [ 'ig-tilde' ] + }, + gom: { + autonym: 'गोंयची कोंकणी / Gõychi Konknni', + inputmethods: [ 'hi-transliteration', 'hi-inscript', 'gom-inscript2' ] + }, + gu: { + autonym: 'ગુજરાતી', + inputmethods: [ 'gu-transliteration', 'gu-inscript', 'gu-inscript2', 'gu-phonetic' ] + }, + he: { + autonym: 'עברית', + inputmethods: [ 'he-standard-2012-extonly', 'he-standard-2012' ] + }, + hi: { + autonym: 'हिन्दी', + inputmethods: [ 'hi-transliteration', 'hi-inscript', 'hi-bolnagri', 'hi-phonetic', 'hi-inscript2' ] + }, + hne: { + autonym: 'छत्तीसगढ़ी', + inputmethods: [ 'hi-transliteration' ] + }, + hoc: { + autonym: '𑢹𑣉𑣉', + inputmethods: [ 'hoc-transliteration' ] + }, + hr: { + autonym: 'Hrvatski', + inputmethods: [ 'hr-kbd' ] + }, + hy: { + autonym: 'հայերեն', + inputmethods: [ 'hy-ephonetic', 'hy-typewriter', 'hy-ephoneticalt', 'hy-emslegacy', 'hy-wmslegacy' ] + }, + inh: { + autonym: 'гӀалгӀай', + inputmethods: [ 'cyrl-palochka' ] + }, + is: { + autonym: 'Íslenska', + inputmethods: [ 'is-normforms' ] + }, + jv: { + autonym: 'ꦧꦱꦗꦮ', + inputmethods: [ 'jv-transliteration' ] + }, + ka: { + autonym: 'ქართული ენა', + inputmethods: [ 'ka-transliteration', 'ka-kbd' ] + }, + kab: { + autonym: 'Taqbaylit / ⵜⴰⵇⴱⴰⵢⵍⵉⵜ', + inputmethods: [ 'kab-tilde', 'ber-tfng' ] + }, + kbd: { + autonym: 'адыгэбзэ (къэбэрдеибзэ)', + inputmethods: [ 'cyrl-palochka' ] + }, + kbp: { + autonym: 'Kabɩyɛ', + inputmethods: [ 'kbp-tilde' ] + }, + ki: { + autonym: 'Gĩkũyũ', + inputmethods: [ 'ki-tilde' ] + }, + kk: { + autonym: 'Қазақша', + inputmethods: [ 'kk-kbd', 'kk-arabic' ] + }, + km: { + autonym: 'ភាសាខ្មែរ', + inputmethods: [ 'km-nidakyk' ] + }, + kn: { + autonym: 'ಕನ್ನಡ', + inputmethods: [ 'kn-transliteration', 'kn-inscript', 'kn-kgp', 'kn-inscript2' ] + }, + ks: { + autonym: 'कॉशुर / کٲشُر', + inputmethods: [ 'ks-inscript', 'ks-kbd' ] + }, + ky: { + autonym: 'Кыргыз', + inputmethods: [ 'ky-cyrl-alt' ] + }, + ku: { + autonym: 'Kurdî', + inputmethods: [ 'ku-h', 'ku-tr' ] + }, + lbe: { + autonym: 'лакку', + inputmethods: [ 'cyrl-palochka' ] + }, + lez: { + autonym: 'лезги', + inputmethods: [ 'cyrl-palochka' ] + }, + lg: { + autonym: 'Luganda', + inputmethods: [ 'lg-tilde' ] + }, + ln: { + autonym: 'Lingála', + inputmethods: [ 'ln-tilde' ] + }, + lo: { + autonym: 'ລາວ', + inputmethods: [ 'lo-kbd' ] + }, + lrc: { + autonym: 'لۊری شومالی', + inputmethods: [ 'lrc-kbd' ] + }, + lud: { + autonym: 'lüüdi', + inputmethods: [ 'lud-transliteration' ] + }, + lut: { + autonym: 'dxʷləšucid', + inputmethods: [ 'lut-tulalip' ] + }, + mai: { + autonym: 'मैथिली', + inputmethods: [ 'mai-inscript', 'mai-inscript2' ] + }, + mg: { + autonym: 'Malagasy', + inputmethods: [ 'mg-tilde' ] + }, + mh: { + autonym: 'Kajin M̧ajeļ', + inputmethods: [ 'mh' ] + }, + ml: { + autonym: 'മലയാളം', + inputmethods: [ 'ml-transliteration', 'ml-inscript', 'ml-inscript2' ] + }, + mn: { + autonym: 'Монгол', + inputmethods: [ 'mn-cyrl', 'mn-trad', 'mn-todo', 'mn-tradali', 'mn-todoali', 'phagspa' ] + }, + mnc: { + autonym: 'ᠮᠠᠨᠵᡠ', + inputmethods: [ 'mnc', 'mnc-ali' ] + }, + mni: { + autonym: 'Manipuri', + inputmethods: [ 'mni-inscript2' ] + }, + mr: { + autonym: 'मराठी', + inputmethods: [ 'mr-transliteration', 'mr-inscript2', 'mr-inscript', 'mr-phonetic' ] + }, + my: { + autonym: 'မြန်မာ', + inputmethods: [ 'my-mm3', 'my-xkb' ] + }, + nb: { + autonym: 'Norsk (bokmål)', + inputmethods: [ 'nb-normforms', 'nb-tildeforms' ] + }, + ne: { + autonym: 'नेपाली', + inputmethods: [ 'ne-transliteration', 'ne-inscript2', 'ne-inscript', 'ne-rom', 'ne-trad' ] + }, + 'new': { + autonym: 'नेपाल भाषा', + inputmethods: [ 'hi-transliteration', 'hi-inscript' ] + }, + nn: { + autonym: 'Norsk (nynorsk)', + inputmethods: [ 'nb-normforms', 'nn-tildeforms' ] + }, + nso: { + autonym: 'Sesotho sa Leboa', + inputmethods: [ 'nso-tilde' ] + }, + or: { + autonym: 'ଓଡ଼ିଆ', + inputmethods: [ 'or-phonetic', 'or-transliteration', 'or-inscript', 'or-inscript2', 'or-lekhani', 'or-OdiScript' ] + }, + pa: { + autonym: 'ਪੰਜਾਬੀ', + inputmethods: [ 'pa-transliteration', 'pa-inscript', 'pa-phonetic', 'pa-inscript2', 'pa-jhelum' ] + }, + pms: { + autonym: 'Piemontèis', + inputmethods: [ 'pms' ] + }, + rif: { + autonym: 'ⵜⴰⵔⵉⴼⵉⵜ', + inputmethods: [ 'ber-tfng' ] + }, + 'roa-tara': { + autonym: 'Tarandine', + inputmethods: [ 'roa-tara-GVU' ] + }, + ru: { + autonym: 'русский', + inputmethods: [ 'ru-jcuken', 'ru-kbd', 'ru-phonetic', 'ru-yawerty' ] + }, + sa: { + autonym: 'संस्कृत', + inputmethods: [ 'sa-transliteration', 'sa-inscript2', 'sa-inscript', 'sa-iast' ] + }, + sah: { + autonym: 'саха тыла', + inputmethods: [ 'sah-transliteration' ] + }, + sat: { + autonym: 'ᱥᱟᱱᱛᱟᱞᱤ (संताली)', + inputmethods: [ 'sat-inscript2', 'sat-inscript2-ol-chiki', 'sat-sarjom-baha' ] + }, + sd: { + autonym: 'सिंधी', + inputmethods: [ 'sd-inscript2' ] + }, + sdh: { + autonym: 'کوردی خوارگ', + inputmethods: [ 'sdh-kbd' ] + }, + se: { + autonym: 'Davvisámegiella', + inputmethods: [ 'se-normforms' ] + }, + ses: { + autonym: 'Koyraboro Senni', + inputmethods: [ 'ses-tilde' ] + }, + sg: { + autonym: 'Sängö', + inputmethods: [ 'sg-tilde' ] + }, + shi: { + autonym: 'ⵜⴰⵛⵍⵃⵉⵜ', + inputmethods: [ 'ber-tfng' ] + }, + si: { + autonym: 'සිංහල', + inputmethods: [ 'si-singlish', 'si-wijesekara' ] + }, + sjo: { + autonym: 'ᠰᡞᠪᡝ', + inputmethods: [ 'sjo' ] + }, + sk: { + autonym: 'Slovenčina', + inputmethods: [ 'sk-kbd' ] + }, + sr: { + autonym: 'Српски / srpski', + inputmethods: [ 'sr-kbd' ] + }, + st: { + autonym: 'Sesotho', + inputmethods: [ 'st-tilde' ] + }, + sv: { + autonym: 'Svenska', + inputmethods: [ 'sv-normforms' ] + }, + ta: { + autonym: 'தமிழ்', + inputmethods: [ 'ta-transliteration', 'ta-99', 'ta-inscript', 'ta-bamini', 'ta-inscript2' ] + }, + tcy: { + autonym: 'ತುಳು', + inputmethods: [ 'kn-transliteration', 'kn-inscript', 'kn-kgp', 'kn-inscript2' ] + }, + te: { + autonym: 'తెలుగు', + inputmethods: [ 'te-transliteration', 'te-inscript', 'te-inscript2', 'te-apple', 'te-modular' ] + }, + th: { + autonym: 'ไทย', + inputmethods: [ 'th-kedmanee', 'th-pattachote' ] + }, + ti: { + autonym: 'ትግርኛ', + inputmethods: [ 'ti-geezim' ] + }, + tig: { + autonym: 'ትግረ', + inputmethods: [ 'tig-geezim' ] + }, + tkr: { + autonym: 'цӀаӀхна миз', + inputmethods: [ 'cyrl-palochka' ] + }, + tw: { + autonym: 'Twi', + inputmethods: [ 'ak-qx' ] + }, + tzm: { + autonym: 'ⵜⴰⵎⴰⵣⵉⵖⵜ', + inputmethods: [ 'ber-tfng' ] + }, + udm: { + autonym: 'удмурт', + inputmethods: [ 'udm-alt' ] + }, + uk: { + autonym: 'Українська', + inputmethods: [ 'uk-kbd' ] + }, + ug: { + autonym: 'ئۇيغۇرچە / Uyghurche', + inputmethods: [ 'ug-kbd' ] + }, + ur: { + autonym: 'اردو', + inputmethods: [ 'ur-transliteration', 'ur-phonetic' ] + }, + uz: { + autonym: 'Oʻzbekcha', + inputmethods: [ 'uz-kbd' ] + }, + ve: { + autonym: 'TshiVenḓa', + inputmethods: [ 've-tilde' ] + }, + vec: { + autonym: 'Vèneto', + inputmethods: [ 'vec-GVU' ] + }, + wo: { + autonym: 'Wolof', + inputmethods: [ 'wo-alt' ] + }, + yo: { + autonym: 'Yorùbá', + inputmethods: [ 'yo-alt', 'yo-tilde' ] + }, + zh: { + autonym: '中文', + inputmethods: [ 'zh-pinyin-transliteration' ] + } + } ); + +}( jQuery ) ); |