core/templates/pages/exploration-player-page/services/audio-translation-language.service.ts

Summary

Maintainability
A
2 hrs
Test Coverage
// Copyright 2017 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Service to manage the current language being
 * used for audio translations.
 */

import {downgradeInjectable} from '@angular/upgrade/static';
import {Injectable} from '@angular/core';

import {BrowserCheckerService} from 'domain/utilities/browser-checker.service';
import {LanguageUtilService} from 'domain/utilities/language-util.service';

export interface ExplorationLanguageInfo {
  /**
   * This inteface is used to keep track of the audio language code (value)
   * and the audio language description to display (displayed) for the
   * _languagesInExploration property.
   */
  value: string;
  displayed: string;
}

@Injectable({
  providedIn: 'root',
})
export class AudioTranslationLanguageService {
  constructor(
    private browserCheckerService: BrowserCheckerService,
    private languageUtilService: LanguageUtilService
  ) {}

  _currentAudioLanguageCode: string | null = null;
  _allAudioLanguageCodesInExploration: string[] = [];
  _explorationLanguageCode: string | null = null;
  _automaticTextToSpeechEnabled: boolean = false;
  _languagesInExploration: ExplorationLanguageInfo[] = [];

  attemptToSetAudioLanguageToExplorationLanguage(): void {
    // We minimize the number of related languages, because we want to
    // pick the language that is the most directly related to the exploration
    // language. For example, this would prioritize Hindi over Hinglish
    // if both were available as audio languages.
    let numRelatedLanguages = Number.MAX_VALUE;
    this._allAudioLanguageCodesInExploration.forEach(
      (audioLanguageCode: string) => {
        let relatedLanguageCodes =
          this.languageUtilService.getLanguageCodesRelatedToAudioLanguageCode(
            audioLanguageCode
          );
        if (
          relatedLanguageCodes.length < numRelatedLanguages &&
          this._explorationLanguageCode &&
          relatedLanguageCodes.indexOf(this._explorationLanguageCode) !== -1
        ) {
          this._currentAudioLanguageCode = audioLanguageCode;
          numRelatedLanguages = relatedLanguageCodes.length;
        }
      }
    );
  }

  _isAutogeneratedAudioAllowed(): boolean {
    return (
      this._automaticTextToSpeechEnabled &&
      this._explorationLanguageCode !== null &&
      this.languageUtilService.supportsAutogeneratedAudio(
        this._explorationLanguageCode
      )
    );
  }

  _init(
    allAudioLanguageCodesInExploration: string[],
    preferredAudioLanguageCode: string | null,
    explorationLanguageCode: string,
    automaticTextToSpeechEnabled: boolean
  ): void {
    this._allAudioLanguageCodesInExploration =
      allAudioLanguageCodesInExploration;
    this._explorationLanguageCode = explorationLanguageCode;
    this._automaticTextToSpeechEnabled = automaticTextToSpeechEnabled;
    this._languagesInExploration = [];
    // Set the audio language that is chosen initially.
    // Use the following priority (highest to lowest):
    // 1. If the learner has a preferred audio language set, then set it to
    // that language if it is available.
    // 2. If the exploration language has a related audio language, then set
    // it to that.
    // 3. If only the autogenerated audio language is available, then set it
    // to that.
    // 4. Otherwise, just pick an available non-autogenerated audio language
    // at random.
    if (
      preferredAudioLanguageCode &&
      allAudioLanguageCodesInExploration.indexOf(preferredAudioLanguageCode) !==
        -1
    ) {
      this._currentAudioLanguageCode = preferredAudioLanguageCode;
    }

    if (this._currentAudioLanguageCode === null) {
      this.attemptToSetAudioLanguageToExplorationLanguage();
    }

    if (
      this._currentAudioLanguageCode === null &&
      this._allAudioLanguageCodesInExploration.length >= 1
    ) {
      this._currentAudioLanguageCode =
        this._allAudioLanguageCodesInExploration[0];
    }

    if (
      this._currentAudioLanguageCode === null &&
      this._allAudioLanguageCodesInExploration.length === 0 &&
      this._isAutogeneratedAudioAllowed()
    ) {
      this._currentAudioLanguageCode =
        this.languageUtilService.getAutogeneratedAudioLanguage(
          this._explorationLanguageCode
        ).id;
    }

    this._allAudioLanguageCodesInExploration.forEach((languageCode: string) => {
      let languageDescription =
        this.languageUtilService.getAudioLanguageDescription(languageCode);
      if (languageDescription) {
        this._languagesInExploration.push({
          value: languageCode,
          displayed: languageDescription,
        });
      }
    });

    if (this._isAutogeneratedAudioAllowed()) {
      let autogeneratedAudioLanguage =
        this.languageUtilService.getAutogeneratedAudioLanguage(
          this._explorationLanguageCode
        );
      this._languagesInExploration.push({
        value: autogeneratedAudioLanguage.id,
        displayed: autogeneratedAudioLanguage.description,
      });
    }
  }

  init(
    allAudioLanguageCodesInExploration: string[],
    preferredAudioLanguageCode: string | null,
    explorationLanguageCode: string,
    automaticTextToSpeechEnabled: boolean
  ): void {
    this._init(
      allAudioLanguageCodesInExploration,
      preferredAudioLanguageCode,
      explorationLanguageCode,
      automaticTextToSpeechEnabled
    );
  }

  /**
   * @return {string} The current audio language code (eg. en).
   */
  getCurrentAudioLanguageCode(): string | null {
    return this._currentAudioLanguageCode;
  }

  /**
   * @return {string} The current language description (eg. English).
   */
  getCurrentAudioLanguageDescription(): string | null {
    if (this._currentAudioLanguageCode) {
      return this.languageUtilService.getAudioLanguageDescription(
        this._currentAudioLanguageCode
      );
    }
    return null;
  }

  /**
   * @return {string[]} An array of the audio language codes in
   *  exploration.
   */
  getAllAudioLanguageCodesInExploration(): string[] {
    return this._allAudioLanguageCodesInExploration;
  }

  /**
   * @return {Array<ExplorationLanguageInfo>}
   * An array of ExplorationLanguageInfo objects which consist of audio
   * language codes as well as their displayed language description for
   * the exploration.
   */
  getLanguageOptionsForDropdown(): ExplorationLanguageInfo[] {
    return this._languagesInExploration;
  }

  clearCurrentAudioLanguageCode(): void {
    this._currentAudioLanguageCode = null;
  }

  /**
   * @param {string} set a new language code.
   */
  setCurrentAudioLanguageCode(newLanguageCode: string): void {
    this._currentAudioLanguageCode = newLanguageCode;
  }

  /**
   * @return {boolean} Whether auto generation for the audio is allowed.
   */
  isAutogeneratedAudioAllowed(): boolean {
    return this._isAutogeneratedAudioAllowed();
  }

  /**
   * @return {boolean} Whether an auto generated language code is selected.
   */
  isAutogeneratedLanguageCodeSelected(): boolean {
    if (this._currentAudioLanguageCode) {
      return this.languageUtilService.isAutogeneratedAudioLanguage(
        this._currentAudioLanguageCode
      );
    }
    return false;
  }

  /**
   * @return {boolean} Whether an automatic text to speech is enabled.
   */
  isAutomaticTextToSpeechEnabled(): boolean {
    return this._automaticTextToSpeechEnabled;
  }

  /**
   * @return {string} the speech synthesis language code.
   */
  getSpeechSynthesisLanguageCode(): string | null {
    if (!this._explorationLanguageCode) {
      return null;
    }
    let autogeneratedAudioLanguage =
      this.languageUtilService.getAutogeneratedAudioLanguage(
        this._explorationLanguageCode
      );
    if (this.browserCheckerService.isMobileDevice()) {
      return autogeneratedAudioLanguage.speechSynthesisCodeMobile;
    }
    return autogeneratedAudioLanguage.speechSynthesisCode;
  }
}

angular
  .module('oppia')
  .factory(
    'AudioTranslationLanguageService',
    downgradeInjectable(AudioTranslationLanguageService)
  );