mirror of https://github.com/OpenVidu/openvidu.git
ov-components: Refactored translate service
parent
7336f4f609
commit
28a0574d99
|
@ -3,7 +3,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
|
||||||
import { MatSelect } from '@angular/material/select';
|
import { MatSelect } from '@angular/material/select';
|
||||||
import { StorageService } from '../../../services/storage/storage.service';
|
import { StorageService } from '../../../services/storage/storage.service';
|
||||||
import { TranslateService } from '../../../services/translate/translate.service';
|
import { TranslateService } from '../../../services/translate/translate.service';
|
||||||
import { LangOption } from '../../../models/lang.model';
|
import { AvailableLangs, LangOption } from '../../../models/lang.model';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,20 +42,20 @@ export class LangSelectorComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.subscribeToLangSelected();
|
this.subscribeToLangSelected();
|
||||||
this.languages = this.translateService.getLanguagesInfo();
|
this.languages = this.translateService.getAvailableLanguages();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.langSub?.unsubscribe();
|
this.langSub?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLangSelected(lang: string) {
|
onLangSelected(lang: AvailableLangs) {
|
||||||
this.translateService.setLanguage(lang);
|
this.translateService.setCurrentLanguage(lang);
|
||||||
this.storageSrv.setLang(lang);
|
this.storageSrv.setLang(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeToLangSelected() {
|
subscribeToLangSelected() {
|
||||||
this.langSub = this.translateService.langSelectedObs.subscribe((lang) => {
|
this.langSub = this.translateService.selectedLanguageOption$.subscribe((lang) => {
|
||||||
this.langSelected = lang;
|
this.langSelected = lang;
|
||||||
this.onLangChanged.emit(lang);
|
this.onLangChanged.emit(lang);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { CaptionsLangOption } from '../../models/caption.model';
|
||||||
// import { CaptionService } from '../../services/caption/caption.service';
|
// import { CaptionService } from '../../services/caption/caption.service';
|
||||||
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
||||||
import { TranslateService } from '../../services/translate/translate.service';
|
import { TranslateService } from '../../services/translate/translate.service';
|
||||||
import { LangOption } from '../../models/lang.model';
|
import { AvailableLangs, LangOption } from '../../models/lang.model';
|
||||||
import { StorageService } from '../../services/storage/storage.service';
|
import { StorageService } from '../../services/storage/storage.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,7 +244,7 @@ export class LangDirective implements OnDestroy {
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
@Input() set lang(value: string) {
|
@Input() set lang(value: AvailableLangs) {
|
||||||
this.update(value);
|
this.update(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,8 +273,8 @@ export class LangDirective implements OnDestroy {
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
update(value: string) {
|
update(value: AvailableLangs) {
|
||||||
this.translateService.setLanguage(value);
|
this.translateService.setCurrentLanguage(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ export class LangOptionsDirective implements OnDestroy {
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
update(value: LangOption[] | undefined) {
|
update(value: LangOption[] | undefined) {
|
||||||
this.translateService.setLanguageOptions(value);
|
this.translateService.updateLanguageOptions(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
export type AvailableLangs = 'en' | 'es' | 'de' | 'fr' | 'cn' | 'hi' | 'it' | 'ja' | 'nl' | 'pt';
|
||||||
|
|
||||||
|
export type AdditionalTranslationsType = Record<AvailableLangs, Record<string, any>>;
|
||||||
|
|
||||||
export interface LangOption {
|
export interface LangOption {
|
||||||
name: string;
|
name: string;
|
||||||
lang: string;
|
lang: AvailableLangs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,6 @@ import { OpenViduComponentsDirectiveModule } from './directives/template/openvid
|
||||||
import { AppMaterialModule } from './openvidu-components-angular.material.module';
|
import { AppMaterialModule } from './openvidu-components-angular.material.module';
|
||||||
import { VirtualBackgroundService } from './services/virtual-background/virtual-background.service';
|
import { VirtualBackgroundService } from './services/virtual-background/virtual-background.service';
|
||||||
import { BroadcastingService } from './services/broadcasting/broadcasting.service';
|
import { BroadcastingService } from './services/broadcasting/broadcasting.service';
|
||||||
import { TranslateService } from './services/translate/translate.service';
|
|
||||||
import { GlobalConfigService } from './services/config/global-config.service';
|
import { GlobalConfigService } from './services/config/global-config.service';
|
||||||
import { OpenViduComponentsConfigService } from './services/config/directive-config.service';
|
import { OpenViduComponentsConfigService } from './services/config/directive-config.service';
|
||||||
|
|
||||||
|
@ -119,6 +118,7 @@ const privateComponents = [
|
||||||
RemoteParticipantTracksPipe,
|
RemoteParticipantTracksPipe,
|
||||||
DurationFromSecondsPipe,
|
DurationFromSecondsPipe,
|
||||||
TrackPublishedTypesPipe,
|
TrackPublishedTypesPipe,
|
||||||
|
TranslatePipe,
|
||||||
OpenViduComponentsDirectiveModule,
|
OpenViduComponentsDirectiveModule,
|
||||||
ApiDirectiveModule
|
ApiDirectiveModule
|
||||||
],
|
],
|
||||||
|
@ -150,7 +150,6 @@ const privateComponents = [
|
||||||
PlatformService,
|
PlatformService,
|
||||||
RecordingService,
|
RecordingService,
|
||||||
StorageService,
|
StorageService,
|
||||||
TranslateService,
|
|
||||||
VirtualBackgroundService,
|
VirtualBackgroundService,
|
||||||
provideHttpClient(withInterceptorsFromDi())
|
provideHttpClient(withInterceptorsFromDi())
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Injectable, Inject, Injector, Type, Optional } from '@angular/core';
|
import { Injectable, Inject, Injector, Optional } from '@angular/core';
|
||||||
import { LayoutService } from '../layout/layout.service';
|
import { LayoutService } from '../layout/layout.service';
|
||||||
import { OpenViduComponentsConfig } from '../../config/openvidu-components-angular.config';
|
import { OpenViduComponentsConfig } from '../../config/openvidu-components-angular.config';
|
||||||
|
|
||||||
|
|
|
@ -10,18 +10,27 @@ import * as ja from '../../lang/ja.json';
|
||||||
import * as nl from '../../lang/nl.json';
|
import * as nl from '../../lang/nl.json';
|
||||||
import * as pt from '../../lang/pt.json';
|
import * as pt from '../../lang/pt.json';
|
||||||
import { StorageService } from '../storage/storage.service';
|
import { StorageService } from '../storage/storage.service';
|
||||||
import { LangOption } from '../../models/lang.model';
|
import { AdditionalTranslationsType, AvailableLangs, LangOption } from '../../models/lang.model';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* Service responsible for managing translations for the application.
|
||||||
|
* This service provides methods to add additional translations and to translate keys into the currently selected language.
|
||||||
|
*
|
||||||
|
* The pipe {@link TranslatePipe} is used to translate keys in the templates.
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class TranslateService {
|
export class TranslateService {
|
||||||
private availableLanguages = { en, es, de, fr, cn, hi, it, ja, nl, pt };
|
// Maps language codes to their respective translations
|
||||||
private langOptions: LangOption[] = [
|
private translationsByLanguage: Record<AvailableLangs, any> = { en, es, de, fr, cn, hi, it, ja, nl, pt };
|
||||||
|
|
||||||
|
// Stores additional translations provided by the application
|
||||||
|
private additionalTranslations: Record<AvailableLangs, any> | {} = {};
|
||||||
|
|
||||||
|
// List of available language options with their display names and language codes
|
||||||
|
private languageOptions: LangOption[] = [
|
||||||
{ name: 'English', lang: 'en' },
|
{ name: 'English', lang: 'en' },
|
||||||
{ name: 'Español', lang: 'es' },
|
{ name: 'Español', lang: 'es' },
|
||||||
{ name: 'Deutsch', lang: 'de' },
|
{ name: 'Deutsch', lang: 'de' },
|
||||||
|
@ -33,68 +42,152 @@ export class TranslateService {
|
||||||
{ name: 'Dutch', lang: 'nl' },
|
{ name: 'Dutch', lang: 'nl' },
|
||||||
{ name: 'Português', lang: 'pt' }
|
{ name: 'Português', lang: 'pt' }
|
||||||
];
|
];
|
||||||
private currentLang: any;
|
|
||||||
langSelected: LangOption;
|
// The currently active translations for the selected language
|
||||||
langSelectedObs: Observable<LangOption>;
|
private activeTranslations: any;
|
||||||
private _langSelected: BehaviorSubject<LangOption> = new BehaviorSubject<LangOption>({ name: 'English', lang: 'en' });
|
|
||||||
|
// The currently selected language option
|
||||||
|
private selectedLanguageOption: LangOption;
|
||||||
|
|
||||||
|
// BehaviorSubject to manage the currently selected language option
|
||||||
|
private _selectedLanguageSubject: BehaviorSubject<LangOption> = new BehaviorSubject<LangOption>({ name: 'English', lang: 'en' });
|
||||||
|
|
||||||
|
// Observable that emits changes to the selected language option
|
||||||
|
selectedLanguageOption$: Observable<LangOption>;
|
||||||
|
|
||||||
constructor(private storageService: StorageService) {
|
constructor(private storageService: StorageService) {
|
||||||
this.langSelectedObs = this._langSelected.asObservable();
|
this.selectedLanguageOption$ = this._selectedLanguageSubject.asObservable();
|
||||||
this.updateLangSelected();
|
this.refreshSelectedLanguage();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLanguage(lang: string) {
|
/**
|
||||||
const matchingLang = this.langOptions.find((l) => l.lang === lang);
|
* Adds multiple translations to the additional translations storage.
|
||||||
|
* @param translations - A record where each key is a language code and the value is an object of translations for that language.
|
||||||
|
*/
|
||||||
|
addTranslations(translations: Partial<AdditionalTranslationsType>): void {
|
||||||
|
this.additionalTranslations = translations;
|
||||||
|
}
|
||||||
|
|
||||||
if (matchingLang) {
|
/**
|
||||||
this.currentLang = await this.getLangData(lang);
|
* Sets the current language based on the provided language code.
|
||||||
this.langSelected = matchingLang;
|
* Updates the selected language and emits the change.
|
||||||
this._langSelected.next(this.langSelected);
|
* @param lang - The language code to set.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async setCurrentLanguage(lang: AvailableLangs): Promise<void> {
|
||||||
|
// Find the language option that matches the provided language code
|
||||||
|
const selectedLanguageOption = this.languageOptions.find((option) => option.lang === lang);
|
||||||
|
|
||||||
|
if (selectedLanguageOption) {
|
||||||
|
// Fetch the language data and update the current language
|
||||||
|
this.activeTranslations = await this.fetchLanguageData(lang);
|
||||||
|
this.selectedLanguageOption = selectedLanguageOption;
|
||||||
|
this._selectedLanguageSubject.next(this.selectedLanguageOption);
|
||||||
|
// Notify subscribers of the language change
|
||||||
|
this._selectedLanguageSubject.next(this.selectedLanguageOption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setLanguageOptions(options: LangOption[] | undefined) {
|
/**
|
||||||
|
* Updates the available language options.
|
||||||
|
* @param options - The new language options to set.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
updateLanguageOptions(options?: LangOption[]): void {
|
||||||
if (options && options.length > 0) {
|
if (options && options.length > 0) {
|
||||||
this.langOptions = options;
|
this.languageOptions = options;
|
||||||
this.updateLangSelected();
|
this.refreshSelectedLanguage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getLangSelected(): LangOption {
|
/**
|
||||||
return this.langSelected;
|
* Retrieves the currently selected language option.
|
||||||
|
* @returns The currently selected language option.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
getSelectedLanguage(): LangOption {
|
||||||
|
return this.selectedLanguageOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLanguagesInfo(): LangOption[] {
|
/**
|
||||||
return this.langOptions;
|
* Retrieves the list of all available language options.
|
||||||
|
* @returns An array of available language options.
|
||||||
|
*/
|
||||||
|
getAvailableLanguages(): LangOption[] {
|
||||||
|
return this.languageOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a given key into the current language.
|
||||||
|
*
|
||||||
|
* This method first attempts to find the translation in the official translations.
|
||||||
|
* If the translation is not found, it then looks for the translation in the additional translations registered by the app.
|
||||||
|
*
|
||||||
|
* @param key - The key to be translated.
|
||||||
|
* @returns The translated string if found, otherwise an empty string.
|
||||||
|
*/
|
||||||
translate(key: string): string {
|
translate(key: string): string {
|
||||||
let result = this.currentLang;
|
// Attempt to find the translation in the official translations
|
||||||
|
let translation = this.findTranslation(this.activeTranslations, key);
|
||||||
|
|
||||||
key.split('.').forEach((prop) => {
|
if (!translation) {
|
||||||
|
// If not found, look for the translation in the additional translations
|
||||||
|
const additionalLangTranslations = this.additionalTranslations[this.selectedLanguageOption.lang];
|
||||||
|
translation = this.findTranslation(additionalLangTranslations, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return translation || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and returns a translation string from a nested translations source object based on a dot-separated key.
|
||||||
|
*
|
||||||
|
* @param translationsSource - The source object containing nested translation strings.
|
||||||
|
* @param key - A dot-separated string representing the path to the desired translation.
|
||||||
|
* @returns The translation string if found, otherwise `undefined`.
|
||||||
|
*/
|
||||||
|
private findTranslation(translationsSource: any, key: string): string | undefined {
|
||||||
|
let translation = translationsSource;
|
||||||
|
|
||||||
|
// Traverse the object tree based on the key structure
|
||||||
|
key.split('.').forEach((nestedKey) => {
|
||||||
try {
|
try {
|
||||||
result = result[prop];
|
|
||||||
|
translation = translation[nestedKey];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
|
return translation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateLangSelected() {
|
/**
|
||||||
const storageLang = this.storageService.getLang();
|
* Updates the currently selected language based on the stored language setting.
|
||||||
const langOpt = this.langOptions.find((opt) => opt.lang === storageLang);
|
*/
|
||||||
if (storageLang && langOpt) {
|
private async refreshSelectedLanguage() {
|
||||||
this.langSelected = langOpt;
|
const storedLang = this.storageService.getLang();
|
||||||
|
const matchingOption = this.languageOptions.find((option) => option.lang === storedLang);
|
||||||
|
|
||||||
|
if (storedLang && matchingOption) {
|
||||||
|
this.selectedLanguageOption = matchingOption;
|
||||||
} else {
|
} else {
|
||||||
this.langSelected = this.langOptions[0];
|
// Default to the first language option if no language is found in storage
|
||||||
|
this.selectedLanguageOption = this.languageOptions[0];
|
||||||
}
|
}
|
||||||
this.currentLang = await this.getLangData(this.langSelected.lang);
|
this.activeTranslations = await this.fetchLanguageData(this.selectedLanguageOption.lang);
|
||||||
this._langSelected.next(this.langSelected);
|
this._selectedLanguageSubject.next(this.selectedLanguageOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getLangData(lang: string): Promise<void> {
|
/**
|
||||||
if (!(lang in this.availableLanguages)) {
|
* Fetches the language data from the source based on the provided language code.
|
||||||
|
* @param lang - The language code to fetch data for.
|
||||||
|
* @returns The language data associated with the provided language code.
|
||||||
|
*/
|
||||||
|
private async fetchLanguageData(lang: AvailableLangs): Promise<any> {
|
||||||
|
if (!(lang in this.translationsByLanguage)) {
|
||||||
// Language not found in default languages options
|
// Language not found in default languages options
|
||||||
// Try to find it in the assets/lang directory
|
// Try to find it in the assets/lang directory
|
||||||
try {
|
try {
|
||||||
|
@ -102,9 +195,10 @@ export class TranslateService {
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Not found ${lang}.json in assets/lang`, error);
|
console.error(`Not found ${lang}.json in assets/lang`, error);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.availableLanguages[lang];
|
return this.translationsByLanguage[lang];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,12 @@ export * from './lib/models/room.model';
|
||||||
export * from './lib/models/toolbar.model';
|
export * from './lib/models/toolbar.model';
|
||||||
export * from './lib/models/logger.model'
|
export * from './lib/models/logger.model'
|
||||||
export * from './lib/models/storage.model';
|
export * from './lib/models/storage.model';
|
||||||
|
export * from './lib/models/lang.model';
|
||||||
export * from './lib/openvidu-components-angular.module';
|
export * from './lib/openvidu-components-angular.module';
|
||||||
// Pipes
|
// Pipes
|
||||||
export * from './lib/pipes/participant.pipe';
|
export * from './lib/pipes/participant.pipe';
|
||||||
export * from './lib/pipes/recording.pipe';
|
export * from './lib/pipes/recording.pipe';
|
||||||
|
export * from './lib/pipes/translate.pipe';
|
||||||
// Services
|
// Services
|
||||||
export * from './lib/services/action/action.service';
|
export * from './lib/services/action/action.service';
|
||||||
export * from './lib/services/broadcasting/broadcasting.service';
|
export * from './lib/services/broadcasting/broadcasting.service';
|
||||||
|
@ -54,5 +56,6 @@ export * from './lib/services/recording/recording.service';
|
||||||
export * from './lib/services/config/global-config.service';
|
export * from './lib/services/config/global-config.service';
|
||||||
export * from './lib/services/logger/logger.service';
|
export * from './lib/services/logger/logger.service';
|
||||||
export * from './lib/services/storage/storage.service';
|
export * from './lib/services/storage/storage.service';
|
||||||
|
export * from './lib/services/translate/translate.service';
|
||||||
|
|
||||||
export * from 'livekit-client';
|
export * from 'livekit-client';
|
||||||
|
|
Loading…
Reference in New Issue