chromium/chrome/browser/resources/settings/languages_page/translate_page.ts

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview 'settings-translate-page' is the settings page
 * translate settings.
 */

import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_collapse/cr_collapse.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/icons.html.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/cr_elements/md_select.css.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import './add_languages_dialog.js';
import './languages.js';
import '../controls/settings_toggle_button.js';
import '../icons.html.js';
import '../settings_shared.css.js';

import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js';
import type {DomRepeatEvent} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js';

import type {LanguageSettingsMetricsProxy} from './languages_settings_metrics_proxy.js';
import {LanguageSettingsActionType, LanguageSettingsMetricsProxyImpl} from './languages_settings_metrics_proxy.js';
import type {LanguageHelper, LanguagesModel} from './languages_types.js';
import {getTemplate} from './translate_page.html.js';

const SettingsTranslatePageElementBase = PrefsMixin(I18nMixin(PolymerElement));

export class SettingsTranslatePageElement extends
    SettingsTranslatePageElementBase {
  static get is() {
    return 'settings-translate-page';
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      /**
       * Preferences state.
       */
      prefs: {
        type: Object,
        notify: true,
      },

      /**
       * Read-only reference to the languages model provided by the
       * 'settings-languages' instance.
       */
      languages: {
        type: Object,
        notify: true,
      },

      languageHelper: Object,

      showAddAlwaysTranslateDialog_: Boolean,
      showAddNeverTranslateDialog_: Boolean,
      addLanguagesDialogLanguages_: Array,
    };
  }

  languages?: LanguagesModel;
  languageHelper: LanguageHelper;
  private showAddAlwaysTranslateDialog_: boolean;
  private showAddNeverTranslateDialog_: boolean;
  private addLanguagesDialogLanguages_:
      chrome.languageSettingsPrivate.Language[]|null;
  private languageSettingsMetricsProxy_: LanguageSettingsMetricsProxy =
      LanguageSettingsMetricsProxyImpl.getInstance();

  private onTargetLanguageChange_() {
    this.languageHelper.setTranslateTargetLanguage(
        this.shadowRoot!.querySelector<HTMLSelectElement>(
                            '#targetLanguage')!.value);
    this.languageSettingsMetricsProxy_.recordSettingsMetric(
        LanguageSettingsActionType.CHANGE_TRANSLATE_TARGET);
  }

  /**
   * Helper function to get the text to display in the target language drop down
   * list. Returns the display name in the current UI language and the native
   * name of the language.
   */
  private getTargetLanguageDisplayOption_(
      item: chrome.languageSettingsPrivate.Language): string {
    return this.languageHelper.getFullName(item);
  }

  /**
   * Checks if a Chrome language code is equal to the translate language code.
   * Used in the translate language selector. If the item matches the translate
   * target language, it will set that item as selected.
   */
  private translateLanguageEqual_(
      chromeItemCode: string, translateTarget: string): boolean {
    return chromeItemCode ===
        this.languageHelper.convertLanguageCodeForChrome(translateTarget);
  }

  /**
   * A function used for sorting languages alphabetically by display name.
   */
  private alphabeticalSort_(
      first: chrome.languageSettingsPrivate.Language,
      second: chrome.languageSettingsPrivate.Language) {
    return first.displayName.localeCompare(second.displayName);
  }

  /**
   * A filter function to return true if language is not undefined and has a
   * displayName.
   */
  private hasDisplayName_(language: chrome.languageSettingsPrivate.Language|
                          undefined): boolean {
    return !!language && !!language!.displayName;
  }

  /**
   * Stamps and opens the Add Languages dialog, registering a listener to
   * disable the dialog's dom-if again on close.
   */
  private onAddAlwaysTranslateLanguagesClick_(e: Event) {
    e.preventDefault();
    const translatableLanguages = this.getTranslatableLanguages_();
    this.addLanguagesDialogLanguages_ = translatableLanguages.filter(
        language => !this.languages!.alwaysTranslate.includes(language));
    this.showAddAlwaysTranslateDialog_ = true;
  }

  private onAlwaysTranslateDialogClose_() {
    this.showAddAlwaysTranslateDialog_ = false;
    this.addLanguagesDialogLanguages_ = null;
    const toFocus =
        this.shadowRoot!.querySelector<HTMLElement>('#addAlwaysTranslate');
    assert(toFocus);
    focusWithoutInk(toFocus);
  }

  /**
   * Helper function fired by the add dialog's on-languages-added event. Adds
   * selected languages to the always-translate languages list.
   */
  private onAlwaysTranslateLanguagesAdded_(e: CustomEvent<string[]>) {
    const languagesToAdd = e.detail;
    languagesToAdd.forEach(languageCode => {
      this.languageHelper.setLanguageAlwaysTranslateState(languageCode, true);
      this.languageSettingsMetricsProxy_.recordSettingsMetric(
          LanguageSettingsActionType.ADD_TO_ALWAYS_TRANSLATE);
    });
  }

  /**
   * Removes a language from the always translate languages list.
   */
  private onRemoveAlwaysTranslateLanguageClick_(
      e: DomRepeatEvent<chrome.languageSettingsPrivate.Language>) {
    const languageCode = e.model.item.code;
    this.languageHelper.setLanguageAlwaysTranslateState(languageCode, false);
    this.languageSettingsMetricsProxy_.recordSettingsMetric(
        LanguageSettingsActionType.REMOVE_FROM_ALWAYS_TRANSLATE);
  }

  /**
   * Stamps and opens the Add Languages dialog, registering a listener to
   * disable the dialog's dom-if again on close.
   */
  private onAddNeverTranslateLanguagesClick_(e: Event) {
    e.preventDefault();
    const translatableLanguages = this.getTranslatableLanguages_();
    this.addLanguagesDialogLanguages_ = translatableLanguages.filter(
        language => !this.languages!.neverTranslate.includes(language));
    this.showAddNeverTranslateDialog_ = true;
  }

  private onNeverTranslateDialogClose_() {
    this.showAddNeverTranslateDialog_ = false;
    this.addLanguagesDialogLanguages_ = null;
    const toFocus =
        this.shadowRoot!.querySelector<HTMLElement>('#addNeverTranslate');
    assert(toFocus);
    focusWithoutInk(toFocus);
  }

  private onNeverTranslateLanguagesAdded_(e: CustomEvent<string[]>) {
    const languagesToAdd = e.detail;
    languagesToAdd.forEach(languageCode => {
      this.languageHelper.disableTranslateLanguage(languageCode);
      this.languageSettingsMetricsProxy_.recordSettingsMetric(
          LanguageSettingsActionType.ADD_TO_NEVER_TRANSLATE);
    });
  }

  /**
   * Removes a language from the never translate languages list.
   */
  private onRemoveNeverTranslateLanguageClick_(
      e: DomRepeatEvent<chrome.languageSettingsPrivate.Language>) {
    const languageCode = e.model.item.code;
    this.languageHelper.enableTranslateLanguage(languageCode);
    this.languageSettingsMetricsProxy_.recordSettingsMetric(
        LanguageSettingsActionType.REMOVE_FROM_NEVER_TRANSLATE);
  }

  private onTranslateToggleChange_(e: Event) {
    this.languageSettingsMetricsProxy_.recordSettingsMetric(
        (e.target as SettingsToggleButtonElement).checked ?
            LanguageSettingsActionType.ENABLE_TRANSLATE_GLOBALLY :
            LanguageSettingsActionType.DISABLE_TRANSLATE_GLOBALLY);
  }

  /**
   * @return Whether the list has any items.
   */
  private hasSome_(list: any[]): boolean {
    return !!list.length;
  }

  /**
   * @return Whether the list is has the given length.
   */
  private hasLength_(list: any[], length: number): boolean {
    return list.length === length;
  }

  /**
   * Gets the list of languages that chrome can translate
   */
  private getTranslatableLanguages_():
      chrome.languageSettingsPrivate.Language[] {
    return this.languages!.supported.filter(language => {
      return this.isTranslateSupported_(language);
    });
  }

  /**
   * Filters only for translate supported languages
   */
  private isTranslateSupported_(
      language: chrome.languageSettingsPrivate.Language): boolean {
    return this.languageHelper.isTranslateBaseLanguage(language);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'settings-translate-page': SettingsTranslatePageElement;
  }
}

customElements.define(
    SettingsTranslatePageElement.is, SettingsTranslatePageElement);