import axios from 'axios'; import po2Vuei18n from 'po-to-vue-i18n'; import _ from 'lodash'; import store2 from 'store2'; export interface WeblateServiceSetup { host: string; translationProject: string; translationComponent: string; timeout?: number; } export enum LanguageDirection { LEFT_TO_RIGHT = 'ltr', RIGHT_TO_LEFT = 'rtl', } export interface LanguageData { code: string; name: string; direction: LanguageDirection; url: string; } export interface IWeblateService { getAvailableLanguages(): Promise; } export class WeblateService implements IWeblateService { private storageInstance: any; private axiosInstance: any; private host: string; private translationProject: string; private translationComponent: string; private cachedTranslationsKey: string = 'cachedTranslations'; private lastUpdatedKey: string = 'lastUpdate'; private availableLanguagesKey: string = 'availableLanguages'; private cacheLifespan: number = 86400000; // 1 day constructor( private i18nInstance: any, private setup: WeblateServiceSetup, ) { this.storageInstance = store2; this.host = this.setup.host; this.translationProject = this.setup.translationProject; this.translationComponent = this.setup.translationComponent; this.axiosInstance = axios.create({ baseURL: this.setup.host, timeout: this.setup.timeout, headers: { 'Content-Type': 'application/json', }, }); this.fetchTranslations(); } public async getAvailableLanguages(): Promise { return Promise.resolve() .then(() => { if (!this.languageCacheExpired()) { return Promise.resolve(this.getCachedLanguages()); } else { return this.getUpdatedAvailableLanguages(); } }); } private async getUpdatedAvailableLanguages(): Promise { return this.axiosInstance.request('/api/languages/?format=json') .then((response: any) => { if ((response?.data?.results || []).length === 0) { return []; } else { const availableLanguages: LanguageData[] = response?.data?.results as LanguageData[]; this.storeAvailableLanguages(availableLanguages); return availableLanguages; } }); } private async fetchTranslations(): Promise { return this.getAvailableLanguages() .then((languages: LanguageData[]) => { languages.forEach((language: LanguageData) => { this.fetchCachedTranslations(language.code); }); if (this.languageCacheExpired()) { return Promise.all(languages.map((language: LanguageData) => { return this.fetchTranslationsForLang(language); })); } else { return Promise.resolve([]); } }) .then(() => { return Promise.resolve(); }); } private async fetchTranslationsForLang(language: LanguageData): Promise { return po2Vuei18n.po2Vuei18nJSONFromUri( `${this.host}/download/${this.translationProject}/` + `${this.translationComponent}/${language.code}/`, ) .then((translations: any) => { const result: any = {}; _.each(translations, (value, key) => { if (_.isObject(value)) { result[key] = (value as any)[Object.keys(value)[0]]; } else { result[key] = value; } }); this.storeTranslations(language.code, result); this.i18nInstance.setLocaleMessage(language.code, result); }); } private getLocalLanguage(): string { return this.storageInstance.local.get('lang') || this.storageInstance.session.get('lang') || navigator.language.split('-')[0]; } private fetchCachedTranslations(code: string): any { const key: string = `${this.cachedTranslationsKey}-${code}`; const cachedData: string = this.storageInstance.local.get(key) || this.storageInstance.session.get(key); if (Object.keys(cachedData || {}).length > 0) { this.i18nInstance.setLocaleMessage(code, cachedData); } } private storeTranslations(code: string, translations: any) { const key: string = `${this.cachedTranslationsKey}-${code}`; this.storageInstance.local.set(key, translations); this.storageInstance.local.set(this.lastUpdatedKey, new Date()); } private storeAvailableLanguages(languages: any) { this.storageInstance.local.set(this.availableLanguagesKey, languages); } private getCachedLanguages(): LanguageData[] { return this.storageInstance.local.get(this.availableLanguagesKey) || this.storageInstance.session.get(this.availableLanguagesKey); } private languageCacheExpired(): boolean { const lastTimeUpdated = this.storageInstance.local.get(this.lastUpdatedKey) || this.storageInstance.session.get(this.lastUpdatedKey); return !lastTimeUpdated || (new Date().getTime() - new Date(lastTimeUpdated).getTime()) > this.cacheLifespan; } }