1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // TODO(kochi): Generalize the notification as a component and put it
6 // in js/cr/ui/notification.js .
8 cr.define('options', function() {
9 /** @const */ var OptionsPage = options.OptionsPage;
10 /** @const */ var LanguageList = options.LanguageList;
13 * Spell check dictionary download status.
16 /** @const*/ var DOWNLOAD_STATUS = {
22 * The preference is a boolean that enables/disables spell checking.
26 var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
29 * The preference is a CSV string that describes preload engines
30 * (i.e. active input methods).
34 var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
37 * The preference that lists the extension IMEs that are enabled in the
42 var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
45 * The preference that lists the languages which are not translated.
49 var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
52 * The preference key that is a string that describes the spell check
53 * dictionary language, like "en-US".
57 var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary';
60 * The preference that indicates if the Translate feature is enabled.
64 var ENABLE_TRANSLATE = 'translate.enabled';
66 /////////////////////////////////////////////////////////////////////////////
67 // LanguageOptions class:
70 * Encapsulated handling of ChromeOS language options page.
73 function LanguageOptions(model) {
74 OptionsPage.call(this, 'languages',
75 loadTimeData.getString('languagePageTabTitle'),
79 cr.addSingletonGetter(LanguageOptions);
81 // Inherit LanguageOptions from OptionsPage.
82 LanguageOptions.prototype = {
83 __proto__: OptionsPage.prototype,
85 /* For recording the prospective language (the next locale after relaunch).
89 prospectiveUiLanguageCode_: null,
92 * Map from language code to spell check dictionary download status for that
97 spellcheckDictionaryDownloadStatus_: [],
100 * Number of times a spell check dictionary download failed.
104 spellcheckDictionaryDownloadFailures_: 0,
107 * The list of preload engines, like ['mozc', 'pinyin'].
114 * The list of extension IMEs that are enabled out of the language menu.
118 enabledExtensionImes_: [],
121 * The list of the languages which is not translated.
125 translateBlockedLanguages_: [],
128 * The list of the languages supported by Translate server
132 translateSupportedLanguages_: [],
135 * The preference is a string that describes the spell check dictionary
136 * language, like "en-US".
140 spellCheckDictionary_: '',
143 * The map of language code to input method IDs, like:
144 * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
148 languageCodeToInputMethodIdsMap_: {},
151 * The value that indicates if Translate feature is enabled or not.
155 enableTranslate_: false,
158 * Initializes LanguageOptions page.
159 * Calls base class implementation to start preference initialization.
161 initializePage: function() {
162 OptionsPage.prototype.initializePage.call(this);
164 var languageOptionsList = $('language-options-list');
165 LanguageList.decorate(languageOptionsList);
167 languageOptionsList.addEventListener('change',
168 this.handleLanguageOptionsListChange_.bind(this));
169 languageOptionsList.addEventListener('save',
170 this.handleLanguageOptionsListSave_.bind(this));
172 this.prospectiveUiLanguageCode_ =
173 loadTimeData.getString('prospectiveUiLanguageCode');
174 this.addEventListener('visibleChange',
175 this.handleVisibleChange_.bind(this));
178 this.initializeInputMethodList_();
179 this.initializeLanguageCodeToInputMethodIdsMap_();
182 var checkbox = $('offer-to-translate-in-this-language');
183 checkbox.addEventListener('click',
184 this.handleOfferToTranslateCheckboxClick_.bind(this));
186 Preferences.getInstance().addEventListener(
187 TRANSLATE_BLOCKED_LANGUAGES_PREF,
188 this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
189 Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
190 this.handleSpellCheckDictionaryPrefChange_.bind(this));
191 Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
192 this.handleEnableTranslatePrefChange_.bind(this));
193 this.translateSupportedLanguages_ =
194 loadTimeData.getValue('translateSupportedLanguages');
196 // Set up add button.
197 $('language-options-add-button').onclick = function(e) {
198 // Add the language without showing the overlay if it's specified in
199 // the URL hash (ex. lang_add=ja). Used for automated testing.
200 var match = document.location.hash.match(/\blang_add=([\w-]+)/);
202 var addLanguageCode = match[1];
203 $('language-options-list').addLanguage(addLanguageCode);
204 this.addBlockedLanguage_(addLanguageCode);
206 OptionsPage.navigateToPage('addLanguage');
211 // Set up the button for editing custom spelling dictionary.
212 $('edit-dictionary-button').onclick = function(e) {
213 OptionsPage.navigateToPage('editDictionary');
215 $('dictionary-download-retry-button').onclick = function(e) {
216 chrome.send('retryDictionaryDownload');
220 // Listen to add language dialog ok button.
221 $('add-language-overlay-ok-button').addEventListener(
222 'click', this.handleAddLanguageOkButtonClick_.bind(this));
224 if (!cr.isChromeOS) {
225 // Show experimental features if enabled.
226 if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
227 $('auto-spell-correction-option').hidden = false;
229 // Handle spell check enable/disable.
231 Preferences.getInstance().addEventListener(
232 ENABLE_SPELL_CHECK_PREF,
233 this.updateEnableSpellCheck_.bind(this));
237 // Handle clicks on "Use this language for spell checking" button.
239 var spellCheckLanguageButton = getRequiredElement(
240 'language-options-spell-check-language-button');
241 spellCheckLanguageButton.addEventListener(
243 this.handleSpellCheckLanguageButtonClick_.bind(this));
247 $('language-options-ui-restart-button').onclick = function() {
248 chrome.send('uiLanguageRestart');
252 $('language-confirm').onclick =
253 OptionsPage.closeOverlay.bind(OptionsPage);
257 * Initializes the input method list.
259 initializeInputMethodList_: function() {
260 var inputMethodList = $('language-options-input-method-list');
261 var inputMethodPrototype = $('language-options-input-method-template');
263 // Add all input methods, but make all of them invisible here. We'll
264 // change the visibility in handleLanguageOptionsListChange_() based
265 // on the selected language. Note that we only have less than 100
266 // input methods, so creating DOM nodes at once here should be ok.
267 this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
268 this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
269 this.appendComponentExtensionIme_(
270 loadTimeData.getValue('componentExtensionImeList'));
272 // Listen to pref change once the input method list is initialized.
273 Preferences.getInstance().addEventListener(
274 PRELOAD_ENGINES_PREF,
275 this.handlePreloadEnginesPrefChange_.bind(this));
276 Preferences.getInstance().addEventListener(
277 ENABLED_EXTENSION_IME_PREF,
278 this.handleEnabledExtensionsPrefChange_.bind(this));
282 * Appends input method lists based on component extension ime list.
283 * @param {!Array} componentExtensionImeList A list of input method
287 appendComponentExtensionIme_: function(componentExtensionImeList) {
288 this.appendInputMethodElement_(componentExtensionImeList);
290 for (var i = 0; i < componentExtensionImeList.length; i++) {
291 var inputMethod = componentExtensionImeList[i];
292 for (var languageCode in inputMethod.languageCodeSet) {
293 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
294 this.languageCodeToInputMethodIdsMap_[languageCode].push(
297 this.languageCodeToInputMethodIdsMap_[languageCode] =
305 * Appends input methods into input method list.
306 * @param {!Array} inputMethods A list of input method descriptors.
309 appendInputMethodElement_: function(inputMethods) {
310 var inputMethodList = $('language-options-input-method-list');
311 var inputMethodTemplate = $('language-options-input-method-template');
313 for (var i = 0; i < inputMethods.length; i++) {
314 var inputMethod = inputMethods[i];
315 var element = inputMethodTemplate.cloneNode(true);
317 element.languageCodeSet = inputMethod.languageCodeSet;
319 var input = element.querySelector('input');
320 input.inputMethodId = inputMethod.id;
321 var span = element.querySelector('span');
322 span.textContent = inputMethod.displayName;
324 if (inputMethod.optionsPage) {
325 var button = document.createElement('button');
326 button.textContent = loadTimeData.getString('configure');
327 button.inputMethodId = inputMethod.id;
328 button.onclick = function(inputMethodId, e) {
329 chrome.send('inputMethodOptionsOpen', [inputMethodId]);
330 }.bind(this, inputMethod.id);
331 element.appendChild(button);
334 // Listen to user clicks.
335 input.addEventListener('click',
336 this.handleCheckboxClick_.bind(this));
337 inputMethodList.appendChild(element);
342 * Adds a language to the preference 'translate_blocked_languages'. If
343 * |langCode| is already added, nothing happens. |langCode| is converted
344 * to a Translate language synonym before added.
345 * @param {string} langCode A language code like 'en'
348 addBlockedLanguage_: function(langCode) {
349 langCode = this.convertLangCodeForTranslation_(langCode);
350 if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
351 this.translateBlockedLanguages_.push(langCode);
352 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
353 this.translateBlockedLanguages_, true);
358 * Removes a language from the preference 'translate_blocked_languages'.
359 * If |langCode| doesn't exist in the preference, nothing happens.
360 * |langCode| is converted to a Translate language synonym before removed.
361 * @param {string} langCode A language code like 'en'
364 removeBlockedLanguage_: function(langCode) {
365 langCode = this.convertLangCodeForTranslation_(langCode);
366 if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
367 this.translateBlockedLanguages_ =
368 this.translateBlockedLanguages_.filter(
369 function(langCodeNotTranslated) {
370 return langCodeNotTranslated != langCode;
372 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
373 this.translateBlockedLanguages_, true);
378 * Handles OptionsPage's visible property change event.
379 * @param {Event} e Property change event.
382 handleVisibleChange_: function(e) {
384 $('language-options-list').redraw();
385 chrome.send('languageOptionsOpen');
390 * Handles languageOptionsList's change event.
391 * @param {Event} e Change event.
394 handleLanguageOptionsListChange_: function(e) {
395 var languageOptionsList = $('language-options-list');
396 var languageCode = languageOptionsList.getSelectedLanguageCode();
398 // If there's no selection, just return.
402 // Select the language if it's specified in the URL hash (ex. lang=ja).
403 // Used for automated testing.
404 var match = document.location.hash.match(/\blang=([\w-]+)/);
406 var specifiedLanguageCode = match[1];
407 if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
408 languageCode = specifiedLanguageCode;
412 this.updateOfferToTranslateCheckbox_(languageCode);
414 if (cr.isWindows || cr.isChromeOS)
415 this.updateUiLanguageButton_(languageCode);
417 this.updateSelectedLanguageName_(languageCode);
420 this.updateSpellCheckLanguageButton_(languageCode);
423 this.updateInputMethodList_(languageCode);
425 this.updateLanguageListInAddLanguageOverlay_();
429 * Happens when a user changes back to the language they're currently using.
431 currentLocaleWasReselected: function() {
432 this.updateUiLanguageButton_(
433 loadTimeData.getString('currentUiLanguageCode'));
437 * Handles languageOptionsList's save event.
438 * @param {Event} e Save event.
441 handleLanguageOptionsListSave_: function(e) {
443 // Sort the preload engines per the saved languages before save.
444 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
445 this.savePreloadEnginesPref_();
450 * Sorts preloadEngines_ by languageOptionsList's order.
451 * @param {Array} preloadEngines List of preload engines.
452 * @return {Array} Returns sorted preloadEngines.
455 sortPreloadEngines_: function(preloadEngines) {
456 // For instance, suppose we have two languages and associated input
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();
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;
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_[
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];
497 return newPreloadEngines;
501 * Initializes the map of language code to input method IDs.
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(
513 this.languageCodeToInputMethodIdsMap_[languageCode] =
521 * Updates the currently selected language name.
522 * @param {string} languageCode Language code (ex. "fr").
525 updateSelectedLanguageName_: function(languageCode) {
526 var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
528 var languageDisplayName = languageInfo.displayName;
529 var languageNativeDisplayName = languageInfo.nativeDisplayName;
530 var textDirection = languageInfo.textDirection;
532 // If the native name is different, add it.
533 if (languageDisplayName != languageNativeDisplayName) {
534 languageDisplayName += ' - ' + languageNativeDisplayName;
537 // Update the currently selected language name.
538 var languageName = $('language-options-language-name');
539 languageName.textContent = languageDisplayName;
540 languageName.dir = textDirection;
544 * Updates the UI language button.
545 * @param {string} languageCode Language code (ex. "fr").
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');
553 // Remove the event listener and add it back if useful.
554 uiLanguageButton.onclick = null;
556 // Unhide the language button every time, as it could've been previously
557 // hidden by a language change.
558 uiLanguageButton.hidden = false;
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;
572 uiLanguageButton.textContent =
573 loadTimeData.getString('displayInThisLanguage');
574 showMutuallyExclusiveNodes(
575 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
576 uiLanguageButton.onclick = function(e) {
577 chrome.send('uiLanguageChange', [languageCode]);
581 uiLanguageMessage.textContent =
582 loadTimeData.getString('cannotBeDisplayedInThisLanguage');
583 showMutuallyExclusiveNodes(
584 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
589 * Updates the spell check language button.
590 * @param {string} languageCode Language code (ex. "fr").
593 updateSpellCheckLanguageButton_: function(languageCode) {
594 var spellCheckLanguageSection = $('language-options-spellcheck');
595 var spellCheckLanguageButton =
596 $('language-options-spell-check-language-button');
597 var spellCheckLanguageMessage =
598 $('language-options-spell-check-language-message');
599 var dictionaryDownloadInProgress =
600 $('language-options-dictionary-downloading-message');
601 var dictionaryDownloadFailed =
602 $('language-options-dictionary-download-failed-message');
603 var dictionaryDownloadFailHelp =
604 $('language-options-dictionary-download-fail-help-message');
605 spellCheckLanguageSection.hidden = false;
606 spellCheckLanguageMessage.hidden = true;
607 spellCheckLanguageButton.hidden = true;
608 dictionaryDownloadInProgress.hidden = true;
609 dictionaryDownloadFailed.hidden = true;
610 dictionaryDownloadFailHelp.hidden = true;
612 if (languageCode == this.spellCheckDictionary_) {
613 if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
614 spellCheckLanguageMessage.textContent =
615 loadTimeData.getString('isUsedForSpellChecking');
616 showMutuallyExclusiveNodes(
617 [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
618 } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
619 DOWNLOAD_STATUS.IN_PROGRESS) {
620 dictionaryDownloadInProgress.hidden = false;
621 } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
622 DOWNLOAD_STATUS.FAILED) {
623 spellCheckLanguageSection.hidden = true;
624 dictionaryDownloadFailed.hidden = false;
625 if (this.spellcheckDictionaryDownloadFailures_ > 1)
626 dictionaryDownloadFailHelp.hidden = false;
628 } else if (languageCode in
629 loadTimeData.getValue('spellCheckLanguageCodeSet')) {
630 spellCheckLanguageButton.textContent =
631 loadTimeData.getString('useThisForSpellChecking');
632 showMutuallyExclusiveNodes(
633 [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
634 spellCheckLanguageButton.languageCode = languageCode;
635 } else if (!languageCode) {
636 spellCheckLanguageButton.hidden = true;
637 spellCheckLanguageMessage.hidden = true;
639 spellCheckLanguageMessage.textContent =
640 loadTimeData.getString('cannotBeUsedForSpellChecking');
641 showMutuallyExclusiveNodes(
642 [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
647 * Updates the checkbox for stopping translation.
648 * @param {string} languageCode Language code (ex. "fr").
651 updateOfferToTranslateCheckbox_: function(languageCode) {
652 var div = $('language-options-offer-to-translate');
654 // Translation server supports Chinese (Transitional) and Chinese
655 // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
656 // show this preference when general Chinese is selected.
657 if (languageCode != 'zh') {
664 var offerToTranslate = div.querySelector('div');
665 var cannotTranslate = $('cannot-translate-in-this-language');
666 var nodes = [offerToTranslate, cannotTranslate];
668 var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
669 if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
670 showMutuallyExclusiveNodes(nodes, 0);
672 showMutuallyExclusiveNodes(nodes, 1);
676 var checkbox = $('offer-to-translate-in-this-language');
678 if (!this.enableTranslate_) {
679 checkbox.disabled = true;
680 checkbox.checked = false;
684 // If the language corresponds to the default target language (in most
685 // cases, the user's locale language), "Offer to translate" checkbox
686 // should be always unchecked.
687 var defaultTargetLanguage =
688 loadTimeData.getString('defaultTargetLanguage');
689 if (convertedLangCode == defaultTargetLanguage) {
690 checkbox.disabled = true;
691 checkbox.checked = false;
695 checkbox.disabled = false;
697 var blockedLanguages = this.translateBlockedLanguages_;
698 var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
699 checkbox.checked = checked;
703 * Updates the input method list.
704 * @param {string} languageCode Language code (ex. "fr").
707 updateInputMethodList_: function(languageCode) {
708 // Give one of the checkboxes or buttons focus, if it's specified in the
709 // URL hash (ex. focus=mozc). Used for automated testing.
710 var focusInputMethodId = -1;
711 var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
713 focusInputMethodId = match[1];
715 // Change the visibility of the input method list. Input methods that
716 // matches |languageCode| will become visible.
717 var inputMethodList = $('language-options-input-method-list');
718 var methods = inputMethodList.querySelectorAll('.input-method');
719 for (var i = 0; i < methods.length; i++) {
720 var method = methods[i];
721 if (languageCode in method.languageCodeSet) {
722 method.hidden = false;
723 var input = method.querySelector('input');
724 // Give it focus if the ID matches.
725 if (input.inputMethodId == focusInputMethodId) {
729 method.hidden = true;
733 $('language-options-input-method-none').hidden =
734 (languageCode in this.languageCodeToInputMethodIdsMap_);
736 if (focusInputMethodId == 'add') {
737 $('language-options-add-button').focus();
742 * Updates the language list in the add language overlay.
743 * @param {string} languageCode Language code (ex. "fr").
746 updateLanguageListInAddLanguageOverlay_: function(languageCode) {
747 // Change the visibility of the language list in the add language
748 // overlay. Languages that are already active will become invisible,
749 // so that users don't add the same language twice.
750 var languageOptionsList = $('language-options-list');
751 var languageCodes = languageOptionsList.getLanguageCodes();
752 var languageCodeSet = {};
753 for (var i = 0; i < languageCodes.length; i++) {
754 languageCodeSet[languageCodes[i]] = true;
757 var addLanguageList = $('add-language-overlay-language-list');
758 var options = addLanguageList.querySelectorAll('option');
759 assert(options.length > 0);
760 var selectedFirstItem = false;
761 for (var i = 0; i < options.length; i++) {
762 var option = options[i];
763 option.hidden = option.value in languageCodeSet;
764 if (!option.hidden && !selectedFirstItem) {
765 // Select first visible item, otherwise previously selected hidden
766 // item will be selected by default at the next time.
767 option.selected = true;
768 selectedFirstItem = true;
774 * Handles preloadEnginesPref change.
775 * @param {Event} e Change event.
778 handlePreloadEnginesPrefChange_: function(e) {
779 var value = e.value.value;
780 this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
781 this.updateCheckboxesFromPreloadEngines_();
782 $('language-options-list').updateDeletable();
786 * Handles enabledExtensionImePref change.
787 * @param {Event} e Change event.
790 handleEnabledExtensionsPrefChange_: function(e) {
791 var value = e.value.value;
792 this.enabledExtensionImes_ = value.split(',');
793 this.updateCheckboxesFromEnabledExtensions_();
797 * Handles offer-to-translate checkbox's click event.
798 * @param {Event} e Click event.
801 handleOfferToTranslateCheckboxClick_: function(e) {
802 var checkbox = e.target;
803 var checked = checkbox.checked;
805 var languageOptionsList = $('language-options-list');
806 var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
809 this.removeBlockedLanguage_(selectedLanguageCode);
811 this.addBlockedLanguage_(selectedLanguageCode);
815 * Handles input method checkbox's click event.
816 * @param {Event} e Click event.
819 handleCheckboxClick_: function(e) {
820 var checkbox = e.target;
822 if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
823 this.updateEnabledExtensionsFromCheckboxes_();
824 this.saveEnabledExtensionPref_();
827 if (this.preloadEngines_.length == 1 && !checkbox.checked) {
828 // Don't allow disabling the last input method.
829 this.showNotification_(
830 loadTimeData.getString('pleaseAddAnotherInputMethod'),
831 loadTimeData.getString('okButton'));
832 checkbox.checked = true;
835 if (checkbox.checked) {
836 chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
838 chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
840 this.updatePreloadEnginesFromCheckboxes_();
841 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
842 this.savePreloadEnginesPref_();
845 handleAddLanguageOkButtonClick_: function() {
846 var languagesSelect = $('add-language-overlay-language-list');
847 var selectedIndex = languagesSelect.selectedIndex;
848 if (selectedIndex >= 0) {
849 var selection = languagesSelect.options[selectedIndex];
850 var langCode = String(selection.value);
851 $('language-options-list').addLanguage(langCode);
852 this.addBlockedLanguage_(langCode);
853 OptionsPage.closeOverlay();
858 * Checks if languageCode is deletable or not.
859 * @param {string} languageCode the languageCode to check for deletability.
861 languageIsDeletable: function(languageCode) {
862 // Don't allow removing the language if it's a UI language.
863 if (languageCode == this.prospectiveUiLanguageCode_)
865 return (!cr.isChromeOS ||
866 this.canDeleteLanguage_(languageCode));
870 * Handles browse.enable_spellchecking change.
871 * @param {Event} e Change event.
874 updateEnableSpellCheck_: function() {
875 var value = !$('enable-spell-check').checked;
876 $('language-options-spell-check-language-button').disabled = value;
878 $('edit-dictionary-button').hidden = value;
882 * Handles translateBlockedLanguagesPref change.
883 * @param {Event} e Change event.
886 handleTranslateBlockedLanguagesPrefChange_: function(e) {
887 this.translateBlockedLanguages_ = e.value.value;
888 this.updateOfferToTranslateCheckbox_(
889 $('language-options-list').getSelectedLanguageCode());
893 * Handles spellCheckDictionaryPref change.
894 * @param {Event} e Change event.
897 handleSpellCheckDictionaryPrefChange_: function(e) {
898 var languageCode = e.value.value;
899 this.spellCheckDictionary_ = languageCode;
901 this.updateSpellCheckLanguageButton_(
902 $('language-options-list').getSelectedLanguageCode());
907 * Handles translate.enabled change.
908 * @param {Event} e Change event.
911 handleEnableTranslatePrefChange_: function(e) {
912 var enabled = e.value.value;
913 this.enableTranslate_ = enabled;
914 this.updateOfferToTranslateCheckbox_(
915 $('language-options-list').getSelectedLanguageCode());
919 * Handles spellCheckLanguageButton click.
920 * @param {Event} e Click event.
923 handleSpellCheckLanguageButtonClick_: function(e) {
924 var languageCode = e.target.languageCode;
925 // Save the preference.
926 Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
928 chrome.send('spellCheckLanguageChange', [languageCode]);
932 * Checks whether it's possible to remove the language specified by
933 * languageCode and returns true if possible. This function returns false
934 * if the removal causes the number of preload engines to be zero.
936 * @param {string} languageCode Language code (ex. "fr").
937 * @return {boolean} Returns true on success.
940 canDeleteLanguage_: function(languageCode) {
941 // First create the set of engines to be removed from input methods
942 // associated with the language code.
943 var enginesToBeRemovedSet = {};
944 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
946 // If this language doesn't have any input methods, it can be deleted.
950 for (var i = 0; i < inputMethodIds.length; i++) {
951 enginesToBeRemovedSet[inputMethodIds[i]] = true;
954 // Then eliminate engines that are also used for other active languages.
955 // For instance, if "xkb:us::eng" is used for both English and Filipino.
956 var languageCodes = $('language-options-list').getLanguageCodes();
957 for (var i = 0; i < languageCodes.length; i++) {
958 // Skip the target language code.
959 if (languageCodes[i] == languageCode) {
962 // Check if input methods used in this language are included in
963 // enginesToBeRemovedSet. If so, eliminate these from the set, so
964 // we don't remove this time.
965 var inputMethodIdsForAnotherLanguage =
966 this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
967 if (!inputMethodIdsForAnotherLanguage)
970 for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
971 var inputMethodId = inputMethodIdsForAnotherLanguage[j];
972 if (inputMethodId in enginesToBeRemovedSet) {
973 delete enginesToBeRemovedSet[inputMethodId];
978 // Update the preload engine list with the to-be-removed set.
979 var newPreloadEngines = [];
980 for (var i = 0; i < this.preloadEngines_.length; i++) {
981 if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
982 newPreloadEngines.push(this.preloadEngines_[i]);
985 // Don't allow this operation if it causes the number of preload
986 // engines to be zero.
987 return (newPreloadEngines.length > 0);
991 * Saves the enabled extension preference.
994 saveEnabledExtensionPref_: function() {
995 Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
996 this.enabledExtensionImes_.join(','), true);
1000 * Updates the checkboxes in the input method list from the enabled
1001 * extensions preference.
1004 updateCheckboxesFromEnabledExtensions_: function() {
1005 // Convert the list into a dictonary for simpler lookup.
1006 var dictionary = {};
1007 for (var i = 0; i < this.enabledExtensionImes_.length; i++)
1008 dictionary[this.enabledExtensionImes_[i]] = true;
1010 var inputMethodList = $('language-options-input-method-list');
1011 var checkboxes = inputMethodList.querySelectorAll('input');
1012 for (var i = 0; i < checkboxes.length; i++) {
1013 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1014 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1016 var configureButtons = inputMethodList.querySelectorAll('button');
1017 for (var i = 0; i < configureButtons.length; i++) {
1018 if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1019 configureButtons[i].hidden =
1020 !(configureButtons[i].inputMethodId in dictionary);
1026 * Updates the enabled extensions preference from the checkboxes in the
1027 * input method list.
1030 updateEnabledExtensionsFromCheckboxes_: function() {
1031 this.enabledExtensionImes_ = [];
1032 var inputMethodList = $('language-options-input-method-list');
1033 var checkboxes = inputMethodList.querySelectorAll('input');
1034 for (var i = 0; i < checkboxes.length; i++) {
1035 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1036 if (checkboxes[i].checked)
1037 this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
1043 * Saves the preload engines preference.
1046 savePreloadEnginesPref_: function() {
1047 Preferences.setStringPref(PRELOAD_ENGINES_PREF,
1048 this.preloadEngines_.join(','), true);
1052 * Updates the checkboxes in the input method list from the preload
1053 * engines preference.
1056 updateCheckboxesFromPreloadEngines_: function() {
1057 // Convert the list into a dictonary for simpler lookup.
1058 var dictionary = {};
1059 for (var i = 0; i < this.preloadEngines_.length; i++) {
1060 dictionary[this.preloadEngines_[i]] = true;
1063 var inputMethodList = $('language-options-input-method-list');
1064 var checkboxes = inputMethodList.querySelectorAll('input');
1065 for (var i = 0; i < checkboxes.length; i++) {
1066 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1067 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1069 var configureButtons = inputMethodList.querySelectorAll('button');
1070 for (var i = 0; i < configureButtons.length; i++) {
1071 if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1072 configureButtons[i].hidden =
1073 !(configureButtons[i].inputMethodId in dictionary);
1079 * Updates the preload engines preference from the checkboxes in the
1080 * input method list.
1083 updatePreloadEnginesFromCheckboxes_: function() {
1084 this.preloadEngines_ = [];
1085 var inputMethodList = $('language-options-input-method-list');
1086 var checkboxes = inputMethodList.querySelectorAll('input');
1087 for (var i = 0; i < checkboxes.length; i++) {
1088 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1089 if (checkboxes[i].checked)
1090 this.preloadEngines_.push(checkboxes[i].inputMethodId);
1093 var languageOptionsList = $('language-options-list');
1094 languageOptionsList.updateDeletable();
1098 * Filters bad preload engines in case bad preload engines are
1099 * stored in the preference. Removes duplicates as well.
1100 * @param {Array} preloadEngines List of preload engines.
1103 filterBadPreloadEngines_: function(preloadEngines) {
1104 // Convert the list into a dictonary for simpler lookup.
1105 var dictionary = {};
1106 var list = loadTimeData.getValue('inputMethodList');
1107 for (var i = 0; i < list.length; i++) {
1108 dictionary[list[i].id] = true;
1111 var enabledPreloadEngines = [];
1113 for (var i = 0; i < preloadEngines.length; i++) {
1114 // Check if the preload engine is present in the
1115 // dictionary, and not duplicate. Otherwise, skip it.
1116 // Component Extension IME should be handled same as preloadEngines and
1117 // "_comp_" is the special prefix of its ID.
1118 if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
1119 /^_comp_/.test(preloadEngines[i])) {
1120 enabledPreloadEngines.push(preloadEngines[i]);
1121 seen[preloadEngines[i]] = true;
1124 return enabledPreloadEngines;
1127 // TODO(kochi): This is an adapted copy from new_tab.js.
1128 // If this will go as final UI, refactor this to share the component with
1129 // new new tab page.
1131 * Shows notification
1134 notificationTimeout_: null,
1135 showNotification_: function(text, actionText, opt_delay) {
1136 var notificationElement = $('notification');
1137 var actionLink = notificationElement.querySelector('.link-color');
1138 var delay = opt_delay || 10000;
1141 window.clearTimeout(this.notificationTimeout_);
1142 notificationElement.classList.add('show');
1143 document.body.classList.add('notification-shown');
1147 window.clearTimeout(this.notificationTimeout_);
1148 notificationElement.classList.remove('show');
1149 document.body.classList.remove('notification-shown');
1150 // Prevent tabbing to the hidden link.
1151 actionLink.tabIndex = -1;
1152 // Setting tabIndex to -1 only prevents future tabbing to it. If,
1153 // however, the user switches window or a tab and then moves back to
1154 // this tab the element may gain focus. We therefore make sure that we
1155 // blur the element so that the element focus is not restored when
1156 // coming back to this window.
1160 function delayedHide() {
1161 this.notificationTimeout_ = window.setTimeout(hide, delay);
1164 notificationElement.firstElementChild.textContent = text;
1165 actionLink.textContent = actionText;
1167 actionLink.onclick = hide;
1168 actionLink.onkeydown = function(e) {
1169 if (e.keyIdentifier == 'Enter') {
1173 notificationElement.onmouseover = show;
1174 notificationElement.onmouseout = delayedHide;
1175 actionLink.onfocus = show;
1176 actionLink.onblur = delayedHide;
1177 // Enable tabbing to the link now that it is shown.
1178 actionLink.tabIndex = 0;
1184 onDictionaryDownloadBegin_: function(languageCode) {
1185 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1186 DOWNLOAD_STATUS.IN_PROGRESS;
1189 $('language-options-list').getSelectedLanguageCode()) {
1190 this.updateSpellCheckLanguageButton_(languageCode);
1194 onDictionaryDownloadSuccess_: function(languageCode) {
1195 delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1196 this.spellcheckDictionaryDownloadFailures_ = 0;
1199 $('language-options-list').getSelectedLanguageCode()) {
1200 this.updateSpellCheckLanguageButton_(languageCode);
1204 onDictionaryDownloadFailure_: function(languageCode) {
1205 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1206 DOWNLOAD_STATUS.FAILED;
1207 this.spellcheckDictionaryDownloadFailures_++;
1210 $('language-options-list').getSelectedLanguageCode()) {
1211 this.updateSpellCheckLanguageButton_(languageCode);
1216 * Converts the language code for Translation. There are some differences
1217 * between the language set for Translation and that for Accept-Language.
1218 * @param {string} languageCode The language code like 'fr'.
1219 * @return {string} The converted language code.
1222 convertLangCodeForTranslation_: function(languageCode) {
1223 var tokens = languageCode.split('-');
1224 var main = tokens[0];
1226 // See also: chrome/renderer/translate/translate_helper.cc.
1234 if (main in synonyms) {
1235 return synonyms[main];
1236 } else if (main == 'zh') {
1237 // In Translation, general Chinese is not used, and the sub code is
1238 // necessary as a language code for Translate server.
1239 return languageCode;
1247 * Shows the node at |index| in |nodes|, hides all others.
1248 * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
1249 * @param {number} index The index of |nodes| to show.
1251 function showMutuallyExclusiveNodes(nodes, index) {
1252 assert(index >= 0 && index < nodes.length);
1253 for (var i = 0; i < nodes.length; ++i) {
1254 assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
1255 nodes[i].hidden = i != index;
1260 * Chrome callback for when the UI language preference is saved.
1261 * @param {string} languageCode The newly selected language to use.
1263 LanguageOptions.uiLanguageSaved = function(languageCode) {
1264 this.prospectiveUiLanguageCode_ = languageCode;
1266 // If the user is no longer on the same language code, ignore.
1267 if ($('language-options-list').getSelectedLanguageCode() != languageCode)
1270 // Special case for when a user changes to a different language, and changes
1271 // back to the same language without having restarted Chrome or logged
1272 // in/out of ChromeOS.
1273 if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
1274 LanguageOptions.getInstance().currentLocaleWasReselected();
1278 // Otherwise, show a notification telling the user that their changes will
1279 // only take effect after restart.
1280 showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1281 $('language-options-ui-notification-bar')], 1);
1284 LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1285 LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1288 LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1289 LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1292 LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1293 LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1296 LanguageOptions.onComponentManagerInitialized = function(componentImes) {
1297 LanguageOptions.getInstance().appendComponentExtensionIme_(componentImes);
1302 LanguageOptions: LanguageOptions