UNPKG

bitmovin-player-ui

Version:
286 lines (252 loc) 8.94 kB
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);