language_options.js revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 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   * @extends {cr.ui.pageManager.Page}
76   */
77  function LanguageOptions(model) {
78    Page.call(this, 'languages',
79              loadTimeData.getString('languagePageTabTitle'), 'languagePage');
80  }
81
82  cr.addSingletonGetter(LanguageOptions);
83
84  // Inherit LanguageOptions from Page.
85  LanguageOptions.prototype = {
86    __proto__: Page.prototype,
87
88    /**
89     * For recording the prospective language (the next locale after relaunch).
90     * @type {?string}
91     * @private
92     */
93    prospectiveUiLanguageCode_: null,
94
95    /**
96     * Map from language code to spell check dictionary download status for that
97     * language.
98     * @type {Array}
99     * @private
100     */
101    spellcheckDictionaryDownloadStatus_: [],
102
103    /**
104     * Number of times a spell check dictionary download failed.
105     * @type {number}
106     * @private
107     */
108    spellcheckDictionaryDownloadFailures_: 0,
109
110    /**
111     * The list of preload engines, like ['mozc', 'pinyin'].
112     * @type {Array}
113     * @private
114     */
115    preloadEngines_: [],
116
117    /**
118     * The list of extension IMEs that are enabled out of the language menu.
119     * @type {Array}
120     * @private
121     */
122    enabledExtensionImes_: [],
123
124    /**
125     * The list of the languages which is not translated.
126     * @type {Array}
127     * @private
128     */
129    translateBlockedLanguages_: [],
130
131    /**
132     * The list of the languages supported by Translate server
133     * @type {Array}
134     * @private
135     */
136    translateSupportedLanguages_: [],
137
138    /**
139     * The preference is a string that describes the spell check dictionary
140     * language, like "en-US".
141     * @type {string}
142     * @private
143     */
144    spellCheckDictionary_: '',
145
146    /**
147     * The map of language code to input method IDs, like:
148     * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
149     * @type {Object}
150     * @private
151     */
152    languageCodeToInputMethodIdsMap_: {},
153
154    /**
155     * The value that indicates if Translate feature is enabled or not.
156     * @type {boolean}
157     * @private
158     */
159    enableTranslate_: false,
160
161    /** @override */
162    initializePage: function() {
163      Page.prototype.initializePage.call(this);
164
165      var languageOptionsList = $('language-options-list');
166      LanguageList.decorate(languageOptionsList);
167
168      languageOptionsList.addEventListener('change',
169          this.handleLanguageOptionsListChange_.bind(this));
170      languageOptionsList.addEventListener('save',
171          this.handleLanguageOptionsListSave_.bind(this));
172
173      this.prospectiveUiLanguageCode_ =
174          loadTimeData.getString('prospectiveUiLanguageCode');
175      this.addEventListener('visibleChange',
176                            this.handleVisibleChange_.bind(this));
177
178      if (cr.isChromeOS) {
179        this.initializeInputMethodList_();
180        this.initializeLanguageCodeToInputMethodIdsMap_();
181      }
182
183      var checkbox = $('offer-to-translate-in-this-language');
184      checkbox.addEventListener('click',
185          this.handleOfferToTranslateCheckboxClick_.bind(this));
186
187      Preferences.getInstance().addEventListener(
188          TRANSLATE_BLOCKED_LANGUAGES_PREF,
189          this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
190      Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
191          this.handleSpellCheckDictionaryPrefChange_.bind(this));
192      Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
193          this.handleEnableTranslatePrefChange_.bind(this));
194      this.translateSupportedLanguages_ =
195          loadTimeData.getValue('translateSupportedLanguages');
196
197      // Set up add button.
198      var onclick = function(e) {
199        // Add the language without showing the overlay if it's specified in
200        // the URL hash (ex. lang_add=ja).  Used for automated testing.
201        var match = document.location.hash.match(/\blang_add=([\w-]+)/);
202        if (match) {
203          var addLanguageCode = match[1];
204          $('language-options-list').addLanguage(addLanguageCode);
205          this.addBlockedLanguage_(addLanguageCode);
206        } else {
207          PageManager.showPageByName('addLanguage');
208        }
209      };
210      $('language-options-add-button').onclick = onclick.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          PageManager.showPageByName('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          PageManager.closeOverlay.bind(PageManager);
256
257      // Public session users cannot change the locale.
258      if (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount())
259        $('language-options-ui-language-section').hidden = true;
260          PageManager.closeOverlay.bind(PageManager);
261    },
262
263    /**
264     * Initializes the input method list.
265     */
266    initializeInputMethodList_: function() {
267      var inputMethodList = $('language-options-input-method-list');
268      var inputMethodPrototype = $('language-options-input-method-template');
269
270      // Add all input methods, but make all of them invisible here. We'll
271      // change the visibility in handleLanguageOptionsListChange_() based
272      // on the selected language. Note that we only have less than 100
273      // input methods, so creating DOM nodes at once here should be ok.
274      this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
275      this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
276      this.appendComponentExtensionIme_(
277          loadTimeData.getValue('componentExtensionImeList'));
278
279      // Listen to pref change once the input method list is initialized.
280      Preferences.getInstance().addEventListener(
281          PRELOAD_ENGINES_PREF,
282          this.handlePreloadEnginesPrefChange_.bind(this));
283      Preferences.getInstance().addEventListener(
284          ENABLED_EXTENSION_IME_PREF,
285          this.handleEnabledExtensionsPrefChange_.bind(this));
286    },
287
288    /**
289     * Appends input method lists based on component extension ime list.
290     * @param {!Array} componentExtensionImeList A list of input method
291     *     descriptors.
292     * @private
293     */
294    appendComponentExtensionIme_: function(componentExtensionImeList) {
295      this.appendInputMethodElement_(componentExtensionImeList);
296
297      for (var i = 0; i < componentExtensionImeList.length; i++) {
298        var inputMethod = componentExtensionImeList[i];
299        for (var languageCode in inputMethod.languageCodeSet) {
300          if (languageCode in this.languageCodeToInputMethodIdsMap_) {
301            this.languageCodeToInputMethodIdsMap_[languageCode].push(
302                inputMethod.id);
303          } else {
304            this.languageCodeToInputMethodIdsMap_[languageCode] =
305                [inputMethod.id];
306          }
307        }
308      }
309    },
310
311    /**
312     * Appends input methods into input method list.
313     * @param {!Array} inputMethods A list of input method descriptors.
314     * @private
315     */
316    appendInputMethodElement_: function(inputMethods) {
317      var inputMethodList = $('language-options-input-method-list');
318      var inputMethodTemplate = $('language-options-input-method-template');
319
320      for (var i = 0; i < inputMethods.length; i++) {
321        var inputMethod = inputMethods[i];
322        var element = inputMethodTemplate.cloneNode(true);
323        element.id = '';
324        element.languageCodeSet = inputMethod.languageCodeSet;
325
326        var input = element.querySelector('input');
327        input.inputMethodId = inputMethod.id;
328        input.imeProvider = inputMethod.extensionName;
329        var span = element.querySelector('span');
330        span.textContent = inputMethod.displayName;
331
332        if (inputMethod.optionsPage) {
333          var button = document.createElement('button');
334          button.textContent = loadTimeData.getString('configure');
335          button.inputMethodId = inputMethod.id;
336          button.onclick = function(inputMethodId, e) {
337            chrome.send('inputMethodOptionsOpen', [inputMethodId]);
338          }.bind(this, inputMethod.id);
339          element.appendChild(button);
340        }
341
342        // Listen to user clicks.
343        input.addEventListener('click',
344                               this.handleCheckboxClick_.bind(this));
345        inputMethodList.appendChild(element);
346      }
347    },
348
349    /**
350     * Adds a language to the preference 'translate_blocked_languages'. If
351     * |langCode| is already added, nothing happens. |langCode| is converted
352     * to a Translate language synonym before added.
353     * @param {string} langCode A language code like 'en'
354     * @private
355     */
356    addBlockedLanguage_: function(langCode) {
357      langCode = this.convertLangCodeForTranslation_(langCode);
358      if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
359        this.translateBlockedLanguages_.push(langCode);
360        Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
361                                this.translateBlockedLanguages_, true);
362      }
363    },
364
365    /**
366     * Removes a language from the preference 'translate_blocked_languages'.
367     * If |langCode| doesn't exist in the preference, nothing happens.
368     * |langCode| is converted to a Translate language synonym before removed.
369     * @param {string} langCode A language code like 'en'
370     * @private
371     */
372    removeBlockedLanguage_: function(langCode) {
373      langCode = this.convertLangCodeForTranslation_(langCode);
374      if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
375        this.translateBlockedLanguages_ =
376            this.translateBlockedLanguages_.filter(
377                function(langCodeNotTranslated) {
378                  return langCodeNotTranslated != langCode;
379                });
380        Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
381                                this.translateBlockedLanguages_, true);
382      }
383    },
384
385    /**
386     * Handles Page's visible property change event.
387     * @param {Event} e Property change event.
388     * @private
389     */
390    handleVisibleChange_: function(e) {
391      if (this.visible) {
392        $('language-options-list').redraw();
393        chrome.send('languageOptionsOpen');
394      }
395    },
396
397    /**
398     * Handles languageOptionsList's change event.
399     * @param {Event} e Change event.
400     * @private
401     */
402    handleLanguageOptionsListChange_: function(e) {
403      var languageOptionsList = $('language-options-list');
404      var languageCode = languageOptionsList.getSelectedLanguageCode();
405
406      // If there's no selection, just return.
407      if (!languageCode)
408        return;
409
410      // Select the language if it's specified in the URL hash (ex. lang=ja).
411      // Used for automated testing.
412      var match = document.location.hash.match(/\blang=([\w-]+)/);
413      if (match) {
414        var specifiedLanguageCode = match[1];
415        if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
416          languageCode = specifiedLanguageCode;
417        }
418      }
419
420      this.updateOfferToTranslateCheckbox_(languageCode);
421
422      if (cr.isWindows || cr.isChromeOS)
423        this.updateUiLanguageButton_(languageCode);
424
425      this.updateSelectedLanguageName_(languageCode);
426
427      if (!cr.isMac)
428        this.updateSpellCheckLanguageButton_(languageCode);
429
430      if (cr.isChromeOS)
431        this.updateInputMethodList_(languageCode);
432
433      this.updateLanguageListInAddLanguageOverlay_();
434    },
435
436    /**
437     * Handles languageOptionsList's save event.
438     * @param {Event} e Save event.
439     * @private
440     */
441    handleLanguageOptionsListSave_: function(e) {
442      if (cr.isChromeOS) {
443        // Sort the preload engines per the saved languages before save.
444        this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
445        this.savePreloadEnginesPref_();
446      }
447    },
448
449    /**
450     * Sorts preloadEngines_ by languageOptionsList's order.
451     * @param {Array} preloadEngines List of preload engines.
452     * @return {Array} Returns sorted preloadEngines.
453     * @private
454     */
455    sortPreloadEngines_: function(preloadEngines) {
456      // For instance, suppose we have two languages and associated input
457      // methods:
458      //
459      // - Korean: hangul
460      // - Chinese: pinyin
461      //
462      // The preloadEngines preference should look like "hangul,pinyin".
463      // If the user reverse the order, the preference should be reorderd
464      // to "pinyin,hangul".
465      var languageOptionsList = $('language-options-list');
466      var languageCodes = languageOptionsList.getLanguageCodes();
467
468      // Convert the list into a dictonary for simpler lookup.
469      var preloadEngineSet = {};
470      for (var i = 0; i < preloadEngines.length; i++) {
471        preloadEngineSet[preloadEngines[i]] = true;
472      }
473
474      // Create the new preload engine list per the language codes.
475      var newPreloadEngines = [];
476      for (var i = 0; i < languageCodes.length; i++) {
477        var languageCode = languageCodes[i];
478        var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
479            languageCode];
480        if (!inputMethodIds)
481          continue;
482
483        // Check if we have active input methods associated with the language.
484        for (var j = 0; j < inputMethodIds.length; j++) {
485          var inputMethodId = inputMethodIds[j];
486          if (inputMethodId in preloadEngineSet) {
487            // If we have, add it to the new engine list.
488            newPreloadEngines.push(inputMethodId);
489            // And delete it from the set. This is necessary as one input
490            // method can be associated with more than one language thus
491            // we should avoid having duplicates in the new list.
492            delete preloadEngineSet[inputMethodId];
493          }
494        }
495      }
496
497      return newPreloadEngines;
498    },
499
500    /**
501     * Initializes the map of language code to input method IDs.
502     * @private
503     */
504    initializeLanguageCodeToInputMethodIdsMap_: function() {
505      var inputMethodList = loadTimeData.getValue('inputMethodList');
506      for (var i = 0; i < inputMethodList.length; i++) {
507        var inputMethod = inputMethodList[i];
508        for (var languageCode in inputMethod.languageCodeSet) {
509          if (languageCode in this.languageCodeToInputMethodIdsMap_) {
510            this.languageCodeToInputMethodIdsMap_[languageCode].push(
511                inputMethod.id);
512          } else {
513            this.languageCodeToInputMethodIdsMap_[languageCode] =
514                [inputMethod.id];
515          }
516        }
517      }
518    },
519
520    /**
521     * Updates the currently selected language name.
522     * @param {string} languageCode Language code (ex. "fr").
523     * @private
524     */
525    updateSelectedLanguageName_: function(languageCode) {
526      var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
527          languageCode);
528      var languageDisplayName = languageInfo.displayName;
529      var languageNativeDisplayName = languageInfo.nativeDisplayName;
530      var textDirection = languageInfo.textDirection;
531
532      // If the native name is different, add it.
533      if (languageDisplayName != languageNativeDisplayName) {
534        languageDisplayName += ' - ' + languageNativeDisplayName;
535      }
536
537      // Update the currently selected language name.
538      var languageName = $('language-options-language-name');
539      languageName.textContent = languageDisplayName;
540      languageName.dir = textDirection;
541    },
542
543    /**
544     * Updates the UI language button.
545     * @param {string} languageCode Language code (ex. "fr").
546     * @private
547     */
548    updateUiLanguageButton_: function(languageCode) {
549      var uiLanguageButton = $('language-options-ui-language-button');
550      var uiLanguageMessage = $('language-options-ui-language-message');
551      var uiLanguageNotification = $('language-options-ui-notification-bar');
552
553      // Remove the event listener and add it back if useful.
554      uiLanguageButton.onclick = null;
555
556      // Unhide the language button every time, as it could've been previously
557      // hidden by a language change.
558      uiLanguageButton.hidden = false;
559
560      // Hide the controlled setting indicator.
561      var uiLanguageIndicator = document.querySelector(
562          '.language-options-contents .controlled-setting-indicator');
563      uiLanguageIndicator.removeAttribute('controlled-by');
564
565      if (languageCode == this.prospectiveUiLanguageCode_) {
566        uiLanguageMessage.textContent =
567            loadTimeData.getString('isDisplayedInThisLanguage');
568        showMutuallyExclusiveNodes(
569            [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
570      } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
571        if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
572          // In the guest mode for ChromeOS, changing UI language does not make
573          // sense because it does not take effect after browser restart.
574          uiLanguageButton.hidden = true;
575          uiLanguageMessage.hidden = true;
576        } else {
577          uiLanguageButton.textContent =
578              loadTimeData.getString('displayInThisLanguage');
579
580          if (loadTimeData.valueExists('secondaryUser') &&
581              loadTimeData.getBoolean('secondaryUser')) {
582            uiLanguageButton.disabled = true;
583            uiLanguageIndicator.setAttribute('controlled-by', 'shared');
584          } else {
585            uiLanguageButton.onclick = function(e) {
586              chrome.send('uiLanguageChange', [languageCode]);
587            };
588          }
589          showMutuallyExclusiveNodes(
590              [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
591        }
592      } else {
593        uiLanguageMessage.textContent =
594            loadTimeData.getString('cannotBeDisplayedInThisLanguage');
595        showMutuallyExclusiveNodes(
596            [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
597      }
598    },
599
600    /**
601     * Updates the spell check language button.
602     * @param {string} languageCode Language code (ex. "fr").
603     * @private
604     */
605    updateSpellCheckLanguageButton_: function(languageCode) {
606      var spellCheckLanguageSection = $('language-options-spellcheck');
607      var spellCheckLanguageButton =
608          $('language-options-spell-check-language-button');
609      var spellCheckLanguageMessage =
610          $('language-options-spell-check-language-message');
611      var dictionaryDownloadInProgress =
612          $('language-options-dictionary-downloading-message');
613      var dictionaryDownloadFailed =
614          $('language-options-dictionary-download-failed-message');
615      var dictionaryDownloadFailHelp =
616          $('language-options-dictionary-download-fail-help-message');
617      spellCheckLanguageSection.hidden = false;
618      spellCheckLanguageMessage.hidden = true;
619      spellCheckLanguageButton.hidden = true;
620      dictionaryDownloadInProgress.hidden = true;
621      dictionaryDownloadFailed.hidden = true;
622      dictionaryDownloadFailHelp.hidden = true;
623
624      if (languageCode == this.spellCheckDictionary_) {
625        if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
626          spellCheckLanguageMessage.textContent =
627              loadTimeData.getString('isUsedForSpellChecking');
628          showMutuallyExclusiveNodes(
629              [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
630        } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
631                       DOWNLOAD_STATUS.IN_PROGRESS) {
632          dictionaryDownloadInProgress.hidden = false;
633        } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
634                       DOWNLOAD_STATUS.FAILED) {
635          spellCheckLanguageSection.hidden = true;
636          dictionaryDownloadFailed.hidden = false;
637          if (this.spellcheckDictionaryDownloadFailures_ > 1)
638            dictionaryDownloadFailHelp.hidden = false;
639        }
640      } else if (languageCode in
641          loadTimeData.getValue('spellCheckLanguageCodeSet')) {
642        spellCheckLanguageButton.textContent =
643            loadTimeData.getString('useThisForSpellChecking');
644        showMutuallyExclusiveNodes(
645            [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
646        spellCheckLanguageButton.languageCode = languageCode;
647      } else if (!languageCode) {
648        spellCheckLanguageButton.hidden = true;
649        spellCheckLanguageMessage.hidden = true;
650      } else {
651        spellCheckLanguageMessage.textContent =
652            loadTimeData.getString('cannotBeUsedForSpellChecking');
653        showMutuallyExclusiveNodes(
654            [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
655      }
656    },
657
658    /**
659     * Updates the checkbox for stopping translation.
660     * @param {string} languageCode Language code (ex. "fr").
661     * @private
662     */
663    updateOfferToTranslateCheckbox_: function(languageCode) {
664      var div = $('language-options-offer-to-translate');
665
666      // Translation server supports Chinese (Transitional) and Chinese
667      // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
668      // show this preference when general Chinese is selected.
669      if (languageCode != 'zh') {
670        div.hidden = false;
671      } else {
672        div.hidden = true;
673        return;
674      }
675
676      var offerToTranslate = div.querySelector('div');
677      var cannotTranslate = $('cannot-translate-in-this-language');
678      var nodes = [offerToTranslate, cannotTranslate];
679
680      var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
681      if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
682        showMutuallyExclusiveNodes(nodes, 0);
683      } else {
684        showMutuallyExclusiveNodes(nodes, 1);
685        return;
686      }
687
688      var checkbox = $('offer-to-translate-in-this-language');
689
690      if (!this.enableTranslate_) {
691        checkbox.disabled = true;
692        checkbox.checked = false;
693        return;
694      }
695
696      // If the language corresponds to the default target language (in most
697      // cases, the user's locale language), "Offer to translate" checkbox
698      // should be always unchecked.
699      var defaultTargetLanguage =
700          loadTimeData.getString('defaultTargetLanguage');
701      if (convertedLangCode == defaultTargetLanguage) {
702        checkbox.disabled = true;
703        checkbox.checked = false;
704        return;
705      }
706
707      checkbox.disabled = false;
708
709      var blockedLanguages = this.translateBlockedLanguages_;
710      var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
711      checkbox.checked = checked;
712    },
713
714    /**
715     * Updates the input method list.
716     * @param {string} languageCode Language code (ex. "fr").
717     * @private
718     */
719    updateInputMethodList_: function(languageCode) {
720      // Give one of the checkboxes or buttons focus, if it's specified in the
721      // URL hash (ex. focus=mozc). Used for automated testing.
722      var focusInputMethodId = -1;
723      var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
724      if (match) {
725        focusInputMethodId = match[1];
726      }
727      // Change the visibility of the input method list. Input methods that
728      // matches |languageCode| will become visible.
729      var inputMethodList = $('language-options-input-method-list');
730      var methods = inputMethodList.querySelectorAll('.input-method');
731      for (var i = 0; i < methods.length; i++) {
732        var method = methods[i];
733        if (languageCode in method.languageCodeSet) {
734          method.hidden = false;
735          var input = method.querySelector('input');
736          // Give it focus if the ID matches.
737          if (input.inputMethodId == focusInputMethodId) {
738            input.focus();
739          }
740        } else {
741          method.hidden = true;
742        }
743      }
744
745      $('language-options-input-method-none').hidden =
746          (languageCode in this.languageCodeToInputMethodIdsMap_);
747
748      if (focusInputMethodId == 'add') {
749        $('language-options-add-button').focus();
750      }
751    },
752
753    /**
754     * Updates the language list in the add language overlay.
755     * @private
756     */
757    updateLanguageListInAddLanguageOverlay_: function() {
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 = assertInstanceof(e.target, Element);
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(e) {
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     * @private
1173     */
1174    notificationTimeout_: null,
1175
1176    /**
1177     * Shows notification.
1178     * @param {string} text
1179     * @param {string} actionText
1180     * @param {number=} opt_delay
1181     * @private
1182     */
1183    showNotification_: function(text, actionText, opt_delay) {
1184      var notificationElement = $('notification');
1185      var actionLink = notificationElement.querySelector('.link-color');
1186      var delay = opt_delay || 10000;
1187
1188      function show() {
1189        window.clearTimeout(this.notificationTimeout_);
1190        notificationElement.classList.add('show');
1191        document.body.classList.add('notification-shown');
1192      }
1193
1194      function hide() {
1195        window.clearTimeout(this.notificationTimeout_);
1196        notificationElement.classList.remove('show');
1197        document.body.classList.remove('notification-shown');
1198        // Prevent tabbing to the hidden link.
1199        actionLink.tabIndex = -1;
1200        // Setting tabIndex to -1 only prevents future tabbing to it. If,
1201        // however, the user switches window or a tab and then moves back to
1202        // this tab the element may gain focus. We therefore make sure that we
1203        // blur the element so that the element focus is not restored when
1204        // coming back to this window.
1205        actionLink.blur();
1206      }
1207
1208      function delayedHide() {
1209        this.notificationTimeout_ = window.setTimeout(hide, delay);
1210      }
1211
1212      notificationElement.firstElementChild.textContent = text;
1213      actionLink.textContent = actionText;
1214
1215      actionLink.onclick = hide;
1216      actionLink.onkeydown = function(e) {
1217        if (e.keyIdentifier == 'Enter') {
1218          hide();
1219        }
1220      };
1221      notificationElement.onmouseover = show;
1222      notificationElement.onmouseout = delayedHide;
1223      actionLink.onfocus = show;
1224      actionLink.onblur = delayedHide;
1225      // Enable tabbing to the link now that it is shown.
1226      actionLink.tabIndex = 0;
1227
1228      show();
1229      delayedHide();
1230    },
1231
1232    /**
1233     * Chrome callback for when the UI language preference is saved.
1234     * @param {string} languageCode The newly selected language to use.
1235     * @private
1236     */
1237    uiLanguageSaved_: function(languageCode) {
1238      this.prospectiveUiLanguageCode_ = languageCode;
1239
1240      // If the user is no longer on the same language code, ignore.
1241      if ($('language-options-list').getSelectedLanguageCode() != languageCode)
1242        return;
1243
1244      // Special case for when a user changes to a different language, and
1245      // changes back to the same language without having restarted Chrome or
1246      // logged in/out of ChromeOS.
1247      if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
1248        this.updateUiLanguageButton_(languageCode);
1249        return;
1250      }
1251
1252      // Otherwise, show a notification telling the user that their changes will
1253      // only take effect after restart.
1254      showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1255                                  $('language-options-ui-notification-bar')],
1256                                 1);
1257    },
1258
1259    /**
1260     * A handler for when dictionary for |languageCode| begins downloading.
1261     * @param {string} languageCode The language of the dictionary that just
1262     *     began downloading.
1263     * @private
1264     */
1265    onDictionaryDownloadBegin_: function(languageCode) {
1266      this.spellcheckDictionaryDownloadStatus_[languageCode] =
1267          DOWNLOAD_STATUS.IN_PROGRESS;
1268      if (!cr.isMac &&
1269          languageCode ==
1270              $('language-options-list').getSelectedLanguageCode()) {
1271        this.updateSpellCheckLanguageButton_(languageCode);
1272      }
1273    },
1274
1275    /**
1276     * A handler for when dictionary for |languageCode| successfully downloaded.
1277     * @param {string} languageCode The language of the dictionary that
1278     *     succeeded downloading.
1279     * @private
1280     */
1281    onDictionaryDownloadSuccess_: function(languageCode) {
1282      delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1283      this.spellcheckDictionaryDownloadFailures_ = 0;
1284      if (!cr.isMac &&
1285          languageCode ==
1286              $('language-options-list').getSelectedLanguageCode()) {
1287        this.updateSpellCheckLanguageButton_(languageCode);
1288      }
1289    },
1290
1291    /**
1292     * A handler for when dictionary for |languageCode| fails to download.
1293     * @param {string} languageCode The language of the dictionary that failed
1294     *     to download.
1295     * @private
1296     */
1297    onDictionaryDownloadFailure_: function(languageCode) {
1298      this.spellcheckDictionaryDownloadStatus_[languageCode] =
1299          DOWNLOAD_STATUS.FAILED;
1300      this.spellcheckDictionaryDownloadFailures_++;
1301      if (!cr.isMac &&
1302          languageCode ==
1303              $('language-options-list').getSelectedLanguageCode()) {
1304        this.updateSpellCheckLanguageButton_(languageCode);
1305      }
1306    },
1307
1308    /**
1309     * Converts the language code for Translation. There are some differences
1310     * between the language set for Translation and that for Accept-Language.
1311     * @param {string} languageCode The language code like 'fr'.
1312     * @return {string} The converted language code.
1313     * @private
1314     */
1315    convertLangCodeForTranslation_: function(languageCode) {
1316      var tokens = languageCode.split('-');
1317      var main = tokens[0];
1318
1319      // See also: chrome/renderer/translate/translate_helper.cc.
1320      var synonyms = {
1321        'nb': 'no',
1322        'he': 'iw',
1323        'jv': 'jw',
1324        'fil': 'tl',
1325      };
1326
1327      if (main in synonyms) {
1328        return synonyms[main];
1329      } else if (main == 'zh') {
1330        // In Translation, general Chinese is not used, and the sub code is
1331        // necessary as a language code for Translate server.
1332        return languageCode;
1333      }
1334
1335      return main;
1336    },
1337  };
1338
1339  /**
1340   * Shows the node at |index| in |nodes|, hides all others.
1341   * @param {Array.<HTMLElement>} nodes The nodes to be shown or hidden.
1342   * @param {number} index The index of |nodes| to show.
1343   */
1344  function showMutuallyExclusiveNodes(nodes, index) {
1345    assert(index >= 0 && index < nodes.length);
1346    for (var i = 0; i < nodes.length; ++i) {
1347      assert(nodes[i] instanceof HTMLElement);  // TODO(dbeam): Ignore null?
1348      nodes[i].hidden = i != index;
1349    }
1350  }
1351
1352  LanguageOptions.uiLanguageSaved = function(languageCode) {
1353    LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
1354  };
1355
1356  LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1357    LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1358  };
1359
1360  LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1361    LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1362  };
1363
1364  LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1365    LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1366  };
1367
1368  // Export
1369  return {
1370    LanguageOptions: LanguageOptions
1371  };
1372});
1373