Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
500 changes: 472 additions & 28 deletions cordova-plugin-moodleapp/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cordova-plugin-moodleapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"devDependencies": {
"chokidar-cli": "^3.0.0",
"concurrently": "^9.2.1",
"esbuild": "^0.25.10",
"esbuild": "^0.25.12",
"typescript": "~5.9.2"
}
}
23,162 changes: 10,383 additions & 12,779 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
"@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.5",
"@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1",
"@moodlehq/phonegap-plugin-push": "4.0.0-moodle.13",
"@ngx-translate/core": "16.0.4",
"@ngx-translate/http-loader": "16.0.1",
"@ngx-translate/core": "17.0.0",
"@ngx-translate/http-loader": "17.0.0",
"@sqlite.org/sqlite-wasm": "3.45.0-build1",
"@stylistic/eslint-plugin": "5.4.0",
"@types/cordova": "0.0.34",
Expand Down
6 changes: 3 additions & 3 deletions src/core/base.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@

import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { TranslatePipe } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';

@NgModule({
imports: [
TranslateModule.forChild(),
TranslatePipe,
],
exports: [
CommonModule,
FormsModule,
IonicModule,
ReactiveFormsModule,
TranslateModule,
TranslatePipe,
],
})
export class CoreBaseModule {}
224 changes: 224 additions & 0 deletions src/core/classes/lang-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// (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 { Injectable } from '@angular/core';
import { mergeDeep, TranslateLoader, TranslationObject } from '@ngx-translate/core';
import { Http, Translate } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { firstValueFrom, Observable, of, Subject } from 'rxjs';

@Injectable()
export class MoodleTranslateLoader implements TranslateLoader {

protected translations: { [lang: string]: TranslationObject } = {};

protected customStrings: { [lang: string]: TranslationObject } = {}; // Strings defined using the admin tool.
protected sitePluginsStrings: { [lang: string]: TranslationObject } = {}; // Strings defined by site plugins.

static readonly PARENT_LANG_KEY = 'core.parentlanguage';

protected logger = CoreLogger.getInstance('MoodleTranslateLoader');

/**
* Get the translations for a specific language.
*
* @param lang Language code (e.g., 'en', 'es').
* @returns Observable resolved with the translations object.
*/
getTranslation(lang: string): Observable<TranslationObject> {
if (this.translations[lang]) {
this.logger.debug('Get translation', lang);
// This is done here to ensure site strings and custom strings are loaded in the proper order.
const translation = this.mergeTranslation(lang);

return of(translation);
}

const observable = new Subject<TranslationObject>();

this.loadLanguage(lang).then((translation) => {
observable.next(translation);
observable.complete();

this.logger.debug('Load translation', lang);

return;
}).catch((error) => {
this.logger.error('Error loading translation', lang, error);
observable.next({});
observable.complete();
});

return observable;
}

/**
* Load translations for a specific language.
*
* @param lang Language code (e.g., 'en', 'es').
* @returns Promise resolved with the translations object or empty object if not found.
*/
protected async loadLanguage(lang: string): Promise<TranslationObject> {
// Return the imported translations for the requested language
let translation = await this.loadLanguageFile(lang);

// Check if it has a parent language.
const parentLang = this.getParentLanguage(lang);
if (parentLang) {
try {
this.logger.debug('Loading parent language', parentLang);

// Merge parent translations with the child ones.
const parentTranslations = await this.loadLanguage(parentLang);
if (Object.keys(parentTranslations).length > 0) {
translation = mergeDeep(parentTranslations, translation);
}
} catch {
// Ignore errors.
}
}

this.translations[lang] = translation;

return this.mergeTranslation(lang);
}

/**
* Load the language file from assets.
*
* @param lang Language code.
* @returns Promise resolved with the translations object.
*/
protected async loadLanguageFile(lang: string): Promise<TranslationObject> {
try {
const request = Http.get(`/assets/lang/${lang}.json`) as Observable<TranslationObject>;
this.translations[lang] = await firstValueFrom(request);
} catch (error) {
this.logger.error('Error loading language file', lang, error);
this.translations[lang] = {};
}

return this.translations[lang];
}

/**
* Get the parent language of a certain language.
* Language translation should be loaded first.
*
* @param lang Language code.
* @returns Parent language code or undefined if not found.
*/
getParentLanguage(lang: string): string | undefined {
const parentLang = this.translations[lang][MoodleTranslateLoader.PARENT_LANG_KEY] as string | undefined;
if (parentLang && parentLang !== MoodleTranslateLoader.PARENT_LANG_KEY && parentLang !== lang) {
return parentLang;
}
}

/**
* Clear current custom strings.
*
* @param currentLang Current language. If defined, reloads the language after resetting the strings.
*/
async clearCustomStrings(currentLang?: string): Promise<void> {
this.customStrings = {};
if (!currentLang) {
return;
}

await firstValueFrom(Translate.reloadLang(currentLang));
}

/**
* Clear current custom strings.
*
* @param currentLang Current language. If defined, reloads the language after resetting the strings.
*/
async clearSitePluginsStrings(currentLang?: string): Promise<void> {
this.sitePluginsStrings = {};

if (!currentLang) {
return;
}

await firstValueFrom(Translate.reloadLang(currentLang));
}

/**
* Set custom strings defined using the admin tool.
*
* @param strings Strings.
* @param currentLang Current language. If defined, reloads the language after setting the strings.
*/
async setCustomStrings(strings: { [lang: string]: TranslationObject }, currentLang?: string): Promise<void> {
this.customStrings = strings;

if (!currentLang || !strings[currentLang]) {
return;
}

// Load them in the current translations.
await firstValueFrom(Translate.reloadLang(currentLang));
}

/**
* Set site plugins strings.
*
* @param strings Strings.
* @param currentLang Current language. If defined, reloads the language after setting the strings.
*/
async setSitePluginsStrings(strings: { [lang: string]: TranslationObject }, currentLang?: string): Promise<void> {
Object.keys(strings).forEach((lang) => {
if (!this.sitePluginsStrings[lang]) {
this.sitePluginsStrings[lang] = {};
}

this.sitePluginsStrings[lang] = mergeDeep(this.sitePluginsStrings[lang], strings[lang]);
});

if (!currentLang || !strings[currentLang]) {
return;
}

// Load them in the current translations.
await firstValueFrom(Translate.reloadLang(currentLang));
}

/**
* Merge all the translation strings for a specific language.
*
* @param lang Language code.
*
* @returns The merged translations object.
*/
protected mergeTranslation(lang: string): TranslationObject {
const translation = this.translations[lang] || {};

const sitePluginsStrings = this.sitePluginsStrings[lang] || {};
const customStrings = this.customStrings[lang] || {};
let siteStrings = mergeDeep(sitePluginsStrings, customStrings);

const parentLang = this.getParentLanguage(lang);
if (parentLang) {
const parentSitePluginsStrings = this.sitePluginsStrings[parentLang] || {};
const parentCustomStrings = this.customStrings[parentLang] || {};

const parentSiteStrings = mergeDeep(parentSitePluginsStrings, parentCustomStrings);
siteStrings = mergeDeep(parentSiteStrings, siteStrings);
}

return mergeDeep(translation, siteStrings);
}

}
11 changes: 7 additions & 4 deletions src/core/features/mainmenu/pages/reload/reload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CoreNavigator } from '@services/navigator';
import { CoreSharedModule } from '@/core/shared.module';

Expand All @@ -26,12 +26,15 @@ import { CoreSharedModule } from '@/core/shared.module';
CoreSharedModule,
],
})
export default class CoreMainMenuReloadPage implements OnInit {
export default class CoreMainMenuReloadPage {

/**
* @inheritdoc
* Runs when the page has fully entered and is now the active page.
* This event will fire, whether it was the first load or a cached page.
*
* This is not done on the ngOnInit because it can happen the page is revisited before destroyed.
*/
ngOnInit(): void {
ionViewDidEnter(): void {
CoreNavigator.navigate('/main', {
reset: true,
});
Expand Down
7 changes: 2 additions & 5 deletions src/core/features/siteplugins/services/siteplugins-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,8 @@ export class CoreSitePluginsInitService {
return;
}

for (const lang in plugin.parsedLang) {
const prefix = this.getPrefixForStrings(plugin.addon);

CoreLang.addSitePluginsStrings(lang, plugin.parsedLang[lang], prefix);
}
const prefix = this.getPrefixForStrings(plugin.addon);
CoreLang.addSitePluginsStrings(plugin.parsedLang, prefix);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/core/features/siteplugins/services/siteplugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { CoreSite } from '@classes/sites/site';
import { CoreCourseAnyModuleData } from '@features/course/services/course';
import { CoreCourses } from '@features/courses/services/courses';
import { CoreFilepool } from '@services/filepool';
import { CoreLang, CoreLangFormat } from '@services/lang';
import { CoreLang, CoreLangFormat, CoreLangTranslationByLanguage } from '@services/lang';
import { CoreSites } from '@services/sites';
import { CoreText } from '@singletons/text';
import { CoreUtils } from '@singletons/utils';
Expand Down Expand Up @@ -812,7 +812,7 @@ export type CoreSitePluginsWSPlugin = {
*/
export type CoreSitePluginsPlugin = CoreSitePluginsWSPlugin & {
parsedHandlers?: Record<string, CoreSitePluginsHandlerData> | null;
parsedLang?: Record<string, string[]> | null;
parsedLang?: CoreLangTranslationByLanguage | null;
};

/**
Expand Down
Loading