Chromium Code Reviews| Index: chrome/browser/resources/settings/languages_page/languages.js |
| diff --git a/chrome/browser/resources/settings/languages_page/languages.js b/chrome/browser/resources/settings/languages_page/languages.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cb8e7e7342014abacbdcdcadf83e11f19a6d67c0 |
| --- /dev/null |
| +++ b/chrome/browser/resources/settings/languages_page/languages.js |
| @@ -0,0 +1,382 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +/** |
| + * @fileoverview 'cr-settings-languages' provides convenient access to |
| + * Chrome's language and input method settings. |
| + * |
| + * Instances of this element have a 'languages' property, which reflects the |
| + * current language settings. The 'languages' property is read-only, meaning |
| + * hosts using this element cannot change it directly. Instead, changes to |
| + * language settings should be made using this element's public functions. |
| + * |
| + * Use two-way binding syntax to propagate changes from child to host, so that |
| + * changes made internally to 'languages' propagate to your host element: |
| + * |
| + * <template> |
| + * <cr-settings-languages languages="{{languages}}"> |
| + * </cr-settings-languages> |
| + * <div>[[languages.someProperty]]</div> |
| + * </template> |
| + * |
| + * @group Chrome Settings Elements |
| + * @element cr-settings-languages |
| + */ |
| + |
| +/** @typedef {{spellCheckEnabled: boolean}} */ |
| +var LanguageState; |
| + |
| +/** |
| + * @typedef {{language: !chrome.languageSettingsPrivate.Language, |
| + * state: !LanguageState}} |
| + */ |
| +var LanguageInfo; |
| + |
| +/** |
| + * supportedLanguages: an array of languages, ordered alphabetically. |
| + * enabledLanguages: an array of enabled language info and state, ordered by |
| + * preference. |
| + * @typedef {{ |
| + * supportedLanguages: !Array<!chrome.languageSettingsPrivate.Language>, |
| + * enabledLanguages: !Array<!LanguageInfo> |
| + * }} |
| + */ |
| +var LanguagesModel; |
| + |
| +(function() { |
| +'use strict'; |
| + |
| +/** |
| + * This element has a reference to the singleton, exposing the singleton's |
| + * language model to the host of this element as the 'languages' property. |
| + */ |
| +Polymer({ |
| + is: 'cr-settings-languages', |
| + |
| + properties: { |
| + /** |
| + * Singleton element created at startup which provides the languages model. |
| + * @type {!Element} |
| + */ |
| + singleton_: { |
|
stevenjb
2015/09/23 22:27:54
I like 'singleton' here.
michaelpg
2015/09/24 02:15:19
Acknowledged.
|
| + type: Object, |
| + value: document.createElement('cr-settings-languages-singleton'), |
| + }, |
| + |
| + /** |
| + * A reference to the languages model from the singleton, exposed as a |
| + * read-only property so hosts can bind to it, but not change it. |
| + * @type {LanguagesModel|undefined} |
| + */ |
| + languages: { |
|
stevenjb
2015/09/23 22:27:54
Heh, so, FWIW, I was / would be fine with this bei
michaelpg
2015/09/24 02:15:20
Acknowledged.
|
| + type: Object, |
| + notify: true, |
| + readOnly: true, |
| + }, |
| + }, |
| + |
| + ready: function() { |
| + // Set the 'languages' property to reference the singleton's model. |
| + this._setLanguages(this.singleton_.languages); |
| + // Listen for changes to the singleton's languages property, so we know |
| + // when to notify hosts of changes to (our reference to) the property. |
| + this.listen( |
| + this.singleton_, 'languages-changed', 'singletonLanguagesChanged_'); |
| + }, |
| + |
| + /** |
| + * Takes changes reported by the singleton and forwards them to the host, |
| + * manually sending a change notification for our 'languages' property (since |
| + * it's the same object as the singleton's property, but isn't bound by |
| + * Polymer). |
| + * @private |
| + */ |
| + singletonLanguagesChanged_: function(e) { |
| + // Forward the change notification to the host. |
| + this.fire(e.type, e.detail, {bubbles: false}); |
| + }, |
| + |
| + // Forward public methods to the singleton. |
| + |
| + /** @param {string} languageCode */ |
| + setUILanguage: function(languageCode) { |
| + if (cr.isWindows || cr.isChromeOS) |
| + this.singleton_.setUILanguage(languageCode); |
| + }, |
| + |
| + /** @param {string} languageCode */ |
| + enableLanguage: function(languageCode) { |
| + this.singleton_.enableLanguage(languageCode); |
| + }, |
| + |
| + /** @param {string} languageCode */ |
| + disableLanguage: function(languageCode) { |
| + this.singleton_.disableLanguage(languageCode); |
| + }, |
| + |
| + /** |
| + * @param {string} languageCode |
| + * @return {boolean} |
| + */ |
| + isEnabled: function(languageCode) { |
| + return this.singleton_.isEnabled(languageCode); |
| + }, |
| + |
| + /** |
| + * @param {string} languageCode |
| + * @param {boolean} enable |
| + */ |
| + toggleSpellCheck: function(languageCode, enable) { |
| + this.singleton_.toggleSpellCheck(languageCode, enable); |
| + }, |
| +}); |
| + |
| +var preferredLanguagesPath; |
| +if (cr.isChromeOS) |
| + preferredLanguagesPath = 'prefs.settings.language.preferred_languages.value'; |
| +else |
| + preferredLanguagesPath = 'prefs.intl.accept_languages.value'; |
|
stevenjb
2015/09/23 22:27:54
nit: I know that it would add a tiny bit of comple
Dan Beam
2015/09/23 23:28:17
can haz ternary?
michaelpg
2015/09/24 02:15:20
Done.
michaelpg
2015/09/24 02:15:20
Done.
|
| + |
| +/** |
| + * Singleton element created when cr-settings-languages is registered. |
| + * Generates the languages model on start-up, and updates it whenever Chrome's |
| + * pref store and other settings change. These updates propagate to each |
| + * <cr-settings-language> instance so that their 'languages' property updates |
| + * like any other Polymer property. |
| + */ |
| +Polymer({ |
| + is: 'cr-settings-languages-singleton', |
| + |
| + properties: { |
| + /** |
| + * @type {LanguagesModel|undefined} |
| + */ |
| + languages: { |
|
stevenjb
2015/09/23 22:27:54
Same here, but it definitely makes sense to me to
michaelpg
2015/09/24 02:15:19
yep. If we don't match property names, we'd have t
|
| + type: Object, |
| + notify: true, |
| + }, |
| + |
| + /** |
| + * Object containing all preferences. |
| + */ |
| + prefs: { |
| + type: Object, |
| + notify: true, |
| + }, |
| + }, |
| + |
| + /** |
| + * Hash map of languages.supportedLanguages using language codes as keys for |
| + * fast lookup. |
| + * @private {!Object<!chrome.languageSettingsPrivate.Language>} |
| + */ |
| + supportedLanguageMap_: {}, |
| + |
| + /** |
| + * Hash map of languages.enabledLanguages using language codes as keys for |
| + * fast lookup. |
| + * @private {!Object<!LanguageInfo>} |
| + */ |
| + enabledLanguageMap_: {}, |
| + |
| + observers: [ |
| + 'preferredLanguagesPrefChanged_(' + preferredLanguagesPath + ')', |
| + 'spellCheckDictionariesPrefChanged_(prefs.spellcheck.dictionaries.value.*)', |
| + ], |
| + |
| + /** @override */ |
| + created: function() { |
| + chrome.languageSettingsPrivate.getLanguageList(function(languageList) { |
| + // Wait until prefs are initialized before creating the model, so we can |
| + // include information about enabled languages. |
| + CrSettingsPrefs.initialized.then(function() { |
| + this.createModel_(languageList); |
| + this.initialized_ = true; |
| + }.bind(this)); |
| + }.bind(this)); |
| + }, |
| + |
| + /** |
| + * Constructs the languages model from the given language list. |
| + * @param {!Array<!chrome.languageSettingsPrivate.Language>} |
| + * supportedLanguages |
| + */ |
| + createModel_: function(supportedLanguages) { |
| + // Populate the hash map of supported languages. |
| + for (var i = 0; i < supportedLanguages.length; i++) { |
| + this.supportedLanguageMap_[supportedLanguages[i].code] = |
| + supportedLanguages[i]; |
| + } |
| + |
| + // Create a list of enabled language info from the supported languages. |
| + var enabledLanguages = this.getEnabledLanguages_(); |
| + // Populate the hash map of enabled languages. |
| + for (var i = 0; i < enabledLanguages.length; i++) { |
| + var languageInfo = enabledLanguages[i]; |
| + this.enabledLanguageMap_[languageInfo.language.code] = languageInfo; |
| + } |
| + |
| + // Initialize the Polymer languages model. |
| + this.languages = { |
| + supportedLanguages: supportedLanguages, |
| + enabledLanguages: enabledLanguages, |
| + }; |
| + }, |
| + |
| + /** |
| + * Returns a list of LanguageInfos for each enabled language in the supported |
| + * languages list. |
| + * @private |
| + * @return {!Array<!LanguageInfo>} |
| + */ |
| + getEnabledLanguages_: function() { |
| + assert(CrSettingsPrefs.isInitialized); |
| + |
| + var languageCodes = this.get(preferredLanguagesPath).split(','); |
|
stevenjb
2015/09/23 22:27:54
What do you think of:
getPref_: function(path) {
michaelpg
2015/09/24 02:15:20
Done. It's not bad, but I'm not sure about the pre
stevenjb
2015/09/24 15:46:06
Yeah, I've been trying to think of a nice way we c
stevenjb
2015/09/24 15:46:06
Yeah, I've been trying to think of a nice way we c
michaelpg
2015/09/24 17:21:11
Yeah, a behavior would be a good way to make this
|
| + var enabledLanguages = []; |
| + var spellCheckMap = this.getSpellCheckMap_(); |
| + for (var i = 0; i < languageCodes.length; i++) { |
| + var code = languageCodes[i]; |
| + var language = this.supportedLanguageMap_[code]; |
| + if (!language) |
| + continue; |
| + var state = {spellCheckEnabled: !!spellCheckMap[code]}; |
|
Dan Beam
2015/09/23 23:28:17
why does this need to be a full dictionary?
michaelpg
2015/09/24 02:15:19
other properties are a'comin, like translateEnable
|
| + enabledLanguages.push({language: language, state: state}); |
| + } |
| + return enabledLanguages; |
| + }, |
| + |
| + /** |
| + * Creates a map whose keys are languages enabled for spell check. |
| + * @return {!Object<boolean>} |
| + */ |
| + getSpellCheckMap_: function() { |
| + assert(CrSettingsPrefs.isInitialized); |
| + |
| + var spellCheckPref = /** @type {chrome.settingsPrivate.PrefObject} */( |
| + this.get('prefs.spellcheck.dictionaries')); |
| + var spellCheckCodes = spellCheckPref.value; |
| + var spellCheckMap = {}; |
| + for (var i = 0; i < spellCheckCodes.length; i++) |
| + spellCheckMap[spellCheckCodes[i]] = true; |
| + return spellCheckMap; |
| + }, |
| + |
| + /** |
| + * Updates the list of enabled languages from the preferred languages pref. |
| + * @private |
| + * */ |
| + preferredLanguagesPrefChanged_: function() { |
| + if (!this.initialized_) |
| + return; |
| + |
| + var enabledLanguages = this.getEnabledLanguages_(); |
| + // Reset the enabled language map. Do this before notifying of the change |
| + // via languages.enabledLanguages. |
| + this.enabledLanguageMap_ = {}; |
| + for (var i = 0; i < enabledLanguages.length; i++) { |
| + var languageInfo = enabledLanguages[i]; |
| + this.enabledLanguageMap_[languageInfo.language.code] = languageInfo; |
| + } |
| + this.set('languages.enabledLanguages', enabledLanguages); |
| + }, |
| + |
| + /** |
| + * Updates the spellCheckEnabled state of each enabled language. |
| + * @private |
| + */ |
| + spellCheckDictionariesPrefChanged_: function() { |
| + if (!this.initialized_) |
| + return; |
| + |
| + var spellCheckMap = this.getSpellCheckMap_(); |
| + for (var i = 0; i < this.languages.enabledLanguages.length; i++) { |
| + var languageCode = this.languages.enabledLanguages[i].language.code; |
| + this.set('languages.enabledLanguages.' + i + '.state.spellCheckEnabled', |
| + !!spellCheckMap[languageCode]); |
| + } |
| + }, |
| + |
| + /** |
| + * Windows and Chrome OS only: Sets the prospective UI language to the chosen |
| + * language. This dosen't affect the actual UI language until a restart. |
| + * @param {string} languageCode |
| + */ |
| + setUILanguage: function(languageCode) { |
| + chrome.send('setUILanguage', [languageCode]); |
| + }, |
| + |
| + /** |
| + * Enables the language, making it available for spell check and input. |
| + * @param {string} languageCode |
| + */ |
| + enableLanguage: function(languageCode) { |
| + if (!CrSettingsPrefs.isInitialized) |
| + return; |
| + |
| + var languageCodes = this.get(preferredLanguagesPath); |
| + var index = languageCodes.split(',').indexOf(languageCode); |
| + if (index > -1) |
| + return; |
| + this.set(preferredLanguagesPath, languageCodes + ',' + languageCode); |
| + }, |
| + |
| + /** |
| + * Disables the language. |
| + * @param {string} languageCode |
| + */ |
| + disableLanguage: function(languageCode) { |
| + if (!CrSettingsPrefs.isInitialized) |
| + return; |
| + |
| + // Don't disable the UI language. |
| + var appLocale = this.get('prefs.intl.app_locale.value') || |
| + navigator.language; |
| + if (languageCode == appLocale) |
|
Dan Beam
2015/09/23 23:28:17
wait, how does this happen? can this be assert(la
michaelpg
2015/09/24 02:15:20
Done.
|
| + return; |
| + |
| + // Don't disable the only enabled language. |
|
Dan Beam
2015/09/23 23:28:17
same question: don't we prevent this in the UI (so
michaelpg
2015/09/24 02:15:20
Done.
|
| + var languageCodes = this.get(preferredLanguagesPath).split(','); |
| + if (languageCodes.length == 1) |
| + return; |
| + |
| + // Remove the language from spell check. |
| + this.arrayDelete('prefs.spellcheck.dictionaries.value', languageCode); |
| + |
| + var languageIndex = languageCodes.indexOf(languageCode); |
| + if (languageIndex == -1) |
| + return; |
| + languageCodes.splice(languageIndex, 1); |
| + this.set(preferredLanguagesPath, languageCodes.join(',')); |
| + }, |
| + |
| + /** |
| + * @param {string} languageCode |
| + * @return {boolean} True if the language is enabled. |
| + */ |
| + isEnabled: function(languageCode) { |
| + return !!this.enabledLanguageMap_[languageCode]; |
| + }, |
| + |
| + /** |
| + * Enables or disables spell check for the given language. |
| + * @param {string} languageCode |
| + * @param {boolean} enable |
| + */ |
| + toggleSpellCheck: function(languageCode, enable) { |
| + if (!this.initialized_) |
| + return; |
| + |
| + var spellCheckPref = /** @type {chrome.settingsPrivate.PrefObject} */( |
| + this.get('prefs.spellcheck.dictionaries')); |
| + if (enable) { |
| + if (spellCheckPref.value.indexOf(languageCode) == -1) |
| + this.push('prefs.spellcheck.dictionaries.value', languageCode); |
| + } else { |
| + // TODO: need update externs before committing |
|
Dan Beam
2015/09/23 23:28:17
TODO(michaelpg): or actually do
michaelpg
2015/09/24 02:15:20
actually done (after four tries): https://coderevi
|
| + this.arrayDelete('prefs.spellcheck.dictionaries.value', languageCode); |
| + } |
| + }, |
| +}); |
| +})(); |