1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // TODO(kochi): Generalize the notification as a component and put it 6 // in js/cr/ui/notification.js . 7 8 cr.define('options', function() { 9 /** @const */ var Page = cr.ui.pageManager.Page; 10 /** @const */ var PageManager = cr.ui.pageManager.PageManager; 11 /** @const */ var LanguageList = options.LanguageList; 12 /** @const */ var ThirdPartyImeConfirmOverlay = 13 options.ThirdPartyImeConfirmOverlay; 14 15 /** 16 * Spell check dictionary download status. 17 * @type {Enum} 18 */ 19 /** @const*/ var DOWNLOAD_STATUS = { 20 IN_PROGRESS: 1, 21 FAILED: 2 22 }; 23 24 /** 25 * The preference is a boolean that enables/disables spell checking. 26 * @type {string} 27 * @const 28 */ 29 var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking'; 30 31 /** 32 * The preference is a CSV string that describes preload engines 33 * (i.e. active input methods). 34 * @type {string} 35 * @const 36 */ 37 var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines'; 38 39 /** 40 * The preference that lists the extension IMEs that are enabled in the 41 * language menu. 42 * @type {string} 43 * @const 44 */ 45 var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes'; 46 47 /** 48 * The preference that lists the languages which are not translated. 49 * @type {string} 50 * @const 51 */ 52 var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages'; 53 54 /** 55 * The preference key that is a string that describes the spell check 56 * dictionary language, like "en-US". 57 * @type {string} 58 * @const 59 */ 60 var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary'; 61 62 /** 63 * The preference that indicates if the Translate feature is enabled. 64 * @type {string} 65 * @const 66 */ 67 var ENABLE_TRANSLATE = 'translate.enabled'; 68 69 ///////////////////////////////////////////////////////////////////////////// 70 // LanguageOptions class: 71 72 /** 73 * Encapsulated handling of ChromeOS language options page. 74 * @constructor 75 * @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