diff --git a/src/addons/badges/badges.module.ts b/src/addons/badges/badges.module.ts index f874b7af662..ebcd554bc0c 100644 --- a/src/addons/badges/badges.module.ts +++ b/src/addons/badges/badges.module.ts @@ -47,11 +47,12 @@ const mobileRoutes: Routes = [ path: '', pathMatch: 'full', loadComponent: () => import('./pages/user-badges/user-badges'), + data: { checkForcedLanguage: 'course' }, }, { path: ':badgeHash', loadComponent: () => import('./pages/issued-badge/issued-badge'), - data: { usesSwipeNavigation: true }, + data: { usesSwipeNavigation: true, checkForcedLanguage: 'course' }, }, ]; @@ -66,6 +67,7 @@ const tabletRoutes: Routes = [ data: { usesSwipeNavigation: true }, }, ], + data: { checkForcedLanguage: 'course' }, }, ]; diff --git a/src/addons/blog/blog.module.ts b/src/addons/blog/blog.module.ts index a1bddb89847..af3d692f09b 100644 --- a/src/addons/blog/blog.module.ts +++ b/src/addons/blog/blog.module.ts @@ -38,6 +38,7 @@ const routes: Routes = [ { path: ADDON_BLOG_MAINMENU_PAGE_NAME, loadChildren: () => import('@addons/blog/blog-lazy.module'), + data: { checkForcedLanguage: 'course' }, }, ]; diff --git a/src/addons/competency/competency.module.ts b/src/addons/competency/competency.module.ts index 109b405c6b6..755e408fb40 100644 --- a/src/addons/competency/competency.module.ts +++ b/src/addons/competency/competency.module.ts @@ -155,10 +155,12 @@ const mainMenuChildrenRoutes: Routes = [ { path: `${CORE_COURSE_PAGE_NAME}/:courseId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, loadChildren: () => getCompetencyCourseDetailsRoutes(), + data: { checkForcedLanguage: 'course' }, }, { path: `${CORE_COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`, loadChildren: () => getCompetencyCourseDetailsRoutes(), + data: { checkForcedLanguage: 'course' }, }, ]; diff --git a/src/addons/coursecompletion/coursecompletion.module.ts b/src/addons/coursecompletion/coursecompletion.module.ts index e68672c9114..c8520c5c4b9 100644 --- a/src/addons/coursecompletion/coursecompletion.module.ts +++ b/src/addons/coursecompletion/coursecompletion.module.ts @@ -40,6 +40,7 @@ const routes: Routes = [ { path: 'coursecompletion', loadComponent: () => import('./pages/report/report'), + data: { checkForcedLanguage: 'course' }, }, ]; diff --git a/src/addons/mod/assign/assign.module.ts b/src/addons/mod/assign/assign.module.ts index 2947df81cc1..7bf83bad578 100644 --- a/src/addons/mod/assign/assign.module.ts +++ b/src/addons/mod/assign/assign.module.ts @@ -121,6 +121,7 @@ const routes: Routes = [ ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/assign/services/assign.ts b/src/addons/mod/assign/services/assign.ts index 5efecd435b6..cd58a4ca150 100644 --- a/src/addons/mod/assign/services/assign.ts +++ b/src/addons/mod/assign/services/assign.ts @@ -46,7 +46,7 @@ import { AddonModAssignSubmissionStatusValues, } from '../constants'; import { CoreTextFormat } from '@singletons/text'; -import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper'; +import { CoreCourseModuleHelper, CoreCourseModuleStandardElements } from '@features/course/services/course-module-helper'; import { CoreUserDescriptionExporter } from '@features/user/services/user'; declare module '@singletons/events' { @@ -1460,12 +1460,12 @@ export type AddonModAssignSubmissionStatusOptions = CoreCourseCommonModWSOptions /** * Assign data returned by mod_assign_get_assignments. + * We're using Omit to exclude properties because type is not consistent with the rest of the WS but + * it should be. */ -export type AddonModAssignAssign = { - id: number; // Assignment id. +export type AddonModAssignAssign = + Omit & { cmid: number; // Course module id. - course: number; // Course id. - name: string; // Assignment name. nosubmissions: number; // No submissions. submissiondrafts: number; // Submissions drafts. sendnotifications: number; // Send notifications. @@ -1493,9 +1493,6 @@ export type AddonModAssignAssign = { submissionstatement?: string; // Submission statement formatted. submissionstatementformat?: CoreTextFormat; // Submissionstatement format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). configs: AddonModAssignConfig[]; // Configuration settings. - intro?: string; // Assignment intro, not allways returned because it deppends on the activity configuration. - introformat?: CoreTextFormat; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). - introfiles?: CoreWSExternalFile[]; introattachments?: CoreWSExternalFile[]; activity?: string; // @since 4.0. Description of activity. activityformat?: CoreTextFormat; // @since 4.0. Format of activity. diff --git a/src/addons/mod/assign/services/handlers/prefetch.ts b/src/addons/mod/assign/services/handlers/prefetch.ts index 32223c980a1..8cd7baf3aef 100644 --- a/src/addons/mod/assign/services/handlers/prefetch.ts +++ b/src/addons/mod/assign/services/handlers/prefetch.ts @@ -256,7 +256,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref promises.push(CoreCourse.getModuleBasicInfoByInstance(assign.id, ADDON_MOD_ASSIGN_MODNAME, { siteId })); // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, { siteId }))); // Download intro files and attachments. Do not call getFiles because it'd call some WS twice. let files: CoreWSFile[] = assign.introattachments || []; diff --git a/src/addons/mod/bigbluebuttonbn/bigbluebuttonbn.module.ts b/src/addons/mod/bigbluebuttonbn/bigbluebuttonbn.module.ts index e2ea244dc00..a3d7da2fa4e 100644 --- a/src/addons/mod/bigbluebuttonbn/bigbluebuttonbn.module.ts +++ b/src/addons/mod/bigbluebuttonbn/bigbluebuttonbn.module.ts @@ -26,6 +26,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_BBB_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/bigbluebuttonbn/services/handlers/module.ts b/src/addons/mod/bigbluebuttonbn/services/handlers/module.ts index 46348769584..dea0b7fd08e 100644 --- a/src/addons/mod/bigbluebuttonbn/services/handlers/module.ts +++ b/src/addons/mod/bigbluebuttonbn/services/handlers/module.ts @@ -21,6 +21,7 @@ import { makeSingleton } from '@singletons'; import { AddonModBBB } from '../bigbluebuttonbn'; import { ADDON_MOD_BBB_COMPONENT, ADDON_MOD_BBB_MODNAME, ADDON_MOD_BBB_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support Big Blue Button activities. @@ -103,6 +104,19 @@ export class AddonModBBBModuleHandlerService extends CoreModuleHandlerBase imple return AddonModBBBIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModBBB.getBBB( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModBBBModuleHandler = makeSingleton(AddonModBBBModuleHandlerService); diff --git a/src/addons/mod/book/book.module.ts b/src/addons/mod/book/book.module.ts index 0fcc6df65ef..0ce9cea6556 100644 --- a/src/addons/mod/book/book.module.ts +++ b/src/addons/mod/book/book.module.ts @@ -41,6 +41,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/contents/contents'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/book/services/handlers/module.ts b/src/addons/mod/book/services/handlers/module.ts index 140796b3917..ff6f8e0906d 100644 --- a/src/addons/mod/book/services/handlers/module.ts +++ b/src/addons/mod/book/services/handlers/module.ts @@ -19,6 +19,8 @@ import { makeSingleton } from '@singletons'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { ADDON_MOD_BOOK_COMPONENT, ADDON_MOD_BOOK_MODNAME, ADDON_MOD_BOOK_PAGE_NAME } from '../../constants'; import { ModFeature, ModArchetype, ModPurpose } from '@addons/mod/constants'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support book modules. @@ -59,5 +61,18 @@ export class AddonModBookModuleHandlerService extends CoreModuleHandlerBase impl return AddonModBookIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModBook.getBook( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModBookModuleHandler = makeSingleton(AddonModBookModuleHandlerService); diff --git a/src/addons/mod/chat/chat.module.ts b/src/addons/mod/chat/chat.module.ts index 4e9c9d5bd89..1562ed0b9c1 100644 --- a/src/addons/mod/chat/chat.module.ts +++ b/src/addons/mod/chat/chat.module.ts @@ -73,6 +73,7 @@ const routes: Routes = [ ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/chat/services/handlers/module.ts b/src/addons/mod/chat/services/handlers/module.ts index bd15b448eb1..22e35db0efc 100644 --- a/src/addons/mod/chat/services/handlers/module.ts +++ b/src/addons/mod/chat/services/handlers/module.ts @@ -18,6 +18,9 @@ import { CoreCourseModuleHandler } from '@features/course/services/module-delega import { makeSingleton } from '@singletons'; import { ADDON_MOD_CHAT_MODNAME, ADDON_MOD_CHAT_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModChat } from '../chat'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support chat modules. @@ -50,6 +53,19 @@ export class AddonModChatModuleHandlerService extends CoreModuleHandlerBase impl return AddonModChatIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModChat.getChat( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModChatModuleHandler = makeSingleton(AddonModChatModuleHandlerService); diff --git a/src/addons/mod/choice/choice.module.ts b/src/addons/mod/choice/choice.module.ts index 5ca24d8e650..de655aae043 100644 --- a/src/addons/mod/choice/choice.module.ts +++ b/src/addons/mod/choice/choice.module.ts @@ -35,6 +35,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_CHOICE_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/choice/services/handlers/module.ts b/src/addons/mod/choice/services/handlers/module.ts index 265f900e067..60b4d84a8d7 100644 --- a/src/addons/mod/choice/services/handlers/module.ts +++ b/src/addons/mod/choice/services/handlers/module.ts @@ -18,6 +18,9 @@ import { CoreCourseModuleHandler } from '@features/course/services/module-delega import { makeSingleton } from '@singletons'; import { ADDON_MOD_CHOICE_MODNAME, ADDON_MOD_CHOICE_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModChoice } from '../choice'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support choice modules. @@ -51,6 +54,19 @@ export class AddonModChoiceModuleHandlerService extends CoreModuleHandlerBase im return AddonModChoiceIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModChoice.getChoice( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModChoiceModuleHandler = makeSingleton(AddonModChoiceModuleHandlerService); diff --git a/src/addons/mod/data/data.module.ts b/src/addons/mod/data/data.module.ts index fecf0d68a0d..021377e61b6 100644 --- a/src/addons/mod/data/data.module.ts +++ b/src/addons/mod/data/data.module.ts @@ -60,6 +60,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/entry/entry'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/data/services/handlers/module.ts b/src/addons/mod/data/services/handlers/module.ts index b6b827f7c43..bdf03779636 100644 --- a/src/addons/mod/data/services/handlers/module.ts +++ b/src/addons/mod/data/services/handlers/module.ts @@ -18,6 +18,9 @@ import { CoreCourseModuleHandler } from '@features/course/services/module-delega import { makeSingleton } from '@singletons'; import { ADDON_MOD_DATA_COMPONENT, ADDON_MOD_DATA_MODNAME, ADDON_MOD_DATA_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModData } from '../data'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support data modules. @@ -53,5 +56,18 @@ export class AddonModDataModuleHandlerService extends CoreModuleHandlerBase impl return AddonModDataIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModData.getDatabase( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModDataModuleHandler = makeSingleton(AddonModDataModuleHandlerService); diff --git a/src/addons/mod/data/services/handlers/prefetch-lazy.ts b/src/addons/mod/data/services/handlers/prefetch-lazy.ts index 88df9905ac2..9aa99c29afa 100644 --- a/src/addons/mod/data/services/handlers/prefetch-lazy.ts +++ b/src/addons/mod/data/services/handlers/prefetch-lazy.ts @@ -262,7 +262,7 @@ export class AddonModDataPrefetchHandlerLazyService extends AddonModDataPrefetch promises.push(CoreCourse.getModuleBasicInfoByInstance(database.id, ADDON_MOD_DATA_MODNAME, { siteId })); // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, { siteId }))); await Promise.all(promises); } diff --git a/src/addons/mod/feedback/feedback.module.ts b/src/addons/mod/feedback/feedback.module.ts index f2f5f730373..6ae4f20cb45 100644 --- a/src/addons/mod/feedback/feedback.module.ts +++ b/src/addons/mod/feedback/feedback.module.ts @@ -78,6 +78,7 @@ const tabletRoutes: Routes = [ loadComponent: () => import('./pages/attempt/attempt'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/feedback/services/handlers/module.ts b/src/addons/mod/feedback/services/handlers/module.ts index 1b26060e863..6e2162a2de2 100644 --- a/src/addons/mod/feedback/services/handlers/module.ts +++ b/src/addons/mod/feedback/services/handlers/module.ts @@ -18,6 +18,9 @@ import { makeSingleton } from '@singletons'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { ADDON_MOD_FEEDBACK_MODNAME, ADDON_MOD_FEEDBACK_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModFeedback } from '../feedback'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support feedback modules. @@ -51,6 +54,19 @@ export class AddonModFeedbackModuleHandlerService extends CoreModuleHandlerBase return AddonModFeedbackIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModFeedback.getFeedback( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModFeedbackModuleHandler = makeSingleton(AddonModFeedbackModuleHandlerService); diff --git a/src/addons/mod/folder/folder.module.ts b/src/addons/mod/folder/folder.module.ts index 3ba6fc0208c..bc02fc06839 100644 --- a/src/addons/mod/folder/folder.module.ts +++ b/src/addons/mod/folder/folder.module.ts @@ -40,6 +40,7 @@ const routes: Routes = [ pathMatch: 'full', }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/folder/services/handlers/module.ts b/src/addons/mod/folder/services/handlers/module.ts index e5d4d8ac5b4..d8a405d3f40 100644 --- a/src/addons/mod/folder/services/handlers/module.ts +++ b/src/addons/mod/folder/services/handlers/module.ts @@ -21,6 +21,8 @@ import { convertTextToHTMLElement } from '@/core/utils/create-html-element'; import { makeSingleton } from '@singletons'; import { ADDON_MOD_FOLDER_MODNAME, ADDON_MOD_FOLDER_PAGE_NAME } from '../../constants'; import { ModFeature, ModArchetype, ModPurpose } from '@addons/mod/constants'; +import { AddonModFolder } from '../folder'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support folder modules. @@ -89,5 +91,18 @@ export class AddonModFolderModuleHandlerService extends CoreModuleHandlerBase im return AddonModFolderIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModFolder.getFolder( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModFolderModuleHandler = makeSingleton(AddonModFolderModuleHandlerService); diff --git a/src/addons/mod/forum/forum.module.ts b/src/addons/mod/forum/forum.module.ts index 6e2bbf94e81..9055c37dc08 100644 --- a/src/addons/mod/forum/forum.module.ts +++ b/src/addons/mod/forum/forum.module.ts @@ -105,6 +105,7 @@ const mainMenuRoutes: Routes = [ ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), ], + data: { checkForcedLanguage: 'module' }, }, ...conditionalRoutes( [ diff --git a/src/addons/mod/forum/services/forum.ts b/src/addons/mod/forum/services/forum.ts index 1ff83cceecd..9f8da9d5286 100644 --- a/src/addons/mod/forum/services/forum.ts +++ b/src/addons/mod/forum/services/forum.ts @@ -53,7 +53,7 @@ import { CorePromiseUtils } from '@singletons/promise-utils'; import { CoreWSError } from '@classes/errors/wserror'; import { CoreObject } from '@singletons/object'; import { CoreTextFormat } from '@singletons/text'; -import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper'; +import { CoreCourseModuleHelper, CoreCourseModuleStandardElements } from '@features/course/services/course-module-helper'; import { CoreUserPreferences } from '@features/user/services/user-preferences'; declare module '@singletons/events' { @@ -1375,15 +1375,12 @@ type AddonModForumGetForumsByCoursesWSParams = { /** * General forum activity data. + * We're using Omit to exclude properties because type is not consistent with the rest of the WS but + * it should be. */ -export type AddonModForumData = { - id: number; // Forum id. - course: number; // Course id. +export type AddonModForumData = + Omit & { type: AddonModForumType; // The forum type. - name: string; // Forum name. - intro: string; // The forum intro. - introformat: CoreTextFormat; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). - introfiles?: CoreWSExternalFile[]; duedate?: number; // Duedate for the user. cutoffdate?: number; // Cutoffdate for the user. assessed: number; // Aggregate type. diff --git a/src/addons/mod/forum/services/handlers/module.ts b/src/addons/mod/forum/services/handlers/module.ts index 517669c56d9..fb997d3ee8b 100644 --- a/src/addons/mod/forum/services/handlers/module.ts +++ b/src/addons/mod/forum/services/handlers/module.ts @@ -180,6 +180,19 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp return super.getOverviewItemContent(item, activity, courseId); } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModForum.getForum( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModForumModuleHandler = makeSingleton(AddonModForumModuleHandlerService); diff --git a/src/addons/mod/forum/services/handlers/prefetch.ts b/src/addons/mod/forum/services/handlers/prefetch.ts index bca6b0e1482..a3e7fce19b6 100644 --- a/src/addons/mod/forum/services/handlers/prefetch.ts +++ b/src/addons/mod/forum/services/handlers/prefetch.ts @@ -250,7 +250,7 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe promises.push(AddonModForum.getAccessInformation(forum.id, modOptions)); // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, { siteId }))); await Promise.all(promises); } diff --git a/src/addons/mod/glossary/glossary.module.ts b/src/addons/mod/glossary/glossary.module.ts index 7482e824c71..28b3976bbeb 100644 --- a/src/addons/mod/glossary/glossary.module.ts +++ b/src/addons/mod/glossary/glossary.module.ts @@ -89,6 +89,7 @@ const mainMenuRoutes: Routes = [ ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), ], + data: { checkForcedLanguage: 'module' }, }, // Single Activity format navigation. diff --git a/src/addons/mod/glossary/services/handlers/module.ts b/src/addons/mod/glossary/services/handlers/module.ts index e1c9f7226ed..443a1d388e0 100644 --- a/src/addons/mod/glossary/services/handlers/module.ts +++ b/src/addons/mod/glossary/services/handlers/module.ts @@ -18,6 +18,9 @@ import { CoreCourseModuleHandler } from '@features/course/services/module-delega import { makeSingleton } from '@singletons'; import { ADDON_MOD_GLOSSARY_MODNAME, ADDON_MOD_GLOSSARY_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModGlossary } from '../glossary'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support glossary modules. @@ -60,6 +63,19 @@ export class AddonModGlossaryModuleHandlerService extends CoreModuleHandlerBase return false; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModGlossary.getGlossary( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModGlossaryModuleHandler = makeSingleton(AddonModGlossaryModuleHandlerService); diff --git a/src/addons/mod/glossary/services/handlers/prefetch.ts b/src/addons/mod/glossary/services/handlers/prefetch.ts index 048dbd9f034..35b11bf0602 100644 --- a/src/addons/mod/glossary/services/handlers/prefetch.ts +++ b/src/addons/mod/glossary/services/handlers/prefetch.ts @@ -191,7 +191,7 @@ export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPr promises.push(CoreCourse.getModuleBasicInfoByInstance(glossary.id, ADDON_MOD_GLOSSARY_MODNAME, { siteId })); // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, { siteId }))); await Promise.all(promises); } diff --git a/src/addons/mod/h5pactivity/h5pactivity.module.ts b/src/addons/mod/h5pactivity/h5pactivity.module.ts index 8183bcd8580..5b0394a62f4 100644 --- a/src/addons/mod/h5pactivity/h5pactivity.module.ts +++ b/src/addons/mod/h5pactivity/h5pactivity.module.ts @@ -49,6 +49,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/users-attempts/users-attempts'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/h5pactivity/services/h5pactivity.ts b/src/addons/mod/h5pactivity/services/h5pactivity.ts index e59d0f458fa..43580932691 100644 --- a/src/addons/mod/h5pactivity/services/h5pactivity.ts +++ b/src/addons/mod/h5pactivity/services/h5pactivity.ts @@ -35,8 +35,7 @@ import { CoreCacheUpdateFrequency } from '@/core/constants'; import { CoreFileHelper } from '@services/file-helper'; import { CorePromiseUtils } from '@singletons/promise-utils'; import { CoreH5PMissingDependencyDBRecord } from '@features/h5p/services/database/h5p'; -import { CoreTextFormat } from '@singletons/text'; -import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper'; +import { CoreCourseModuleHelper, CoreCourseModuleStandardElements } from '@features/course/services/course-module-helper'; /** * Service that provides some features for H5P activity. @@ -874,23 +873,19 @@ export const AddonModH5PActivity = makeSingleton(AddonModH5PActivityProvider); /** * Basic data for an H5P activity, exported by Moodle class h5pactivity_summary_exporter. + * We're using Omit to exclude properties because type is not consistent with the rest of the WS but + * it should be. */ -export type AddonModH5PActivityWSData = { - id: number; // The primary key of the record. - course: number; // Course id this h5p activity is part of. - name: string; // The name of the activity module instance. +export type AddonModH5PActivityWSData = + Omit & { timecreated?: number; // Timestamp of when the instance was added to the course. timemodified?: number; // Timestamp of when the instance was last modified. - intro: string; // H5P activity description. - introformat: CoreTextFormat; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). grade?: number; // The maximum grade for submission. displayoptions: number; // H5P Button display options. enabletracking: number; // Enable xAPI tracking. grademethod: AddonModH5PActivityGradeMethod; // Which H5P attempt is used for grading. contenthash?: string; // Sha1 hash of file content. - coursemodule: number; // Coursemodule. context: number; // Context ID. - introfiles: CoreWSExternalFile[]; package: CoreWSExternalFile[]; deployedfile?: { filename?: string; // File name. diff --git a/src/addons/mod/imscp/imscp.module.ts b/src/addons/mod/imscp/imscp.module.ts index 49e645dc34f..baaf5f7668f 100644 --- a/src/addons/mod/imscp/imscp.module.ts +++ b/src/addons/mod/imscp/imscp.module.ts @@ -39,6 +39,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/view/view'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/imscp/services/handlers/module.ts b/src/addons/mod/imscp/services/handlers/module.ts index fa913459b8d..6530a67cafe 100644 --- a/src/addons/mod/imscp/services/handlers/module.ts +++ b/src/addons/mod/imscp/services/handlers/module.ts @@ -19,6 +19,8 @@ import { makeSingleton } from '@singletons'; import { AddonModImscp } from '../imscp'; import { ADDON_MOD_IMSCP_MODNAME, ADDON_MOD_IMSCP_PAGE_NAME } from '../../constants'; import { ModFeature, ModArchetype, ModPurpose } from '@addons/mod/constants'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support IMSCP modules. @@ -59,5 +61,18 @@ export class AddonModImscpModuleHandlerService extends CoreModuleHandlerBase imp return AddonModImscpIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModImscp.getImscp( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModImscpModuleHandler = makeSingleton(AddonModImscpModuleHandlerService); diff --git a/src/addons/mod/label/services/handlers/module.ts b/src/addons/mod/label/services/handlers/module.ts index b547d8a99b6..4061074a0b0 100644 --- a/src/addons/mod/label/services/handlers/module.ts +++ b/src/addons/mod/label/services/handlers/module.ts @@ -19,6 +19,8 @@ import { CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { makeSingleton } from '@singletons'; import { ADDON_MOD_LABEL_MODNAME } from '../../constants'; +import { AddonModLabel } from '../label'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support label modules. @@ -82,5 +84,18 @@ export class AddonModLabelModuleHandlerService extends CoreModuleHandlerBase imp return ''; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModLabel.getLabel( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModLabelModuleHandler = makeSingleton(AddonModLabelModuleHandlerService); diff --git a/src/addons/mod/lesson/lesson.module.ts b/src/addons/mod/lesson/lesson.module.ts index 46ec315b685..10d9fcc1091 100644 --- a/src/addons/mod/lesson/lesson.module.ts +++ b/src/addons/mod/lesson/lesson.module.ts @@ -53,6 +53,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/user-retake/user-retake'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/lesson/services/handlers/module.ts b/src/addons/mod/lesson/services/handlers/module.ts index be0ec2ecfdb..0e9bd89efb3 100644 --- a/src/addons/mod/lesson/services/handlers/module.ts +++ b/src/addons/mod/lesson/services/handlers/module.ts @@ -19,6 +19,9 @@ import { makeSingleton } from '@singletons'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { ADDON_MOD_LESSON_MODNAME, ADDON_MOD_LESSON_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModLesson } from '../lesson'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support lesson modules. @@ -52,6 +55,19 @@ export class AddonModLessonModuleHandlerService extends CoreModuleHandlerBase im return AddonModLessonIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModLesson.getLesson( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModLessonModuleHandler = makeSingleton(AddonModLessonModuleHandlerService); diff --git a/src/addons/mod/lti/lti.module.ts b/src/addons/mod/lti/lti.module.ts index 2dce3fa35d7..5815ede73f5 100644 --- a/src/addons/mod/lti/lti.module.ts +++ b/src/addons/mod/lti/lti.module.ts @@ -29,6 +29,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_LTI_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/lti/services/handlers/module.ts b/src/addons/mod/lti/services/handlers/module.ts index bee7976ec2a..fa42e1e1cce 100644 --- a/src/addons/mod/lti/services/handlers/module.ts +++ b/src/addons/mod/lti/services/handlers/module.ts @@ -22,6 +22,8 @@ import { CoreModuleHandlerBase } from '@features/course/classes/module-base-hand import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper'; import { ADDON_MOD_LTI_MODNAME, ADDON_MOD_LTI_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModLti } from '../lti'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support LTI modules. @@ -86,6 +88,19 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple return module?.modicon ?? modicon ?? CoreCourseModuleHelper.getModuleIconSrc(this.modName); } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModLti.getLti( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModLtiModuleHandler = makeSingleton(AddonModLtiModuleHandlerService); diff --git a/src/addons/mod/page/page.module.ts b/src/addons/mod/page/page.module.ts index 23fefef5c3a..138edb89140 100644 --- a/src/addons/mod/page/page.module.ts +++ b/src/addons/mod/page/page.module.ts @@ -30,6 +30,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_PAGE_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/page/services/handlers/module.ts b/src/addons/mod/page/services/handlers/module.ts index 292f8ba6ca3..fedbc5b093c 100644 --- a/src/addons/mod/page/services/handlers/module.ts +++ b/src/addons/mod/page/services/handlers/module.ts @@ -19,6 +19,8 @@ import { makeSingleton } from '@singletons'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { ADDON_MOD_PAGE_COMPONENT, ADDON_MOD_PAGE_MODNAME, ADDON_MOD_PAGE_PAGE_NAME } from '../../constants'; import { ModFeature, ModArchetype, ModPurpose } from '@addons/mod/constants'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support page modules. @@ -59,5 +61,18 @@ export class AddonModPageModuleHandlerService extends CoreModuleHandlerBase impl return AddonModPageIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModPage.getPageData( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModPageModuleHandler = makeSingleton(AddonModPageModuleHandlerService); diff --git a/src/addons/mod/quiz/quiz.module.ts b/src/addons/mod/quiz/quiz.module.ts index 0c89f952ae5..ea50dfe443d 100644 --- a/src/addons/mod/quiz/quiz.module.ts +++ b/src/addons/mod/quiz/quiz.module.ts @@ -75,6 +75,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/review/review'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/quiz/services/handlers/module.ts b/src/addons/mod/quiz/services/handlers/module.ts index 1d67c896df7..d04b141374f 100644 --- a/src/addons/mod/quiz/services/handlers/module.ts +++ b/src/addons/mod/quiz/services/handlers/module.ts @@ -19,6 +19,9 @@ import { makeSingleton } from '@singletons'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { ADDON_MOD_QUIZ_MODNAME, ADDON_MOD_QUIZ_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModQuiz } from '../quiz'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support quiz modules. @@ -55,6 +58,19 @@ export class AddonModQuizModuleHandlerService extends CoreModuleHandlerBase impl return AddonModQuizIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModQuiz.getQuiz( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModQuizModuleHandler = makeSingleton(AddonModQuizModuleHandlerService); diff --git a/src/addons/mod/quiz/services/handlers/prefetch.ts b/src/addons/mod/quiz/services/handlers/prefetch.ts index 8c5023b43b7..a60872ccfb0 100644 --- a/src/addons/mod/quiz/services/handlers/prefetch.ts +++ b/src/addons/mod/quiz/services/handlers/prefetch.ts @@ -321,7 +321,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet promises.push(AddonModQuiz.getAttemptAccessInformation(quiz.id, 0, modOptions)); // Last attempt. // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, { siteId }))); await Promise.all(promises); diff --git a/src/addons/mod/resource/resource.module.ts b/src/addons/mod/resource/resource.module.ts index f654295e21f..3711e6ecc2d 100644 --- a/src/addons/mod/resource/resource.module.ts +++ b/src/addons/mod/resource/resource.module.ts @@ -30,6 +30,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_RESOURCE_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/resource/services/handlers/module.ts b/src/addons/mod/resource/services/handlers/module.ts index 7341c1157f0..665c004860e 100644 --- a/src/addons/mod/resource/services/handlers/module.ts +++ b/src/addons/mod/resource/services/handlers/module.ts @@ -28,7 +28,7 @@ import { ADDON_MOD_RESOURCE_MODNAME, ADDON_MOD_RESOURCE_PAGE_NAME } from '../../ import { DownloadStatus } from '@/core/constants'; import { ModFeature, ModArchetype, ModPurpose } from '@addons/mod/constants'; import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper'; -import { CoreSites } from '@services/sites'; +import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support resource modules. @@ -175,5 +175,18 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase return AddonModResourceIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModResource.getResourceData( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModResourceModuleHandler = makeSingleton(AddonModResourceModuleHandlerService); diff --git a/src/addons/mod/scorm/scorm.module.ts b/src/addons/mod/scorm/scorm.module.ts index fb4358bc76b..d9eeef56fde 100644 --- a/src/addons/mod/scorm/scorm.module.ts +++ b/src/addons/mod/scorm/scorm.module.ts @@ -50,6 +50,7 @@ const routes: Routes = [ loadComponent: () => import('./pages/online-player/online-player'), }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/scorm/services/handlers/module.ts b/src/addons/mod/scorm/services/handlers/module.ts index 162b817ab43..8cf9665732e 100644 --- a/src/addons/mod/scorm/services/handlers/module.ts +++ b/src/addons/mod/scorm/services/handlers/module.ts @@ -18,6 +18,9 @@ import { CoreCourseModuleHandler } from '@features/course/services/module-delega import { makeSingleton } from '@singletons'; import { ADDON_MOD_SCORM_MODNAME, ADDON_MOD_SCORM_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModScorm } from '../scorm'; +import { CoreSitesReadingStrategy } from '@services/sites'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; /** * Handler to support SCORM modules. @@ -51,6 +54,19 @@ export class AddonModScormModuleHandlerService extends CoreModuleHandlerBase imp return AddonModScormIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModScorm.getScorm( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModScormModuleHandler = makeSingleton(AddonModScormModuleHandlerService); diff --git a/src/addons/mod/survey/services/handlers/module.ts b/src/addons/mod/survey/services/handlers/module.ts index a0784246f36..8450ddb3431 100644 --- a/src/addons/mod/survey/services/handlers/module.ts +++ b/src/addons/mod/survey/services/handlers/module.ts @@ -18,6 +18,9 @@ import { Injectable, Type } from '@angular/core'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { CoreCourseModuleHandler } from '@features/course/services/module-delegate'; import { makeSingleton } from '@singletons'; +import { AddonModSurvey } from '../survey'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support survey modules. @@ -51,5 +54,18 @@ export class AddonModSurveyModuleHandlerService extends CoreModuleHandlerBase im return AddonModSurveyIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModSurvey.getSurvey( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModSurveyModuleHandler = makeSingleton(AddonModSurveyModuleHandlerService); diff --git a/src/addons/mod/survey/survey.module.ts b/src/addons/mod/survey/survey.module.ts index d0e78fbfb7f..20e5a137171 100644 --- a/src/addons/mod/survey/survey.module.ts +++ b/src/addons/mod/survey/survey.module.ts @@ -32,6 +32,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_SURVEY_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/url/services/handlers/module.ts b/src/addons/mod/url/services/handlers/module.ts index 7648dddb810..00757f08013 100644 --- a/src/addons/mod/url/services/handlers/module.ts +++ b/src/addons/mod/url/services/handlers/module.ts @@ -30,7 +30,7 @@ import { CoreMimetype } from '@singletons/mimetype'; import { ADDON_MOD_URL_COMPONENT, ADDON_MOD_URL_MODNAME, ADDON_MOD_URL_PAGE_NAME } from '../../constants'; import { ModFeature, ModArchetype, ModPurpose, ModResourceDisplay } from '@addons/mod/constants'; import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper'; -import { CoreSites } from '@services/sites'; +import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support url modules. @@ -257,5 +257,18 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple }); } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModUrl.getUrl( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModUrlModuleHandler = makeSingleton(AddonModUrlModuleHandlerService); diff --git a/src/addons/mod/url/url.module.ts b/src/addons/mod/url/url.module.ts index fd5ff488f37..5413f994342 100644 --- a/src/addons/mod/url/url.module.ts +++ b/src/addons/mod/url/url.module.ts @@ -28,6 +28,7 @@ const routes: Routes = [ { path: `${ADDON_MOD_URL_PAGE_NAME}/:courseId/:cmId`, loadComponent: () => import('./pages/index/index'), + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/wiki/services/handlers/module.ts b/src/addons/mod/wiki/services/handlers/module.ts index 2dc1864a46a..9ef350eb096 100644 --- a/src/addons/mod/wiki/services/handlers/module.ts +++ b/src/addons/mod/wiki/services/handlers/module.ts @@ -18,6 +18,9 @@ import { CoreCourseModuleHandler } from '@features/course/services/module-delega import { makeSingleton } from '@singletons'; import { ADDON_MOD_WIKI_MODNAME, ADDON_MOD_WIKI_PAGE_NAME } from '../../constants'; import { ModFeature, ModPurpose } from '@addons/mod/constants'; +import { AddonModWiki } from '../wiki'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support wiki modules. @@ -52,6 +55,19 @@ export class AddonModWikiModuleHandlerService extends CoreModuleHandlerBase impl return AddonModWikiIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModWiki.getWiki( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModWikiModuleHandler = makeSingleton(AddonModWikiModuleHandlerService); diff --git a/src/addons/mod/wiki/wiki.module.ts b/src/addons/mod/wiki/wiki.module.ts index 57905df0313..9c063b7d8d1 100644 --- a/src/addons/mod/wiki/wiki.module.ts +++ b/src/addons/mod/wiki/wiki.module.ts @@ -53,6 +53,7 @@ const routes: Routes = [ canDeactivate: [canLeaveGuard], }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/mod/workshop/services/handlers/module.ts b/src/addons/mod/workshop/services/handlers/module.ts index 459868701b1..1dac5b15659 100644 --- a/src/addons/mod/workshop/services/handlers/module.ts +++ b/src/addons/mod/workshop/services/handlers/module.ts @@ -18,6 +18,9 @@ import { Injectable, Type } from '@angular/core'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { CoreCourseModuleHandler } from '@features/course/services/module-delegate'; import { makeSingleton } from '@singletons'; +import { AddonModWorkshop } from '../workshop'; +import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreSitesReadingStrategy } from '@services/sites'; /** * Handler to support workshop modules. @@ -50,5 +53,18 @@ export class AddonModWorkshopModuleHandlerService extends CoreModuleHandlerBase return AddonModWorkshopIndexComponent; } + /** + * @inheritdoc + */ + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + const mod = await AddonModWorkshop.getWorkshop( + module.course, + module.id, + { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }, + ); + + return mod?.lang; + } + } export const AddonModWorkshopModuleHandler = makeSingleton(AddonModWorkshopModuleHandlerService); diff --git a/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts b/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts index a6a4df9c904..bf27516c0e1 100644 --- a/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts +++ b/src/addons/mod/workshop/services/handlers/prefetch-lazy.ts @@ -394,7 +394,7 @@ export class AddonModWorkshopPrefetchHandlerLazyService extends AddonModWorkshop promises.push(CoreCourse.getModuleBasicGradeInfo(module.id, siteId)); // Get course data, needed to determine upload max size if it's configured to be course limit. - promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, siteId))); + promises.push(CorePromiseUtils.ignoreErrors(CoreCourses.getCourseByField('id', courseId, { siteId }))); await Promise.all(promises); diff --git a/src/addons/mod/workshop/workshop.module.ts b/src/addons/mod/workshop/workshop.module.ts index 17d45ba4ede..b72e8745355 100644 --- a/src/addons/mod/workshop/workshop.module.ts +++ b/src/addons/mod/workshop/workshop.module.ts @@ -89,6 +89,7 @@ const routes: Routes = [ canDeactivate: [canLeaveGuard], }, ], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/addons/notes/notes.module.ts b/src/addons/notes/notes.module.ts index bc3c6318edd..649234f776e 100644 --- a/src/addons/notes/notes.module.ts +++ b/src/addons/notes/notes.module.ts @@ -46,6 +46,7 @@ const routes: Routes = [ { path: 'notes', loadComponent: () => import('./pages/list/list'), + data: { checkForcedLanguage: 'course' }, }, ]; diff --git a/src/addons/storagemanager/storagemanager.module.ts b/src/addons/storagemanager/storagemanager.module.ts index 4471277a7fb..6e4689dc796 100644 --- a/src/addons/storagemanager/storagemanager.module.ts +++ b/src/addons/storagemanager/storagemanager.module.ts @@ -28,6 +28,7 @@ const routes: Routes = [ { path: `${ADDON_STORAGE_MANAGER_PAGE_NAME}/:courseId`, loadComponent: () => import('./pages/course-storage/course-storage'), + data: { checkForcedLanguage: 'course' }, }, ]; diff --git a/src/core/features/course/classes/module-base-handler.ts b/src/core/features/course/classes/module-base-handler.ts index 843937e1d1f..b55b1755cdb 100644 --- a/src/core/features/course/classes/module-base-handler.ts +++ b/src/core/features/course/classes/module-base-handler.ts @@ -176,4 +176,12 @@ export class CoreModuleHandlerBase implements Partial { } } + /** + * @inheritdoc + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getModuleForcedLang(module: CoreCourseModuleData): Promise { + return; + } + } diff --git a/src/core/features/course/course-lazy.module.ts b/src/core/features/course/course-lazy.module.ts index 66969f1926d..bf6ca56c23e 100644 --- a/src/core/features/course/course-lazy.module.ts +++ b/src/core/features/course/course-lazy.module.ts @@ -41,18 +41,22 @@ function buildRoutes(injector: Injector): Routes { }, ...indexRoutes.siblings, ], + data: { checkForcedLanguage: 'course' }, }, { path: ':courseId/:cmId/module-preview', loadComponent: () => import('@features/course/pages/module-preview/module-preview'), + data: { checkForcedLanguage: 'course' }, }, { path: ':courseId/list-mod-type', loadComponent: () => import('@features/course/pages/list-mod-type/list-mod-type'), + data: { checkForcedLanguage: 'course' }, }, { path: ':courseId/summary', loadComponent: () => CoreCourseHelper.getCourseSummaryPage(), + data: { checkForcedLanguage: 'course' }, }, ]; } diff --git a/src/core/features/course/course.module.ts b/src/core/features/course/course.module.ts index de9f3ca35f2..2f8799820e4 100644 --- a/src/core/features/course/course.module.ts +++ b/src/core/features/course/course.module.ts @@ -36,6 +36,7 @@ import { CoreCourseOptionsDelegate } from '@features/course/services/course-opti import { CoreCourseOverviewOptionHandler } from './services/handlers/overview-option'; import { CoreCourseOverviewLinkHandler } from './services/handlers/overview-link'; import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreCourseForceLanguage } from './services/course-force-language'; /** * Get course services. @@ -186,6 +187,7 @@ const courseIndexRoutes: Routes = [ CoreContentLinksDelegate.registerHandler(CoreCourseOverviewLinkHandler.instance); CoreCourse.initialize(); + CoreCourseForceLanguage.initialize(); CoreCourseModulePrefetchDelegate.initialize(); }), ], diff --git a/src/core/features/course/services/course-force-language.ts b/src/core/features/course/services/course-force-language.ts new file mode 100644 index 00000000000..e108317208e --- /dev/null +++ b/src/core/features/course/services/course-force-language.ts @@ -0,0 +1,165 @@ +// (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 { ActivatedRoute, NavigationEnd } from '@angular/router'; + +import { CoreSitesReadingStrategy } from '@services/sites'; +import { makeSingleton, Router } from '@singletons'; +import { + CoreCourses, + CoreCourseSearchedData, +} from '../../courses/services/courses'; +import { CoreNavigator } from '@services/navigator'; +import { filter } from 'rxjs/operators'; +import { CorePromiseUtils } from '@singletons/promise-utils'; +import { CoreLang } from '@services/lang'; +import { CoreCourse } from './course'; +import { CoreCourseModuleDelegate } from './module-delegate'; + +/** + * Service that provides some features course and module forced languages. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCourseForceLanguageService { + + protected lastNavigationCheck: { + courseId: number; + courseLang: string | undefined; + cmId: number; + cmLang: string | undefined; + timestamp: number; + }= { + courseId: 0, + courseLang: undefined, + cmId: 0, + cmLang: undefined, + timestamp: 0, + }; + + /** + * Initialize. + */ + initialize(): void { + Router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(async () => { + this.lastNavigationCheck.timestamp = Date.now(); + const currentNavigationCheck = this.lastNavigationCheck.timestamp; + + let route: ActivatedRoute | null = CoreNavigator.getCurrentRoute(); + let routeData = CoreNavigator.getRouteData(route); + while (!routeData.checkForcedLanguage && route) { + route = route.parent; + routeData = route ? CoreNavigator.getRouteData(route) : {}; + } + + let lang: string | undefined = undefined; + if (route && routeData.checkForcedLanguage) { + lang = await this.getForcedLanguageFromRoute(routeData.checkForcedLanguage); + } + + if (currentNavigationCheck === this.lastNavigationCheck.timestamp) { + await CoreLang.forceCourseLanguage(lang); + } + }); + } + + /** + * Get forced language from route. + * + * @param origin Forced language origin. + * @returns Language code if forced. + */ + protected async getForcedLanguageFromRoute(origin: string): Promise { + let course = CoreNavigator.getRouteParam('course'); + const courseId = course?.id ?? CoreNavigator.getRouteNumberParam('courseId'); + + if (!courseId) { + // Not in a course/module, empty the cache and return. + this.lastNavigationCheck.courseId = 0; + this.lastNavigationCheck.courseLang = undefined; + this.lastNavigationCheck.cmId = 0; + this.lastNavigationCheck.cmLang = undefined; + + return; + } + + if (origin === 'module') { + const modLang = await this.getModuleForcedLangFromRoute(courseId); + if (modLang) { + return modLang; + } + } else { + this.lastNavigationCheck.cmId = 0; + this.lastNavigationCheck.cmLang = undefined; + } + + let lang: string | undefined = undefined; + if (this.lastNavigationCheck.courseId === courseId) { + console.error('Using cached course language', this.lastNavigationCheck.courseLang); + + lang = this.lastNavigationCheck.courseLang; + } else if (course?.lang !== undefined) { + lang = course.lang; + } else { + course = await CorePromiseUtils.ignoreErrors( + CoreCourses.getCourseByField('id', courseId, { readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE }), + ); + + lang = course?.lang; + } + + this.lastNavigationCheck.courseId = courseId; + this.lastNavigationCheck.courseLang = lang; + + return lang; + } + + /** + * Get module forced language from route. + * + * @param courseId Course ID of the module. + * @returns Module forced language if found. + */ + protected async getModuleForcedLangFromRoute(courseId: number): Promise { + const cmId = CoreNavigator.getRouteNumberParam('cmId'); + let lang: string | undefined = undefined; + if (cmId) { + if (this.lastNavigationCheck.cmId === cmId) { + console.error('Using cached cm language', this.lastNavigationCheck.cmLang); + + lang = this.lastNavigationCheck.cmLang; + } else { + // TODO: In the future this should directly return the module language instead of checking the delegate. + const module = await CorePromiseUtils.ignoreErrors( + CoreCourse.getModule(cmId, courseId, undefined, true), + ); + + if (module) { + lang = await CorePromiseUtils.ignoreErrors( + CoreCourseModuleDelegate.getModuleForcedLang(module), + ); + } + } + } + + this.lastNavigationCheck.cmId = cmId ?? 0; + this.lastNavigationCheck.cmLang = lang; + + return lang; + } + +} +export const CoreCourseForceLanguage = makeSingleton(CoreCourseForceLanguageService); diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index b60de1a5025..522b353b336 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -967,7 +967,7 @@ export class CoreCourseHelperProvider { // Not enrolled or an error happened. Try to use another WebService. } - const course = await CoreCourses.getCourseByField('id', courseId, siteId); + const course = await CoreCourses.getCourseByField('id', courseId, { siteId }); return ({ enrolled: false, course: course }); } @@ -2037,7 +2037,7 @@ export class CoreCourseHelperProvider { return course.communicationroomurl; } - course = await CoreCourses.getCourseByField('id', course.id, site.id); + course = await CoreCourses.getCourseByField('id', course.id, { siteId: site.id }); if ('communicationroomurl' in course) { return course.communicationroomurl; } diff --git a/src/core/features/course/services/course-module-helper.ts b/src/core/features/course/services/course-module-helper.ts index fc54f63739f..9eb3e8d193d 100644 --- a/src/core/features/course/services/course-module-helper.ts +++ b/src/core/features/course/services/course-module-helper.ts @@ -36,6 +36,7 @@ import { CoreCourseModuleSummary } from './course'; import { CoreCourseModuleData } from './course-helper'; import { CoreCourseModuleDelegate } from './module-delegate'; import { CoreWSExternalFile } from '@services/ws'; +import { CoreTextFormat } from '@singletons/text'; /** * Service that provides some features regarding a course. @@ -308,7 +309,7 @@ export type CoreCourseModuleStandardElements = { course: number; // Course id. name: string; // Activity name. intro?: string; // Activity introduction. - introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN, or 4 = MARKDOWN). + introformat?: CoreTextFormat; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN, or 4 = MARKDOWN). introfiles?: CoreWSExternalFile[]; section?: number; // Course section id. visible?: boolean; // Visible. diff --git a/src/core/features/course/services/module-delegate.ts b/src/core/features/course/services/module-delegate.ts index ce41dbac919..6a24ced8ae1 100644 --- a/src/core/features/course/services/module-delegate.ts +++ b/src/core/features/course/services/module-delegate.ts @@ -138,6 +138,14 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { activity: CoreCourseOverviewActivity, courseId: number, ): Promise; + + /** + * Get the forced language for a module, if any. + * + * @param module The module data. + * @returns The forced language, undefined if not set. + */ + getModuleForcedLang(module: CoreCourseModuleData): Promise; } /** @@ -484,6 +492,16 @@ export class CoreCourseModuleDelegateService extends CoreDelegate { + return await this.executeFunctionOnEnabled(module.modname, 'getModuleForcedLang', [module]); + } + } export const CoreCourseModuleDelegate = makeSingleton(CoreCourseModuleDelegateService); diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index 0764082e130..cfc64810c45 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -428,11 +428,23 @@ export class CoreCoursesProvider { * category: category id the course belongs to. * sectionid: section id that belongs to a course, since 4.5. * @param value The value to match. - * @param siteId Site ID. If not defined, use current site. + * @param options Other options. Direct usage of siteId is deprecated since 5.1. * @returns Promise resolved with the first course. */ - async getCourseByField(field?: string, value?: string | number, siteId?: string): Promise { - const courses = await this.getCoursesByField(field, value, siteId); + async getCourseByField( + field?: string, + value?: string | number, + options?: CoreSitesCommonWSOptions, + ): Promise; + async getCourseByField(field?: string, value?: string | number, siteId?: string): Promise; + async getCourseByField( + field: string = '', + value: string | number = '', + siteIdOrOptions?: string | CoreSitesCommonWSOptions, + ): Promise { + const options = + typeof siteIdOrOptions === 'string' ? { siteId: siteIdOrOptions } : siteIdOrOptions || {}; + const courses = await this.getCoursesByField(field, value, options); if (courses?.length > 0) { return courses[0]; @@ -452,15 +464,28 @@ export class CoreCoursesProvider { * category: category id the course belongs to. * sectionid: section id that belongs to a course, since 4.5. * @param value The value to match. - * @param siteId Site ID. If not defined, use current site. + * @param options Other options. Direct usage of siteId is deprecated since 5.1. * @returns Promise resolved with the courses. */ + async getCoursesByField( + field?: string, + value?: string | number, + options?: CoreSitesCommonWSOptions, + ): Promise; + async getCoursesByField( + field?: string, + value?: string | number, + siteId?: string, + ): Promise; async getCoursesByField( field: string = '', value: string | number = '', - siteId?: string, + siteIdOrOptions?: string | CoreSitesCommonWSOptions, ): Promise { - return await firstValueFrom(this.getCoursesByFieldObservable(field, value, { siteId })); + const options = + typeof siteIdOrOptions === 'string' ? { siteId: siteIdOrOptions } : siteIdOrOptions || {}; + + return await firstValueFrom(this.getCoursesByFieldObservable(field, value, options)); } /** diff --git a/src/core/features/courses/services/handlers/section-link.ts b/src/core/features/courses/services/handlers/section-link.ts index 617dc743d7e..74c9bb8aaca 100644 --- a/src/core/features/courses/services/handlers/section-link.ts +++ b/src/core/features/courses/services/handlers/section-link.ts @@ -75,7 +75,7 @@ export class CoreCoursesSectionLinkHandlerService extends CoreCoursesLinksHandle if (site.isVersionGreaterEqualThan('4.5')) { try { - return CoreCourses.getCourseByField('sectionid', sectionId, siteId); + return CoreCourses.getCourseByField('sectionid', sectionId, { siteId }); } catch { // Fallback to searching courses stored in cache. } diff --git a/src/core/features/grades/grades.module.ts b/src/core/features/grades/grades.module.ts index f3c63355cfe..a1e930b4378 100644 --- a/src/core/features/grades/grades.module.ts +++ b/src/core/features/grades/grades.module.ts @@ -54,6 +54,7 @@ const mobileRoutes: Routes = [ { path: ':courseId', loadComponent: () => import('@features/grades/pages/course/course'), + data: { checkForcedLanguage: 'course' }, }, ]; @@ -65,6 +66,7 @@ const tabletRoutes: Routes = [ { path: ':courseId', loadComponent: () => import('@features/grades/pages/course/course'), + data: { checkForcedLanguage: 'course' }, }, ], }, @@ -82,6 +84,7 @@ const mainMenuChildrenRoutes: Routes = [ { path: `${CORE_COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${GRADES_PAGE_NAME}`, loadComponent: () => import('@features/grades/pages/course/course'), + data: { checkForcedLanguage: 'course' }, }, ...conditionalRoutes([ { diff --git a/src/core/features/siteplugins/siteplugins.module.ts b/src/core/features/siteplugins/siteplugins.module.ts index 71edb167fbe..4797a378ce0 100644 --- a/src/core/features/siteplugins/siteplugins.module.ts +++ b/src/core/features/siteplugins/siteplugins.module.ts @@ -119,6 +119,7 @@ const moduleRoutes: Routes = [ path: `${CORE_SITE_PLUGINS_PATH}/module/:courseId/:cmId`, loadComponent: () => import('@features/siteplugins/pages/module-index/module-index'), canDeactivate: [canLeaveGuard], + data: { checkForcedLanguage: 'module' }, }, ]; diff --git a/src/core/features/user/user.module.ts b/src/core/features/user/user.module.ts index 3dff445521a..84d31b0e4ca 100644 --- a/src/core/features/user/user.module.ts +++ b/src/core/features/user/user.module.ts @@ -98,6 +98,7 @@ const routes: Routes = [ loadComponent: () => import('@features/user/pages/about/about'), }, ], + data: { checkForcedLanguage: 'course' }, }, ...conditionalRoutes([ { @@ -105,6 +106,7 @@ const routes: Routes = [ loadComponent: () => import('@features/user/pages/profile/profile'), data: { swipeManagerSource: 'participants', + checkForcedLanguage: 'course', }, }, ], () => CoreScreen.isMobile), diff --git a/src/core/services/lang.ts b/src/core/services/lang.ts index 1a566524d8d..ba4aa8748d3 100644 --- a/src/core/services/lang.ts +++ b/src/core/services/lang.ts @@ -183,9 +183,10 @@ export class CoreLangProvider { * Change current language. * * @param language New language to use. + * @param saveSetting Whether to save the setting. Defaults to true. * @returns Promise resolved when the change is finished. */ - async changeCurrentLanguage(language: string): Promise { + async changeCurrentLanguage(language: string, saveSetting = true): Promise { await this.loadDayJSLocale(language); const previousLanguage = this.currentLanguage ?? this.getDefaultLanguage(); @@ -194,12 +195,14 @@ export class CoreLangProvider { try { await this.reloadLanguageStrings(); - await CoreConfig.set('current_language', language); + if (saveSetting) { + await CoreConfig.set('current_language', language); + } } catch (error) { if (language !== previousLanguage) { this.logger.error(`Language ${language} not available, reverting to ${previousLanguage}`, error); - return this.changeCurrentLanguage(previousLanguage); + return this.changeCurrentLanguage(previousLanguage, saveSetting); } throw error; @@ -353,15 +356,13 @@ export class CoreLangProvider { // No forced language, try to get current language from browser. let preferredLanguage = navigator.language.toLowerCase(); - if (preferredLanguage.indexOf('-') > -1) { - // Language code defined by locale has a dash, like en-US or es-ES. Check if it's supported. - if (CoreConstants.CONFIG.languages && CoreConstants.CONFIG.languages[preferredLanguage] === undefined) { - // Code is NOT supported. Fallback to language without dash. E.g. 'en-US' would fallback to 'en'. - preferredLanguage = preferredLanguage.substring(0, preferredLanguage.indexOf('-')); - } + if (preferredLanguage.indexOf('-') > -1 && !this.isLanguageSupported(preferredLanguage)) { + // Language code defined by locale has a dash, like en-US or es-ES. + // Code is NOT supported. Fallback to language without dash. E.g. 'en-US' would fallback to 'en'. + preferredLanguage = preferredLanguage.substring(0, preferredLanguage.indexOf('-')); } - if (CoreConstants.CONFIG.languages[preferredLanguage] === undefined) { + if (!this.isLanguageSupported(preferredLanguage)) { // Language not supported, use default language. return this.defaultLanguage; } @@ -369,6 +370,16 @@ export class CoreLangProvider { return preferredLanguage; } + /** + * Check if a language is supported in the app. + * + * @param lang Language code. + * @returns Whether the language is supported. + */ + protected isLanguageSupported(lang: string): boolean { + return CoreConstants.CONFIG.languages[lang] !== undefined; + } + /** * Get the default language. * @@ -742,6 +753,29 @@ export class CoreLangProvider { }); } + /** + * Force the app to use a certain language when inside a course or module. + * + * @param lang Language code to force. If not defined, restore the detected language. + */ + async forceCourseLanguage(lang?: string): Promise { + const force = lang ? 'FORCE' : 'RESTORE'; + if (!lang) { + // Restore the language to the detected one. + lang = await this.detectLanguage(); + } + + if (!this.isLanguageSupported(lang)) { + return; + } + + if (this.currentLanguage !== lang) { + // Force the language. + console.error(force, lang); + await this.changeCurrentLanguage(lang, false); + } + } + } export const CoreLang = makeSingleton(CoreLangProvider);