blob: 2f4942b53e4473e7660559ee2215c7f69a060a18 [file] [log] [blame]
// Copyright 2020 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 'os-settings-languages-page-v2' is the languages sub-page
* for languages and inputs settings.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/js/action_link.js';
import 'chrome://resources/cr_elements/action_link_css.m.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
import './change_device_language_dialog.js';
import './os_add_languages_dialog.js';
import 'chrome://resources/cr_components/localized_link/localized_link.js';
import '../../controls/settings_toggle_button.js';
import '../../settings_shared.css.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {loadTimeData} from '../../i18n_setup.js';
import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
import {Route, Router} from '../../router.js';
import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
import {recordSettingChange} from '../metrics_recorder.js';
import {routes} from '../os_route.js';
import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
import {LanguagesMetricsProxy, LanguagesMetricsProxyImpl, LanguagesPageInteraction} from './languages_metrics_proxy.js';
import {LanguageHelper, LanguagesModel, LanguageState} from './languages_types.js';
/**
* @type {number} Millisecond delay that can be used when closing an action
* menu to keep it briefly on-screen so users can see the changes.
*/
const kMenuCloseDelay = 100;
/**
* @constructor
* @extends {PolymerElement}
* @implements {DeepLinkingBehaviorInterface}
* @implements {I18nBehaviorInterface}
* @implements {PrefsBehaviorInterface}
* @implements {RouteObserverBehaviorInterface}
*/
const OsSettingsLanguagesPageV2ElementBase = mixinBehaviors(
[DeepLinkingBehavior, I18nBehavior, PrefsBehavior, RouteObserverBehavior],
PolymerElement);
/** @polymer */
class OsSettingsLanguagesPageV2Element extends
OsSettingsLanguagesPageV2ElementBase {
static get is() {
return 'os-settings-languages-page-v2';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
/**
* Read-only reference to the languages model provided by the
* 'os-settings-languages' instance.
* @type {!LanguagesModel|undefined}
*/
languages: {
type: Object,
notify: true,
},
/** @type {!LanguageHelper} */
languageHelper: Object,
/**
* The language to display the details for and its index.
* @type {{state: !LanguageState, index: number}|undefined}
* @private
*/
detailLanguage_: Object,
/** @private */
showAddLanguagesDialog_: Boolean,
/** @private */
showChangeDeviceLanguageDialog_: {
type: Boolean,
value: false,
},
/** @private */
isGuest_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isGuest');
},
},
/** @private */
isSecondaryUser_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('isSecondaryUser');
},
},
/** @private */
primaryUserEmail_: {
type: String,
value() {
return loadTimeData.getString('primaryUserEmail');
},
},
/**
* Used by DeepLinkingBehavior to focus this page's deep links.
* @type {!Set<!Setting>}
*/
supportedSettingIds: {
type: Object,
value: () => new Set([
Setting.kAddLanguage,
Setting.kChangeDeviceLanguage,
Setting.kOfferTranslation,
]),
},
/** @private */
languageSettingsV2Update2Enabled_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('enableLanguageSettingsV2Update2');
},
},
};
}
/** @override */
constructor() {
super();
/** @private {!LanguagesMetricsProxy} */
this.languagesMetricsProxy_ = LanguagesMetricsProxyImpl.getInstance();
}
/**
* @param {!Route} route
* @param {!Route=} oldRoute
*/
currentRouteChanged(route, oldRoute) {
// Does not apply to this page.
if (route !== routes.OS_LANGUAGES_LANGUAGES) {
return;
}
this.attemptDeepLink();
}
/**
* @param {string} language
* @return {string}
* @private
*/
getLanguageDisplayName_(language) {
return this.languageHelper.getLanguage(language).displayName;
}
/** @private */
onChangeDeviceLanguageClick_() {
this.showChangeDeviceLanguageDialog_ = true;
}
/** @private */
onChangeDeviceLanguageDialogClose_() {
this.showChangeDeviceLanguageDialog_ = false;
focusWithoutInk(
assert(this.shadowRoot.querySelector('#changeDeviceLanguage')));
}
/**
* @param {string} language
* @return {string}
* @private
*/
getChangeDeviceLanguageButtonDescription_(language) {
return this.i18n(
'changeDeviceLanguageButtonDescription',
this.getLanguageDisplayName_(language));
}
/**
* Stamps and opens the Add Languages dialog, registering a listener to
* disable the dialog's dom-if again on close.
* @param {!Event} e
* @private
*/
onAddLanguagesClick_(e) {
e.preventDefault();
this.languagesMetricsProxy_.recordAddLanguages();
this.showAddLanguagesDialog_ = true;
}
/** @private */
onAddLanguagesDialogClose_() {
this.showAddLanguagesDialog_ = false;
focusWithoutInk(assert(this.$.addLanguages));
}
/**
* Checks if there are supported languages that are not enabled but can be
* enabled.
* @param {LanguagesModel|undefined} languages
* @return {boolean} True if there is at least one available language.
* @private
*/
canEnableSomeSupportedLanguage_(languages) {
return languages !== undefined && languages.supported.some(language => {
return this.languageHelper.canEnableLanguage(language);
});
}
/**
* @return {boolean} True if the translate checkbox should be disabled.
* @private
*/
disableTranslateCheckbox_() {
if (!this.detailLanguage_ || !this.detailLanguage_.state) {
return true;
}
const languageState = this.detailLanguage_.state;
if (!languageState.language || !languageState.language.supportsTranslate) {
return true;
}
if (this.languageHelper.isOnlyTranslateBlockedLanguage(languageState)) {
return true;
}
return this.languageHelper.convertLanguageCodeForTranslate(
languageState.language.code) === this.languages.translateTarget;
}
/**
* Handler for changes to the translate checkbox.
* @param {!{target: !Element}} e
* @private
*/
onTranslateCheckboxChange_(e) {
if (e.target.checked) {
this.languageHelper.enableTranslateLanguage(
this.detailLanguage_.state.language.code);
} else {
this.languageHelper.disableTranslateLanguage(
this.detailLanguage_.state.language.code);
}
this.languagesMetricsProxy_.recordTranslateCheckboxChanged(
e.target.checked);
recordSettingChange();
this.closeMenuSoon_();
}
/**
* Closes the shared action menu after a short delay, so when a checkbox is
* clicked it can be seen to change state before disappearing.
* @private
*/
closeMenuSoon_() {
const menu = /** @type {!CrActionMenuElement} */ (
this.shadowRoot.querySelector('#menu').get());
setTimeout(() => {
if (menu.open) {
menu.close();
}
}, kMenuCloseDelay);
}
/**
* @return {boolean} True if the "Move to top" option for |language| should
* be visible.
* @private
*/
showMoveToTop_() {
// "Move To Top" is a no-op for the top language.
return this.detailLanguage_ !== undefined &&
this.detailLanguage_.index === 0;
}
/**
* @return {boolean} True if the "Move up" option for |language| should
* be visible.
* @private
*/
showMoveUp_() {
// "Move up" is a no-op for the top language, and redundant with
// "Move to top" for the 2nd language.
return this.detailLanguage_ !== undefined &&
this.detailLanguage_.index !== 0 && this.detailLanguage_.index !== 1;
}
/**
* @return {boolean} True if the "Move down" option for |language| should be
* visible.
* @private
*/
showMoveDown_() {
return this.languages !== undefined && this.detailLanguage_ !== undefined &&
this.detailLanguage_.index !== this.languages.enabled.length - 1;
}
/**
* Moves the language to the top of the list.
* @private
*/
onMoveToTopClick_() {
/** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
this.languageHelper.moveLanguageToFront(
this.detailLanguage_.state.language.code);
recordSettingChange();
}
/**
* Moves the language up in the list.
* @private
*/
onMoveUpClick_() {
/** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
this.languageHelper.moveLanguage(
this.detailLanguage_.state.language.code, /*upDirection=*/ true);
recordSettingChange();
}
/**
* Moves the language down in the list.
* @private
*/
onMoveDownClick_() {
/** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
this.languageHelper.moveLanguage(
this.detailLanguage_.state.language.code, /*upDirection=*/ false);
recordSettingChange();
}
/**
* Disables the language.
* @private
*/
onRemoveLanguageClick_() {
/** @type {!CrActionMenuElement} */ (this.$.menu.get()).close();
this.languageHelper.disableLanguage(
this.detailLanguage_.state.language.code);
recordSettingChange();
}
/**
* @param {!Event} e
* @private
*/
onDotsClick_(e) {
// Sets a copy of the LanguageState object since it is not data-bound to
// the languages model directly.
this.detailLanguage_ =
/** @type {{state: !LanguageState, index: number}} */ ({
state: /** @type {!LanguageState} */ (e.model.item),
index: /** @type {number} */ (e.model.index),
});
const menu = /** @type {!CrActionMenuElement} */ (this.$.menu.get());
menu.showAt(/** @type {!HTMLElement} */ (e.target));
}
/**
* @param {!Event} e
* @private
*/
onTranslateToggleChange_(e) {
this.languagesMetricsProxy_.recordToggleTranslate(e.target.checked);
}
/**
* @param {string} languageCode The language code identifying a language.
* @param {string} translateTarget The translate target language.
* @return {string} class name for whether it's a translate-target or not.
* @private
*/
getTranslationTargetClass_(languageCode, translateTarget) {
return this.languageHelper.convertLanguageCodeForTranslate(languageCode) ===
translateTarget ?
'translate-target' :
'non-translate-target';
}
/**
* @param {boolean} update2Enabled
* @return {string}
* @private
*/
getOfferTranslationLabel_(update2Enabled) {
return this.i18n(
update2Enabled ? 'offerGoogleTranslateLabel' : 'offerTranslationLabel');
}
/**
* @param {boolean} update2Enabled
* @return {string}
* @private
*/
getOfferTranslationSublabel_(update2Enabled) {
return update2Enabled ? '' : this.i18n('offerTranslationSublabel');
}
/**
* @param {boolean} update2Enabled
* @return {string}
* @private
*/
getLanguagePreferenceTitle_(update2Enabled) {
return this.i18n(
update2Enabled ? 'websiteLanguagesTitle' : 'languagesPreferenceTitle');
}
/**
* @param {boolean} update2Enabled
* @return {string}
* @private
*/
getLanguagePreferenceDescription_(update2Enabled) {
return this.i18nAdvanced(
update2Enabled ? 'websiteLanguagesDescription' :
'languagesPreferenceDescription');
}
/** @private */
openManageGoogleAccountLanguage_() {
this.languagesMetricsProxy_.recordInteraction(
LanguagesPageInteraction.OPEN_MANAGE_GOOGLE_ACCOUNT_LANGUAGE);
window.open(loadTimeData.getString('googleAccountLanguagesURL'));
}
/** @private */
onLanguagePreferenceDescriptionLinkClick_() {
this.languagesMetricsProxy_.recordInteraction(
LanguagesPageInteraction.OPEN_WEB_LANGUAGES_LEARN_MORE);
}
}
customElements.define(
OsSettingsLanguagesPageV2Element.is, OsSettingsLanguagesPageV2Element);