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