bitmovin-player-ui
Version:
Bitmovin Player UI Framework
286 lines (252 loc) • 8.94 kB
text/typescript
import * as vocabularyDe from './languages/de.json';
import * as vocabularyEn from './languages/en.json';
import * as vocabularyEs from './languages/es.json';
import * as vocabularyFr from './languages/fr.json';
import * as vocabularyNl from './languages/nl.json';
import * as vocabularyPt from './languages/pt.json';
import { LocalizationConfig } from '../UIManager';
import { EventDispatcher } from '../EventDispatcher';
export const defaultVocabularies: Vocabularies = {
de: vocabularyDe,
en: vocabularyEn,
es: vocabularyEs,
fr: vocabularyFr,
nl: vocabularyNl,
pt: vocabularyPt,
};
export interface LanguageChangedArgument {
newLanguage: string;
oldLanguage: string;
}
const defaultLocalizationConfig: LocalizationConfig = {
language: 'en',
vocabularies: defaultVocabularies,
events: {
onLanguageChanged: new EventDispatcher<I18n, LanguageChangedArgument>(),
},
};
/**
* @category Localization
*/
export type Localizer = () => string;
/**
* @category Localization
*/
export type LocalizableText = string | Localizer;
/**
* @category Localization
*/
export interface Vocabulary {
'settings.video.quality': string;
'settings.audio.quality': string;
'settings.audio.track': string;
'settings.audio.mute': string;
'settings.audio.volume': string;
'settings.subtitles': string;
'settings.subtitles.options': string;
'settings.subtitles.font.color': string;
'settings.subtitles.font.opacity': string;
'settings.subtitles.background.color': string;
'settings.subtitles.background.opacity': string;
'settings.subtitles.font.size': string;
'settings.subtitles.font.style': string;
'settings.subtitles.font.style.bold': string;
'settings.subtitles.font.style.italic': string;
'settings.subtitles.characterEdge': string;
'settings.subtitles.characterEdge.raised': string;
'settings.subtitles.characterEdge.depressed': string;
'settings.subtitles.characterEdge.uniform': string;
'settings.subtitles.characterEdge.dropshadowed': string;
'settings.subtitles.characterEdge.color': string;
'settings.subtitles.font.family': string;
'settings.subtitles.font.family.monospacedserif': string;
'settings.subtitles.font.family.proportionalserif': string;
'settings.subtitles.font.family.monospacedsansserif': string;
'settings.subtitles.font.family.proportionalsansserif': string;
'settings.subtitles.font.family.casual': string;
'settings.subtitles.font.family.cursive': string;
'settings.subtitles.font.family.smallcapital': string;
'settings.subtitles.window.color': string;
'settings.subtitles.window.opacity': string;
play: string;
pause: string;
'settings.time.hours': string;
'settings.time.minutes': string;
'settings.time.seconds': string;
'colors.white': string;
'colors.black': string;
'colors.red': string;
'colors.green': string;
'colors.blue': string;
'colors.cyan': string;
'colors.yellow': string;
'colors.magenta': string;
percent: string;
settings: string;
'ads.remainingTime': string;
'ads.skip': string;
'ads.skippableIn': string;
'ads.adNumberOfTotal': string;
pictureInPicture: string;
appleAirplay: string;
googleCast: string;
vr: string;
off: string;
auto: string;
back: string;
reset: string;
replay: string;
normal: string;
default: string;
open: string;
close: string;
fullscreen: string;
speed: string;
playPause: string;
live: string;
'subtitle.example': string;
'subtitle.select': string;
playingOn: string;
connectingTo: string;
watermarkLink: string;
controlBar: string;
player: string;
seekBar: string;
'seekBar.value': string;
'seekBar.timeshift': string;
'seekBar.durationText': string;
'quickseek.forward': string;
'quickseek.rewind': string;
ecoMode: string;
'ecoMode.title': string;
}
/**
* @category Localization
*/
export type CustomVocabulary<V> = V & Partial<Vocabulary>;
/**
* @category Localization
*/
export interface Vocabularies {
[key: string]: CustomVocabulary<Record<string, string>>;
}
/**
* @category Localization
*/
export class I18n {
private language: string;
private defaultLanguage: string;
private vocabulary: CustomVocabulary<Record<string, string>>;
private vocabularies: Vocabularies;
private config: LocalizationConfig;
constructor(config: LocalizationConfig) {
this.setConfig(config);
}
public setConfig(config: LocalizationConfig) {
this.config = { ...defaultLocalizationConfig, ...config };
const detectBrowserLanguage = this.config.language === 'auto';
this.vocabularies = this.mergeVocabulariesWithDefaultVocabularies(this.config.vocabularies);
this.initializeLanguage(this.config.language, detectBrowserLanguage, this.vocabularies);
this.defaultLanguage = this.resolveDefaultLanguage(this.language);
if (!I18n.containsLanguage(this.vocabularies, this.language)) {
this.language = this.defaultLanguage;
}
this.initializeVocabulary(this.vocabularies);
}
public getConfig(): LocalizationConfig {
return this.config;
}
public setLanguage(language: string): void {
const oldLanguage = this.language;
if (I18n.containsLanguage(this.vocabularies, language)) {
this.language = language;
} else if (I18n.containsLanguage(this.vocabularies, language.slice(0, 2))) {
this.language = language.slice(0, 2);
} else {
this.language = this.defaultLanguage;
}
this.initializeVocabulary(this.vocabularies);
if (this.language !== oldLanguage) {
this.config.events.onLanguageChanged.dispatch(this, { newLanguage: this.language, oldLanguage: oldLanguage });
}
}
private static containsLanguage(vocabularies: Vocabularies, language: string) {
return vocabularies.hasOwnProperty(language);
}
private resolveDefaultLanguage(language: string): string {
if (I18n.containsLanguage(this.vocabularies, language)) {
return language;
}
if (I18n.containsLanguage(this.vocabularies, 'en')) {
return 'en';
}
const availableLanguages = Object.keys(this.vocabularies);
return availableLanguages.length > 0 ? availableLanguages[0] : 'en';
}
private mergeVocabulariesWithDefaultVocabularies(vocabularies: Vocabularies = {}) {
const rawVocabularies: Vocabularies = { ...defaultVocabularies, ...vocabularies };
return Object.keys(rawVocabularies).reduce((mergedVocabularies, language) => {
let vocabulary = rawVocabularies[language];
if (I18n.containsLanguage(defaultVocabularies, language) && I18n.containsLanguage(vocabularies, language)) {
vocabulary = { ...defaultVocabularies[language], ...vocabularies[language] };
}
return { ...mergedVocabularies, [language]: vocabulary };
}, {});
}
private initializeLanguage(language: string, browserLanguageDetectionEnabled: boolean, vocabularies: Vocabularies) {
if (browserLanguageDetectionEnabled) {
const userLanguage = window.navigator.language;
if (I18n.containsLanguage(vocabularies, userLanguage)) {
this.language = userLanguage;
return;
}
const shortenedUserLanguage = userLanguage.slice(0, 2);
if (I18n.containsLanguage(vocabularies, shortenedUserLanguage)) {
this.language = shortenedUserLanguage;
return;
}
}
this.language = language;
}
private initializeVocabulary(vocabularies: Vocabularies) {
this.vocabulary = ['en', this.language].reduce((vocab, lang) => ({ ...vocab, ...(vocabularies[lang] || {}) }), {});
}
private replaceVariableWithPlaceholderIfExists(text: string, config: any) {
const matches = text.match(new RegExp('{[a-zA-Z0-9]+}', 'g'));
if (matches.length === 0) {
return text;
}
return (
matches
.map((m: string) => ({ match: m, key: m.slice(1, -1) }))
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
.reduce((str, { key, match }) => (config.hasOwnProperty(key) ? str.replace(match, config[key]) : str), text)
);
}
public getLocalizer<V extends CustomVocabulary<Record<string, string>> = CustomVocabulary<Record<string, string>>>(
key: keyof V,
config?: Record<string, string | number>,
): Localizer {
return () => {
if (key == null) {
// because sometimes we call toDomElement() without configuring the component or setting text...
return undefined;
}
let vocabularyString = this.vocabulary[key as string];
if (vocabularyString == null) {
vocabularyString = key as string;
}
if (config != null) {
vocabularyString = this.replaceVariableWithPlaceholderIfExists(vocabularyString, config);
}
return vocabularyString;
};
}
public performLocalization(text: LocalizableText) {
return typeof text === 'function' ? text() : text;
}
}
/**
* @category Localization
*/
export const i18n = new I18n(defaultLocalizationConfig);