summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/options/language_options.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/options/language_options.js')
-rw-r--r--chromium/chrome/browser/resources/options/language_options.js1304
1 files changed, 1304 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/options/language_options.js b/chromium/chrome/browser/resources/options/language_options.js
new file mode 100644
index 00000000000..3ebf7d2665b
--- /dev/null
+++ b/chromium/chrome/browser/resources/options/language_options.js
@@ -0,0 +1,1304 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(kochi): Generalize the notification as a component and put it
+// in js/cr/ui/notification.js .
+
+cr.define('options', function() {
+ /** @const */ var OptionsPage = options.OptionsPage;
+ /** @const */ var LanguageList = options.LanguageList;
+
+ /**
+ * Spell check dictionary download status.
+ * @type {Enum}
+ */
+ /** @const*/ var DOWNLOAD_STATUS = {
+ IN_PROGRESS: 1,
+ FAILED: 2
+ };
+
+ /**
+ * The preference is a boolean that enables/disables spell checking.
+ * @type {string}
+ * @const
+ */
+ var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
+
+ /**
+ * The preference is a CSV string that describes preload engines
+ * (i.e. active input methods).
+ * @type {string}
+ * @const
+ */
+ var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
+
+ /**
+ * The preference that lists the extension IMEs that are enabled in the
+ * language menu.
+ * @type {string}
+ * @const
+ */
+ var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
+
+ /**
+ * The preference that lists the languages which are not translated.
+ * @type {string}
+ * @const
+ */
+ var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
+
+ /**
+ * The preference key that is a string that describes the spell check
+ * dictionary language, like "en-US".
+ * @type {string}
+ * @const
+ */
+ var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary';
+
+ /**
+ * The preference that indicates if the Translate feature is enabled.
+ * @type {string}
+ * @const
+ */
+ var ENABLE_TRANSLATE = 'translate.enabled';
+
+ /////////////////////////////////////////////////////////////////////////////
+ // LanguageOptions class:
+
+ /**
+ * Encapsulated handling of ChromeOS language options page.
+ * @constructor
+ */
+ function LanguageOptions(model) {
+ OptionsPage.call(this, 'languages',
+ loadTimeData.getString('languagePageTabTitle'),
+ 'languagePage');
+ }
+
+ cr.addSingletonGetter(LanguageOptions);
+
+ // Inherit LanguageOptions from OptionsPage.
+ LanguageOptions.prototype = {
+ __proto__: OptionsPage.prototype,
+
+ /* For recording the prospective language (the next locale after relaunch).
+ * @type {?string}
+ * @private
+ */
+ prospectiveUiLanguageCode_: null,
+
+ /*
+ * Map from language code to spell check dictionary download status for that
+ * language.
+ * @type {Array}
+ * @private
+ */
+ spellcheckDictionaryDownloadStatus_: [],
+
+ /**
+ * Number of times a spell check dictionary download failed.
+ * @type {int}
+ * @private
+ */
+ spellcheckDictionaryDownloadFailures_: 0,
+
+ /**
+ * The list of preload engines, like ['mozc', 'pinyin'].
+ * @type {Array}
+ * @private
+ */
+ preloadEngines_: [],
+
+ /**
+ * The list of extension IMEs that are enabled out of the language menu.
+ * @type {Array}
+ * @private
+ */
+ enabledExtensionImes_: [],
+
+ /**
+ * The list of the languages which is not translated.
+ * @type {Array}
+ * @private
+ */
+ translateBlockedLanguages_: [],
+
+ /**
+ * The list of the languages supported by Translate server
+ * @type {Array}
+ * @private
+ */
+ translateSupportedLanguages_: [],
+
+ /**
+ * The preference is a string that describes the spell check dictionary
+ * language, like "en-US".
+ * @type {string}
+ * @private
+ */
+ spellCheckDictionary_: '',
+
+ /**
+ * The map of language code to input method IDs, like:
+ * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
+ * @type {Object}
+ * @private
+ */
+ languageCodeToInputMethodIdsMap_: {},
+
+ /**
+ * The value that indicates if Translate feature is enabled or not.
+ * @type {boolean}
+ * @private
+ */
+ enableTranslate_: false,
+
+ /**
+ * Initializes LanguageOptions page.
+ * Calls base class implementation to start preference initialization.
+ */
+ initializePage: function() {
+ OptionsPage.prototype.initializePage.call(this);
+
+ var languageOptionsList = $('language-options-list');
+ LanguageList.decorate(languageOptionsList);
+
+ languageOptionsList.addEventListener('change',
+ this.handleLanguageOptionsListChange_.bind(this));
+ languageOptionsList.addEventListener('save',
+ this.handleLanguageOptionsListSave_.bind(this));
+
+ this.prospectiveUiLanguageCode_ =
+ loadTimeData.getString('prospectiveUiLanguageCode');
+ this.addEventListener('visibleChange',
+ this.handleVisibleChange_.bind(this));
+
+ if (cr.isChromeOS) {
+ this.initializeInputMethodList_();
+ this.initializeLanguageCodeToInputMethodIdsMap_();
+ }
+
+ var checkbox = $('offer-to-translate-in-this-language');
+ checkbox.addEventListener('click',
+ this.handleOfferToTranslateCheckboxClick_.bind(this));
+
+ Preferences.getInstance().addEventListener(
+ TRANSLATE_BLOCKED_LANGUAGES_PREF,
+ this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
+ Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
+ this.handleSpellCheckDictionaryPrefChange_.bind(this));
+ Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
+ this.handleEnableTranslatePrefChange_.bind(this));
+ this.translateSupportedLanguages_ =
+ loadTimeData.getValue('translateSupportedLanguages');
+
+ // Set up add button.
+ $('language-options-add-button').onclick = function(e) {
+ // Add the language without showing the overlay if it's specified in
+ // the URL hash (ex. lang_add=ja). Used for automated testing.
+ var match = document.location.hash.match(/\blang_add=([\w-]+)/);
+ if (match) {
+ var addLanguageCode = match[1];
+ $('language-options-list').addLanguage(addLanguageCode);
+ this.addBlockedLanguage_(addLanguageCode);
+ } else {
+ OptionsPage.navigateToPage('addLanguage');
+ }
+ }.bind(this);
+
+ if (!cr.isMac) {
+ // Set up the button for editing custom spelling dictionary.
+ $('edit-dictionary-button').onclick = function(e) {
+ OptionsPage.navigateToPage('editDictionary');
+ };
+ $('dictionary-download-retry-button').onclick = function(e) {
+ chrome.send('retryDictionaryDownload');
+ };
+ }
+
+ // Listen to add language dialog ok button.
+ $('add-language-overlay-ok-button').addEventListener(
+ 'click', this.handleAddLanguageOkButtonClick_.bind(this));
+
+ if (!cr.isChromeOS) {
+ // Show experimental features if enabled.
+ if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
+ $('auto-spell-correction-option').hidden = false;
+
+ // Handle spell check enable/disable.
+ if (!cr.isMac) {
+ Preferences.getInstance().addEventListener(
+ ENABLE_SPELL_CHECK_PREF,
+ this.updateEnableSpellCheck_.bind(this));
+ }
+ }
+
+ // Handle clicks on "Use this language for spell checking" button.
+ if (!cr.isMac) {
+ var spellCheckLanguageButton = getRequiredElement(
+ 'language-options-spell-check-language-button');
+ spellCheckLanguageButton.addEventListener(
+ 'click',
+ this.handleSpellCheckLanguageButtonClick_.bind(this));
+ }
+
+ if (cr.isChromeOS) {
+ $('language-options-ui-restart-button').onclick = function() {
+ chrome.send('uiLanguageRestart');
+ };
+ }
+
+ $('language-confirm').onclick =
+ OptionsPage.closeOverlay.bind(OptionsPage);
+ },
+
+ /**
+ * Initializes the input method list.
+ */
+ initializeInputMethodList_: function() {
+ var inputMethodList = $('language-options-input-method-list');
+ var inputMethodPrototype = $('language-options-input-method-template');
+
+ // Add all input methods, but make all of them invisible here. We'll
+ // change the visibility in handleLanguageOptionsListChange_() based
+ // on the selected language. Note that we only have less than 100
+ // input methods, so creating DOM nodes at once here should be ok.
+ this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
+ this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
+ this.appendComponentExtensionIme_(
+ loadTimeData.getValue('componentExtensionImeList'));
+
+ // Listen to pref change once the input method list is initialized.
+ Preferences.getInstance().addEventListener(
+ PRELOAD_ENGINES_PREF,
+ this.handlePreloadEnginesPrefChange_.bind(this));
+ Preferences.getInstance().addEventListener(
+ ENABLED_EXTENSION_IME_PREF,
+ this.handleEnabledExtensionsPrefChange_.bind(this));
+ },
+
+ /**
+ * Appends input method lists based on component extension ime list.
+ * @param {!Array} componentExtensionImeList A list of input method
+ * descriptors.
+ * @private
+ */
+ appendComponentExtensionIme_: function(componentExtensionImeList) {
+ this.appendInputMethodElement_(componentExtensionImeList);
+
+ for (var i = 0; i < componentExtensionImeList.length; i++) {
+ var inputMethod = componentExtensionImeList[i];
+ for (var languageCode in inputMethod.languageCodeSet) {
+ if (languageCode in this.languageCodeToInputMethodIdsMap_) {
+ this.languageCodeToInputMethodIdsMap_[languageCode].push(
+ inputMethod.id);
+ } else {
+ this.languageCodeToInputMethodIdsMap_[languageCode] =
+ [inputMethod.id];
+ }
+ }
+ }
+ },
+
+ /**
+ * Appends input methods into input method list.
+ * @param {!Array} inputMethods A list of input method descriptors.
+ * @private
+ */
+ appendInputMethodElement_: function(inputMethods) {
+ var inputMethodList = $('language-options-input-method-list');
+ var inputMethodTemplate = $('language-options-input-method-template');
+
+ for (var i = 0; i < inputMethods.length; i++) {
+ var inputMethod = inputMethods[i];
+ var element = inputMethodTemplate.cloneNode(true);
+ element.id = '';
+ element.languageCodeSet = inputMethod.languageCodeSet;
+
+ var input = element.querySelector('input');
+ input.inputMethodId = inputMethod.id;
+ var span = element.querySelector('span');
+ span.textContent = inputMethod.displayName;
+
+ if (inputMethod.optionsPage) {
+ var button = document.createElement('button');
+ button.textContent = loadTimeData.getString('configure');
+ button.inputMethodId = inputMethod.id;
+ button.onclick = function(inputMethodId, e) {
+ chrome.send('inputMethodOptionsOpen', [inputMethodId]);
+ }.bind(this, inputMethod.id);
+ element.appendChild(button);
+ }
+
+ // Listen to user clicks.
+ input.addEventListener('click',
+ this.handleCheckboxClick_.bind(this));
+ inputMethodList.appendChild(element);
+ }
+ },
+
+ /**
+ * Adds a language to the preference 'translate_blocked_languages'. If
+ * |langCode| is already added, nothing happens. |langCode| is converted
+ * to a Translate language synonym before added.
+ * @param {string} langCode A language code like 'en'
+ * @private
+ */
+ addBlockedLanguage_: function(langCode) {
+ langCode = this.convertLangCodeForTranslation_(langCode);
+ if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
+ this.translateBlockedLanguages_.push(langCode);
+ Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
+ this.translateBlockedLanguages_, true);
+ }
+ },
+
+ /**
+ * Removes a language from the preference 'translate_blocked_languages'.
+ * If |langCode| doesn't exist in the preference, nothing happens.
+ * |langCode| is converted to a Translate language synonym before removed.
+ * @param {string} langCode A language code like 'en'
+ * @private
+ */
+ removeBlockedLanguage_: function(langCode) {
+ langCode = this.convertLangCodeForTranslation_(langCode);
+ if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
+ this.translateBlockedLanguages_ =
+ this.translateBlockedLanguages_.filter(
+ function(langCodeNotTranslated) {
+ return langCodeNotTranslated != langCode;
+ });
+ Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
+ this.translateBlockedLanguages_, true);
+ }
+ },
+
+ /**
+ * Handles OptionsPage's visible property change event.
+ * @param {Event} e Property change event.
+ * @private
+ */
+ handleVisibleChange_: function(e) {
+ if (this.visible) {
+ $('language-options-list').redraw();
+ chrome.send('languageOptionsOpen');
+ }
+ },
+
+ /**
+ * Handles languageOptionsList's change event.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleLanguageOptionsListChange_: function(e) {
+ var languageOptionsList = $('language-options-list');
+ var languageCode = languageOptionsList.getSelectedLanguageCode();
+
+ // If there's no selection, just return.
+ if (!languageCode)
+ return;
+
+ // Select the language if it's specified in the URL hash (ex. lang=ja).
+ // Used for automated testing.
+ var match = document.location.hash.match(/\blang=([\w-]+)/);
+ if (match) {
+ var specifiedLanguageCode = match[1];
+ if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
+ languageCode = specifiedLanguageCode;
+ }
+ }
+
+ this.updateOfferToTranslateCheckbox_(languageCode);
+
+ if (cr.isWindows || cr.isChromeOS)
+ this.updateUiLanguageButton_(languageCode);
+
+ this.updateSelectedLanguageName_(languageCode);
+
+ if (!cr.isMac)
+ this.updateSpellCheckLanguageButton_(languageCode);
+
+ if (cr.isChromeOS)
+ this.updateInputMethodList_(languageCode);
+
+ this.updateLanguageListInAddLanguageOverlay_();
+ },
+
+ /**
+ * Happens when a user changes back to the language they're currently using.
+ */
+ currentLocaleWasReselected: function() {
+ this.updateUiLanguageButton_(
+ loadTimeData.getString('currentUiLanguageCode'));
+ },
+
+ /**
+ * Handles languageOptionsList's save event.
+ * @param {Event} e Save event.
+ * @private
+ */
+ handleLanguageOptionsListSave_: function(e) {
+ if (cr.isChromeOS) {
+ // Sort the preload engines per the saved languages before save.
+ this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
+ this.savePreloadEnginesPref_();
+ }
+ },
+
+ /**
+ * Sorts preloadEngines_ by languageOptionsList's order.
+ * @param {Array} preloadEngines List of preload engines.
+ * @return {Array} Returns sorted preloadEngines.
+ * @private
+ */
+ sortPreloadEngines_: function(preloadEngines) {
+ // For instance, suppose we have two languages and associated input
+ // methods:
+ //
+ // - Korean: hangul
+ // - Chinese: pinyin
+ //
+ // The preloadEngines preference should look like "hangul,pinyin".
+ // If the user reverse the order, the preference should be reorderd
+ // to "pinyin,hangul".
+ var languageOptionsList = $('language-options-list');
+ var languageCodes = languageOptionsList.getLanguageCodes();
+
+ // Convert the list into a dictonary for simpler lookup.
+ var preloadEngineSet = {};
+ for (var i = 0; i < preloadEngines.length; i++) {
+ preloadEngineSet[preloadEngines[i]] = true;
+ }
+
+ // Create the new preload engine list per the language codes.
+ var newPreloadEngines = [];
+ for (var i = 0; i < languageCodes.length; i++) {
+ var languageCode = languageCodes[i];
+ var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
+ languageCode];
+ if (!inputMethodIds)
+ continue;
+
+ // Check if we have active input methods associated with the language.
+ for (var j = 0; j < inputMethodIds.length; j++) {
+ var inputMethodId = inputMethodIds[j];
+ if (inputMethodId in preloadEngineSet) {
+ // If we have, add it to the new engine list.
+ newPreloadEngines.push(inputMethodId);
+ // And delete it from the set. This is necessary as one input
+ // method can be associated with more than one language thus
+ // we should avoid having duplicates in the new list.
+ delete preloadEngineSet[inputMethodId];
+ }
+ }
+ }
+
+ return newPreloadEngines;
+ },
+
+ /**
+ * Initializes the map of language code to input method IDs.
+ * @private
+ */
+ initializeLanguageCodeToInputMethodIdsMap_: function() {
+ var inputMethodList = loadTimeData.getValue('inputMethodList');
+ for (var i = 0; i < inputMethodList.length; i++) {
+ var inputMethod = inputMethodList[i];
+ for (var languageCode in inputMethod.languageCodeSet) {
+ if (languageCode in this.languageCodeToInputMethodIdsMap_) {
+ this.languageCodeToInputMethodIdsMap_[languageCode].push(
+ inputMethod.id);
+ } else {
+ this.languageCodeToInputMethodIdsMap_[languageCode] =
+ [inputMethod.id];
+ }
+ }
+ }
+ },
+
+ /**
+ * Updates the currently selected language name.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateSelectedLanguageName_: function(languageCode) {
+ var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
+ languageCode);
+ var languageDisplayName = languageInfo.displayName;
+ var languageNativeDisplayName = languageInfo.nativeDisplayName;
+ var textDirection = languageInfo.textDirection;
+
+ // If the native name is different, add it.
+ if (languageDisplayName != languageNativeDisplayName) {
+ languageDisplayName += ' - ' + languageNativeDisplayName;
+ }
+
+ // Update the currently selected language name.
+ var languageName = $('language-options-language-name');
+ languageName.textContent = languageDisplayName;
+ languageName.dir = textDirection;
+ },
+
+ /**
+ * Updates the UI language button.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateUiLanguageButton_: function(languageCode) {
+ var uiLanguageButton = $('language-options-ui-language-button');
+ var uiLanguageMessage = $('language-options-ui-language-message');
+ var uiLanguageNotification = $('language-options-ui-notification-bar');
+
+ // Remove the event listener and add it back if useful.
+ uiLanguageButton.onclick = null;
+
+ // Unhide the language button every time, as it could've been previously
+ // hidden by a language change.
+ uiLanguageButton.hidden = false;
+
+ if (languageCode == this.prospectiveUiLanguageCode_) {
+ uiLanguageMessage.textContent =
+ loadTimeData.getString('isDisplayedInThisLanguage');
+ showMutuallyExclusiveNodes(
+ [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
+ } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
+ if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
+ // In the guest mode for ChromeOS, changing UI language does not make
+ // sense because it does not take effect after browser restart.
+ uiLanguageButton.hidden = true;
+ uiLanguageMessage.hidden = true;
+ } else {
+ uiLanguageButton.textContent =
+ loadTimeData.getString('displayInThisLanguage');
+ showMutuallyExclusiveNodes(
+ [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
+ uiLanguageButton.onclick = function(e) {
+ chrome.send('uiLanguageChange', [languageCode]);
+ };
+ }
+ } else {
+ uiLanguageMessage.textContent =
+ loadTimeData.getString('cannotBeDisplayedInThisLanguage');
+ showMutuallyExclusiveNodes(
+ [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
+ }
+ },
+
+ /**
+ * Updates the spell check language button.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateSpellCheckLanguageButton_: function(languageCode) {
+ var spellCheckLanguageSection = $('language-options-spellcheck');
+ var spellCheckLanguageButton =
+ $('language-options-spell-check-language-button');
+ var spellCheckLanguageMessage =
+ $('language-options-spell-check-language-message');
+ var dictionaryDownloadInProgress =
+ $('language-options-dictionary-downloading-message');
+ var dictionaryDownloadFailed =
+ $('language-options-dictionary-download-failed-message');
+ var dictionaryDownloadFailHelp =
+ $('language-options-dictionary-download-fail-help-message');
+ spellCheckLanguageSection.hidden = false;
+ spellCheckLanguageMessage.hidden = true;
+ spellCheckLanguageButton.hidden = true;
+ dictionaryDownloadInProgress.hidden = true;
+ dictionaryDownloadFailed.hidden = true;
+ dictionaryDownloadFailHelp.hidden = true;
+
+ if (languageCode == this.spellCheckDictionary_) {
+ if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
+ spellCheckLanguageMessage.textContent =
+ loadTimeData.getString('isUsedForSpellChecking');
+ showMutuallyExclusiveNodes(
+ [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
+ } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
+ DOWNLOAD_STATUS.IN_PROGRESS) {
+ dictionaryDownloadInProgress.hidden = false;
+ } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
+ DOWNLOAD_STATUS.FAILED) {
+ spellCheckLanguageSection.hidden = true;
+ dictionaryDownloadFailed.hidden = false;
+ if (this.spellcheckDictionaryDownloadFailures_ > 1)
+ dictionaryDownloadFailHelp.hidden = false;
+ }
+ } else if (languageCode in
+ loadTimeData.getValue('spellCheckLanguageCodeSet')) {
+ spellCheckLanguageButton.textContent =
+ loadTimeData.getString('useThisForSpellChecking');
+ showMutuallyExclusiveNodes(
+ [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
+ spellCheckLanguageButton.languageCode = languageCode;
+ } else if (!languageCode) {
+ spellCheckLanguageButton.hidden = true;
+ spellCheckLanguageMessage.hidden = true;
+ } else {
+ spellCheckLanguageMessage.textContent =
+ loadTimeData.getString('cannotBeUsedForSpellChecking');
+ showMutuallyExclusiveNodes(
+ [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
+ }
+ },
+
+ /**
+ * Updates the checkbox for stopping translation.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateOfferToTranslateCheckbox_: function(languageCode) {
+ var div = $('language-options-offer-to-translate');
+
+ // Translation server supports Chinese (Transitional) and Chinese
+ // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
+ // show this preference when general Chinese is selected.
+ if (languageCode != 'zh') {
+ div.hidden = false;
+ } else {
+ div.hidden = true;
+ return;
+ }
+
+ var offerToTranslate = div.querySelector('div');
+ var cannotTranslate = $('cannot-translate-in-this-language');
+ var nodes = [offerToTranslate, cannotTranslate];
+
+ var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
+ if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
+ showMutuallyExclusiveNodes(nodes, 0);
+ } else {
+ showMutuallyExclusiveNodes(nodes, 1);
+ return;
+ }
+
+ var checkbox = $('offer-to-translate-in-this-language');
+
+ if (!this.enableTranslate_) {
+ checkbox.disabled = true;
+ checkbox.checked = false;
+ return;
+ }
+
+ // If the language corresponds to the default target language (in most
+ // cases, the user's locale language), "Offer to translate" checkbox
+ // should be always unchecked.
+ var defaultTargetLanguage =
+ loadTimeData.getString('defaultTargetLanguage');
+ if (convertedLangCode == defaultTargetLanguage) {
+ checkbox.disabled = true;
+ checkbox.checked = false;
+ return;
+ }
+
+ checkbox.disabled = false;
+
+ var blockedLanguages = this.translateBlockedLanguages_;
+ var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
+ checkbox.checked = checked;
+ },
+
+ /**
+ * Updates the input method list.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateInputMethodList_: function(languageCode) {
+ // Give one of the checkboxes or buttons focus, if it's specified in the
+ // URL hash (ex. focus=mozc). Used for automated testing.
+ var focusInputMethodId = -1;
+ var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
+ if (match) {
+ focusInputMethodId = match[1];
+ }
+ // Change the visibility of the input method list. Input methods that
+ // matches |languageCode| will become visible.
+ var inputMethodList = $('language-options-input-method-list');
+ var methods = inputMethodList.querySelectorAll('.input-method');
+ for (var i = 0; i < methods.length; i++) {
+ var method = methods[i];
+ if (languageCode in method.languageCodeSet) {
+ method.hidden = false;
+ var input = method.querySelector('input');
+ // Give it focus if the ID matches.
+ if (input.inputMethodId == focusInputMethodId) {
+ input.focus();
+ }
+ } else {
+ method.hidden = true;
+ }
+ }
+
+ $('language-options-input-method-none').hidden =
+ (languageCode in this.languageCodeToInputMethodIdsMap_);
+
+ if (focusInputMethodId == 'add') {
+ $('language-options-add-button').focus();
+ }
+ },
+
+ /**
+ * Updates the language list in the add language overlay.
+ * @param {string} languageCode Language code (ex. "fr").
+ * @private
+ */
+ updateLanguageListInAddLanguageOverlay_: function(languageCode) {
+ // Change the visibility of the language list in the add language
+ // overlay. Languages that are already active will become invisible,
+ // so that users don't add the same language twice.
+ var languageOptionsList = $('language-options-list');
+ var languageCodes = languageOptionsList.getLanguageCodes();
+ var languageCodeSet = {};
+ for (var i = 0; i < languageCodes.length; i++) {
+ languageCodeSet[languageCodes[i]] = true;
+ }
+
+ var addLanguageList = $('add-language-overlay-language-list');
+ var options = addLanguageList.querySelectorAll('option');
+ assert(options.length > 0);
+ var selectedFirstItem = false;
+ for (var i = 0; i < options.length; i++) {
+ var option = options[i];
+ option.hidden = option.value in languageCodeSet;
+ if (!option.hidden && !selectedFirstItem) {
+ // Select first visible item, otherwise previously selected hidden
+ // item will be selected by default at the next time.
+ option.selected = true;
+ selectedFirstItem = true;
+ }
+ }
+ },
+
+ /**
+ * Handles preloadEnginesPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handlePreloadEnginesPrefChange_: function(e) {
+ var value = e.value.value;
+ this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
+ this.updateCheckboxesFromPreloadEngines_();
+ $('language-options-list').updateDeletable();
+ },
+
+ /**
+ * Handles enabledExtensionImePref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleEnabledExtensionsPrefChange_: function(e) {
+ var value = e.value.value;
+ this.enabledExtensionImes_ = value.split(',');
+ this.updateCheckboxesFromEnabledExtensions_();
+ },
+
+ /**
+ * Handles offer-to-translate checkbox's click event.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleOfferToTranslateCheckboxClick_: function(e) {
+ var checkbox = e.target;
+ var checked = checkbox.checked;
+
+ var languageOptionsList = $('language-options-list');
+ var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
+
+ if (checked)
+ this.removeBlockedLanguage_(selectedLanguageCode);
+ else
+ this.addBlockedLanguage_(selectedLanguageCode);
+ },
+
+ /**
+ * Handles input method checkbox's click event.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleCheckboxClick_: function(e) {
+ var checkbox = e.target;
+
+ if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
+ this.updateEnabledExtensionsFromCheckboxes_();
+ this.saveEnabledExtensionPref_();
+ return;
+ }
+ if (this.preloadEngines_.length == 1 && !checkbox.checked) {
+ // Don't allow disabling the last input method.
+ this.showNotification_(
+ loadTimeData.getString('pleaseAddAnotherInputMethod'),
+ loadTimeData.getString('okButton'));
+ checkbox.checked = true;
+ return;
+ }
+ if (checkbox.checked) {
+ chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
+ } else {
+ chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
+ }
+ this.updatePreloadEnginesFromCheckboxes_();
+ this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
+ this.savePreloadEnginesPref_();
+ },
+
+ handleAddLanguageOkButtonClick_: function() {
+ var languagesSelect = $('add-language-overlay-language-list');
+ var selectedIndex = languagesSelect.selectedIndex;
+ if (selectedIndex >= 0) {
+ var selection = languagesSelect.options[selectedIndex];
+ var langCode = String(selection.value);
+ $('language-options-list').addLanguage(langCode);
+ this.addBlockedLanguage_(langCode);
+ OptionsPage.closeOverlay();
+ }
+ },
+
+ /**
+ * Checks if languageCode is deletable or not.
+ * @param {string} languageCode the languageCode to check for deletability.
+ */
+ languageIsDeletable: function(languageCode) {
+ // Don't allow removing the language if it's a UI language.
+ if (languageCode == this.prospectiveUiLanguageCode_)
+ return false;
+ return (!cr.isChromeOS ||
+ this.canDeleteLanguage_(languageCode));
+ },
+
+ /**
+ * Handles browse.enable_spellchecking change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ updateEnableSpellCheck_: function() {
+ var value = !$('enable-spell-check').checked;
+ $('language-options-spell-check-language-button').disabled = value;
+ if (!cr.IsMac)
+ $('edit-dictionary-button').hidden = value;
+ },
+
+ /**
+ * Handles translateBlockedLanguagesPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleTranslateBlockedLanguagesPrefChange_: function(e) {
+ this.translateBlockedLanguages_ = e.value.value;
+ this.updateOfferToTranslateCheckbox_(
+ $('language-options-list').getSelectedLanguageCode());
+ },
+
+ /**
+ * Handles spellCheckDictionaryPref change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleSpellCheckDictionaryPrefChange_: function(e) {
+ var languageCode = e.value.value;
+ this.spellCheckDictionary_ = languageCode;
+ if (!cr.isMac) {
+ this.updateSpellCheckLanguageButton_(
+ $('language-options-list').getSelectedLanguageCode());
+ }
+ },
+
+ /**
+ * Handles translate.enabled change.
+ * @param {Event} e Change event.
+ * @private
+ */
+ handleEnableTranslatePrefChange_: function(e) {
+ var enabled = e.value.value;
+ this.enableTranslate_ = enabled;
+ this.updateOfferToTranslateCheckbox_(
+ $('language-options-list').getSelectedLanguageCode());
+ },
+
+ /**
+ * Handles spellCheckLanguageButton click.
+ * @param {Event} e Click event.
+ * @private
+ */
+ handleSpellCheckLanguageButtonClick_: function(e) {
+ var languageCode = e.target.languageCode;
+ // Save the preference.
+ Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
+ languageCode, true);
+ chrome.send('spellCheckLanguageChange', [languageCode]);
+ },
+
+ /**
+ * Checks whether it's possible to remove the language specified by
+ * languageCode and returns true if possible. This function returns false
+ * if the removal causes the number of preload engines to be zero.
+ *
+ * @param {string} languageCode Language code (ex. "fr").
+ * @return {boolean} Returns true on success.
+ * @private
+ */
+ canDeleteLanguage_: function(languageCode) {
+ // First create the set of engines to be removed from input methods
+ // associated with the language code.
+ var enginesToBeRemovedSet = {};
+ var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
+
+ // If this language doesn't have any input methods, it can be deleted.
+ if (!inputMethodIds)
+ return true;
+
+ for (var i = 0; i < inputMethodIds.length; i++) {
+ enginesToBeRemovedSet[inputMethodIds[i]] = true;
+ }
+
+ // Then eliminate engines that are also used for other active languages.
+ // For instance, if "xkb:us::eng" is used for both English and Filipino.
+ var languageCodes = $('language-options-list').getLanguageCodes();
+ for (var i = 0; i < languageCodes.length; i++) {
+ // Skip the target language code.
+ if (languageCodes[i] == languageCode) {
+ continue;
+ }
+ // Check if input methods used in this language are included in
+ // enginesToBeRemovedSet. If so, eliminate these from the set, so
+ // we don't remove this time.
+ var inputMethodIdsForAnotherLanguage =
+ this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
+ if (!inputMethodIdsForAnotherLanguage)
+ continue;
+
+ for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
+ var inputMethodId = inputMethodIdsForAnotherLanguage[j];
+ if (inputMethodId in enginesToBeRemovedSet) {
+ delete enginesToBeRemovedSet[inputMethodId];
+ }
+ }
+ }
+
+ // Update the preload engine list with the to-be-removed set.
+ var newPreloadEngines = [];
+ for (var i = 0; i < this.preloadEngines_.length; i++) {
+ if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
+ newPreloadEngines.push(this.preloadEngines_[i]);
+ }
+ }
+ // Don't allow this operation if it causes the number of preload
+ // engines to be zero.
+ return (newPreloadEngines.length > 0);
+ },
+
+ /**
+ * Saves the enabled extension preference.
+ * @private
+ */
+ saveEnabledExtensionPref_: function() {
+ Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
+ this.enabledExtensionImes_.join(','), true);
+ },
+
+ /**
+ * Updates the checkboxes in the input method list from the enabled
+ * extensions preference.
+ * @private
+ */
+ updateCheckboxesFromEnabledExtensions_: function() {
+ // Convert the list into a dictonary for simpler lookup.
+ var dictionary = {};
+ for (var i = 0; i < this.enabledExtensionImes_.length; i++)
+ dictionary[this.enabledExtensionImes_[i]] = true;
+
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
+ checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
+ }
+ var configureButtons = inputMethodList.querySelectorAll('button');
+ for (var i = 0; i < configureButtons.length; i++) {
+ if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
+ configureButtons[i].hidden =
+ !(configureButtons[i].inputMethodId in dictionary);
+ }
+ }
+ },
+
+ /**
+ * Updates the enabled extensions preference from the checkboxes in the
+ * input method list.
+ * @private
+ */
+ updateEnabledExtensionsFromCheckboxes_: function() {
+ this.enabledExtensionImes_ = [];
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
+ if (checkboxes[i].checked)
+ this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
+ }
+ }
+ },
+
+ /**
+ * Saves the preload engines preference.
+ * @private
+ */
+ savePreloadEnginesPref_: function() {
+ Preferences.setStringPref(PRELOAD_ENGINES_PREF,
+ this.preloadEngines_.join(','), true);
+ },
+
+ /**
+ * Updates the checkboxes in the input method list from the preload
+ * engines preference.
+ * @private
+ */
+ updateCheckboxesFromPreloadEngines_: function() {
+ // Convert the list into a dictonary for simpler lookup.
+ var dictionary = {};
+ for (var i = 0; i < this.preloadEngines_.length; i++) {
+ dictionary[this.preloadEngines_[i]] = true;
+ }
+
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
+ checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
+ }
+ var configureButtons = inputMethodList.querySelectorAll('button');
+ for (var i = 0; i < configureButtons.length; i++) {
+ if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
+ configureButtons[i].hidden =
+ !(configureButtons[i].inputMethodId in dictionary);
+ }
+ }
+ },
+
+ /**
+ * Updates the preload engines preference from the checkboxes in the
+ * input method list.
+ * @private
+ */
+ updatePreloadEnginesFromCheckboxes_: function() {
+ this.preloadEngines_ = [];
+ var inputMethodList = $('language-options-input-method-list');
+ var checkboxes = inputMethodList.querySelectorAll('input');
+ for (var i = 0; i < checkboxes.length; i++) {
+ if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
+ if (checkboxes[i].checked)
+ this.preloadEngines_.push(checkboxes[i].inputMethodId);
+ }
+ }
+ var languageOptionsList = $('language-options-list');
+ languageOptionsList.updateDeletable();
+ },
+
+ /**
+ * Filters bad preload engines in case bad preload engines are
+ * stored in the preference. Removes duplicates as well.
+ * @param {Array} preloadEngines List of preload engines.
+ * @private
+ */
+ filterBadPreloadEngines_: function(preloadEngines) {
+ // Convert the list into a dictonary for simpler lookup.
+ var dictionary = {};
+ var list = loadTimeData.getValue('inputMethodList');
+ for (var i = 0; i < list.length; i++) {
+ dictionary[list[i].id] = true;
+ }
+
+ var enabledPreloadEngines = [];
+ var seen = {};
+ for (var i = 0; i < preloadEngines.length; i++) {
+ // Check if the preload engine is present in the
+ // dictionary, and not duplicate. Otherwise, skip it.
+ // Component Extension IME should be handled same as preloadEngines and
+ // "_comp_" is the special prefix of its ID.
+ if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
+ /^_comp_/.test(preloadEngines[i])) {
+ enabledPreloadEngines.push(preloadEngines[i]);
+ seen[preloadEngines[i]] = true;
+ }
+ }
+ return enabledPreloadEngines;
+ },
+
+ // TODO(kochi): This is an adapted copy from new_tab.js.
+ // If this will go as final UI, refactor this to share the component with
+ // new new tab page.
+ /**
+ * Shows notification
+ * @private
+ */
+ notificationTimeout_: null,
+ showNotification_: function(text, actionText, opt_delay) {
+ var notificationElement = $('notification');
+ var actionLink = notificationElement.querySelector('.link-color');
+ var delay = opt_delay || 10000;
+
+ function show() {
+ window.clearTimeout(this.notificationTimeout_);
+ notificationElement.classList.add('show');
+ document.body.classList.add('notification-shown');
+ }
+
+ function hide() {
+ window.clearTimeout(this.notificationTimeout_);
+ notificationElement.classList.remove('show');
+ document.body.classList.remove('notification-shown');
+ // Prevent tabbing to the hidden link.
+ actionLink.tabIndex = -1;
+ // Setting tabIndex to -1 only prevents future tabbing to it. If,
+ // however, the user switches window or a tab and then moves back to
+ // this tab the element may gain focus. We therefore make sure that we
+ // blur the element so that the element focus is not restored when
+ // coming back to this window.
+ actionLink.blur();
+ }
+
+ function delayedHide() {
+ this.notificationTimeout_ = window.setTimeout(hide, delay);
+ }
+
+ notificationElement.firstElementChild.textContent = text;
+ actionLink.textContent = actionText;
+
+ actionLink.onclick = hide;
+ actionLink.onkeydown = function(e) {
+ if (e.keyIdentifier == 'Enter') {
+ hide();
+ }
+ };
+ notificationElement.onmouseover = show;
+ notificationElement.onmouseout = delayedHide;
+ actionLink.onfocus = show;
+ actionLink.onblur = delayedHide;
+ // Enable tabbing to the link now that it is shown.
+ actionLink.tabIndex = 0;
+
+ show();
+ delayedHide();
+ },
+
+ onDictionaryDownloadBegin_: function(languageCode) {
+ this.spellcheckDictionaryDownloadStatus_[languageCode] =
+ DOWNLOAD_STATUS.IN_PROGRESS;
+ if (!cr.isMac &&
+ languageCode ==
+ $('language-options-list').getSelectedLanguageCode()) {
+ this.updateSpellCheckLanguageButton_(languageCode);
+ }
+ },
+
+ onDictionaryDownloadSuccess_: function(languageCode) {
+ delete this.spellcheckDictionaryDownloadStatus_[languageCode];
+ this.spellcheckDictionaryDownloadFailures_ = 0;
+ if (!cr.isMac &&
+ languageCode ==
+ $('language-options-list').getSelectedLanguageCode()) {
+ this.updateSpellCheckLanguageButton_(languageCode);
+ }
+ },
+
+ onDictionaryDownloadFailure_: function(languageCode) {
+ this.spellcheckDictionaryDownloadStatus_[languageCode] =
+ DOWNLOAD_STATUS.FAILED;
+ this.spellcheckDictionaryDownloadFailures_++;
+ if (!cr.isMac &&
+ languageCode ==
+ $('language-options-list').getSelectedLanguageCode()) {
+ this.updateSpellCheckLanguageButton_(languageCode);
+ }
+ },
+
+ /*
+ * Converts the language code for Translation. There are some differences
+ * between the language set for Translation and that for Accept-Language.
+ * @param {string} languageCode The language code like 'fr'.
+ * @return {string} The converted language code.
+ * @private
+ */
+ convertLangCodeForTranslation_: function(languageCode) {
+ var tokens = languageCode.split('-');
+ var main = tokens[0];
+
+ // See also: chrome/renderer/translate/translate_helper.cc.
+ var synonyms = {
+ 'nb': 'no',
+ 'he': 'iw',
+ 'jv': 'jw',
+ 'fil': 'tl',
+ };
+
+ if (main in synonyms) {
+ return synonyms[main];
+ } else if (main == 'zh') {
+ // In Translation, general Chinese is not used, and the sub code is
+ // necessary as a language code for Translate server.
+ return languageCode;
+ }
+
+ return main;
+ },
+ };
+
+ /**
+ * Shows the node at |index| in |nodes|, hides all others.
+ * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
+ * @param {number} index The index of |nodes| to show.
+ */
+ function showMutuallyExclusiveNodes(nodes, index) {
+ assert(index >= 0 && index < nodes.length);
+ for (var i = 0; i < nodes.length; ++i) {
+ assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
+ nodes[i].hidden = i != index;
+ }
+ }
+
+ /**
+ * Chrome callback for when the UI language preference is saved.
+ * @param {string} languageCode The newly selected language to use.
+ */
+ LanguageOptions.uiLanguageSaved = function(languageCode) {
+ this.prospectiveUiLanguageCode_ = languageCode;
+
+ // If the user is no longer on the same language code, ignore.
+ if ($('language-options-list').getSelectedLanguageCode() != languageCode)
+ return;
+
+ // Special case for when a user changes to a different language, and changes
+ // back to the same language without having restarted Chrome or logged
+ // in/out of ChromeOS.
+ if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
+ LanguageOptions.getInstance().currentLocaleWasReselected();
+ return;
+ }
+
+ // Otherwise, show a notification telling the user that their changes will
+ // only take effect after restart.
+ showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
+ $('language-options-ui-notification-bar')], 1);
+ };
+
+ LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
+ LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
+ };
+
+ LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
+ LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
+ };
+
+ LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
+ LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
+ };
+
+ LanguageOptions.onComponentManagerInitialized = function(componentImes) {
+ LanguageOptions.getInstance().appendComponentExtensionIme_(componentImes);
+ };
+
+ // Export
+ return {
+ LanguageOptions: LanguageOptions
+ };
+});