@@ -18,12 +18,11 @@ import { CoreConstants } from '@/core/constants';
18
18
import { LangChangeEvent } from '@ngx-translate/core' ;
19
19
import { CoreConfig } from '@services/config' ;
20
20
import { CoreSubscriptions } from '@singletons/subscriptions' ;
21
- import { makeSingleton , Translate , Http } from '@singletons' ;
21
+ import { makeSingleton , Translate } from '@singletons' ;
22
22
23
23
import moment from 'moment-timezone' ;
24
24
import { CoreSite } from '../classes/sites/site' ;
25
25
import { CorePlatform } from '@services/platform' ;
26
- import { firstValueFrom } from 'rxjs' ;
27
26
import { CoreLogger } from '@singletons/logger' ;
28
27
import { CoreSites } from './sites' ;
29
28
@@ -86,14 +85,9 @@ export class CoreLangProvider {
86
85
* @param strings Object with the strings to add.
87
86
* @param prefix A prefix to add to all keys.
88
87
*/
89
- addSitePluginsStrings ( lang : string , strings : string [ ] , prefix ?: string ) : void {
88
+ async addSitePluginsStrings ( lang : string , strings : string [ ] , prefix ?: string ) : Promise < void > {
90
89
lang = lang . replace ( / _ / g, '-' ) ; // Use the app format instead of Moodle format.
91
90
92
- // Initialize structure if it doesn't exist.
93
- if ( ! this . sitePluginsStrings [ lang ] ) {
94
- this . sitePluginsStrings [ lang ] = { } ;
95
- }
96
-
97
91
for ( const key in strings ) {
98
92
const prefixedKey = prefix + key ;
99
93
let value = strings [ key ] ;
@@ -111,7 +105,7 @@ export class CoreLangProvider {
111
105
value = value . replace ( / { { { ( [ ^ ] + ) } } } / gm, '{{$1}}' ) ;
112
106
113
107
// Load the string.
114
- this . loadString ( this . sitePluginsStrings , lang , prefixedKey , value ) ;
108
+ await this . loadString ( this . sitePluginsStrings , lang , prefixedKey , value ) ;
115
109
}
116
110
}
117
111
@@ -146,6 +140,12 @@ export class CoreLangProvider {
146
140
* @returns Messages.
147
141
*/
148
142
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.
149
149
return new Promise ( resolve => CoreSubscriptions . once (
150
150
Translate . getTranslation ( lang ) ,
151
151
messages => resolve ( messages ) ,
@@ -154,9 +154,9 @@ export class CoreLangProvider {
154
154
}
155
155
156
156
/**
157
- * Get the parent language defined on the language strings.
157
+ * Get the parent language for the current language defined on the language strings.
158
158
*
159
- * @returns If a parent language is set, return the index name .
159
+ * @returns If a parent language is set, return the parent language .
160
160
*/
161
161
getParentLanguage ( ) : string | undefined {
162
162
const parentLang = Translate . instant ( 'core.parentlanguage' ) ;
@@ -165,6 +165,20 @@ export class CoreLangProvider {
165
165
}
166
166
}
167
167
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
+
168
182
/**
169
183
* Change current language.
170
184
*
@@ -192,7 +206,12 @@ export class CoreLangProvider {
192
206
throw error ;
193
207
} finally {
194
208
// 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 ) {
196
215
// Some lang strings have changed, emit an event to update the pipes.
197
216
Translate . onLangChange . emit ( { lang : language , translations : Translate . translations [ language ] } ) ;
198
217
}
@@ -388,12 +407,45 @@ export class CoreLangProvider {
388
407
} ) ;
389
408
}
390
409
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
+
391
443
/**
392
444
* Loads custom strings obtained from site.
393
445
*
394
446
* @param currentSite Current site object. If not defined, use current site.
395
447
*/
396
- loadCustomStringsFromSite ( currentSite ?: CoreSite ) : void {
448
+ async loadCustomStringsFromSite ( currentSite ?: CoreSite ) : Promise < void > {
397
449
currentSite = currentSite ?? CoreSites . getCurrentSite ( ) ;
398
450
399
451
if ( ! currentSite ) {
@@ -406,15 +458,15 @@ export class CoreLangProvider {
406
458
return ;
407
459
}
408
460
409
- this . loadCustomStrings ( customStrings ) ;
461
+ await this . loadCustomStrings ( customStrings ) ;
410
462
}
411
463
412
464
/**
413
465
* Load certain custom strings.
414
466
*
415
467
* @param strings Custom strings to load (tool_mobile_customlangstrings).
416
468
*/
417
- loadCustomStrings ( strings : string ) : void {
469
+ async loadCustomStrings ( strings : string ) : Promise < void > {
418
470
if ( strings === this . customStringsRaw ) {
419
471
// Strings haven't changed, stop.
420
472
return ;
@@ -430,7 +482,7 @@ export class CoreLangProvider {
430
482
let currentLangChanged = false ;
431
483
432
484
const list : string [ ] = strings . split ( / (?: \r \n | \r | \n ) / ) ;
433
- list . forEach ( ( entry : string ) => {
485
+ await Promise . all ( list . map ( async ( entry : string ) => {
434
486
const values : string [ ] = entry . split ( '|' ) . map ( value => value . trim ( ) ) ;
435
487
436
488
if ( values . length < 3 ) {
@@ -444,12 +496,8 @@ export class CoreLangProvider {
444
496
currentLangChanged = true ;
445
497
}
446
498
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
+ } ) ) ;
453
501
454
502
this . customStringsRaw = strings ;
455
503
@@ -469,9 +517,35 @@ export class CoreLangProvider {
469
517
* @param lang Language to load.
470
518
* @returns Whether the translation table was modified.
471
519
*/
472
- loadLangStrings ( langObject : CoreLanguageObject , lang : string ) : boolean {
520
+ async loadLangStrings ( langObject : CoreLanguageObject , lang : string ) : Promise < boolean > {
473
521
let langApplied = false ;
474
522
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
+
475
549
if ( langObject [ lang ] ) {
476
550
for ( const key in langObject [ lang ] ) {
477
551
const entry = langObject [ lang ] [ key ] ;
@@ -500,9 +574,26 @@ export class CoreLangProvider {
500
574
* @param key String key.
501
575
* @param value String value.
502
576
*/
503
- loadString ( langObject : CoreLanguageObject , lang : string , key : string , value : string ) : void {
577
+ async loadString ( langObject : CoreLanguageObject , lang : string , key : string , value : string ) : Promise < void > {
504
578
lang = lang . replace ( / _ / g, '-' ) ; // Use the app format instead of Moodle format.
505
579
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
+
506
597
if ( Translate . translations [ lang ] ) {
507
598
// The language is loaded.
508
599
// Store the original value of the string.
@@ -529,13 +620,10 @@ export class CoreLangProvider {
529
620
*
530
621
* @param lang Language code.
531
622
* @returns Promise resolved with the file contents.
623
+ * @deprecated since 5.0. Use getMessages instead.
532
624
*/
533
625
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 ) ;
539
627
}
540
628
541
629
/**
@@ -598,7 +686,7 @@ export class CoreLangProvider {
598
686
if ( fallbackLang ) {
599
687
try {
600
688
// Merge parent translations with the child ones.
601
- const parentTranslations = Translate . translations [ fallbackLang ] ?? await this . readLangFile ( fallbackLang ) ;
689
+ const parentTranslations = await this . getMessages ( fallbackLang ) ;
602
690
603
691
const mergedData = {
604
692
...parentTranslations ,
0 commit comments