Skip to content

Commit 6dc0af9

Browse files
authored
Merge pull request #4299 from dpalou/MOBILE-4603
MOBILE-4603 lang: Inherit custom strings from parent language
2 parents fa60592 + 1350def commit 6dc0af9

File tree

2 files changed

+119
-30
lines changed

2 files changed

+119
-30
lines changed

src/core/services/lang.ts

Lines changed: 118 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import { CoreConstants } from '@/core/constants';
1818
import { LangChangeEvent } from '@ngx-translate/core';
1919
import { CoreConfig } from '@services/config';
2020
import { CoreSubscriptions } from '@singletons/subscriptions';
21-
import { makeSingleton, Translate, Http } from '@singletons';
21+
import { makeSingleton, Translate } from '@singletons';
2222

2323
import moment from 'moment-timezone';
2424
import { CoreSite } from '../classes/sites/site';
2525
import { CorePlatform } from '@services/platform';
26-
import { firstValueFrom } from 'rxjs';
2726
import { CoreLogger } from '@singletons/logger';
2827
import { 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,

upgrade.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ For more information about upgrading, read the official documentation: https://m
88
- The parameters of treatDownloadedFile of plugin file handlers have changed. Now the third parameter is an object with all the optional parameters.
99
- Some CoreColors functions have been refactored to handle alpha and to validate colors.
1010
- The parameters of CoreUrl.addParamsToUrl have changed. Now the third parameter is an object with all the optional parameters.
11+
- The following CoreLang functions were converted to async to properly handle child languages: addSitePluginsStrings, loadCustomStringsFromSite, loadCustomStrings, loadLangStrings, loadString.
1112

1213
=== 4.5.0 ===
1314

0 commit comments

Comments
 (0)