@@ -18,12 +18,11 @@ import { CoreConstants } from '@/core/constants';
1818import { LangChangeEvent } from '@ngx-translate/core' ;
1919import { CoreConfig } from '@services/config' ;
2020import { CoreSubscriptions } from '@singletons/subscriptions' ;
21- import { makeSingleton , Translate , Http } from '@singletons' ;
21+ import { makeSingleton , Translate } from '@singletons' ;
2222
2323import moment from 'moment-timezone' ;
2424import { CoreSite } from '../classes/sites/site' ;
2525import { CorePlatform } from '@services/platform' ;
26- import { firstValueFrom } from 'rxjs' ;
2726import { CoreLogger } from '@singletons/logger' ;
2827import { CoreSites } from './sites' ;
2928
@@ -86,14 +85,9 @@ export class CoreLangProvider {
8685 * @param strings Object with the strings to add.
8786 * @param prefix A prefix to add to all keys.
8887 */
89- addSitePluginsStrings ( lang : string , strings : string [ ] , prefix ?: string ) : void {
88+ async addSitePluginsStrings ( lang : string , strings : string [ ] , prefix ?: string ) : Promise < void > {
9089 lang = lang . replace ( / _ / g, '-' ) ; // Use the app format instead of Moodle format.
9190
92- // Initialize structure if it doesn't exist.
93- if ( ! this . sitePluginsStrings [ lang ] ) {
94- this . sitePluginsStrings [ lang ] = { } ;
95- }
96-
9791 for ( const key in strings ) {
9892 const prefixedKey = prefix + key ;
9993 let value = strings [ key ] ;
@@ -111,7 +105,7 @@ export class CoreLangProvider {
111105 value = value . replace ( / { { { ( [ ^ ] + ) } } } / gm, '{{$1}}' ) ;
112106
113107 // Load the string.
114- this . loadString ( this . sitePluginsStrings , lang , prefixedKey , value ) ;
108+ await this . loadString ( this . sitePluginsStrings , lang , prefixedKey , value ) ;
115109 }
116110 }
117111
@@ -146,6 +140,12 @@ export class CoreLangProvider {
146140 * @returns Messages.
147141 */
148142 getMessages ( lang : string ) : Promise < Record < string , string > > {
143+ // Try to use the loaded language first because Translate.getTranslation always reads from the file.
144+ if ( Translate . translations [ lang ] ) {
145+ return Promise . resolve ( Translate . translations [ lang ] ) ;
146+ }
147+
148+ // Use Translate.getTranslation to read the translations from the file and store them in the translations variable.
149149 return new Promise ( resolve => CoreSubscriptions . once (
150150 Translate . getTranslation ( lang ) ,
151151 messages => resolve ( messages ) ,
@@ -154,9 +154,9 @@ export class CoreLangProvider {
154154 }
155155
156156 /**
157- * Get the parent language defined on the language strings.
157+ * Get the parent language for the current language defined on the language strings.
158158 *
159- * @returns If a parent language is set, return the index name .
159+ * @returns If a parent language is set, return the parent language .
160160 */
161161 getParentLanguage ( ) : string | undefined {
162162 const parentLang = Translate . instant ( 'core.parentlanguage' ) ;
@@ -165,6 +165,20 @@ export class CoreLangProvider {
165165 }
166166 }
167167
168+ /**
169+ * Get the parent language for a certain language.
170+ *
171+ * @returns If a parent language is set, return the parent language.
172+ */
173+ protected async getParentLanguageForLang ( lang : string ) : Promise < string | undefined > {
174+ const translations = await this . getMessages ( lang ) ;
175+
176+ const parentLang : string | undefined = translations [ 'core.parentlanguage' ] ;
177+ if ( parentLang && parentLang !== 'core.parentlanguage' && parentLang !== lang ) {
178+ return parentLang ;
179+ }
180+ }
181+
168182 /**
169183 * Change current language.
170184 *
@@ -192,7 +206,12 @@ export class CoreLangProvider {
192206 throw error ;
193207 } finally {
194208 // Load the custom and site plugins strings for the language.
195- if ( this . loadLangStrings ( this . customStrings , language ) || this . loadLangStrings ( this . sitePluginsStrings , language ) ) {
209+ const [ customStringsChangedLang , pluginsStringsChangedLang ] = await Promise . all ( [
210+ this . loadLangStrings ( this . customStrings , language ) ,
211+ this . loadLangStrings ( this . sitePluginsStrings , language ) ,
212+ ] ) ;
213+
214+ if ( customStringsChangedLang || pluginsStringsChangedLang ) {
196215 // Some lang strings have changed, emit an event to update the pipes.
197216 Translate . onLangChange . emit ( { lang : language , translations : Translate . translations [ language ] } ) ;
198217 }
@@ -388,12 +407,45 @@ export class CoreLangProvider {
388407 } ) ;
389408 }
390409
410+ /**
411+ * Check if a certain string is inherited from the parent language.
412+ *
413+ * @param lang Language being checked.
414+ * @param key Key of the string to check.
415+ * @param parentLang Parent language. If not set it will be calculated.
416+ * @returns True if the string is inherited (same as parent), false otherwise.
417+ */
418+ protected async isInheritedString ( lang : string , key : string , parentLang ?: string ) : Promise < boolean > {
419+ parentLang = parentLang ?? await this . getParentLanguageForLang ( lang ) ;
420+ if ( ! parentLang ) {
421+ return false ;
422+ }
423+
424+ const parentTranslations = await this . getMessages ( parentLang ) ;
425+ const childTranslations = await this . getMessages ( lang ) ;
426+
427+ return parentTranslations [ key ] === childTranslations [ key ] ;
428+ }
429+
430+ /**
431+ * Check if a language is parent of another language.
432+ *
433+ * @param possibleParentLang Possible parent language.
434+ * @param possibleChildLang Possible children language.
435+ * @returns True if lang is child of the possible parent language.
436+ */
437+ protected async isParentLang ( possibleParentLang : string , possibleChildLang : string ) : Promise < boolean > {
438+ const parentLang = await this . getParentLanguageForLang ( possibleChildLang ) ;
439+
440+ return ! ! parentLang && parentLang === possibleParentLang ;
441+ }
442+
391443 /**
392444 * Loads custom strings obtained from site.
393445 *
394446 * @param currentSite Current site object. If not defined, use current site.
395447 */
396- loadCustomStringsFromSite ( currentSite ?: CoreSite ) : void {
448+ async loadCustomStringsFromSite ( currentSite ?: CoreSite ) : Promise < void > {
397449 currentSite = currentSite ?? CoreSites . getCurrentSite ( ) ;
398450
399451 if ( ! currentSite ) {
@@ -406,15 +458,15 @@ export class CoreLangProvider {
406458 return ;
407459 }
408460
409- this . loadCustomStrings ( customStrings ) ;
461+ await this . loadCustomStrings ( customStrings ) ;
410462 }
411463
412464 /**
413465 * Load certain custom strings.
414466 *
415467 * @param strings Custom strings to load (tool_mobile_customlangstrings).
416468 */
417- loadCustomStrings ( strings : string ) : void {
469+ async loadCustomStrings ( strings : string ) : Promise < void > {
418470 if ( strings === this . customStringsRaw ) {
419471 // Strings haven't changed, stop.
420472 return ;
@@ -430,7 +482,7 @@ export class CoreLangProvider {
430482 let currentLangChanged = false ;
431483
432484 const list : string [ ] = strings . split ( / (?: \r \n | \r | \n ) / ) ;
433- list . forEach ( ( entry : string ) => {
485+ await Promise . all ( list . map ( async ( entry : string ) => {
434486 const values : string [ ] = entry . split ( '|' ) . map ( value => value . trim ( ) ) ;
435487
436488 if ( values . length < 3 ) {
@@ -444,12 +496,8 @@ export class CoreLangProvider {
444496 currentLangChanged = true ;
445497 }
446498
447- if ( ! this . customStrings [ lang ] ) {
448- this . customStrings [ lang ] = { } ;
449- }
450-
451- this . loadString ( this . customStrings , lang , values [ 0 ] , values [ 1 ] ) ;
452- } ) ;
499+ await this . loadString ( this . customStrings , lang , values [ 0 ] , values [ 1 ] ) ;
500+ } ) ) ;
453501
454502 this . customStringsRaw = strings ;
455503
@@ -469,9 +517,35 @@ export class CoreLangProvider {
469517 * @param lang Language to load.
470518 * @returns Whether the translation table was modified.
471519 */
472- loadLangStrings ( langObject : CoreLanguageObject , lang : string ) : boolean {
520+ async loadLangStrings ( langObject : CoreLanguageObject , lang : string ) : Promise < boolean > {
473521 let langApplied = false ;
474522
523+ // First load the strings of the parent language if they're inherited.
524+ const parentLanguage = await this . getParentLanguageForLang ( lang ) ;
525+ if ( parentLanguage && langObject [ parentLanguage ] ) {
526+ for ( const key in langObject [ parentLanguage ] ) {
527+ if ( langObject [ lang ] && langObject [ lang ] [ key ] ) {
528+ // There is a custom string for the child language, ignore the parent one.
529+ continue ;
530+ }
531+
532+ const isInheritedString = await this . isInheritedString ( lang , key , parentLanguage ) ;
533+ if ( isInheritedString ) {
534+ // Store the modification in langObject so it can be undone later.
535+ langObject [ lang ] = langObject [ lang ] || { } ;
536+ langObject [ lang ] [ key ] = {
537+ original : Translate . translations [ lang ] [ key ] ,
538+ value : langObject [ parentLanguage ] [ key ] . value ,
539+ applied : true ,
540+ } ;
541+
542+ // Store the string in the translations table.
543+ Translate . translations [ lang ] [ key ] = langObject [ parentLanguage ] [ key ] . value ;
544+ langApplied = true ;
545+ }
546+ }
547+ }
548+
475549 if ( langObject [ lang ] ) {
476550 for ( const key in langObject [ lang ] ) {
477551 const entry = langObject [ lang ] [ key ] ;
@@ -500,9 +574,26 @@ export class CoreLangProvider {
500574 * @param key String key.
501575 * @param value String value.
502576 */
503- loadString ( langObject : CoreLanguageObject , lang : string , key : string , value : string ) : void {
577+ async loadString ( langObject : CoreLanguageObject , lang : string , key : string , value : string ) : Promise < void > {
504578 lang = lang . replace ( / _ / g, '-' ) ; // Use the app format instead of Moodle format.
505579
580+ // If the language to modify is the parent language of a loaded language and the value is inherited,
581+ // update the child language too.
582+ for ( const loadedLang in Translate . translations ) {
583+ if ( loadedLang === lang ) {
584+ continue ;
585+ }
586+
587+ const isInheritedString = await this . isParentLang ( lang , loadedLang ) &&
588+ await this . isInheritedString ( loadedLang , key , lang ) ;
589+ if ( isInheritedString ) {
590+ // Modify the child language too.
591+ await this . loadString ( langObject , loadedLang , key , value ) ;
592+ }
593+ }
594+
595+ langObject [ lang ] = langObject [ lang ] || { } ;
596+
506597 if ( Translate . translations [ lang ] ) {
507598 // The language is loaded.
508599 // Store the original value of the string.
@@ -529,13 +620,10 @@ export class CoreLangProvider {
529620 *
530621 * @param lang Language code.
531622 * @returns Promise resolved with the file contents.
623+ * @deprecated since 5.0. Use getMessages instead.
532624 */
533625 async readLangFile ( lang : CoreLangLanguage ) : Promise < Record < string , string > > {
534- const observable = Http . get ( `assets/lang/${ lang } .json` , {
535- responseType : 'json' ,
536- } ) ;
537-
538- return < Record < string , string > > await firstValueFrom ( observable ) ;
626+ return this . getMessages ( lang ) ;
539627 }
540628
541629 /**
@@ -598,7 +686,7 @@ export class CoreLangProvider {
598686 if ( fallbackLang ) {
599687 try {
600688 // Merge parent translations with the child ones.
601- const parentTranslations = Translate . translations [ fallbackLang ] ?? await this . readLangFile ( fallbackLang ) ;
689+ const parentTranslations = await this . getMessages ( fallbackLang ) ;
602690
603691 const mergedData = {
604692 ...parentTranslations ,
0 commit comments