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