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