From 7e8332d978d2da3f01473a139e187bd1ec7f91af Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 17 May 2018 12:46:24 +0200 Subject: [PATCH] MOBILE-2413 lang: Fix custom and plugins strings --- src/app/app.component.ts | 29 ++++++++- src/providers/lang.ts | 126 +++++++++++++++++++++++++++++++-------- 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e3ba3417f..22f8c0aec 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -18,7 +18,9 @@ import { StatusBar } from '@ionic-native/status-bar'; import { SplashScreen } from '@ionic-native/splash-screen'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; +import { CoreLangProvider } from '@providers/lang'; import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreLoginHelperProvider } from '@core/login/providers/helper'; @Component({ @@ -33,7 +35,7 @@ export class MoodleMobileApp implements OnInit { constructor(private platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, logger: CoreLoggerProvider, private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, - private appProvider: CoreAppProvider) { + private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) { this.logger = logger.getInstance('AppComponent'); platform.ready().then(() => { @@ -49,9 +51,12 @@ export class MoodleMobileApp implements OnInit { * Component being initialized. */ ngOnInit(): void { - // Go to sites page when user is logged out. this.eventsProvider.on(CoreEventsProvider.LOGOUT, () => { + // Go to sites page when user is logged out. this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage'); + + // Unload lang custom strings. + this.langProvider.clearCustomStrings(); }); // Listen for session expired events. @@ -111,5 +116,25 @@ export class MoodleMobileApp implements OnInit { this.eventsProvider.on(CoreEventsProvider.APP_LAUNCHED_URL, (url) => { this.loginHelper.appLaunchedByURL(url); }); + + // Load custom lang strings. This cannot be done inside the lang provider because it causes circular dependencies. + const loadCustomStrings = (): void => { + const currentSite = this.sitesProvider.getCurrentSite(), + customStrings = currentSite && currentSite.getStoredConfig('tool_mobile_customlangstrings'); + + if (typeof customStrings != 'undefined') { + this.langProvider.loadCustomStrings(customStrings); + } + }; + + this.eventsProvider.on(CoreEventsProvider.LOGIN, () => { + loadCustomStrings(); + }); + + this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (siteId) => { + if (siteId == this.sitesProvider.getCurrentSiteId()) { + loadCustomStrings(); + } + }); } } diff --git a/src/providers/lang.ts b/src/providers/lang.ts index 6207168de..038001174 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -53,13 +53,10 @@ export class CoreLangProvider { * @param {string} [prefix] A prefix to add to all keys. */ addSitePluginsStrings(lang: string, strings: any, prefix?: string): void { - // Initialize structures if they don't exist. + // Initialize structure if it doesn't exist. if (!this.sitePluginsStrings[lang]) { this.sitePluginsStrings[lang] = {}; } - if (!this.translate.translations[lang]) { - this.translate.translations[lang] = {}; - } for (const key in strings) { const prefixedKey = prefix + key; @@ -75,19 +72,8 @@ export class CoreLangProvider { // Make sure we didn't add to many brackets in some case. value = value.replace(/{{{([^ ]+)}}}/gm, '{{$1}}'); - if (!this.sitePluginsStrings[lang][prefixedKey]) { - // It's a new site plugin string. Store the original value. - this.sitePluginsStrings[lang][prefixedKey] = { - original: this.translate.translations[lang][prefixedKey], - value: value - }; - } else { - // Site plugin string already defined. Store the new value. - this.sitePluginsStrings[lang][prefixedKey].value = value; - } - - // Store the string in the translations table. - this.translate.translations[lang][prefixedKey] = value; + // Load the string. + this.loadString(this.sitePluginsStrings, lang, prefixedKey, value); } } @@ -100,13 +86,38 @@ export class CoreLangProvider { changeCurrentLanguage(language: string): Promise { const promises = []; - promises.push(this.translate.use(language)); + // Change the language, resolving the promise when we receive the first value. + promises.push(new Promise((resolve, reject): void => { + const subscription = this.translate.use(language).subscribe((data) => { + resolve(data); + + // Data received, unsubscribe. Use a timeout because we can receive a value immediately. + setTimeout(() => { + subscription.unsubscribe(); + }); + }, (error) => { + reject(error); + + // Error received, unsubscribe. Use a timeout because we can receive a value immediately. + setTimeout(() => { + subscription.unsubscribe(); + }); + }); + })); + + // Change the config. promises.push(this.configProvider.set('current_language', language)); moment.locale(language); this.currentLanguage = language; - return Promise.all(promises); + return Promise.all(promises).finally(() => { + // Load the custom and site plugins strings for the language. + if (this.loadLangStrings(this.customStrings, language) || this.loadLangStrings(this.sitePluginsStrings, language)) { + // Some lang strings have changed, emit an event to update the pipes. + this.translate.onLangChange.emit({lang: language, translations: this.translate.translations[language]}); + } + }); } /** @@ -227,15 +238,75 @@ export class CoreLangProvider { this.customStrings[lang] = {}; } - // Store the original value of the custom string. - this.customStrings[lang][values[0]] = { - original: this.translate.translations[lang][values[0]], - value: values[1] + // Convert old keys format to new one. + const key = values[0].replace(/^mm\.core/, 'core').replace(/^mm\./, 'core.').replace(/^mma\./, 'addon.') + .replace(/^core\.sidemenu/, 'core.mainmenu').replace(/^addon\.grades/, 'core.grades') + .replace(/^addon\.participants/, 'core.user'); + + this.loadString(this.customStrings, lang, key, values[1]); + }); + + this.customStringsRaw = strings; + } + + /** + * Load custom strings for a certain language that weren't loaded because the language wasn't active. + * + * @param {any} langObject The object with the strings to load. + * @param {string} lang Language to load. + * @return {boolean} Whether the translation table was modified. + */ + loadLangStrings(langObject: any, lang: string): boolean { + let langApplied = false; + + if (langObject[lang]) { + for (const key in langObject[lang]) { + const entry = langObject[lang][key]; + + if (!entry.applied) { + // Store the original value of the string. + entry.original = this.translate.translations[lang][key]; + + // Store the string in the translations table. + this.translate.translations[lang][key] = entry.value; + + entry.applied = true; + langApplied = true; + } + } + } + + return langApplied; + } + + /** + * Load a string in a certain lang object and in the translate table if the lang is loaded. + * + * @param {any} langObject The object where to store the lang. + * @param {string} lang Language code. + * @param {string} key String key. + * @param {string} value String value. + */ + loadString(langObject: any, lang: string, key: string, value: string): void { + if (this.translate.translations[lang]) { + // The language is loaded. + // Store the original value of the string. + langObject[lang][key] = { + original: this.translate.translations[lang][key], + value: value, + applied: true }; // Store the string in the translations table. - this.translate.translations[lang][values[0]] = values[1]; - }); + this.translate.translations[lang][key] = value; + } else { + // The language isn't loaded. + // Save it in our object but not in the translations table, it will be loaded when the lang is loaded. + langObject[lang][key] = { + value: value, + applied: false + }; + } } /** @@ -246,6 +317,11 @@ export class CoreLangProvider { protected unloadStrings(strings: any): void { // Iterate over all languages and strings. for (const lang in strings) { + if (!this.translate.translations[lang]) { + // Language isn't loaded, nothing to unload. + continue; + } + const langStrings = strings[lang]; for (const key in langStrings) { const entry = langStrings[key];