Merge pull request #3643 from NoelDeMartin/MOBILE-4288
MOBILE-4288 multilang: Use parent languagesmain
commit
70d473397b
|
@ -44,27 +44,34 @@ export class AddonFilterMultilangHandlerService extends CoreFilterDefaultHandler
|
|||
options?: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
): Promise<string> {
|
||||
let language = await CoreLang.getCurrentLanguage();
|
||||
// Get available languages.
|
||||
const regex = /<(?:lang|span)[^>]+lang="([a-zA-Z0-9_-]+)"[^>]*>.*?<\/(?:lang|span)>/img;
|
||||
const languages: Set<string> = new Set();
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
// Match the current language.
|
||||
const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g;
|
||||
let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g');
|
||||
while ((match = regex.exec(text))) {
|
||||
const language = match[1].toLowerCase().replace(/_/g, '-');
|
||||
|
||||
if (!text.match(currentLangRegEx)) {
|
||||
// Current lang not found. Try to find the first language.
|
||||
const matches = text.match(anyLangRegEx);
|
||||
if (matches?.[0]) {
|
||||
language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)?.[1] || language;
|
||||
currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g');
|
||||
} else {
|
||||
// No multi-lang tag found, stop.
|
||||
return text;
|
||||
}
|
||||
languages.add(language);
|
||||
}
|
||||
|
||||
// Extract contents of current language.
|
||||
// Find language to use.
|
||||
let language: string | undefined = await CoreLang.getCurrentLanguage();
|
||||
|
||||
if (!languages.has(language)) {
|
||||
language = CoreLang.getParentLanguage();
|
||||
}
|
||||
|
||||
if (!language) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Apply filter.
|
||||
const anyLangRegEx = /<(lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>.*?<\/(lang|span)>/img;
|
||||
const languageRegEx = language.replace(/-/g, '(?:-|_)');
|
||||
const currentLangRegEx = new RegExp(`<(?:lang|span)[^>]+lang="${languageRegEx}"[^>]*>(.*?)</(?:lang|span)>`, 'img');
|
||||
|
||||
text = text.replace(currentLangRegEx, '$1');
|
||||
// Delete the rest of languages
|
||||
text = text.replace(anyLangRegEx, '');
|
||||
|
||||
return text;
|
||||
|
|
|
@ -45,7 +45,7 @@ export class AddonFilterMultilang2HandlerService extends CoreFilterDefaultHandle
|
|||
|
||||
const currentLang = await CoreLang.getCurrentLanguage();
|
||||
this.replacementDone = false;
|
||||
const parentLanguage = CoreLang.getParentLanguage(currentLang);
|
||||
const parentLanguage = CoreLang.getParentLanguage();
|
||||
|
||||
const search = /{\s*mlang\s+((?:[a-z0-9_-]+)(?:\s*,\s*[a-z0-9_-]+\s*)*)\s*}(.*?){\s*mlang\s*}/gim;
|
||||
const result = text.replace(
|
||||
|
|
|
@ -152,12 +152,11 @@ export class CoreLangProvider {
|
|||
/**
|
||||
* Get the parent language defined on the language strings.
|
||||
*
|
||||
* @param currentLanguage Current language.
|
||||
* @returns If a parent language is set, return the index name.
|
||||
*/
|
||||
getParentLanguage(currentLanguage: string): string | undefined {
|
||||
getParentLanguage(): string | undefined {
|
||||
const parentLang = Translate.instant('core.parentlanguage');
|
||||
if (parentLang != '' && parentLang != 'core.parentlanguage' && parentLang != currentLanguage) {
|
||||
if (parentLang !== '' && parentLang !== 'core.parentlanguage' && parentLang !== this.currentLanguage) {
|
||||
return parentLang;
|
||||
}
|
||||
}
|
||||
|
@ -169,41 +168,16 @@ export class CoreLangProvider {
|
|||
* @returns Promise resolved when the change is finished.
|
||||
*/
|
||||
async changeCurrentLanguage(language: string): Promise<void> {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// Change the language, resolving the promise when we receive the first value.
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
CoreSubscriptions.once(Translate.use(language), async data => {
|
||||
// Check if it has a parent language.
|
||||
const fallbackLang = this.getParentLanguage(language);
|
||||
|
||||
if (fallbackLang) {
|
||||
try {
|
||||
// Merge parent translations with the child ones.
|
||||
const parentTranslations = Translate.translations[fallbackLang] ?? await this.readLangFile(fallbackLang);
|
||||
|
||||
const mergedData = Object.assign(parentTranslations, data);
|
||||
|
||||
Object.assign(data, mergedData);
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
resolve(data);
|
||||
}, reject);
|
||||
}));
|
||||
|
||||
// Change the config.
|
||||
promises.push(CoreConfig.set('current_language', language));
|
||||
|
||||
// Use british english when parent english is loaded.
|
||||
moment.locale(language == 'en' ? 'en-gb' : language);
|
||||
|
||||
this.currentLanguage = language;
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
await Promise.all([
|
||||
this.reloadLanguageStrings(),
|
||||
CoreConfig.set('current_language', language),
|
||||
]);
|
||||
} finally {
|
||||
// Load the custom and site plugins strings for the language.
|
||||
if (this.loadLangStrings(this.customStrings, language) || this.loadLangStrings(this.sitePluginsStrings, language)) {
|
||||
|
@ -558,6 +532,39 @@ export class CoreLangProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload language strings for the current language.
|
||||
*/
|
||||
protected async reloadLanguageStrings(): Promise<void> {
|
||||
const currentLanguage = this.currentLanguage;
|
||||
|
||||
if (!currentLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
CoreSubscriptions.once(Translate.use(currentLanguage), async data => {
|
||||
// Check if it has a parent language.
|
||||
const fallbackLang = this.getParentLanguage();
|
||||
|
||||
if (fallbackLang) {
|
||||
try {
|
||||
// Merge parent translations with the child ones.
|
||||
const parentTranslations = Translate.translations[fallbackLang] ?? await this.readLangFile(fallbackLang);
|
||||
|
||||
const mergedData = Object.assign(parentTranslations, data);
|
||||
|
||||
Object.assign(data, mergedData);
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreLang = makeSingleton(CoreLangProvider);
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { mockSingleton } from '@/testing/utils';
|
||||
import { CoreLang, CoreLangProvider, multilangString } from '@services/lang';
|
||||
|
||||
describe('Lang', () => {
|
||||
|
||||
let lang: CoreLangProvider;
|
||||
let currentLanguage: string;
|
||||
let parentLanguage: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
lang = new CoreLangProvider();
|
||||
currentLanguage = 'en';
|
||||
|
||||
mockSingleton(CoreLang, {
|
||||
getCurrentLanguage: () => Promise.resolve(currentLanguage),
|
||||
getParentLanguage: () => parentLanguage,
|
||||
});
|
||||
});
|
||||
|
||||
it('filters multilang text', async () => {
|
||||
await expectMultilangFilter('foo', 'foo');
|
||||
|
||||
await expectMultilangFilter(`
|
||||
<span class="multilang" lang="es">Spanish</span>
|
||||
<span class="multilang" lang="en">English</span>
|
||||
<span class="multilang" lang="ja">Japanese</span>
|
||||
<span class="multilang" lang="ES">(ES)</span>
|
||||
<span class="multilang" lang="EN">(EN)</span>
|
||||
<span class="multilang" lang="JA">(JA)</span>
|
||||
<span lang="es" class="multilang">[Spain]</span>
|
||||
<span lang="en" class="multilang">[United States]</span>
|
||||
<span lang="ja" class="multilang">[Japan]</span>
|
||||
text
|
||||
`, 'English (EN) [United States] text');
|
||||
|
||||
await expectMultilangFilter(`
|
||||
{mlang es}Spanish{mlang}
|
||||
{mlang en}English{mlang}
|
||||
{mlang ja}Japanese{mlang}
|
||||
{mlang ES}(ES){mlang}
|
||||
{mlang EN}(EN){mlang}
|
||||
{mlang JA}(JA){mlang}
|
||||
text
|
||||
`, 'English (EN) text');
|
||||
});
|
||||
|
||||
it('filters multilang text using regions', async () => {
|
||||
currentLanguage = 'en-au';
|
||||
|
||||
await expectMultilangFilter(`
|
||||
<span class="multilang" lang="en">English</span>
|
||||
<span class="multilang" lang="en-US">English</span>
|
||||
<span class="multilang" lang="en_US">(US)</span>
|
||||
<span class="multilang" lang="en-AU">English</span>
|
||||
<span class="multilang" lang="en_AU">(AU)</span>
|
||||
text
|
||||
`, 'English (AU) text');
|
||||
|
||||
await expectMultilangFilter(`
|
||||
{mlang en}English{mlang}
|
||||
{mlang en-US}English{mlang}
|
||||
{mlang en_US}(US){mlang}
|
||||
{mlang en-AU}English{mlang}
|
||||
{mlang en_AU}(AU){mlang}
|
||||
text
|
||||
`, 'English (AU) text');
|
||||
});
|
||||
|
||||
it('filters multilang text using the current language', async () => {
|
||||
const multilangText = `
|
||||
<span class="multilang" lang="es">Spanish</span>
|
||||
<span class="multilang" lang="en">English</span>
|
||||
<span class="multilang" lang="ja">Japanese</span>
|
||||
text
|
||||
`;
|
||||
const multilang2Text = `
|
||||
{mlang es}Spanish{mlang}
|
||||
{mlang en}English{mlang}
|
||||
{mlang ja}Japanese{mlang}
|
||||
text
|
||||
`;
|
||||
|
||||
currentLanguage = 'en';
|
||||
await expectMultilangFilter(multilangText, 'English text');
|
||||
await expectMultilangFilter(multilang2Text, 'English text');
|
||||
|
||||
currentLanguage = 'es';
|
||||
await expectMultilangFilter(multilangText, 'Spanish text');
|
||||
await expectMultilangFilter(multilang2Text, 'Spanish text');
|
||||
});
|
||||
|
||||
it('filters multilang text using the parent language', async () => {
|
||||
currentLanguage = 'ca';
|
||||
parentLanguage = 'ja';
|
||||
|
||||
await expectMultilangFilter(`
|
||||
<span class="multilang" lang="es">Spanish</span>
|
||||
<span class="multilang" lang="en">English</span>
|
||||
<span class="multilang" lang="ja">Japanese</span>
|
||||
text
|
||||
`, 'Japanese text');
|
||||
|
||||
await expectMultilangFilter(`
|
||||
{mlang es}Spanish{mlang}
|
||||
{mlang en}English{mlang}
|
||||
{mlang ja}Japanese{mlang}
|
||||
text
|
||||
`, 'Japanese text');
|
||||
});
|
||||
|
||||
/**
|
||||
* Test multilang filter (normalizing whitespace).
|
||||
*/
|
||||
async function expectMultilangFilter(text: string, expected: string): Promise<void> {
|
||||
const actual = await lang.filterMultilang(multilangString(text));
|
||||
|
||||
expect(actual.replace(/\s+/g, ' ').trim()).toEqual(expected);
|
||||
}
|
||||
|
||||
});
|
|
@ -261,7 +261,7 @@ export class CoreUrlUtilsProvider {
|
|||
|
||||
try {
|
||||
let lang = await CoreLang.getCurrentLanguage();
|
||||
lang = CoreLang.getParentLanguage(lang) || lang;
|
||||
lang = CoreLang.getParentLanguage() || lang;
|
||||
|
||||
return docsUrl.replace('/en/', '/' + lang + '/');
|
||||
} catch (error) {
|
||||
|
|
Loading…
Reference in New Issue