Extract code handling PrinterProviderAPI from PrintPreviewHandler
[chromium-blink-merge.git] / chrome / browser / resources / options / language_options.js
blobf40284495dfc5564871f5f03283bb6c3127fe4fb
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // TODO(kochi): Generalize the notification as a component and put it
6 // in js/cr/ui/notification.js .
8 cr.define('options', function() {
9   /** @const */ var Page = cr.ui.pageManager.Page;
10   /** @const */ var PageManager = cr.ui.pageManager.PageManager;
11   /** @const */ var LanguageList = options.LanguageList;
12   /** @const */ var ThirdPartyImeConfirmOverlay =
13       options.ThirdPartyImeConfirmOverlay;
15   /**
16    * Spell check dictionary download status.
17    * @type {Enum}
18    */
19   /** @const*/ var DOWNLOAD_STATUS = {
20     IN_PROGRESS: 1,
21     FAILED: 2
22   };
24   /**
25    * The preference is a boolean that enables/disables spell checking.
26    * @type {string}
27    * @const
28    */
29   var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
31   /**
32    * The preference is a CSV string that describes preload engines
33    * (i.e. active input methods).
34    * @type {string}
35    * @const
36    */
37   var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
39   /**
40    * The preference that lists the extension IMEs that are enabled in the
41    * language menu.
42    * @type {string}
43    * @const
44    */
45   var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
47   /**
48    * The preference that lists the languages which are not translated.
49    * @type {string}
50    * @const
51    */
52   var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
54   /**
55    * The preference key that is a string that describes the spell check
56    * dictionary language, like "en-US".
57    * @type {string}
58    * @const
59    */
60   var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary';
62   /**
63    * The preference that indicates if the Translate feature is enabled.
64    * @type {string}
65    * @const
66    */
67   var ENABLE_TRANSLATE = 'translate.enabled';
69   /////////////////////////////////////////////////////////////////////////////
70   // LanguageOptions class:
72   /**
73    * Encapsulated handling of ChromeOS language options page.
74    * @constructor
75    * @extends {cr.ui.pageManager.Page}
76    */
77   function LanguageOptions(model) {
78     Page.call(this, 'languages',
79               loadTimeData.getString('languagePageTabTitle'), 'languagePage');
80   }
82   cr.addSingletonGetter(LanguageOptions);
84   // Inherit LanguageOptions from Page.
85   LanguageOptions.prototype = {
86     __proto__: Page.prototype,
88     /**
89      * For recording the prospective language (the next locale after relaunch).
90      * @type {?string}
91      * @private
92      */
93     prospectiveUiLanguageCode_: null,
95     /**
96      * Map from language code to spell check dictionary download status for that
97      * language.
98      * @type {Array}
99      * @private
100      */
101     spellcheckDictionaryDownloadStatus_: [],
103     /**
104      * Number of times a spell check dictionary download failed.
105      * @type {number}
106      * @private
107      */
108     spellcheckDictionaryDownloadFailures_: 0,
110     /**
111      * The list of preload engines, like ['mozc', 'pinyin'].
112      * @type {Array}
113      * @private
114      */
115     preloadEngines_: [],
117     /**
118      * The list of extension IMEs that are enabled out of the language menu.
119      * @type {Array}
120      * @private
121      */
122     enabledExtensionImes_: [],
124     /**
125      * The list of the languages which is not translated.
126      * @type {Array}
127      * @private
128      */
129     translateBlockedLanguages_: [],
131     /**
132      * The list of the languages supported by Translate server
133      * @type {Array}
134      * @private
135      */
136     translateSupportedLanguages_: [],
138     /**
139      * The preference is a string that describes the spell check dictionary
140      * language, like "en-US".
141      * @type {string}
142      * @private
143      */
144     spellCheckDictionary_: '',
146     /**
147      * The map of language code to input method IDs, like:
148      * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
149      * @type {Object}
150      * @private
151      */
152     languageCodeToInputMethodIdsMap_: {},
154     /**
155      * The value that indicates if Translate feature is enabled or not.
156      * @type {boolean}
157      * @private
158      */
159     enableTranslate_: false,
161     /** @override */
162     initializePage: function() {
163       Page.prototype.initializePage.call(this);
165       var languageOptionsList = $('language-options-list');
166       LanguageList.decorate(languageOptionsList);
168       languageOptionsList.addEventListener('change',
169           this.handleLanguageOptionsListChange_.bind(this));
170       languageOptionsList.addEventListener('save',
171           this.handleLanguageOptionsListSave_.bind(this));
173       this.prospectiveUiLanguageCode_ =
174           loadTimeData.getString('prospectiveUiLanguageCode');
175       this.addEventListener('visibleChange',
176                             this.handleVisibleChange_.bind(this));
178       if (cr.isChromeOS) {
179         this.initializeInputMethodList_();
180         this.initializeLanguageCodeToInputMethodIdsMap_();
181       }
183       var checkbox = $('offer-to-translate-in-this-language');
184       checkbox.addEventListener('click',
185           this.handleOfferToTranslateCheckboxClick_.bind(this));
187       Preferences.getInstance().addEventListener(
188           TRANSLATE_BLOCKED_LANGUAGES_PREF,
189           this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
190       Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
191           this.handleSpellCheckDictionaryPrefChange_.bind(this));
192       Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
193           this.handleEnableTranslatePrefChange_.bind(this));
194       this.translateSupportedLanguages_ =
195           loadTimeData.getValue('translateSupportedLanguages');
197       // Set up add button.
198       var onclick = function(e) {
199         // Add the language without showing the overlay if it's specified in
200         // the URL hash (ex. lang_add=ja).  Used for automated testing.
201         var match = document.location.hash.match(/\blang_add=([\w-]+)/);
202         if (match) {
203           var addLanguageCode = match[1];
204           $('language-options-list').addLanguage(addLanguageCode);
205           this.addBlockedLanguage_(addLanguageCode);
206         } else {
207           PageManager.showPageByName('addLanguage');
208           chrome.send('coreOptionsUserMetricsAction',
209                       ['Options_Languages_Add']);
210         }
211       };
212       $('language-options-add-button').onclick = onclick.bind(this);
214       if (!cr.isMac) {
215         // Set up the button for editing custom spelling dictionary.
216         $('edit-dictionary-button').onclick = function(e) {
217           PageManager.showPageByName('editDictionary');
218         };
219         $('dictionary-download-retry-button').onclick = function(e) {
220           chrome.send('retryDictionaryDownload');
221         };
222       }
224       // Listen to add language dialog ok button.
225       $('add-language-overlay-ok-button').addEventListener(
226           'click', this.handleAddLanguageOkButtonClick_.bind(this));
228       if (!cr.isChromeOS) {
229         // Show experimental features if enabled.
230         if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
231           $('auto-spell-correction-option').hidden = false;
233         // Handle spell check enable/disable.
234         if (!cr.isMac) {
235           Preferences.getInstance().addEventListener(
236               ENABLE_SPELL_CHECK_PREF,
237               this.updateEnableSpellCheck_.bind(this));
238         }
239       }
241       // Handle clicks on "Use this language for spell checking" button.
242       if (!cr.isMac) {
243         var spellCheckLanguageButton = getRequiredElement(
244             'language-options-spell-check-language-button');
245         spellCheckLanguageButton.addEventListener(
246             'click',
247             this.handleSpellCheckLanguageButtonClick_.bind(this));
248       }
250       if (cr.isChromeOS) {
251         $('language-options-ui-restart-button').onclick = function() {
252           chrome.send('uiLanguageRestart');
253         };
254       }
256       $('language-confirm').onclick =
257           PageManager.closeOverlay.bind(PageManager);
259       // Public session users cannot change the locale.
260       if (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount())
261         $('language-options-ui-language-section').hidden = true;
262           PageManager.closeOverlay.bind(PageManager);
263     },
265     /**
266      * Initializes the input method list.
267      */
268     initializeInputMethodList_: function() {
269       var inputMethodList = $('language-options-input-method-list');
270       var inputMethodPrototype = $('language-options-input-method-template');
272       // Add all input methods, but make all of them invisible here. We'll
273       // change the visibility in handleLanguageOptionsListChange_() based
274       // on the selected language. Note that we only have less than 100
275       // input methods, so creating DOM nodes at once here should be ok.
276       this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
277       this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
278       this.appendComponentExtensionIme_(
279           loadTimeData.getValue('componentExtensionImeList'));
281       // Listen to pref change once the input method list is initialized.
282       Preferences.getInstance().addEventListener(
283           PRELOAD_ENGINES_PREF,
284           this.handlePreloadEnginesPrefChange_.bind(this));
285       Preferences.getInstance().addEventListener(
286           ENABLED_EXTENSION_IME_PREF,
287           this.handleEnabledExtensionsPrefChange_.bind(this));
288     },
290     /**
291      * Appends input method lists based on component extension ime list.
292      * @param {!Array} componentExtensionImeList A list of input method
293      *     descriptors.
294      * @private
295      */
296     appendComponentExtensionIme_: function(componentExtensionImeList) {
297       this.appendInputMethodElement_(componentExtensionImeList);
299       for (var i = 0; i < componentExtensionImeList.length; i++) {
300         var inputMethod = componentExtensionImeList[i];
301         for (var languageCode in inputMethod.languageCodeSet) {
302           if (languageCode in this.languageCodeToInputMethodIdsMap_) {
303             this.languageCodeToInputMethodIdsMap_[languageCode].push(
304                 inputMethod.id);
305           } else {
306             this.languageCodeToInputMethodIdsMap_[languageCode] =
307                 [inputMethod.id];
308           }
309         }
310       }
311     },
313     /**
314      * Appends input methods into input method list.
315      * @param {!Array} inputMethods A list of input method descriptors.
316      * @private
317      */
318     appendInputMethodElement_: function(inputMethods) {
319       var inputMethodList = $('language-options-input-method-list');
320       var inputMethodTemplate = $('language-options-input-method-template');
322       for (var i = 0; i < inputMethods.length; i++) {
323         var inputMethod = inputMethods[i];
324         var element = inputMethodTemplate.cloneNode(true);
325         element.id = '';
326         element.languageCodeSet = inputMethod.languageCodeSet;
328         var input = element.querySelector('input');
329         input.inputMethodId = inputMethod.id;
330         input.imeProvider = inputMethod.extensionName;
331         var span = element.querySelector('span');
332         span.textContent = inputMethod.displayName;
334         if (inputMethod.optionsPage) {
335           var button = document.createElement('button');
336           button.textContent = loadTimeData.getString('configure');
337           button.inputMethodId = inputMethod.id;
338           button.onclick = function(inputMethodId, e) {
339             chrome.send('inputMethodOptionsOpen', [inputMethodId]);
340           }.bind(this, inputMethod.id);
341           element.appendChild(button);
342         }
344         // Listen to user clicks.
345         input.addEventListener('click',
346                                this.handleCheckboxClick_.bind(this));
347         inputMethodList.appendChild(element);
348       }
349     },
351     /**
352      * Adds a language to the preference 'translate_blocked_languages'. If
353      * |langCode| is already added, nothing happens. |langCode| is converted
354      * to a Translate language synonym before added.
355      * @param {string} langCode A language code like 'en'
356      * @private
357      */
358     addBlockedLanguage_: function(langCode) {
359       langCode = this.convertLangCodeForTranslation_(langCode);
360       if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
361         this.translateBlockedLanguages_.push(langCode);
362         Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
363                                 this.translateBlockedLanguages_, true);
364       }
365     },
367     /**
368      * Removes a language from the preference 'translate_blocked_languages'.
369      * If |langCode| doesn't exist in the preference, nothing happens.
370      * |langCode| is converted to a Translate language synonym before removed.
371      * @param {string} langCode A language code like 'en'
372      * @private
373      */
374     removeBlockedLanguage_: function(langCode) {
375       langCode = this.convertLangCodeForTranslation_(langCode);
376       if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
377         this.translateBlockedLanguages_ =
378             this.translateBlockedLanguages_.filter(
379                 function(langCodeNotTranslated) {
380                   return langCodeNotTranslated != langCode;
381                 });
382         Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
383                                 this.translateBlockedLanguages_, true);
384       }
385     },
387     /**
388      * Handles Page's visible property change event.
389      * @param {Event} e Property change event.
390      * @private
391      */
392     handleVisibleChange_: function(e) {
393       if (this.visible) {
394         $('language-options-list').redraw();
395         chrome.send('languageOptionsOpen');
396       }
397     },
399     /**
400      * Handles languageOptionsList's change event.
401      * @param {Event} e Change event.
402      * @private
403      */
404     handleLanguageOptionsListChange_: function(e) {
405       var languageOptionsList = $('language-options-list');
406       var languageCode = languageOptionsList.getSelectedLanguageCode();
408       // If there's no selection, just return.
409       if (!languageCode)
410         return;
412       // Select the language if it's specified in the URL hash (ex. lang=ja).
413       // Used for automated testing.
414       var match = document.location.hash.match(/\blang=([\w-]+)/);
415       if (match) {
416         var specifiedLanguageCode = match[1];
417         if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
418           languageCode = specifiedLanguageCode;
419         }
420       }
422       this.updateOfferToTranslateCheckbox_(languageCode);
424       if (cr.isWindows || cr.isChromeOS)
425         this.updateUiLanguageButton_(languageCode);
427       this.updateSelectedLanguageName_(languageCode);
429       if (!cr.isMac)
430         this.updateSpellCheckLanguageButton_(languageCode);
432       if (cr.isChromeOS)
433         this.updateInputMethodList_(languageCode);
435       this.updateLanguageListInAddLanguageOverlay_();
436     },
438     /**
439      * Handles languageOptionsList's save event.
440      * @param {Event} e Save event.
441      * @private
442      */
443     handleLanguageOptionsListSave_: function(e) {
444       if (cr.isChromeOS) {
445         // Sort the preload engines per the saved languages before save.
446         this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
447         this.savePreloadEnginesPref_();
448       }
449     },
451     /**
452      * Sorts preloadEngines_ by languageOptionsList's order.
453      * @param {Array} preloadEngines List of preload engines.
454      * @return {Array} Returns sorted preloadEngines.
455      * @private
456      */
457     sortPreloadEngines_: function(preloadEngines) {
458       // For instance, suppose we have two languages and associated input
459       // methods:
460       //
461       // - Korean: hangul
462       // - Chinese: pinyin
463       //
464       // The preloadEngines preference should look like "hangul,pinyin".
465       // If the user reverse the order, the preference should be reorderd
466       // to "pinyin,hangul".
467       var languageOptionsList = $('language-options-list');
468       var languageCodes = languageOptionsList.getLanguageCodes();
470       // Convert the list into a dictonary for simpler lookup.
471       var preloadEngineSet = {};
472       for (var i = 0; i < preloadEngines.length; i++) {
473         preloadEngineSet[preloadEngines[i]] = true;
474       }
476       // Create the new preload engine list per the language codes.
477       var newPreloadEngines = [];
478       for (var i = 0; i < languageCodes.length; i++) {
479         var languageCode = languageCodes[i];
480         var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
481             languageCode];
482         if (!inputMethodIds)
483           continue;
485         // Check if we have active input methods associated with the language.
486         for (var j = 0; j < inputMethodIds.length; j++) {
487           var inputMethodId = inputMethodIds[j];
488           if (inputMethodId in preloadEngineSet) {
489             // If we have, add it to the new engine list.
490             newPreloadEngines.push(inputMethodId);
491             // And delete it from the set. This is necessary as one input
492             // method can be associated with more than one language thus
493             // we should avoid having duplicates in the new list.
494             delete preloadEngineSet[inputMethodId];
495           }
496         }
497       }
499       return newPreloadEngines;
500     },
502     /**
503      * Initializes the map of language code to input method IDs.
504      * @private
505      */
506     initializeLanguageCodeToInputMethodIdsMap_: function() {
507       var inputMethodList = loadTimeData.getValue('inputMethodList');
508       for (var i = 0; i < inputMethodList.length; i++) {
509         var inputMethod = inputMethodList[i];
510         for (var languageCode in inputMethod.languageCodeSet) {
511           if (languageCode in this.languageCodeToInputMethodIdsMap_) {
512             this.languageCodeToInputMethodIdsMap_[languageCode].push(
513                 inputMethod.id);
514           } else {
515             this.languageCodeToInputMethodIdsMap_[languageCode] =
516                 [inputMethod.id];
517           }
518         }
519       }
520     },
522     /**
523      * Updates the currently selected language name.
524      * @param {string} languageCode Language code (ex. "fr").
525      * @private
526      */
527     updateSelectedLanguageName_: function(languageCode) {
528       var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
529           languageCode);
530       var languageDisplayName = languageInfo.displayName;
531       var languageNativeDisplayName = languageInfo.nativeDisplayName;
532       var textDirection = languageInfo.textDirection;
534       // If the native name is different, add it.
535       if (languageDisplayName != languageNativeDisplayName) {
536         languageDisplayName += ' - ' + languageNativeDisplayName;
537       }
539       // Update the currently selected language name.
540       var languageName = $('language-options-language-name');
541       languageName.textContent = languageDisplayName;
542       languageName.dir = textDirection;
543     },
545     /**
546      * Updates the UI language button.
547      * @param {string} languageCode Language code (ex. "fr").
548      * @private
549      */
550     updateUiLanguageButton_: function(languageCode) {
551       var uiLanguageButton = $('language-options-ui-language-button');
552       var uiLanguageMessage = $('language-options-ui-language-message');
553       var uiLanguageNotification = $('language-options-ui-notification-bar');
555       // Remove the event listener and add it back if useful.
556       uiLanguageButton.onclick = null;
558       // Unhide the language button every time, as it could've been previously
559       // hidden by a language change.
560       uiLanguageButton.hidden = false;
562       // Hide the controlled setting indicator.
563       var uiLanguageIndicator = document.querySelector(
564           '.language-options-contents .controlled-setting-indicator');
565       uiLanguageIndicator.removeAttribute('controlled-by');
567       if (languageCode == this.prospectiveUiLanguageCode_) {
568         uiLanguageMessage.textContent =
569             loadTimeData.getString('isDisplayedInThisLanguage');
570         showMutuallyExclusiveNodes(
571             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
572       } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
573         if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
574           // In the guest mode for ChromeOS, changing UI language does not make
575           // sense because it does not take effect after browser restart.
576           uiLanguageButton.hidden = true;
577           uiLanguageMessage.hidden = true;
578         } else {
579           uiLanguageButton.textContent =
580               loadTimeData.getString('displayInThisLanguage');
582           if (loadTimeData.valueExists('secondaryUser') &&
583               loadTimeData.getBoolean('secondaryUser')) {
584             uiLanguageButton.disabled = true;
585             uiLanguageIndicator.setAttribute('controlled-by', 'shared');
586           } else {
587             uiLanguageButton.onclick = function(e) {
588               chrome.send('uiLanguageChange', [languageCode]);
589             };
590           }
591           showMutuallyExclusiveNodes(
592               [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
593         }
594       } else {
595         uiLanguageMessage.textContent =
596             loadTimeData.getString('cannotBeDisplayedInThisLanguage');
597         showMutuallyExclusiveNodes(
598             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
599       }
600     },
602     /**
603      * Updates the spell check language button.
604      * @param {string} languageCode Language code (ex. "fr").
605      * @private
606      */
607     updateSpellCheckLanguageButton_: function(languageCode) {
608       var spellCheckLanguageSection = $('language-options-spellcheck');
609       var spellCheckLanguageButton =
610           $('language-options-spell-check-language-button');
611       var spellCheckLanguageMessage =
612           $('language-options-spell-check-language-message');
613       var dictionaryDownloadInProgress =
614           $('language-options-dictionary-downloading-message');
615       var dictionaryDownloadFailed =
616           $('language-options-dictionary-download-failed-message');
617       var dictionaryDownloadFailHelp =
618           $('language-options-dictionary-download-fail-help-message');
619       spellCheckLanguageSection.hidden = false;
620       spellCheckLanguageMessage.hidden = true;
621       spellCheckLanguageButton.hidden = true;
622       dictionaryDownloadInProgress.hidden = true;
623       dictionaryDownloadFailed.hidden = true;
624       dictionaryDownloadFailHelp.hidden = true;
626       if (languageCode == this.spellCheckDictionary_) {
627         if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
628           spellCheckLanguageMessage.textContent =
629               loadTimeData.getString('isUsedForSpellChecking');
630           showMutuallyExclusiveNodes(
631               [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
632         } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
633                        DOWNLOAD_STATUS.IN_PROGRESS) {
634           dictionaryDownloadInProgress.hidden = false;
635         } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
636                        DOWNLOAD_STATUS.FAILED) {
637           spellCheckLanguageSection.hidden = true;
638           dictionaryDownloadFailed.hidden = false;
639           if (this.spellcheckDictionaryDownloadFailures_ > 1)
640             dictionaryDownloadFailHelp.hidden = false;
641         }
642       } else if (languageCode in
643           loadTimeData.getValue('spellCheckLanguageCodeSet')) {
644         spellCheckLanguageButton.textContent =
645             loadTimeData.getString('useThisForSpellChecking');
646         showMutuallyExclusiveNodes(
647             [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
648         spellCheckLanguageButton.languageCode = languageCode;
649       } else if (!languageCode) {
650         spellCheckLanguageButton.hidden = true;
651         spellCheckLanguageMessage.hidden = true;
652       } else {
653         spellCheckLanguageMessage.textContent =
654             loadTimeData.getString('cannotBeUsedForSpellChecking');
655         showMutuallyExclusiveNodes(
656             [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
657       }
658     },
660     /**
661      * Updates the checkbox for stopping translation.
662      * @param {string} languageCode Language code (ex. "fr").
663      * @private
664      */
665     updateOfferToTranslateCheckbox_: function(languageCode) {
666       var div = $('language-options-offer-to-translate');
668       // Translation server supports Chinese (Transitional) and Chinese
669       // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
670       // show this preference when general Chinese is selected.
671       if (languageCode != 'zh') {
672         div.hidden = false;
673       } else {
674         div.hidden = true;
675         return;
676       }
678       var offerToTranslate = div.querySelector('div');
679       var cannotTranslate = $('cannot-translate-in-this-language');
680       var nodes = [offerToTranslate, cannotTranslate];
682       var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
683       if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
684         showMutuallyExclusiveNodes(nodes, 0);
685       } else {
686         showMutuallyExclusiveNodes(nodes, 1);
687         return;
688       }
690       var checkbox = $('offer-to-translate-in-this-language');
692       if (!this.enableTranslate_) {
693         checkbox.disabled = true;
694         checkbox.checked = false;
695         return;
696       }
698       // If the language corresponds to the default target language (in most
699       // cases, the user's locale language), "Offer to translate" checkbox
700       // should be always unchecked.
701       var defaultTargetLanguage =
702           loadTimeData.getString('defaultTargetLanguage');
703       if (convertedLangCode == defaultTargetLanguage) {
704         checkbox.disabled = true;
705         checkbox.checked = false;
706         return;
707       }
709       checkbox.disabled = false;
711       var blockedLanguages = this.translateBlockedLanguages_;
712       var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
713       checkbox.checked = checked;
714     },
716     /**
717      * Updates the input method list.
718      * @param {string} languageCode Language code (ex. "fr").
719      * @private
720      */
721     updateInputMethodList_: function(languageCode) {
722       // Give one of the checkboxes or buttons focus, if it's specified in the
723       // URL hash (ex. focus=mozc). Used for automated testing.
724       var focusInputMethodId = -1;
725       var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
726       if (match) {
727         focusInputMethodId = match[1];
728       }
729       // Change the visibility of the input method list. Input methods that
730       // matches |languageCode| will become visible.
731       var inputMethodList = $('language-options-input-method-list');
732       var methods = inputMethodList.querySelectorAll('.input-method');
733       for (var i = 0; i < methods.length; i++) {
734         var method = methods[i];
735         if (languageCode in method.languageCodeSet) {
736           method.hidden = false;
737           var input = method.querySelector('input');
738           // Give it focus if the ID matches.
739           if (input.inputMethodId == focusInputMethodId) {
740             input.focus();
741           }
742         } else {
743           method.hidden = true;
744         }
745       }
747       $('language-options-input-method-none').hidden =
748           (languageCode in this.languageCodeToInputMethodIdsMap_);
750       if (focusInputMethodId == 'add') {
751         $('language-options-add-button').focus();
752       }
753     },
755     /**
756      * Updates the language list in the add language overlay.
757      * @private
758      */
759     updateLanguageListInAddLanguageOverlay_: function() {
760       // Change the visibility of the language list in the add language
761       // overlay. Languages that are already active will become invisible,
762       // so that users don't add the same language twice.
763       var languageOptionsList = $('language-options-list');
764       var languageCodes = languageOptionsList.getLanguageCodes();
765       var languageCodeSet = {};
766       for (var i = 0; i < languageCodes.length; i++) {
767         languageCodeSet[languageCodes[i]] = true;
768       }
770       var addLanguageList = $('add-language-overlay-language-list');
771       var options = addLanguageList.querySelectorAll('option');
772       assert(options.length > 0);
773       var selectedFirstItem = false;
774       for (var i = 0; i < options.length; i++) {
775         var option = options[i];
776         option.hidden = option.value in languageCodeSet;
777         if (!option.hidden && !selectedFirstItem) {
778           // Select first visible item, otherwise previously selected hidden
779           // item will be selected by default at the next time.
780           option.selected = true;
781           selectedFirstItem = true;
782         }
783       }
784     },
786     /**
787      * Handles preloadEnginesPref change.
788      * @param {Event} e Change event.
789      * @private
790      */
791     handlePreloadEnginesPrefChange_: function(e) {
792       var value = e.value.value;
793       this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
794       this.updateCheckboxesFromPreloadEngines_();
795       $('language-options-list').updateDeletable();
796     },
798     /**
799      * Handles enabledExtensionImePref change.
800      * @param {Event} e Change event.
801      * @private
802      */
803     handleEnabledExtensionsPrefChange_: function(e) {
804       var value = e.value.value;
805       this.enabledExtensionImes_ = value.split(',');
806       this.updateCheckboxesFromEnabledExtensions_();
807     },
809     /**
810      * Handles offer-to-translate checkbox's click event.
811      * @param {Event} e Click event.
812      * @private
813      */
814     handleOfferToTranslateCheckboxClick_: function(e) {
815       var checkbox = e.target;
816       var checked = checkbox.checked;
818       var languageOptionsList = $('language-options-list');
819       var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
821       if (checked)
822         this.removeBlockedLanguage_(selectedLanguageCode);
823       else
824         this.addBlockedLanguage_(selectedLanguageCode);
825     },
827     /**
828      * Handles input method checkbox's click event.
829      * @param {Event} e Click event.
830      * @private
831      */
832     handleCheckboxClick_: function(e) {
833       var checkbox = assertInstanceof(e.target, Element);
835       // Third party IMEs require additional confirmation prior to enabling due
836       // to privacy risk.
837       if (/^_ext_ime_/.test(checkbox.inputMethodId) && checkbox.checked) {
838         var confirmationCallback = this.handleCheckboxUpdate_.bind(this,
839                                                                    checkbox);
840         var cancellationCallback = function() {
841           checkbox.checked = false;
842         };
843         ThirdPartyImeConfirmOverlay.showConfirmationDialog({
844           extension: checkbox.imeProvider,
845           confirm: confirmationCallback,
846           cancel: cancellationCallback
847         });
848       } else {
849         this.handleCheckboxUpdate_(checkbox);
850       }
852       chrome.send('coreOptionsUserMetricsAction',
853                   ['Options_Languages_InputMethodCheckbox' +
854                    (checkbox.checked ? '_Enable' : '_Disable')]);
855     },
857     /**
858      * Updates active IMEs based on change in state of a checkbox for an input
859      * method.
860      * @param {!Element} checkbox Updated checkbox element.
861      * @private
862      */
863     handleCheckboxUpdate_: function(checkbox) {
864       if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
865         this.updateEnabledExtensionsFromCheckboxes_();
866         this.saveEnabledExtensionPref_();
867         return;
868       }
869       if (this.preloadEngines_.length == 1 && !checkbox.checked) {
870         // Don't allow disabling the last input method.
871         this.showNotification_(
872             loadTimeData.getString('pleaseAddAnotherInputMethod'),
873             loadTimeData.getString('okButton'));
874         checkbox.checked = true;
875         return;
876       }
877       if (checkbox.checked) {
878         chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
879       } else {
880         chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
881       }
882       this.updatePreloadEnginesFromCheckboxes_();
883       this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
884       this.savePreloadEnginesPref_();
885     },
887     /**
888      * Handles clicks on the "OK" button of the "Add language" dialog.
889      * @param {Event} e Click event.
890      * @private
891      */
892     handleAddLanguageOkButtonClick_: function(e) {
893       var languagesSelect = $('add-language-overlay-language-list');
894       var selectedIndex = languagesSelect.selectedIndex;
895       if (selectedIndex >= 0) {
896         var selection = languagesSelect.options[selectedIndex];
897         var langCode = String(selection.value);
898         $('language-options-list').addLanguage(langCode);
899         this.addBlockedLanguage_(langCode);
900         PageManager.closeOverlay();
901       }
902     },
904     /**
905      * Checks if languageCode is deletable or not.
906      * @param {string} languageCode the languageCode to check for deletability.
907      */
908     languageIsDeletable: function(languageCode) {
909       // Don't allow removing the language if it's a UI language.
910       if (languageCode == this.prospectiveUiLanguageCode_)
911         return false;
912       return (!cr.isChromeOS ||
913               this.canDeleteLanguage_(languageCode));
914     },
916     /**
917      * Handles browse.enable_spellchecking change.
918      * @param {Event} e Change event.
919      * @private
920      */
921     updateEnableSpellCheck_: function(e) {
922        var value = !$('enable-spell-check').checked;
923        $('language-options-spell-check-language-button').disabled = value;
924        if (!cr.isMac)
925          $('edit-dictionary-button').hidden = value;
926      },
928     /**
929      * Handles translateBlockedLanguagesPref change.
930      * @param {Event} e Change event.
931      * @private
932      */
933     handleTranslateBlockedLanguagesPrefChange_: function(e) {
934       this.translateBlockedLanguages_ = e.value.value;
935       this.updateOfferToTranslateCheckbox_(
936           $('language-options-list').getSelectedLanguageCode());
937     },
939     /**
940      * Handles spellCheckDictionaryPref change.
941      * @param {Event} e Change event.
942      * @private
943      */
944     handleSpellCheckDictionaryPrefChange_: function(e) {
945       var languageCode = e.value.value;
946       this.spellCheckDictionary_ = languageCode;
947       if (!cr.isMac) {
948         this.updateSpellCheckLanguageButton_(
949             $('language-options-list').getSelectedLanguageCode());
950       }
951     },
953     /**
954      * Handles translate.enabled change.
955      * @param {Event} e Change event.
956      * @private
957      */
958     handleEnableTranslatePrefChange_: function(e) {
959       var enabled = e.value.value;
960       this.enableTranslate_ = enabled;
961       this.updateOfferToTranslateCheckbox_(
962           $('language-options-list').getSelectedLanguageCode());
963     },
965     /**
966      * Handles spellCheckLanguageButton click.
967      * @param {Event} e Click event.
968      * @private
969      */
970     handleSpellCheckLanguageButtonClick_: function(e) {
971       var languageCode = e.target.languageCode;
972       // Save the preference.
973       Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
974                                 languageCode, true);
975       chrome.send('spellCheckLanguageChange', [languageCode]);
976       chrome.send('coreOptionsUserMetricsAction',
977                   ['Options_Languages_SpellCheck']);
978     },
980     /**
981      * Checks whether it's possible to remove the language specified by
982      * languageCode and returns true if possible. This function returns false
983      * if the removal causes the number of preload engines to be zero.
984      *
985      * @param {string} languageCode Language code (ex. "fr").
986      * @return {boolean} Returns true on success.
987      * @private
988      */
989     canDeleteLanguage_: function(languageCode) {
990       // First create the set of engines to be removed from input methods
991       // associated with the language code.
992       var enginesToBeRemovedSet = {};
993       var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
995       // If this language doesn't have any input methods, it can be deleted.
996       if (!inputMethodIds)
997         return true;
999       for (var i = 0; i < inputMethodIds.length; i++) {
1000         enginesToBeRemovedSet[inputMethodIds[i]] = true;
1001       }
1003       // Then eliminate engines that are also used for other active languages.
1004       // For instance, if "xkb:us::eng" is used for both English and Filipino.
1005       var languageCodes = $('language-options-list').getLanguageCodes();
1006       for (var i = 0; i < languageCodes.length; i++) {
1007         // Skip the target language code.
1008         if (languageCodes[i] == languageCode) {
1009           continue;
1010         }
1011         // Check if input methods used in this language are included in
1012         // enginesToBeRemovedSet. If so, eliminate these from the set, so
1013         // we don't remove this time.
1014         var inputMethodIdsForAnotherLanguage =
1015             this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
1016         if (!inputMethodIdsForAnotherLanguage)
1017           continue;
1019         for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
1020           var inputMethodId = inputMethodIdsForAnotherLanguage[j];
1021           if (inputMethodId in enginesToBeRemovedSet) {
1022             delete enginesToBeRemovedSet[inputMethodId];
1023           }
1024         }
1025       }
1027       // Update the preload engine list with the to-be-removed set.
1028       var newPreloadEngines = [];
1029       for (var i = 0; i < this.preloadEngines_.length; i++) {
1030         if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
1031           newPreloadEngines.push(this.preloadEngines_[i]);
1032         }
1033       }
1034       // Don't allow this operation if it causes the number of preload
1035       // engines to be zero.
1036       return (newPreloadEngines.length > 0);
1037     },
1039     /**
1040      * Saves the enabled extension preference.
1041      * @private
1042      */
1043     saveEnabledExtensionPref_: function() {
1044       Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
1045                                 this.enabledExtensionImes_.join(','), true);
1046     },
1048     /**
1049      * Updates the checkboxes in the input method list from the enabled
1050      * extensions preference.
1051      * @private
1052      */
1053     updateCheckboxesFromEnabledExtensions_: function() {
1054       // Convert the list into a dictonary for simpler lookup.
1055       var dictionary = {};
1056       for (var i = 0; i < this.enabledExtensionImes_.length; i++)
1057         dictionary[this.enabledExtensionImes_[i]] = true;
1059       var inputMethodList = $('language-options-input-method-list');
1060       var checkboxes = inputMethodList.querySelectorAll('input');
1061       for (var i = 0; i < checkboxes.length; i++) {
1062         if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1063           checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1064       }
1065       var configureButtons = inputMethodList.querySelectorAll('button');
1066       for (var i = 0; i < configureButtons.length; i++) {
1067         if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1068           configureButtons[i].hidden =
1069               !(configureButtons[i].inputMethodId in dictionary);
1070         }
1071       }
1072     },
1074     /**
1075      * Updates the enabled extensions preference from the checkboxes in the
1076      * input method list.
1077      * @private
1078      */
1079     updateEnabledExtensionsFromCheckboxes_: function() {
1080       this.enabledExtensionImes_ = [];
1081       var inputMethodList = $('language-options-input-method-list');
1082       var checkboxes = inputMethodList.querySelectorAll('input');
1083       for (var i = 0; i < checkboxes.length; i++) {
1084         if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1085           if (checkboxes[i].checked)
1086             this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
1087         }
1088       }
1089     },
1091     /**
1092      * Saves the preload engines preference.
1093      * @private
1094      */
1095     savePreloadEnginesPref_: function() {
1096       Preferences.setStringPref(PRELOAD_ENGINES_PREF,
1097                                 this.preloadEngines_.join(','), true);
1098     },
1100     /**
1101      * Updates the checkboxes in the input method list from the preload
1102      * engines preference.
1103      * @private
1104      */
1105     updateCheckboxesFromPreloadEngines_: function() {
1106       // Convert the list into a dictonary for simpler lookup.
1107       var dictionary = {};
1108       for (var i = 0; i < this.preloadEngines_.length; i++) {
1109         dictionary[this.preloadEngines_[i]] = true;
1110       }
1112       var inputMethodList = $('language-options-input-method-list');
1113       var checkboxes = inputMethodList.querySelectorAll('input');
1114       for (var i = 0; i < checkboxes.length; i++) {
1115         if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1116           checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1117       }
1118       var configureButtons = inputMethodList.querySelectorAll('button');
1119       for (var i = 0; i < configureButtons.length; i++) {
1120         if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1121           configureButtons[i].hidden =
1122               !(configureButtons[i].inputMethodId in dictionary);
1123         }
1124       }
1125     },
1127     /**
1128      * Updates the preload engines preference from the checkboxes in the
1129      * input method list.
1130      * @private
1131      */
1132     updatePreloadEnginesFromCheckboxes_: function() {
1133       this.preloadEngines_ = [];
1134       var inputMethodList = $('language-options-input-method-list');
1135       var checkboxes = inputMethodList.querySelectorAll('input');
1136       for (var i = 0; i < checkboxes.length; i++) {
1137         if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1138           if (checkboxes[i].checked)
1139             this.preloadEngines_.push(checkboxes[i].inputMethodId);
1140         }
1141       }
1142       var languageOptionsList = $('language-options-list');
1143       languageOptionsList.updateDeletable();
1144     },
1146     /**
1147      * Filters bad preload engines in case bad preload engines are
1148      * stored in the preference. Removes duplicates as well.
1149      * @param {Array} preloadEngines List of preload engines.
1150      * @private
1151      */
1152     filterBadPreloadEngines_: function(preloadEngines) {
1153       // Convert the list into a dictonary for simpler lookup.
1154       var dictionary = {};
1155       var list = loadTimeData.getValue('inputMethodList');
1156       for (var i = 0; i < list.length; i++) {
1157         dictionary[list[i].id] = true;
1158       }
1160       var enabledPreloadEngines = [];
1161       var seen = {};
1162       for (var i = 0; i < preloadEngines.length; i++) {
1163         // Check if the preload engine is present in the
1164         // dictionary, and not duplicate. Otherwise, skip it.
1165         // Component Extension IME should be handled same as preloadEngines and
1166         // "_comp_" is the special prefix of its ID.
1167         if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
1168             /^_comp_/.test(preloadEngines[i])) {
1169           enabledPreloadEngines.push(preloadEngines[i]);
1170           seen[preloadEngines[i]] = true;
1171         }
1172       }
1173       return enabledPreloadEngines;
1174     },
1176     // TODO(kochi): This is an adapted copy from new_tab.js.
1177     // If this will go as final UI, refactor this to share the component with
1178     // new new tab page.
1179     /**
1180      * @private
1181      */
1182     notificationTimeout_: null,
1184     /**
1185      * Shows notification.
1186      * @param {string} text
1187      * @param {string} actionText
1188      * @param {number=} opt_delay
1189      * @private
1190      */
1191     showNotification_: function(text, actionText, opt_delay) {
1192       var notificationElement = $('notification');
1193       var actionLink = notificationElement.querySelector('.link-color');
1194       var delay = opt_delay || 10000;
1196       function show() {
1197         window.clearTimeout(this.notificationTimeout_);
1198         notificationElement.classList.add('show');
1199         document.body.classList.add('notification-shown');
1200       }
1202       function hide() {
1203         window.clearTimeout(this.notificationTimeout_);
1204         notificationElement.classList.remove('show');
1205         document.body.classList.remove('notification-shown');
1206         // Prevent tabbing to the hidden link.
1207         actionLink.tabIndex = -1;
1208         // Setting tabIndex to -1 only prevents future tabbing to it. If,
1209         // however, the user switches window or a tab and then moves back to
1210         // this tab the element may gain focus. We therefore make sure that we
1211         // blur the element so that the element focus is not restored when
1212         // coming back to this window.
1213         actionLink.blur();
1214       }
1216       function delayedHide() {
1217         this.notificationTimeout_ = window.setTimeout(hide, delay);
1218       }
1220       notificationElement.firstElementChild.textContent = text;
1221       actionLink.textContent = actionText;
1223       actionLink.onclick = hide;
1224       actionLink.onkeydown = function(e) {
1225         if (e.keyIdentifier == 'Enter') {
1226           hide();
1227         }
1228       };
1229       notificationElement.onmouseover = show;
1230       notificationElement.onmouseout = delayedHide;
1231       actionLink.onfocus = show;
1232       actionLink.onblur = delayedHide;
1233       // Enable tabbing to the link now that it is shown.
1234       actionLink.tabIndex = 0;
1236       show();
1237       delayedHide();
1238     },
1240     /**
1241      * Chrome callback for when the UI language preference is saved.
1242      * @param {string} languageCode The newly selected language to use.
1243      * @private
1244      */
1245     uiLanguageSaved_: function(languageCode) {
1246       this.prospectiveUiLanguageCode_ = languageCode;
1248       // If the user is no longer on the same language code, ignore.
1249       if ($('language-options-list').getSelectedLanguageCode() != languageCode)
1250         return;
1252       // Special case for when a user changes to a different language, and
1253       // changes back to the same language without having restarted Chrome or
1254       // logged in/out of ChromeOS.
1255       if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
1256         this.updateUiLanguageButton_(languageCode);
1257         return;
1258       }
1260       // Otherwise, show a notification telling the user that their changes will
1261       // only take effect after restart.
1262       showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1263                                   $('language-options-ui-notification-bar')],
1264                                  1);
1265     },
1267     /**
1268      * A handler for when dictionary for |languageCode| begins downloading.
1269      * @param {string} languageCode The language of the dictionary that just
1270      *     began downloading.
1271      * @private
1272      */
1273     onDictionaryDownloadBegin_: function(languageCode) {
1274       this.spellcheckDictionaryDownloadStatus_[languageCode] =
1275           DOWNLOAD_STATUS.IN_PROGRESS;
1276       if (!cr.isMac &&
1277           languageCode ==
1278               $('language-options-list').getSelectedLanguageCode()) {
1279         this.updateSpellCheckLanguageButton_(languageCode);
1280       }
1281     },
1283     /**
1284      * A handler for when dictionary for |languageCode| successfully downloaded.
1285      * @param {string} languageCode The language of the dictionary that
1286      *     succeeded downloading.
1287      * @private
1288      */
1289     onDictionaryDownloadSuccess_: function(languageCode) {
1290       delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1291       this.spellcheckDictionaryDownloadFailures_ = 0;
1292       if (!cr.isMac &&
1293           languageCode ==
1294               $('language-options-list').getSelectedLanguageCode()) {
1295         this.updateSpellCheckLanguageButton_(languageCode);
1296       }
1297     },
1299     /**
1300      * A handler for when dictionary for |languageCode| fails to download.
1301      * @param {string} languageCode The language of the dictionary that failed
1302      *     to download.
1303      * @private
1304      */
1305     onDictionaryDownloadFailure_: function(languageCode) {
1306       this.spellcheckDictionaryDownloadStatus_[languageCode] =
1307           DOWNLOAD_STATUS.FAILED;
1308       this.spellcheckDictionaryDownloadFailures_++;
1309       if (!cr.isMac &&
1310           languageCode ==
1311               $('language-options-list').getSelectedLanguageCode()) {
1312         this.updateSpellCheckLanguageButton_(languageCode);
1313       }
1314     },
1316     /**
1317      * Converts the language code for Translation. There are some differences
1318      * between the language set for Translation and that for Accept-Language.
1319      * @param {string} languageCode The language code like 'fr'.
1320      * @return {string} The converted language code.
1321      * @private
1322      */
1323     convertLangCodeForTranslation_: function(languageCode) {
1324       var tokens = languageCode.split('-');
1325       var main = tokens[0];
1327       // See also: components/translate/core/browser/common/translate_util.cc
1328       var synonyms = {
1329         'nb': 'no',
1330         'he': 'iw',
1331         'jv': 'jw',
1332         'fil': 'tl',
1333         'zh-HK': 'zh-TW',
1334         'zh-MO': 'zh-TW',
1335         'zh-SG': 'zh-CN',
1336       };
1338       if (main in synonyms) {
1339         return synonyms[main];
1340       } else if (main == 'zh') {
1341         // In Translation, general Chinese is not used, and the sub code is
1342         // necessary as a language code for Translate server.
1343         return languageCode;
1344       }
1346       return main;
1347     },
1348   };
1350   /**
1351    * Shows the node at |index| in |nodes|, hides all others.
1352    * @param {Array.<HTMLElement>} nodes The nodes to be shown or hidden.
1353    * @param {number} index The index of |nodes| to show.
1354    */
1355   function showMutuallyExclusiveNodes(nodes, index) {
1356     assert(index >= 0 && index < nodes.length);
1357     for (var i = 0; i < nodes.length; ++i) {
1358       assert(nodes[i] instanceof HTMLElement);  // TODO(dbeam): Ignore null?
1359       nodes[i].hidden = i != index;
1360     }
1361   }
1363   LanguageOptions.uiLanguageSaved = function(languageCode) {
1364     LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
1365   };
1367   LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1368     LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1369   };
1371   LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1372     LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1373   };
1375   LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1376     LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1377   };
1379   // Export
1380   return {
1381     LanguageOptions: LanguageOptions
1382   };