@openedx/frontend-base 2.0.0-alpha.2 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/runtime/config/index.d.ts +8 -0
- package/dist/runtime/config/index.js +31 -8
- package/dist/runtime/config/index.js.map +1 -1
- package/dist/runtime/i18n/index.d.ts +6 -8
- package/dist/runtime/i18n/index.js +6 -8
- package/dist/runtime/i18n/index.js.map +1 -1
- package/dist/runtime/i18n/lib.d.ts +38 -8
- package/dist/runtime/i18n/lib.js +88 -22
- package/dist/runtime/i18n/lib.js.map +1 -1
- package/dist/runtime/i18n/updateSiteLanguage.d.ts +17 -0
- package/dist/runtime/i18n/updateSiteLanguage.js +91 -0
- package/dist/runtime/i18n/updateSiteLanguage.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/shell/footer/LanguageMenu.js +36 -6
- package/dist/shell/footer/LanguageMenu.js.map +1 -1
- package/dist/shell/footer/LanguageMenuItem.d.ts +4 -1
- package/dist/shell/footer/LanguageMenuItem.js +4 -5
- package/dist/shell/footer/LanguageMenuItem.js.map +1 -1
- package/dist/shell/footer/messages.d.ts +8 -0
- package/dist/shell/footer/messages.js +10 -0
- package/dist/shell/footer/messages.js.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/shell/footer/data/api.d.ts +0 -1
- package/dist/shell/footer/data/api.js +0 -45
- package/dist/shell/footer/data/api.js.map +0 -1
package/README.md
CHANGED
|
@@ -36,7 +36,15 @@ If a change requires corresponding updates to a consuming site (for example, new
|
|
|
36
36
|
|
|
37
37
|
### Releases
|
|
38
38
|
|
|
39
|
-
This library is published
|
|
39
|
+
This library is published to npm by `semantic-release`, and its branches follow [OEP-10 ADR 0002: Frontend Stable Branches](https://docs.openedx.org/projects/openedx-proposals/en/latest/processes/oep-0010/decisions/0002-frontend-stable-branches.html):
|
|
40
|
+
|
|
41
|
+
- **`main`** is unstable. Every merge publishes a prerelease on the `alpha` dist-tag. Breaking changes land here with no DEPR process and no warning, so it is not supported in production. All changes, including bug fixes, should target this branch first.
|
|
42
|
+
- **`stable`** carries the newest stable major and owns the `latest` dist-tag. Changes arrive here as backports from `main`, and no breaking change lands after publication.
|
|
43
|
+
- **`n.x`** and **`n.m.x`** are maintenance branches for majors and minors that `stable` has moved past. Each owns the dist-tag matching its own name, so consumers select a maintained line by semver range, for example `"1.x"`.
|
|
44
|
+
|
|
45
|
+
Both `.releaserc` and the `Release CI` workflow already know the whole layout, including the maintenance branch patterns, so a new line starts publishing as soon as it is pushed.
|
|
46
|
+
|
|
47
|
+
This repository is not branched or tagged for Open edX releases in its own right. It participates by published version instead, per [OEP-10 ADR 0003: Frontend Release Strategy](https://docs.openedx.org/projects/openedx-proposals/en/latest/processes/oep-0010/decisions/0003-frontend-release-strategy.html).
|
|
40
48
|
|
|
41
49
|
## Further reading
|
|
42
50
|
|
|
@@ -169,6 +169,14 @@ export declare function mergeSiteConfig(newSiteConfig: Partial<SiteConfig>, opti
|
|
|
169
169
|
* used at initialization time to process any AppConfigs bundled with the site.
|
|
170
170
|
*/
|
|
171
171
|
export declare function addAppConfigs(): void;
|
|
172
|
+
/**
|
|
173
|
+
* Resolves an app's configuration, deep merging the three sources in order of
|
|
174
|
+
* increasing precedence:
|
|
175
|
+
*
|
|
176
|
+
* - `App.defaultConfig`, bundled by the app author
|
|
177
|
+
* - `SiteConfig.commonAppConfig`, supplied site-wide by an operator
|
|
178
|
+
* - `App.config`, supplied per-app by an operator
|
|
179
|
+
*/
|
|
172
180
|
export declare function getAppConfig(id: string): AppConfig;
|
|
173
181
|
export declare function mergeAppConfig(id: string, newAppConfig: AppConfig): void;
|
|
174
182
|
export declare function setActiveRouteRoles(roles: string[]): void;
|
|
@@ -132,6 +132,8 @@ let siteConfig = {
|
|
|
132
132
|
externalLinkUrlOverrides: [],
|
|
133
133
|
runtimeConfigJsonUrl: null,
|
|
134
134
|
theme: {},
|
|
135
|
+
defaultLanguage: 'en',
|
|
136
|
+
supportedLanguages: [],
|
|
135
137
|
accessTokenCookieName: 'edx-jwt-cookie-header-payload',
|
|
136
138
|
csrfTokenApiPath: '/csrf/api/v1/token',
|
|
137
139
|
ignoredErrorRegex: null,
|
|
@@ -260,7 +262,8 @@ function mergeApps(oldApps, newApps, options = {}) {
|
|
|
260
262
|
}
|
|
261
263
|
/*
|
|
262
264
|
* Merge a pair of Apps with the same appId. Deep-merges `config` (and, in the
|
|
263
|
-
* full-merge case, `provides`); other fields take `newApp`'s
|
|
265
|
+
* full-merge case, `defaultConfig` and `provides`); other fields take `newApp`'s
|
|
266
|
+
* value verbatim.
|
|
264
267
|
* The result is built via `Object.getOwnPropertyDescriptors` so any lazy
|
|
265
268
|
* getters survive: a snapshot via `lodash.merge` or spread would invoke the
|
|
266
269
|
* getter at merge time and freeze its return value, which is typically empty
|
|
@@ -269,8 +272,9 @@ function mergeApps(oldApps, newApps, options = {}) {
|
|
|
269
272
|
* survive element-wise merging anyway.
|
|
270
273
|
*/
|
|
271
274
|
function mergeApp(oldApp, newApp, options = {}) {
|
|
272
|
-
// configOnly mode: preserve `oldApp` (identity, slots, etc.)
|
|
273
|
-
// only `newApp.config` on top.
|
|
275
|
+
// configOnly mode: preserve `oldApp` (identity, slots, defaultConfig, etc.)
|
|
276
|
+
// and deep-merge only `newApp.config` on top. Operator-supplied config can
|
|
277
|
+
// never write into an app's bundled `defaultConfig`.
|
|
274
278
|
if (options.configOnly) {
|
|
275
279
|
if (!newApp.config) {
|
|
276
280
|
return oldApp;
|
|
@@ -279,9 +283,13 @@ function mergeApp(oldApp, newApp, options = {}) {
|
|
|
279
283
|
config: merge({}, oldApp.config, newApp.config),
|
|
280
284
|
});
|
|
281
285
|
}
|
|
282
|
-
// Full mode: take `newApp` (identity, slots, etc.) and deep-merge
|
|
283
|
-
// and `provides` from `oldApp`. Other fields take
|
|
286
|
+
// Full mode: take `newApp` (identity, slots, etc.) and deep-merge
|
|
287
|
+
// `defaultConfig`, `config`, and `provides` from `oldApp`. Other fields take
|
|
288
|
+
// `newApp`'s value verbatim.
|
|
284
289
|
const deepMerged = {};
|
|
290
|
+
if (oldApp.defaultConfig !== undefined || newApp.defaultConfig !== undefined) {
|
|
291
|
+
deepMerged.defaultConfig = merge({}, oldApp.defaultConfig, newApp.defaultConfig);
|
|
292
|
+
}
|
|
285
293
|
if (oldApp.config !== undefined || newApp.config !== undefined) {
|
|
286
294
|
deepMerged.config = merge({}, oldApp.config, newApp.config);
|
|
287
295
|
}
|
|
@@ -297,6 +305,9 @@ function cloneAppDescriptors(source, overrides) {
|
|
|
297
305
|
}
|
|
298
306
|
return Object.create(Object.getPrototypeOf(source), descriptors);
|
|
299
307
|
}
|
|
308
|
+
/* Bundled by app authors via `App.defaultConfig`. Kept separate from
|
|
309
|
+
`appConfigs` so that operator-supplied config can never write into it. */
|
|
310
|
+
const appDefaultConfigs = {};
|
|
300
311
|
const appConfigs = {};
|
|
301
312
|
/**
|
|
302
313
|
* addAppConfigs finds any AppConfig objects in the apps in SiteConfig and makes their config
|
|
@@ -308,19 +319,31 @@ export function addAppConfigs() {
|
|
|
308
319
|
if (!apps)
|
|
309
320
|
return;
|
|
310
321
|
for (const app of apps) {
|
|
311
|
-
const { appId, config } = app;
|
|
322
|
+
const { appId, config, defaultConfig } = app;
|
|
323
|
+
if (defaultConfig !== undefined) {
|
|
324
|
+
appDefaultConfigs[appId] = defaultConfig;
|
|
325
|
+
}
|
|
312
326
|
if (config !== undefined) {
|
|
313
327
|
appConfigs[appId] = config;
|
|
314
328
|
}
|
|
315
329
|
}
|
|
316
330
|
publish(CONFIG_CHANGED);
|
|
317
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Resolves an app's configuration, deep merging the three sources in order of
|
|
334
|
+
* increasing precedence:
|
|
335
|
+
*
|
|
336
|
+
* - `App.defaultConfig`, bundled by the app author
|
|
337
|
+
* - `SiteConfig.commonAppConfig`, supplied site-wide by an operator
|
|
338
|
+
* - `App.config`, supplied per-app by an operator
|
|
339
|
+
*/
|
|
318
340
|
export function getAppConfig(id) {
|
|
319
341
|
const { commonAppConfig } = getSiteConfig();
|
|
320
|
-
|
|
342
|
+
const defaultConfig = appDefaultConfigs[id];
|
|
343
|
+
if (defaultConfig === undefined && commonAppConfig === undefined) {
|
|
321
344
|
return appConfigs[id];
|
|
322
345
|
}
|
|
323
|
-
return merge({}, commonAppConfig, appConfigs[id]);
|
|
346
|
+
return merge({}, defaultConfig, commonAppConfig, appConfigs[id]);
|
|
324
347
|
}
|
|
325
348
|
export function mergeAppConfig(id, newAppConfig) {
|
|
326
349
|
// Non-mutating: produce a fresh entry so consumers holding a reference to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../runtime/config/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * #### Import members from **@edx/frontend-base**\n *\n * The configuration module provides utilities for working with an application's configuration\n * document (SiteConfig). Configuration variables can be supplied to the\n * application in three different ways. They are applied in the following order:\n *\n * - Site Configuration File (site.config.tsx)\n * - Initialization Config Handler\n * - Runtime Configuration\n *\n * Last one in wins, and are deep merged together. Variables with the same name defined via the\n * later methods will override any defined using an earlier method. i.e., if a variable is defined\n * in Runtime Configuration, that will override the same variable defined in either of the earlier\n * methods. Configuration defined in a JS file will override any default values below.\n *\n * ##### Site Configuration File\n *\n * Configuration variables can be supplied in a file named site.config.tsx. This file must\n * export either an Object containing configuration variables or a function. The function must\n * return an Object containing configuration variables or, alternately, a promise which resolves to\n * an Object.\n *\n * Using a function or async function allows the configuration to be resolved at runtime (because\n * the function will be executed at runtime). This is not common, and the capability is included\n * for the sake of flexibility.\n *\n * The Site Configuration File is well-suited to extensibility use cases or component overrides,\n * in that the configuration file can depend on any installed JavaScript module. It is also the\n * preferred way of doing build-time configuration if runtime configuration isn't used by your\n * deployment of the platform.\n *\n * Exporting a config object:\n * ```\n * const siteConfig = {\n * lmsBaseUrl: 'http://localhost:18000'\n * };\n *\n * export default siteConfig;\n * ```\n *\n * Exporting a function that returns an object:\n * ```\n * function getSiteConfig() {\n * return {\n * lmsBaseUrl: 'http://localhost:18000'\n * };\n * }\n * ```\n *\n * Exporting a function that returns a promise that resolves to an object:\n * ```\n * function getAsyncSiteConfig() {\n * return new Promise((resolve, reject) => {\n * resolve({\n * lmsBaseUrl: 'http://localhost:18000'\n * });\n * });\n * }\n *\n * export default getAsyncSiteConfig;\n * ```\n *\n * ##### Initialization Config Handler\n *\n * The configuration document can be extended by\n * applications at run-time using a `config` initialization handler. Please see the Initialization\n * documentation for more information on handlers and initialization phases.\n *\n * ```\n * initialize({\n * handlers: {\n * config: () => {\n * mergeSiteConfig({\n * CUSTOM_VARIABLE: 'custom value',\n * lmsBaseUrl: 'http://localhost:18001' // You can override variables, but this is uncommon.\n * }, 'App config override handler');\n * },\n * },\n * });\n * ```\n *\n * ##### Runtime Configuration\n *\n * Configuration variables can also be supplied using the \"runtime configuration\" method, taking\n * advantage of the Micro-frontend Config API in edx-platform. More information on this API can be\n * found in the ADR which introduced it:\n *\n * https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst\n *\n * The runtime configuration method can be enabled by supplying a runtimeConfigJsonUrl via one of the other\n * two configuration methods above.\n *\n * Runtime configuration is particularly useful if you need to supply different configurations to\n * a single deployment of a micro-frontend, for instance. It is also a perfectly valid alternative\n * to build-time configuration, though it introduces an additional API call to edx-platform on MFE\n * initialization.\n *\n *\n * @module Config\n */\n\nimport isEqual from 'lodash/isEqual';\nimport keyBy from 'lodash/keyBy';\nimport merge from 'lodash/merge';\nimport {\n App,\n AppConfig,\n EnvironmentTypes,\n SiteConfig\n} from '../../types';\nimport { ACTIVE_ROLES_CHANGED, CONFIG_CHANGED } from '../constants';\nimport { publish } from '../subscriptions';\n\nlet siteConfig: SiteConfig = {\n // Required\n siteId: '',\n baseUrl: '',\n siteName: '',\n loginUrl: '',\n logoutUrl: '',\n lmsBaseUrl: '',\n\n // Optional\n environment: EnvironmentTypes.PRODUCTION,\n cmsBaseUrl: '',\n apps: [],\n externalRoutes: [],\n externalLinkUrlOverrides: [],\n runtimeConfigJsonUrl: null,\n theme: {},\n accessTokenCookieName: 'edx-jwt-cookie-header-payload',\n csrfTokenApiPath: '/csrf/api/v1/token',\n ignoredErrorRegex: null,\n languagePreferenceCookieName: 'openedx-language-preference',\n refreshAccessTokenApiPath: '/login_refresh',\n userInfoCookieName: 'edx-user-info',\n segmentKey: null,\n};\n\n/**\n * Getter for the application configuration document. This is synchronous and merely returns a\n * reference to an existing object, and is thus safe to call as often as desired.\n *\n * Example:\n *\n * ```\n * import { getSiteConfig } from '@openedx/frontend-base';\n *\n * const {\n * lmsBaseUrl,\n * } = getSiteConfig();\n * ```\n *\n * @returns {SiteConfig}\n */\nexport function getSiteConfig() {\n return siteConfig;\n}\n\n/**\n * Replaces the existing SiteConfig. This is not commonly used, but can be helpful for tests.\n *\n * Example:\n *\n * ```\n * import { setSiteConfig } from '@openedx/frontend-base';\n *\n * setSiteConfig({\n * lmsBaseUrl, // This is overriding the ENTIRE document - this is not merged in!\n * });\n * ```\n *\n * @param newConfig A replacement SiteConfig which will completely override the current SiteConfig.\n */\nexport function setSiteConfig(newSiteConfig: SiteConfig) {\n siteConfig = newSiteConfig;\n publish(CONFIG_CHANGED);\n}\n\ninterface MergeSiteConfigOptions {\n limitAppMergeToConfig?: boolean;\n}\n\n/**\n * Merges additional configuration values into the site config returned by `getSiteConfig`. Will\n * override any values that exist with the same keys.\n *\n * ```\n * mergeSiteConfig({\n * NEW_KEY: 'new value',\n * OTHER_NEW_KEY: 'other new value',\n * });\n *\n * This function uses lodash.merge internally to merge configuration objects\n * which means they will be merged recursively. See https://lodash.com/docs/latest#merge for\n * documentation on the exact behavior.\n *\n * Apps are merged by appId rather than array index. By default, apps in the incoming config\n * that don't exist in the current config will be added.\n *\n * When `limitAppMergeToConfig` is true:\n * - All non-app parts of the config are still merged normally\n * - Only the `config` property of each existing app is merged\n * - Apps in the incoming config that don't exist in the current config are ignored\n *\n * @param {Object} newSiteConfig\n * @param {Object} options\n * @param {boolean} options.limitAppMergeToConfig - Limit app merging to only the config property of existing apps\n */\nexport function mergeSiteConfig(\n newSiteConfig: Partial<SiteConfig>,\n options: MergeSiteConfigOptions = {}\n) {\n const { limitAppMergeToConfig = false } = options;\n const { apps: newApps, ...restOfNewConfig } = newSiteConfig;\n\n /* `merge({}, ...)` deep-clones into the fresh target, so the new `siteConfig`\n is a brand-new reference and the previous one is never mutated. This is what\n lets React consumers detect that CONFIG_CHANGED actually changed something. */\n siteConfig = merge({}, siteConfig, restOfNewConfig);\n\n // if we don't have new apps, we're done\n if (!newApps?.length) {\n publish(CONFIG_CHANGED);\n return;\n }\n\n // if we're doing a full merge, merge the objects\n if (!limitAppMergeToConfig) {\n siteConfig.apps = mergeApps(siteConfig.apps || [], newApps);\n publish(CONFIG_CHANGED);\n return;\n }\n\n // we're doing a config-only merge, if we don't\n // have apps already, we can't update their configs\n if (!siteConfig.apps?.length) {\n publish(CONFIG_CHANGED);\n return;\n }\n\n // handle config-only merging\n siteConfig.apps = mergeApps(siteConfig.apps, newApps, { configOnly: true });\n\n publish(CONFIG_CHANGED);\n}\n\n/*\n * Merge two App[] by appId. Existing apps stay in their original positions\n * (with their pair-merged counterpart from `newApps` substituted in when\n * present); apps in `newApps` not already in `oldApps` append at the end.\n * With `{ configOnly: true }`, no apps are added and apps not appearing in\n * `newApps` pass through unchanged. Per-pair merging is delegated to\n * `mergeApp`.\n */\nfunction mergeApps(\n oldApps: App[],\n newApps: App[],\n options: { configOnly?: boolean } = {},\n): App[] {\n const incomingByAppId = keyBy(newApps, 'appId');\n\n // Phase 1: walk existing apps in their original order, pair-merging any\n // that have a counterpart in newApps.\n const updatedExisting = oldApps.map((oldApp) => {\n const newApp = incomingByAppId[oldApp.appId];\n return newApp ? mergeApp(oldApp, newApp, options) : oldApp;\n });\n\n // configOnly mode never adds apps, so we're done.\n if (options.configOnly) {\n return updatedExisting;\n }\n\n // Phase 2: append apps from newApps that weren't already in oldApps.\n const existingIds = new Set(oldApps.map((a) => a.appId));\n const additions = newApps.filter((a) => !existingIds.has(a.appId));\n return [...updatedExisting, ...additions];\n}\n\n/*\n * Merge a pair of Apps with the same appId. Deep-merges `config` (and, in the\n * full-merge case, `provides`); other fields take `newApp`'s value verbatim.\n * The result is built via `Object.getOwnPropertyDescriptors` so any lazy\n * getters survive: a snapshot via `lodash.merge` or spread would invoke the\n * getter at merge time and freeze its return value, which is typically empty\n * mid-init. Per-field replacement is also the only sensible behavior for the\n * array fields (`slots`/`routes`/`providers`/`externalScripts`), which don't\n * survive element-wise merging anyway.\n */\nfunction mergeApp(\n oldApp: App,\n newApp: App,\n options: { configOnly?: boolean } = {},\n): App {\n // configOnly mode: preserve `oldApp` (identity, slots, etc.) and deep-merge\n // only `newApp.config` on top.\n if (options.configOnly) {\n if (!newApp.config) {\n return oldApp;\n }\n return cloneAppDescriptors(oldApp, {\n config: merge({}, oldApp.config, newApp.config),\n });\n }\n\n // Full mode: take `newApp` (identity, slots, etc.) and deep-merge `config`\n // and `provides` from `oldApp`. Other fields take `newApp`'s value verbatim.\n const deepMerged: Record<string, unknown> = {};\n if (oldApp.config !== undefined || newApp.config !== undefined) {\n deepMerged.config = merge({}, oldApp.config, newApp.config);\n }\n if (oldApp.provides !== undefined || newApp.provides !== undefined) {\n deepMerged.provides = merge({}, oldApp.provides, newApp.provides);\n }\n return cloneAppDescriptors(newApp, deepMerged);\n}\n\nfunction cloneAppDescriptors(source: App, overrides: Record<string, unknown>): App {\n const descriptors = Object.getOwnPropertyDescriptors(source);\n for (const [key, value] of Object.entries(overrides)) {\n descriptors[key] = { value, writable: true, enumerable: true, configurable: true };\n }\n return Object.create(Object.getPrototypeOf(source), descriptors) as App;\n}\n\nconst appConfigs: Record<string, AppConfig> = {};\n\n/**\n * addAppConfigs finds any AppConfig objects in the apps in SiteConfig and makes their config\n * available to be used by Apps via getAppConfig(appId) or useAppConfig() functions. This is\n * used at initialization time to process any AppConfigs bundled with the site.\n */\nexport function addAppConfigs() {\n const { apps } = getSiteConfig();\n if (!apps) return;\n\n for (const app of apps) {\n const { appId, config } = app;\n if (config !== undefined) {\n appConfigs[appId] = config;\n }\n }\n\n publish(CONFIG_CHANGED);\n}\n\nexport function getAppConfig(id: string) {\n const { commonAppConfig } = getSiteConfig();\n if (commonAppConfig === undefined) {\n return appConfigs[id];\n }\n return merge({}, commonAppConfig, appConfigs[id]);\n}\n\nexport function mergeAppConfig(id: string, newAppConfig: AppConfig) {\n // Non-mutating: produce a fresh entry so consumers holding a reference to\n // the previous one don't observe the change underneath them.\n appConfigs[id] = merge({}, appConfigs[id], newAppConfig);\n publish(CONFIG_CHANGED);\n}\n\nlet activeRouteRoles: string[] = [];\n\nexport function setActiveRouteRoles(roles: string[]) {\n if (isEqual(activeRouteRoles, roles)) return;\n activeRouteRoles = roles;\n publish(ACTIVE_ROLES_CHANGED);\n}\n\nexport function getActiveRouteRoles() {\n return activeRouteRoles;\n}\n\nconst activeWidgetRoles: Record<string, number> = {};\n\nexport function addActiveWidgetRole(role: string) {\n // Only publish when the role transitions from absent to present.\n const wasPresent = (activeWidgetRoles[role] ?? 0) > 0;\n activeWidgetRoles[role] = (activeWidgetRoles[role] ?? 0) + 1;\n if (!wasPresent) publish(ACTIVE_ROLES_CHANGED);\n}\n\nexport function removeActiveWidgetRole(role: string) {\n if (activeWidgetRoles[role] === undefined) return;\n activeWidgetRoles[role] -= 1;\n if (activeWidgetRoles[role] < 1) {\n delete activeWidgetRoles[role];\n // Only publish when the role transitions from present to absent.\n publish(ACTIVE_ROLES_CHANGED);\n }\n}\n\nexport function getActiveWidgetRoles() {\n return Object.entries(activeWidgetRoles)\n .filter(([, count]: [role: string, count: number]) => count !== undefined && count > 0)\n .map(([role]: [role: string, count: number]) => role);\n}\n\n// Gets all active roles from the route roles and widget roles.\nexport function getActiveRoles() {\n return [...getActiveRouteRoles(), ...getActiveWidgetRoles()];\n}\n\n/**\n * Collects all `provides` entries from registered apps that match the given identifier.\n * This enables inter-app data sharing without frontend-base needing to understand the data shape.\n *\n * @param id - The namespaced provides identifier.\n * @returns An array of provided data from all apps that declared data for this identifier.\n */\nexport function getProvides(id: string): unknown[] {\n const { apps } = getSiteConfig();\n if (!apps) return [];\n\n const results: unknown[] = [];\n for (const app of apps) {\n if (app.provides && app.provides[id] !== undefined) {\n results.push(app.provides[id]);\n }\n }\n return results;\n}\n\n/**\n * Collects and flattens all `provides` entries for the given identifier\n * as strings. Each entry can be a single string or a string array; entries\n * of other types are silently skipped.\n *\n * @param id - The namespaced provides identifier.\n * @returns A flat array of strings from all apps that declared data for this identifier.\n */\nexport function getProvidesAsStrings(id: string): string[] {\n return getProvides(id)\n .filter((data): data is string | string[] => typeof data === 'string' || Array.isArray(data))\n .flat();\n}\n\n/**\n * Get an external link URL based on the URL provided. If the passed in URL is overridden in the\n * `externalLinkUrlOverrides` object, it will return the overridden URL. Otherwise, it will return\n * the provided URL.\n *\n *\n * @param {string} url - The default URL.\n * @returns {string} - The external link URL. Defaults to the input URL if not found in the\n * `externalLinkUrlOverrides` object. If the input URL is invalid, '#' is returned.\n *\n * @example\n * import { getExternalLinkUrl } from '@openedx/frontend-base';\n *\n * <Hyperlink\n * destination={getExternalLinkUrl(data.helpLink)}\n * target=\"_blank\"\n * >\n */\nexport function getExternalLinkUrl(url: string): string {\n // Guard against whitespace-only strings\n if (typeof url !== 'string' || !url.trim()) {\n return '#';\n }\n\n const overriddenLinkUrls = getSiteConfig().externalLinkUrlOverrides ?? {};\n return overriddenLinkUrls[url] ?? url;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../runtime/config/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * #### Import members from **@edx/frontend-base**\n *\n * The configuration module provides utilities for working with an application's configuration\n * document (SiteConfig). Configuration variables can be supplied to the\n * application in three different ways. They are applied in the following order:\n *\n * - Site Configuration File (site.config.tsx)\n * - Initialization Config Handler\n * - Runtime Configuration\n *\n * Last one in wins, and are deep merged together. Variables with the same name defined via the\n * later methods will override any defined using an earlier method. i.e., if a variable is defined\n * in Runtime Configuration, that will override the same variable defined in either of the earlier\n * methods. Configuration defined in a JS file will override any default values below.\n *\n * ##### Site Configuration File\n *\n * Configuration variables can be supplied in a file named site.config.tsx. This file must\n * export either an Object containing configuration variables or a function. The function must\n * return an Object containing configuration variables or, alternately, a promise which resolves to\n * an Object.\n *\n * Using a function or async function allows the configuration to be resolved at runtime (because\n * the function will be executed at runtime). This is not common, and the capability is included\n * for the sake of flexibility.\n *\n * The Site Configuration File is well-suited to extensibility use cases or component overrides,\n * in that the configuration file can depend on any installed JavaScript module. It is also the\n * preferred way of doing build-time configuration if runtime configuration isn't used by your\n * deployment of the platform.\n *\n * Exporting a config object:\n * ```\n * const siteConfig = {\n * lmsBaseUrl: 'http://localhost:18000'\n * };\n *\n * export default siteConfig;\n * ```\n *\n * Exporting a function that returns an object:\n * ```\n * function getSiteConfig() {\n * return {\n * lmsBaseUrl: 'http://localhost:18000'\n * };\n * }\n * ```\n *\n * Exporting a function that returns a promise that resolves to an object:\n * ```\n * function getAsyncSiteConfig() {\n * return new Promise((resolve, reject) => {\n * resolve({\n * lmsBaseUrl: 'http://localhost:18000'\n * });\n * });\n * }\n *\n * export default getAsyncSiteConfig;\n * ```\n *\n * ##### Initialization Config Handler\n *\n * The configuration document can be extended by\n * applications at run-time using a `config` initialization handler. Please see the Initialization\n * documentation for more information on handlers and initialization phases.\n *\n * ```\n * initialize({\n * handlers: {\n * config: () => {\n * mergeSiteConfig({\n * CUSTOM_VARIABLE: 'custom value',\n * lmsBaseUrl: 'http://localhost:18001' // You can override variables, but this is uncommon.\n * }, 'App config override handler');\n * },\n * },\n * });\n * ```\n *\n * ##### Runtime Configuration\n *\n * Configuration variables can also be supplied using the \"runtime configuration\" method, taking\n * advantage of the Micro-frontend Config API in edx-platform. More information on this API can be\n * found in the ADR which introduced it:\n *\n * https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst\n *\n * The runtime configuration method can be enabled by supplying a runtimeConfigJsonUrl via one of the other\n * two configuration methods above.\n *\n * Runtime configuration is particularly useful if you need to supply different configurations to\n * a single deployment of a micro-frontend, for instance. It is also a perfectly valid alternative\n * to build-time configuration, though it introduces an additional API call to edx-platform on MFE\n * initialization.\n *\n *\n * @module Config\n */\n\nimport isEqual from 'lodash/isEqual';\nimport keyBy from 'lodash/keyBy';\nimport merge from 'lodash/merge';\nimport {\n App,\n AppConfig,\n EnvironmentTypes,\n SiteConfig\n} from '../../types';\nimport { ACTIVE_ROLES_CHANGED, CONFIG_CHANGED } from '../constants';\nimport { publish } from '../subscriptions';\n\nlet siteConfig: SiteConfig = {\n // Required\n siteId: '',\n baseUrl: '',\n siteName: '',\n loginUrl: '',\n logoutUrl: '',\n lmsBaseUrl: '',\n\n // Optional\n environment: EnvironmentTypes.PRODUCTION,\n cmsBaseUrl: '',\n apps: [],\n externalRoutes: [],\n externalLinkUrlOverrides: [],\n runtimeConfigJsonUrl: null,\n theme: {},\n defaultLanguage: 'en',\n supportedLanguages: [],\n accessTokenCookieName: 'edx-jwt-cookie-header-payload',\n csrfTokenApiPath: '/csrf/api/v1/token',\n ignoredErrorRegex: null,\n languagePreferenceCookieName: 'openedx-language-preference',\n refreshAccessTokenApiPath: '/login_refresh',\n userInfoCookieName: 'edx-user-info',\n segmentKey: null,\n};\n\n/**\n * Getter for the application configuration document. This is synchronous and merely returns a\n * reference to an existing object, and is thus safe to call as often as desired.\n *\n * Example:\n *\n * ```\n * import { getSiteConfig } from '@openedx/frontend-base';\n *\n * const {\n * lmsBaseUrl,\n * } = getSiteConfig();\n * ```\n *\n * @returns {SiteConfig}\n */\nexport function getSiteConfig() {\n return siteConfig;\n}\n\n/**\n * Replaces the existing SiteConfig. This is not commonly used, but can be helpful for tests.\n *\n * Example:\n *\n * ```\n * import { setSiteConfig } from '@openedx/frontend-base';\n *\n * setSiteConfig({\n * lmsBaseUrl, // This is overriding the ENTIRE document - this is not merged in!\n * });\n * ```\n *\n * @param newConfig A replacement SiteConfig which will completely override the current SiteConfig.\n */\nexport function setSiteConfig(newSiteConfig: SiteConfig) {\n siteConfig = newSiteConfig;\n publish(CONFIG_CHANGED);\n}\n\ninterface MergeSiteConfigOptions {\n limitAppMergeToConfig?: boolean;\n}\n\n/**\n * Merges additional configuration values into the site config returned by `getSiteConfig`. Will\n * override any values that exist with the same keys.\n *\n * ```\n * mergeSiteConfig({\n * NEW_KEY: 'new value',\n * OTHER_NEW_KEY: 'other new value',\n * });\n *\n * This function uses lodash.merge internally to merge configuration objects\n * which means they will be merged recursively. See https://lodash.com/docs/latest#merge for\n * documentation on the exact behavior.\n *\n * Apps are merged by appId rather than array index. By default, apps in the incoming config\n * that don't exist in the current config will be added.\n *\n * When `limitAppMergeToConfig` is true:\n * - All non-app parts of the config are still merged normally\n * - Only the `config` property of each existing app is merged\n * - Apps in the incoming config that don't exist in the current config are ignored\n *\n * @param {Object} newSiteConfig\n * @param {Object} options\n * @param {boolean} options.limitAppMergeToConfig - Limit app merging to only the config property of existing apps\n */\nexport function mergeSiteConfig(\n newSiteConfig: Partial<SiteConfig>,\n options: MergeSiteConfigOptions = {}\n) {\n const { limitAppMergeToConfig = false } = options;\n const { apps: newApps, ...restOfNewConfig } = newSiteConfig;\n\n /* `merge({}, ...)` deep-clones into the fresh target, so the new `siteConfig`\n is a brand-new reference and the previous one is never mutated. This is what\n lets React consumers detect that CONFIG_CHANGED actually changed something. */\n siteConfig = merge({}, siteConfig, restOfNewConfig);\n\n // if we don't have new apps, we're done\n if (!newApps?.length) {\n publish(CONFIG_CHANGED);\n return;\n }\n\n // if we're doing a full merge, merge the objects\n if (!limitAppMergeToConfig) {\n siteConfig.apps = mergeApps(siteConfig.apps || [], newApps);\n publish(CONFIG_CHANGED);\n return;\n }\n\n // we're doing a config-only merge, if we don't\n // have apps already, we can't update their configs\n if (!siteConfig.apps?.length) {\n publish(CONFIG_CHANGED);\n return;\n }\n\n // handle config-only merging\n siteConfig.apps = mergeApps(siteConfig.apps, newApps, { configOnly: true });\n\n publish(CONFIG_CHANGED);\n}\n\n/*\n * Merge two App[] by appId. Existing apps stay in their original positions\n * (with their pair-merged counterpart from `newApps` substituted in when\n * present); apps in `newApps` not already in `oldApps` append at the end.\n * With `{ configOnly: true }`, no apps are added and apps not appearing in\n * `newApps` pass through unchanged. Per-pair merging is delegated to\n * `mergeApp`.\n */\nfunction mergeApps(\n oldApps: App[],\n newApps: App[],\n options: { configOnly?: boolean } = {},\n): App[] {\n const incomingByAppId = keyBy(newApps, 'appId');\n\n // Phase 1: walk existing apps in their original order, pair-merging any\n // that have a counterpart in newApps.\n const updatedExisting = oldApps.map((oldApp) => {\n const newApp = incomingByAppId[oldApp.appId];\n return newApp ? mergeApp(oldApp, newApp, options) : oldApp;\n });\n\n // configOnly mode never adds apps, so we're done.\n if (options.configOnly) {\n return updatedExisting;\n }\n\n // Phase 2: append apps from newApps that weren't already in oldApps.\n const existingIds = new Set(oldApps.map((a) => a.appId));\n const additions = newApps.filter((a) => !existingIds.has(a.appId));\n return [...updatedExisting, ...additions];\n}\n\n/*\n * Merge a pair of Apps with the same appId. Deep-merges `config` (and, in the\n * full-merge case, `defaultConfig` and `provides`); other fields take `newApp`'s\n * value verbatim.\n * The result is built via `Object.getOwnPropertyDescriptors` so any lazy\n * getters survive: a snapshot via `lodash.merge` or spread would invoke the\n * getter at merge time and freeze its return value, which is typically empty\n * mid-init. Per-field replacement is also the only sensible behavior for the\n * array fields (`slots`/`routes`/`providers`/`externalScripts`), which don't\n * survive element-wise merging anyway.\n */\nfunction mergeApp(\n oldApp: App,\n newApp: App,\n options: { configOnly?: boolean } = {},\n): App {\n // configOnly mode: preserve `oldApp` (identity, slots, defaultConfig, etc.)\n // and deep-merge only `newApp.config` on top. Operator-supplied config can\n // never write into an app's bundled `defaultConfig`.\n if (options.configOnly) {\n if (!newApp.config) {\n return oldApp;\n }\n return cloneAppDescriptors(oldApp, {\n config: merge({}, oldApp.config, newApp.config),\n });\n }\n\n // Full mode: take `newApp` (identity, slots, etc.) and deep-merge\n // `defaultConfig`, `config`, and `provides` from `oldApp`. Other fields take\n // `newApp`'s value verbatim.\n const deepMerged: Record<string, unknown> = {};\n if (oldApp.defaultConfig !== undefined || newApp.defaultConfig !== undefined) {\n deepMerged.defaultConfig = merge({}, oldApp.defaultConfig, newApp.defaultConfig);\n }\n if (oldApp.config !== undefined || newApp.config !== undefined) {\n deepMerged.config = merge({}, oldApp.config, newApp.config);\n }\n if (oldApp.provides !== undefined || newApp.provides !== undefined) {\n deepMerged.provides = merge({}, oldApp.provides, newApp.provides);\n }\n return cloneAppDescriptors(newApp, deepMerged);\n}\n\nfunction cloneAppDescriptors(source: App, overrides: Record<string, unknown>): App {\n const descriptors = Object.getOwnPropertyDescriptors(source);\n for (const [key, value] of Object.entries(overrides)) {\n descriptors[key] = { value, writable: true, enumerable: true, configurable: true };\n }\n return Object.create(Object.getPrototypeOf(source), descriptors) as App;\n}\n\n/* Bundled by app authors via `App.defaultConfig`. Kept separate from\n `appConfigs` so that operator-supplied config can never write into it. */\nconst appDefaultConfigs: Record<string, AppConfig> = {};\n\nconst appConfigs: Record<string, AppConfig> = {};\n\n/**\n * addAppConfigs finds any AppConfig objects in the apps in SiteConfig and makes their config\n * available to be used by Apps via getAppConfig(appId) or useAppConfig() functions. This is\n * used at initialization time to process any AppConfigs bundled with the site.\n */\nexport function addAppConfigs() {\n const { apps } = getSiteConfig();\n if (!apps) return;\n\n for (const app of apps) {\n const { appId, config, defaultConfig } = app;\n if (defaultConfig !== undefined) {\n appDefaultConfigs[appId] = defaultConfig;\n }\n if (config !== undefined) {\n appConfigs[appId] = config;\n }\n }\n\n publish(CONFIG_CHANGED);\n}\n\n/**\n * Resolves an app's configuration, deep merging the three sources in order of\n * increasing precedence:\n *\n * - `App.defaultConfig`, bundled by the app author\n * - `SiteConfig.commonAppConfig`, supplied site-wide by an operator\n * - `App.config`, supplied per-app by an operator\n */\nexport function getAppConfig(id: string) {\n const { commonAppConfig } = getSiteConfig();\n const defaultConfig = appDefaultConfigs[id];\n if (defaultConfig === undefined && commonAppConfig === undefined) {\n return appConfigs[id];\n }\n return merge({}, defaultConfig, commonAppConfig, appConfigs[id]);\n}\n\nexport function mergeAppConfig(id: string, newAppConfig: AppConfig) {\n // Non-mutating: produce a fresh entry so consumers holding a reference to\n // the previous one don't observe the change underneath them.\n appConfigs[id] = merge({}, appConfigs[id], newAppConfig);\n publish(CONFIG_CHANGED);\n}\n\nlet activeRouteRoles: string[] = [];\n\nexport function setActiveRouteRoles(roles: string[]) {\n if (isEqual(activeRouteRoles, roles)) return;\n activeRouteRoles = roles;\n publish(ACTIVE_ROLES_CHANGED);\n}\n\nexport function getActiveRouteRoles() {\n return activeRouteRoles;\n}\n\nconst activeWidgetRoles: Record<string, number> = {};\n\nexport function addActiveWidgetRole(role: string) {\n // Only publish when the role transitions from absent to present.\n const wasPresent = (activeWidgetRoles[role] ?? 0) > 0;\n activeWidgetRoles[role] = (activeWidgetRoles[role] ?? 0) + 1;\n if (!wasPresent) publish(ACTIVE_ROLES_CHANGED);\n}\n\nexport function removeActiveWidgetRole(role: string) {\n if (activeWidgetRoles[role] === undefined) return;\n activeWidgetRoles[role] -= 1;\n if (activeWidgetRoles[role] < 1) {\n delete activeWidgetRoles[role];\n // Only publish when the role transitions from present to absent.\n publish(ACTIVE_ROLES_CHANGED);\n }\n}\n\nexport function getActiveWidgetRoles() {\n return Object.entries(activeWidgetRoles)\n .filter(([, count]: [role: string, count: number]) => count !== undefined && count > 0)\n .map(([role]: [role: string, count: number]) => role);\n}\n\n// Gets all active roles from the route roles and widget roles.\nexport function getActiveRoles() {\n return [...getActiveRouteRoles(), ...getActiveWidgetRoles()];\n}\n\n/**\n * Collects all `provides` entries from registered apps that match the given identifier.\n * This enables inter-app data sharing without frontend-base needing to understand the data shape.\n *\n * @param id - The namespaced provides identifier.\n * @returns An array of provided data from all apps that declared data for this identifier.\n */\nexport function getProvides(id: string): unknown[] {\n const { apps } = getSiteConfig();\n if (!apps) return [];\n\n const results: unknown[] = [];\n for (const app of apps) {\n if (app.provides && app.provides[id] !== undefined) {\n results.push(app.provides[id]);\n }\n }\n return results;\n}\n\n/**\n * Collects and flattens all `provides` entries for the given identifier\n * as strings. Each entry can be a single string or a string array; entries\n * of other types are silently skipped.\n *\n * @param id - The namespaced provides identifier.\n * @returns A flat array of strings from all apps that declared data for this identifier.\n */\nexport function getProvidesAsStrings(id: string): string[] {\n return getProvides(id)\n .filter((data): data is string | string[] => typeof data === 'string' || Array.isArray(data))\n .flat();\n}\n\n/**\n * Get an external link URL based on the URL provided. If the passed in URL is overridden in the\n * `externalLinkUrlOverrides` object, it will return the overridden URL. Otherwise, it will return\n * the provided URL.\n *\n *\n * @param {string} url - The default URL.\n * @returns {string} - The external link URL. Defaults to the input URL if not found in the\n * `externalLinkUrlOverrides` object. If the input URL is invalid, '#' is returned.\n *\n * @example\n * import { getExternalLinkUrl } from '@openedx/frontend-base';\n *\n * <Hyperlink\n * destination={getExternalLinkUrl(data.helpLink)}\n * target=\"_blank\"\n * >\n */\nexport function getExternalLinkUrl(url: string): string {\n // Guard against whitespace-only strings\n if (typeof url !== 'string' || !url.trim()) {\n return '#';\n }\n\n const overriddenLinkUrls = getSiteConfig().externalLinkUrlOverrides ?? {};\n return overriddenLinkUrls[url] ?? url;\n}\n"]}
|
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
* #### Import members from **@openedx/frontend-base**
|
|
3
3
|
* The i18n module relies on react-intl and re-exports all of that package's exports.
|
|
4
4
|
*
|
|
5
|
-
* For each locale we want to support, react-intl needs
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* For each locale we want to support, react-intl needs the translations as an object holding
|
|
6
|
+
* message id / translated string pairs. A locale string and the messages object are passed into
|
|
7
|
+
* the IntlProvider element that wraps your element hierarchy. The locale data used to format
|
|
8
|
+
* numbers, dates, and plurals comes from the runtime's built-in Intl APIs.
|
|
9
9
|
*
|
|
10
10
|
* Note that react-intl has no way of checking if the translations you give it actually have
|
|
11
11
|
* anything to do with the locale you pass it; it will happily use whatever messages object you pass
|
|
12
|
-
* in.
|
|
13
|
-
* correctly installed with addLocaleData, all of your translations will fall back to the default
|
|
14
|
-
* (in our case English), *even if you gave IntlProvider the correct messages object for that
|
|
15
|
-
* locale*.
|
|
12
|
+
* in.
|
|
16
13
|
*
|
|
17
14
|
* Messages are provided to this module via the configureI18n() function below.
|
|
18
15
|
*
|
|
@@ -24,3 +21,4 @@
|
|
|
24
21
|
*/
|
|
25
22
|
export { createIntl, defineMessages, FormattedDate, FormattedMessage, FormattedNumber, FormattedPlural, FormattedRelativeTime, FormattedTime, IntlProvider, useIntl, type IntlConfig, type ResolvedIntlConfig, type IntlShape, } from 'react-intl';
|
|
26
23
|
export { configureI18n, getLocale, getLocalizedLanguageName, getMessages, getPrimaryLanguageSubtag, getSupportedLanguageList, handleRtl, isRtl, LOCALE_CHANGED, LOCALE_TOPIC, mergeMessages, updateLocale, } from './lib';
|
|
24
|
+
export { updateSiteLanguage } from './updateSiteLanguage';
|
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
* #### Import members from **@openedx/frontend-base**
|
|
3
3
|
* The i18n module relies on react-intl and re-exports all of that package's exports.
|
|
4
4
|
*
|
|
5
|
-
* For each locale we want to support, react-intl needs
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* For each locale we want to support, react-intl needs the translations as an object holding
|
|
6
|
+
* message id / translated string pairs. A locale string and the messages object are passed into
|
|
7
|
+
* the IntlProvider element that wraps your element hierarchy. The locale data used to format
|
|
8
|
+
* numbers, dates, and plurals comes from the runtime's built-in Intl APIs.
|
|
9
9
|
*
|
|
10
10
|
* Note that react-intl has no way of checking if the translations you give it actually have
|
|
11
11
|
* anything to do with the locale you pass it; it will happily use whatever messages object you pass
|
|
12
|
-
* in.
|
|
13
|
-
* correctly installed with addLocaleData, all of your translations will fall back to the default
|
|
14
|
-
* (in our case English), *even if you gave IntlProvider the correct messages object for that
|
|
15
|
-
* locale*.
|
|
12
|
+
* in.
|
|
16
13
|
*
|
|
17
14
|
* Messages are provided to this module via the configureI18n() function below.
|
|
18
15
|
*
|
|
@@ -24,4 +21,5 @@
|
|
|
24
21
|
*/
|
|
25
22
|
export { createIntl, defineMessages, FormattedDate, FormattedMessage, FormattedNumber, FormattedPlural, FormattedRelativeTime, FormattedTime, IntlProvider, useIntl, } from 'react-intl';
|
|
26
23
|
export { configureI18n, getLocale, getLocalizedLanguageName, getMessages, getPrimaryLanguageSubtag, getSupportedLanguageList, handleRtl, isRtl, LOCALE_CHANGED, LOCALE_TOPIC, mergeMessages, updateLocale, } from './lib';
|
|
24
|
+
export { updateSiteLanguage } from './updateSiteLanguage';
|
|
27
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../runtime/i18n/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../runtime/i18n/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,OAAO,GAIR,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,SAAS,EACT,wBAAwB,EACxB,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,SAAS,EACT,KAAK,EACL,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,GACb,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["/**\n * #### Import members from **@openedx/frontend-base**\n * The i18n module relies on react-intl and re-exports all of that package's exports.\n *\n * For each locale we want to support, react-intl needs the translations as an object holding\n * message id / translated string pairs. A locale string and the messages object are passed into\n * the IntlProvider element that wraps your element hierarchy. The locale data used to format\n * numbers, dates, and plurals comes from the runtime's built-in Intl APIs.\n *\n * Note that react-intl has no way of checking if the translations you give it actually have\n * anything to do with the locale you pass it; it will happily use whatever messages object you pass\n * in.\n *\n * Messages are provided to this module via the configureI18n() function below.\n *\n *\n * @module Internationalization\n * @see {@link https://github.com/openedx/frontend-base/blob/master/docs/how_tos/i18n.rst}\n * @see {@link https://formatjs.io/docs/react-intl/components/ Intl} for components exported from this module.\n *\n */\n\nexport {\n createIntl,\n defineMessages,\n FormattedDate,\n FormattedMessage,\n FormattedNumber,\n FormattedPlural,\n FormattedRelativeTime,\n FormattedTime,\n IntlProvider,\n useIntl,\n type IntlConfig,\n type ResolvedIntlConfig,\n type IntlShape,\n} from 'react-intl';\n\nexport {\n configureI18n,\n getLocale,\n getLocalizedLanguageName,\n getMessages,\n getPrimaryLanguageSubtag,\n getSupportedLanguageList,\n handleRtl,\n isRtl,\n LOCALE_CHANGED,\n LOCALE_TOPIC,\n mergeMessages,\n updateLocale,\n} from './lib';\n\nexport { updateSiteLanguage } from './updateSiteLanguage';\n"]}
|
|
@@ -25,12 +25,13 @@ export declare function getCookies(): Cookies;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function getPrimaryLanguageSubtag(code: any): any;
|
|
27
27
|
/**
|
|
28
|
-
* Finds the closest supported locale to the one provided.
|
|
28
|
+
* Finds the closest supported locale to the one provided. This is done in three steps:
|
|
29
29
|
*
|
|
30
|
-
* 1. Returning the locale itself if
|
|
31
|
-
*
|
|
30
|
+
* 1. Returning the locale itself if it is the source language, 'en', or if its exact
|
|
31
|
+
* language code is in the loaded messages AND is in the site's supportedLanguages list.
|
|
32
|
+
* 2. Returning the primary language subtag if it meets the same criteria (ar for ar-eg,
|
|
32
33
|
* for instance).
|
|
33
|
-
* 3. Returning '
|
|
34
|
+
* 3. Returning the site's defaultLanguage if neither of the above match.
|
|
34
35
|
*
|
|
35
36
|
* @param {string} locale
|
|
36
37
|
* @returns {string}
|
|
@@ -39,7 +40,7 @@ export declare function getPrimaryLanguageSubtag(code: any): any;
|
|
|
39
40
|
export declare function findSupportedLocale(locale: any): any;
|
|
40
41
|
/**
|
|
41
42
|
* Get the locale from the cookie or, failing that, the browser setting.
|
|
42
|
-
* Gracefully fall back to a more general primary language subtag or to
|
|
43
|
+
* Gracefully fall back to a more general primary language subtag or to default language
|
|
43
44
|
* if we don't support that language.
|
|
44
45
|
*
|
|
45
46
|
* @param {string|undefined} locale If a locale is provided, returns the closest supported locale. Optional.
|
|
@@ -48,12 +49,41 @@ export declare function findSupportedLocale(locale: any): any;
|
|
|
48
49
|
* @memberof module:Internationalization
|
|
49
50
|
*/
|
|
50
51
|
export declare function getLocale(locale?: string): any;
|
|
52
|
+
/**
|
|
53
|
+
* Returns a language's name in that language, capitalized (e.g. 'Deutsch' for 'de').
|
|
54
|
+
*
|
|
55
|
+
* @param {string} locale
|
|
56
|
+
* @returns {string}
|
|
57
|
+
* @throws {Error} If the runtime has no display name for the locale.
|
|
58
|
+
* @memberof module:Internationalization
|
|
59
|
+
*/
|
|
51
60
|
export declare function getLocalizedLanguageName(locale: any): string;
|
|
61
|
+
/**
|
|
62
|
+
* Returns the languages the language menu offers, as { code, name } pairs sorted by code.
|
|
63
|
+
*
|
|
64
|
+
* The list is the locales with loaded messages plus the site's defaultLanguage, filtered by
|
|
65
|
+
* supportedLanguages when that is configured. 'en' is added after the filter, so English is
|
|
66
|
+
* always offered.
|
|
67
|
+
*
|
|
68
|
+
* @returns {{ code: string, name: string }[]}
|
|
69
|
+
* @memberof module:Internationalization
|
|
70
|
+
*/
|
|
52
71
|
export declare function getSupportedLanguageList(): {
|
|
53
72
|
code: string;
|
|
54
73
|
name: string;
|
|
55
74
|
}[];
|
|
56
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Updates the active UI locale and RTL direction.
|
|
77
|
+
*
|
|
78
|
+
* If a locale is provided, the UI is updated to that locale immediately, without
|
|
79
|
+
* waiting for the language preference cookie to be persisted (that is handled
|
|
80
|
+
* separately, e.g. by updateSiteLanguage()). If no locale is provided, the current
|
|
81
|
+
* locale is read from the language preference cookie or browser setting.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} [locale] The locale code to switch to (e.g. 'es-419', 'ar').
|
|
84
|
+
* @memberof module:Internationalization
|
|
85
|
+
*/
|
|
86
|
+
export declare function updateLocale(locale?: string): void;
|
|
57
87
|
/**
|
|
58
88
|
* Returns messages for the provided locale, or the user's preferred locale if no argument is
|
|
59
89
|
* provided.
|
|
@@ -70,8 +100,8 @@ export declare function getMessages(locale?: any): Record<string, string> | Reco
|
|
|
70
100
|
*/
|
|
71
101
|
export declare function isRtl(locale: any): boolean;
|
|
72
102
|
/**
|
|
73
|
-
* Handles applying the RTL stylesheet and "
|
|
74
|
-
*
|
|
103
|
+
* Handles applying the RTL stylesheet, "dir" and "lang" attributes to the html tag
|
|
104
|
+
* based on the current locale.
|
|
75
105
|
*
|
|
76
106
|
* @memberof module:Internationalization
|
|
77
107
|
*/
|
package/dist/runtime/i18n/lib.js
CHANGED
|
@@ -42,7 +42,18 @@ const rtlLocales = [
|
|
|
42
42
|
'yi', // Yiddish
|
|
43
43
|
'yi-us', // Yiddish (United States)
|
|
44
44
|
];
|
|
45
|
+
/**
|
|
46
|
+
* The language the source strings are written in. It never has an entry in `messages`,
|
|
47
|
+
* since its strings come from each message's `defaultMessage`.
|
|
48
|
+
*/
|
|
49
|
+
const SOURCE_LANGUAGE = 'en';
|
|
45
50
|
let messages;
|
|
51
|
+
/**
|
|
52
|
+
* The locale selected during this session via updateLocale(), used to update the UI
|
|
53
|
+
* immediately without waiting for the language preference cookie to be persisted.
|
|
54
|
+
* Cleared on page load (via configureI18n) so the cookie/browser setting takes effect.
|
|
55
|
+
*/
|
|
56
|
+
let currentLocale;
|
|
46
57
|
/**
|
|
47
58
|
* @memberof module:Internationalization
|
|
48
59
|
*/
|
|
@@ -71,12 +82,13 @@ export function getPrimaryLanguageSubtag(code) {
|
|
|
71
82
|
return code.split('-')[0];
|
|
72
83
|
}
|
|
73
84
|
/**
|
|
74
|
-
* Finds the closest supported locale to the one provided.
|
|
85
|
+
* Finds the closest supported locale to the one provided. This is done in three steps:
|
|
75
86
|
*
|
|
76
|
-
* 1. Returning the locale itself if
|
|
77
|
-
*
|
|
87
|
+
* 1. Returning the locale itself if it is the source language, 'en', or if its exact
|
|
88
|
+
* language code is in the loaded messages AND is in the site's supportedLanguages list.
|
|
89
|
+
* 2. Returning the primary language subtag if it meets the same criteria (ar for ar-eg,
|
|
78
90
|
* for instance).
|
|
79
|
-
* 3. Returning '
|
|
91
|
+
* 3. Returning the site's defaultLanguage if neither of the above match.
|
|
80
92
|
*
|
|
81
93
|
* @param {string} locale
|
|
82
94
|
* @returns {string}
|
|
@@ -86,17 +98,29 @@ export function findSupportedLocale(locale) {
|
|
|
86
98
|
if (messages === undefined) {
|
|
87
99
|
throw new Error('findSupportedLocale called before configuring i18n. Call configureI18n with messages first.');
|
|
88
100
|
}
|
|
89
|
-
|
|
101
|
+
const { defaultLanguage = 'en', supportedLanguages = [] } = getSiteConfig();
|
|
102
|
+
const isLocaleSupported = (code) => {
|
|
103
|
+
// The source language is always available, regardless of supportedLanguages.
|
|
104
|
+
if (code === SOURCE_LANGUAGE) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
if (supportedLanguages.length > 0) {
|
|
108
|
+
return supportedLanguages.includes(code) && messages[code] !== undefined;
|
|
109
|
+
}
|
|
110
|
+
return messages[code] !== undefined;
|
|
111
|
+
};
|
|
112
|
+
if (isLocaleSupported(locale)) {
|
|
90
113
|
return locale;
|
|
91
114
|
}
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
const primarySubtag = getPrimaryLanguageSubtag(locale);
|
|
116
|
+
if (isLocaleSupported(primarySubtag)) {
|
|
117
|
+
return primarySubtag;
|
|
94
118
|
}
|
|
95
|
-
return
|
|
119
|
+
return defaultLanguage;
|
|
96
120
|
}
|
|
97
121
|
/**
|
|
98
122
|
* Get the locale from the cookie or, failing that, the browser setting.
|
|
99
|
-
* Gracefully fall back to a more general primary language subtag or to
|
|
123
|
+
* Gracefully fall back to a more general primary language subtag or to default language
|
|
100
124
|
* if we don't support that language.
|
|
101
125
|
*
|
|
102
126
|
* @param {string|undefined} locale If a locale is provided, returns the closest supported locale. Optional.
|
|
@@ -112,7 +136,11 @@ export function getLocale(locale) {
|
|
|
112
136
|
if (locale !== undefined) {
|
|
113
137
|
return findSupportedLocale(locale);
|
|
114
138
|
}
|
|
115
|
-
// 2.
|
|
139
|
+
// 2. Locale selected in-session via updateLocale()
|
|
140
|
+
if (currentLocale !== undefined) {
|
|
141
|
+
return currentLocale;
|
|
142
|
+
}
|
|
143
|
+
// 3. User setting in cookie
|
|
116
144
|
const { languagePreferenceCookieName } = getSiteConfig();
|
|
117
145
|
if (languagePreferenceCookieName) {
|
|
118
146
|
const languagePreference = cookies.get(languagePreferenceCookieName);
|
|
@@ -120,12 +148,20 @@ export function getLocale(locale) {
|
|
|
120
148
|
return findSupportedLocale(languagePreference.toLowerCase());
|
|
121
149
|
}
|
|
122
150
|
}
|
|
123
|
-
//
|
|
151
|
+
// 4. Browser language (default)
|
|
124
152
|
// Note that some browers prefer upper case for the region part of the locale, while others don't.
|
|
125
153
|
// Thus the toLowerCase, for consistency.
|
|
126
154
|
// https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/language
|
|
127
155
|
return findSupportedLocale(globalThis.navigator.language.toLowerCase());
|
|
128
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Returns a language's name in that language, capitalized (e.g. 'Deutsch' for 'de').
|
|
159
|
+
*
|
|
160
|
+
* @param {string} locale
|
|
161
|
+
* @returns {string}
|
|
162
|
+
* @throws {Error} If the runtime has no display name for the locale.
|
|
163
|
+
* @memberof module:Internationalization
|
|
164
|
+
*/
|
|
129
165
|
export function getLocalizedLanguageName(locale) {
|
|
130
166
|
const localizedName = (new Intl.DisplayNames([locale], { type: 'language' })).of(locale);
|
|
131
167
|
if (localizedName === undefined) {
|
|
@@ -133,16 +169,47 @@ export function getLocalizedLanguageName(locale) {
|
|
|
133
169
|
}
|
|
134
170
|
return `${localizedName.charAt(0).toLocaleUpperCase(locale)}${localizedName.slice(1)}`;
|
|
135
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns the languages the language menu offers, as { code, name } pairs sorted by code.
|
|
174
|
+
*
|
|
175
|
+
* The list is the locales with loaded messages plus the site's defaultLanguage, filtered by
|
|
176
|
+
* supportedLanguages when that is configured. 'en' is added after the filter, so English is
|
|
177
|
+
* always offered.
|
|
178
|
+
*
|
|
179
|
+
* @returns {{ code: string, name: string }[]}
|
|
180
|
+
* @memberof module:Internationalization
|
|
181
|
+
*/
|
|
136
182
|
export function getSupportedLanguageList() {
|
|
137
|
-
const
|
|
138
|
-
locales.
|
|
183
|
+
const { defaultLanguage = 'en', supportedLanguages = [] } = getSiteConfig();
|
|
184
|
+
let locales = Array.from(new Set([...Object.keys(messages), defaultLanguage]));
|
|
185
|
+
if (supportedLanguages.length > 0) {
|
|
186
|
+
locales = locales.filter((locale) => supportedLanguages.includes(locale));
|
|
187
|
+
}
|
|
188
|
+
// The source language is always available, regardless of supportedLanguages.
|
|
189
|
+
if (!locales.includes(SOURCE_LANGUAGE)) {
|
|
190
|
+
locales.push(SOURCE_LANGUAGE);
|
|
191
|
+
}
|
|
139
192
|
locales.sort();
|
|
140
193
|
return locales.map((locale) => ({
|
|
141
194
|
code: locale,
|
|
142
195
|
name: getLocalizedLanguageName(locale),
|
|
143
196
|
}));
|
|
144
197
|
}
|
|
145
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Updates the active UI locale and RTL direction.
|
|
200
|
+
*
|
|
201
|
+
* If a locale is provided, the UI is updated to that locale immediately, without
|
|
202
|
+
* waiting for the language preference cookie to be persisted (that is handled
|
|
203
|
+
* separately, e.g. by updateSiteLanguage()). If no locale is provided, the current
|
|
204
|
+
* locale is read from the language preference cookie or browser setting.
|
|
205
|
+
*
|
|
206
|
+
* @param {string} [locale] The locale code to switch to (e.g. 'es-419', 'ar').
|
|
207
|
+
* @memberof module:Internationalization
|
|
208
|
+
*/
|
|
209
|
+
export function updateLocale(locale) {
|
|
210
|
+
if (locale !== undefined) {
|
|
211
|
+
currentLocale = findSupportedLocale(locale);
|
|
212
|
+
}
|
|
146
213
|
handleRtl();
|
|
147
214
|
publish(LOCALE_CHANGED);
|
|
148
215
|
}
|
|
@@ -169,18 +236,16 @@ export function isRtl(locale) {
|
|
|
169
236
|
return rtlLocales.includes(locale);
|
|
170
237
|
}
|
|
171
238
|
/**
|
|
172
|
-
* Handles applying the RTL stylesheet and "
|
|
173
|
-
*
|
|
239
|
+
* Handles applying the RTL stylesheet, "dir" and "lang" attributes to the html tag
|
|
240
|
+
* based on the current locale.
|
|
174
241
|
*
|
|
175
242
|
* @memberof module:Internationalization
|
|
176
243
|
*/
|
|
177
244
|
export function handleRtl() {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
globalThis.document.getElementsByTagName('html')[0].setAttribute('dir', 'ltr');
|
|
183
|
-
}
|
|
245
|
+
const locale = getLocale();
|
|
246
|
+
const htmlElement = globalThis.document.getElementsByTagName('html')[0];
|
|
247
|
+
htmlElement.setAttribute('lang', locale);
|
|
248
|
+
htmlElement.setAttribute('dir', isRtl(locale) ? 'rtl' : 'ltr');
|
|
184
249
|
}
|
|
185
250
|
/**
|
|
186
251
|
*
|
|
@@ -206,6 +271,7 @@ export function mergeMessages(newMessages = {}) {
|
|
|
206
271
|
*/
|
|
207
272
|
export function configureI18n(options) {
|
|
208
273
|
messages = Array.isArray(options.messages) ? merge({}, ...options.messages) : options.messages;
|
|
274
|
+
currentLocale = undefined;
|
|
209
275
|
handleRtl();
|
|
210
276
|
}
|
|
211
277
|
//# sourceMappingURL=lib.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../../runtime/i18n/lib.ts"],"names":[],"mappings":"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","sourcesContent":["import merge from 'lodash/merge';\nimport { MessageFormatElement } from 'react-intl';\nimport Cookies from 'universal-cookie';\n\nimport { LocalizedMessages } from '../../types';\nimport { getSiteConfig } from '../config';\nimport { publish } from '../subscriptions';\n\nconst cookies = new Cookies();\n\n// This list is based on https://help.smartling.com/hc/en-us/articles/1260802028830-Right-to-left-RTL-Languages\n// There are very few resources available online outlining the locale codes for RTL languages;\n// If this list is inaccurate, we should change it.\nconst rtlLocales = [\n 'ar', // Arabic (International)\n 'ar-ae', // Arabic (United Arab Emirates)\n 'ar-bh', // Arabic (Bahrain)\n 'ar-dj', // Arabic (Djibouti)\n 'ar-dz', // Arabic (Algeria)\n 'ar-eg', // Arabic (Egypt)\n 'ar-iq', // Arabic (Iraq)\n 'ar-jo', // Arabic (Jordan)\n 'ar-kw', // Arabic (Kuwait)\n 'ar-lb', // Arabic (Lebanon)\n 'ar-ly', // Arabic (Libya)\n 'ar-ma', // Arabic (Morocco)\n 'ar-om', // Arabic (Oman)\n 'ar-qa', // Arabic (Qatar)\n 'ar-sa', // Arabic (Saudi Arabia)\n 'ar-sd', // Arabic (Sudan)\n 'ar-sy', // Arabic (Syria)\n 'ar-tn', // Arabic (Tunisia)\n 'ar-ye', // Arabic (Yemen)\n 'fa', // Persian\n 'fa-af', // Dari/Persian (Afghanistan)\n 'fa-ir', // Persian (Iran)\n 'he', // Hebrew (he)\n 'he-il', // Hebrew\n 'iw', // Hebrew (iw)\n 'kd', // Kurdish (Sorani) RTL\n 'pk-pk', // Panjabi-Shahmuki (Pakistan)\n 'ps', // Pushto; Pashto\n 'ug', // Uighur; Uyghur\n 'ur', // Urdu\n 'ur-in', // Urdu (India)\n 'ur-pk', // Urdu (Pakistan)\n 'yi', // Yiddish\n 'yi-us', // Yiddish (United States)\n];\n\nlet messages: Record<string, Record<string, string> | Record<string, MessageFormatElement[]> | undefined>;\n\n/**\n * @memberof module:Internationalization\n */\nexport const LOCALE_TOPIC = 'LOCALE';\n\n/**\n * @memberof module:Internationalization\n */\nexport const LOCALE_CHANGED = `${LOCALE_TOPIC}.CHANGED`;\n\n/**\n *\n * @memberof module:Internationalization\n * @returns {Cookies}\n */\nexport function getCookies() {\n return cookies;\n}\n\n/**\n * Some of our dependencies function on primary language subtags, rather than full locales.\n * This function strips a locale down to that first subtag. Depending on the code, this\n * may be 2 or more characters.\n *\n * @param {string} code\n * @memberof module:Internationalization\n */\nexport function getPrimaryLanguageSubtag(code) {\n return code.split('-')[0];\n}\n\n/**\n * Finds the closest supported locale to the one provided. This is done in three steps:\n *\n * 1. Returning the locale itself if its exact language code is supported.\n * 2. Returning the primary language subtag of the language code if it is supported (ar for ar-eg,\n * for instance).\n * 3. Returning 'en' if neither of the above produce a supported locale.\n *\n * @param {string} locale\n * @returns {string}\n * @memberof module:Internationalization\n */\nexport function findSupportedLocale(locale) {\n if (messages === undefined) {\n throw new Error('findSupportedLocale called before configuring i18n. Call configureI18n with messages first.');\n }\n\n if (messages[locale] !== undefined) {\n return locale;\n }\n\n if (messages[getPrimaryLanguageSubtag(locale)] !== undefined) {\n return getPrimaryLanguageSubtag(locale);\n }\n\n return 'en';\n}\n\n/**\n * Get the locale from the cookie or, failing that, the browser setting.\n * Gracefully fall back to a more general primary language subtag or to English (en)\n * if we don't support that language.\n *\n * @param {string|undefined} locale If a locale is provided, returns the closest supported locale. Optional.\n * @throws An error if i18n has not yet been configured.\n * @returns {string}\n * @memberof module:Internationalization\n */\nexport function getLocale(locale?: string) {\n if (messages === null) {\n throw new Error('getLocale called before configuring i18n. Call configureI18n with messages first.');\n }\n\n // 1. Explicit application request\n if (locale !== undefined) {\n return findSupportedLocale(locale);\n }\n // 2. User setting in cookie\n\n const { languagePreferenceCookieName } = getSiteConfig();\n if (languagePreferenceCookieName) {\n const languagePreference = cookies.get(languagePreferenceCookieName);\n if (languagePreference) {\n return findSupportedLocale(languagePreference.toLowerCase());\n }\n }\n\n // 3. Browser language (default)\n // Note that some browers prefer upper case for the region part of the locale, while others don't.\n // Thus the toLowerCase, for consistency.\n // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/language\n return findSupportedLocale(globalThis.navigator.language.toLowerCase());\n}\n\nexport function getLocalizedLanguageName(locale) {\n const localizedName = (new Intl.DisplayNames([locale], { type: 'language' })).of(locale);\n\n if (localizedName === undefined) {\n throw new Error(`Unsupported locale: ${locale}`);\n }\n\n return `${localizedName.charAt(0).toLocaleUpperCase(locale)}${localizedName.slice(1)}`;\n}\n\nexport function getSupportedLanguageList() {\n const locales = Object.keys(messages);\n locales.push('en'); // 'en' is not in the messages object because it's the default.\n locales.sort();\n\n return locales.map((locale) => ({\n code: locale,\n name: getLocalizedLanguageName(locale),\n }));\n}\n\nexport function updateLocale() {\n handleRtl();\n publish(LOCALE_CHANGED);\n}\n\n/**\n * Returns messages for the provided locale, or the user's preferred locale if no argument is\n * provided.\n *\n * @param {string} [locale=getLocale()]\n * @memberof module:Internationalization\n */\nexport function getMessages(locale = getLocale()) {\n if (messages === undefined) {\n throw new Error('getMessages called before configuring i18n. Call configureI18n with messages first.');\n }\n\n return messages[locale];\n}\n\n/**\n * Determines if the provided locale is a right-to-left language.\n *\n * @param {string} locale\n * @memberof module:Internationalization\n */\nexport function isRtl(locale) {\n return rtlLocales.includes(locale);\n}\n\n/**\n * Handles applying the RTL stylesheet and \"dir=rtl\" attribute to the html tag if the current locale\n * is a RTL language.\n *\n * @memberof module:Internationalization\n */\nexport function handleRtl() {\n if (isRtl(getLocale())) {\n globalThis.document.getElementsByTagName('html')[0].setAttribute('dir', 'rtl');\n } else {\n globalThis.document.getElementsByTagName('html')[0].setAttribute('dir', 'ltr');\n }\n}\n\n/**\n *\n *\n * @param {Object} newMessages\n * @returns {Object}\n * @memberof module:Internationalization\n */\nexport function mergeMessages(newMessages = {}) {\n const msgs = Array.isArray(newMessages) ? merge({}, ...newMessages) : newMessages;\n messages = merge(messages, msgs);\n\n return messages;\n}\n\ninterface ConfigureI18nOptions {\n messages: LocalizedMessages[] | LocalizedMessages;\n}\n\n/**\n * Configures the i18n library with messages for your application.\n *\n * Logs a warning if it detects a locale it doesn't expect (as defined by the supportedLocales list\n * above), or if an expected locale is not provided.\n *\n * @param {Object} options\n * @param {Object} options.messages\n * @memberof module:Internationalization\n */\nexport function configureI18n(options: ConfigureI18nOptions) {\n messages = Array.isArray(options.messages) ? merge({}, ...options.messages) : options.messages;\n\n handleRtl();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../../runtime/i18n/lib.ts"],"names":[],"mappings":"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","sourcesContent":["import merge from 'lodash/merge';\nimport { MessageFormatElement } from 'react-intl';\nimport Cookies from 'universal-cookie';\n\nimport { LocalizedMessages } from '../../types';\nimport { getSiteConfig } from '../config';\nimport { publish } from '../subscriptions';\n\nconst cookies = new Cookies();\n\n// This list is based on https://help.smartling.com/hc/en-us/articles/1260802028830-Right-to-left-RTL-Languages\n// There are very few resources available online outlining the locale codes for RTL languages;\n// If this list is inaccurate, we should change it.\nconst rtlLocales = [\n 'ar', // Arabic (International)\n 'ar-ae', // Arabic (United Arab Emirates)\n 'ar-bh', // Arabic (Bahrain)\n 'ar-dj', // Arabic (Djibouti)\n 'ar-dz', // Arabic (Algeria)\n 'ar-eg', // Arabic (Egypt)\n 'ar-iq', // Arabic (Iraq)\n 'ar-jo', // Arabic (Jordan)\n 'ar-kw', // Arabic (Kuwait)\n 'ar-lb', // Arabic (Lebanon)\n 'ar-ly', // Arabic (Libya)\n 'ar-ma', // Arabic (Morocco)\n 'ar-om', // Arabic (Oman)\n 'ar-qa', // Arabic (Qatar)\n 'ar-sa', // Arabic (Saudi Arabia)\n 'ar-sd', // Arabic (Sudan)\n 'ar-sy', // Arabic (Syria)\n 'ar-tn', // Arabic (Tunisia)\n 'ar-ye', // Arabic (Yemen)\n 'fa', // Persian\n 'fa-af', // Dari/Persian (Afghanistan)\n 'fa-ir', // Persian (Iran)\n 'he', // Hebrew (he)\n 'he-il', // Hebrew\n 'iw', // Hebrew (iw)\n 'kd', // Kurdish (Sorani) RTL\n 'pk-pk', // Panjabi-Shahmuki (Pakistan)\n 'ps', // Pushto; Pashto\n 'ug', // Uighur; Uyghur\n 'ur', // Urdu\n 'ur-in', // Urdu (India)\n 'ur-pk', // Urdu (Pakistan)\n 'yi', // Yiddish\n 'yi-us', // Yiddish (United States)\n];\n\n/**\n * The language the source strings are written in. It never has an entry in `messages`,\n * since its strings come from each message's `defaultMessage`.\n */\nconst SOURCE_LANGUAGE = 'en';\n\nlet messages: Record<string, Record<string, string> | Record<string, MessageFormatElement[]> | undefined>;\n\n/**\n * The locale selected during this session via updateLocale(), used to update the UI\n * immediately without waiting for the language preference cookie to be persisted.\n * Cleared on page load (via configureI18n) so the cookie/browser setting takes effect.\n */\nlet currentLocale: string | undefined;\n\n/**\n * @memberof module:Internationalization\n */\nexport const LOCALE_TOPIC = 'LOCALE';\n\n/**\n * @memberof module:Internationalization\n */\nexport const LOCALE_CHANGED = `${LOCALE_TOPIC}.CHANGED`;\n\n/**\n *\n * @memberof module:Internationalization\n * @returns {Cookies}\n */\nexport function getCookies() {\n return cookies;\n}\n\n/**\n * Some of our dependencies function on primary language subtags, rather than full locales.\n * This function strips a locale down to that first subtag. Depending on the code, this\n * may be 2 or more characters.\n *\n * @param {string} code\n * @memberof module:Internationalization\n */\nexport function getPrimaryLanguageSubtag(code) {\n return code.split('-')[0];\n}\n\n/**\n * Finds the closest supported locale to the one provided. This is done in three steps:\n *\n * 1. Returning the locale itself if it is the source language, 'en', or if its exact\n * language code is in the loaded messages AND is in the site's supportedLanguages list.\n * 2. Returning the primary language subtag if it meets the same criteria (ar for ar-eg,\n * for instance).\n * 3. Returning the site's defaultLanguage if neither of the above match.\n *\n * @param {string} locale\n * @returns {string}\n * @memberof module:Internationalization\n */\nexport function findSupportedLocale(locale) {\n if (messages === undefined) {\n throw new Error('findSupportedLocale called before configuring i18n. Call configureI18n with messages first.');\n }\n\n const { defaultLanguage = 'en', supportedLanguages = [] } = getSiteConfig();\n\n const isLocaleSupported = (code) => {\n // The source language is always available, regardless of supportedLanguages.\n if (code === SOURCE_LANGUAGE) {\n return true;\n }\n\n if (supportedLanguages.length > 0) {\n return supportedLanguages.includes(code) && messages[code] !== undefined;\n }\n return messages[code] !== undefined;\n };\n\n if (isLocaleSupported(locale)) {\n return locale;\n }\n\n const primarySubtag = getPrimaryLanguageSubtag(locale);\n if (isLocaleSupported(primarySubtag)) {\n return primarySubtag;\n }\n\n return defaultLanguage;\n}\n\n/**\n * Get the locale from the cookie or, failing that, the browser setting.\n * Gracefully fall back to a more general primary language subtag or to default language\n * if we don't support that language.\n *\n * @param {string|undefined} locale If a locale is provided, returns the closest supported locale. Optional.\n * @throws An error if i18n has not yet been configured.\n * @returns {string}\n * @memberof module:Internationalization\n */\nexport function getLocale(locale?: string) {\n if (messages === null) {\n throw new Error('getLocale called before configuring i18n. Call configureI18n with messages first.');\n }\n\n // 1. Explicit application request\n if (locale !== undefined) {\n return findSupportedLocale(locale);\n }\n // 2. Locale selected in-session via updateLocale()\n if (currentLocale !== undefined) {\n return currentLocale;\n }\n // 3. User setting in cookie\n\n const { languagePreferenceCookieName } = getSiteConfig();\n if (languagePreferenceCookieName) {\n const languagePreference = cookies.get(languagePreferenceCookieName);\n if (languagePreference) {\n return findSupportedLocale(languagePreference.toLowerCase());\n }\n }\n\n // 4. Browser language (default)\n // Note that some browers prefer upper case for the region part of the locale, while others don't.\n // Thus the toLowerCase, for consistency.\n // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/language\n return findSupportedLocale(globalThis.navigator.language.toLowerCase());\n}\n\n/**\n * Returns a language's name in that language, capitalized (e.g. 'Deutsch' for 'de').\n *\n * @param {string} locale\n * @returns {string}\n * @throws {Error} If the runtime has no display name for the locale.\n * @memberof module:Internationalization\n */\nexport function getLocalizedLanguageName(locale) {\n const localizedName = (new Intl.DisplayNames([locale], { type: 'language' })).of(locale);\n\n if (localizedName === undefined) {\n throw new Error(`Unsupported locale: ${locale}`);\n }\n\n return `${localizedName.charAt(0).toLocaleUpperCase(locale)}${localizedName.slice(1)}`;\n}\n\n/**\n * Returns the languages the language menu offers, as { code, name } pairs sorted by code.\n *\n * The list is the locales with loaded messages plus the site's defaultLanguage, filtered by\n * supportedLanguages when that is configured. 'en' is added after the filter, so English is\n * always offered.\n *\n * @returns {{ code: string, name: string }[]}\n * @memberof module:Internationalization\n */\nexport function getSupportedLanguageList() {\n const { defaultLanguage = 'en', supportedLanguages = [] } = getSiteConfig();\n\n let locales = Array.from(new Set([...Object.keys(messages), defaultLanguage]));\n\n if (supportedLanguages.length > 0) {\n locales = locales.filter((locale) => supportedLanguages.includes(locale));\n }\n\n // The source language is always available, regardless of supportedLanguages.\n if (!locales.includes(SOURCE_LANGUAGE)) {\n locales.push(SOURCE_LANGUAGE);\n }\n\n locales.sort();\n\n return locales.map((locale) => ({\n code: locale,\n name: getLocalizedLanguageName(locale),\n }));\n}\n\n/**\n * Updates the active UI locale and RTL direction.\n *\n * If a locale is provided, the UI is updated to that locale immediately, without\n * waiting for the language preference cookie to be persisted (that is handled\n * separately, e.g. by updateSiteLanguage()). If no locale is provided, the current\n * locale is read from the language preference cookie or browser setting.\n *\n * @param {string} [locale] The locale code to switch to (e.g. 'es-419', 'ar').\n * @memberof module:Internationalization\n */\nexport function updateLocale(locale?: string) {\n if (locale !== undefined) {\n currentLocale = findSupportedLocale(locale);\n }\n handleRtl();\n publish(LOCALE_CHANGED);\n}\n\n/**\n * Returns messages for the provided locale, or the user's preferred locale if no argument is\n * provided.\n *\n * @param {string} [locale=getLocale()]\n * @memberof module:Internationalization\n */\nexport function getMessages(locale = getLocale()) {\n if (messages === undefined) {\n throw new Error('getMessages called before configuring i18n. Call configureI18n with messages first.');\n }\n\n return messages[locale];\n}\n\n/**\n * Determines if the provided locale is a right-to-left language.\n *\n * @param {string} locale\n * @memberof module:Internationalization\n */\nexport function isRtl(locale) {\n return rtlLocales.includes(locale);\n}\n\n/**\n * Handles applying the RTL stylesheet, \"dir\" and \"lang\" attributes to the html tag\n * based on the current locale.\n *\n * @memberof module:Internationalization\n */\nexport function handleRtl() {\n const locale = getLocale();\n const htmlElement = globalThis.document.getElementsByTagName('html')[0];\n htmlElement.setAttribute('lang', locale);\n htmlElement.setAttribute('dir', isRtl(locale) ? 'rtl' : 'ltr');\n}\n\n/**\n *\n *\n * @param {Object} newMessages\n * @returns {Object}\n * @memberof module:Internationalization\n */\nexport function mergeMessages(newMessages = {}) {\n const msgs = Array.isArray(newMessages) ? merge({}, ...newMessages) : newMessages;\n messages = merge(messages, msgs);\n\n return messages;\n}\n\ninterface ConfigureI18nOptions {\n messages: LocalizedMessages[] | LocalizedMessages;\n}\n\n/**\n * Configures the i18n library with messages for your application.\n *\n * Logs a warning if it detects a locale it doesn't expect (as defined by the supportedLocales list\n * above), or if an expected locale is not provided.\n *\n * @param {Object} options\n * @param {Object} options.messages\n * @memberof module:Internationalization\n */\nexport function configureI18n(options: ConfigureI18nOptions) {\n messages = Array.isArray(options.messages) ? merge({}, ...options.messages) : options.messages;\n currentLocale = undefined;\n\n handleRtl();\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Changes the user's site language. This is the supported way to switch languages.
|
|
3
|
+
*
|
|
4
|
+
* - Updates the UI locale immediately via updateLocale(), so the change is reflected
|
|
5
|
+
* without waiting for the network requests to complete.
|
|
6
|
+
* - For authenticated users, persists the preference to the LMS API.
|
|
7
|
+
* - For all users (authenticated and anonymous), sets the language cookie via the LMS language preference endpoint.
|
|
8
|
+
*
|
|
9
|
+
* Both requests are attempted regardless of whether the other one fails, so a failed
|
|
10
|
+
* preference save doesn't prevent the session cookie from being set, and vice versa.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} locale The locale code to switch to (e.g. 'es-419', 'ar').
|
|
13
|
+
* @returns {Promise<void>} Resolves when the switch is complete. Rejects with an
|
|
14
|
+
* AggregateError of the failures if any request fails.
|
|
15
|
+
* @memberof module:Internationalization
|
|
16
|
+
*/
|
|
17
|
+
export declare function updateSiteLanguage(locale: string): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { getAuthenticatedHttpClient, getAuthenticatedUser, } from '../auth';
|
|
11
|
+
import { getSiteConfig } from '../config';
|
|
12
|
+
import { updateLocale } from './lib';
|
|
13
|
+
/**
|
|
14
|
+
* Changes the user's site language. This is the supported way to switch languages.
|
|
15
|
+
*
|
|
16
|
+
* - Updates the UI locale immediately via updateLocale(), so the change is reflected
|
|
17
|
+
* without waiting for the network requests to complete.
|
|
18
|
+
* - For authenticated users, persists the preference to the LMS API.
|
|
19
|
+
* - For all users (authenticated and anonymous), sets the language cookie via the LMS language preference endpoint.
|
|
20
|
+
*
|
|
21
|
+
* Both requests are attempted regardless of whether the other one fails, so a failed
|
|
22
|
+
* preference save doesn't prevent the session cookie from being set, and vice versa.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} locale The locale code to switch to (e.g. 'es-419', 'ar').
|
|
25
|
+
* @returns {Promise<void>} Resolves when the switch is complete. Rejects with an
|
|
26
|
+
* AggregateError of the failures if any request fails.
|
|
27
|
+
* @memberof module:Internationalization
|
|
28
|
+
*/
|
|
29
|
+
export function updateSiteLanguage(locale) {
|
|
30
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
+
const user = getAuthenticatedUser();
|
|
32
|
+
// Update the UI locale and RTL direction immediately, before waiting on any
|
|
33
|
+
// network requests. This ensures that the UI reflects the change without delay.
|
|
34
|
+
updateLocale(locale);
|
|
35
|
+
const requests = [setSessionLanguage(locale)];
|
|
36
|
+
// Save the preference for authenticated users.
|
|
37
|
+
if (user !== null) {
|
|
38
|
+
requests.push(patchUserPreferences(user.username, locale));
|
|
39
|
+
}
|
|
40
|
+
const results = yield Promise.allSettled(requests);
|
|
41
|
+
const failures = results
|
|
42
|
+
.filter((result) => result.status === 'rejected')
|
|
43
|
+
.map((result) => result.reason);
|
|
44
|
+
if (failures.length > 0) {
|
|
45
|
+
throw new AggregateError(failures, `Failed to persist the site language '${locale}'.`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Updates user language preferences via the preferences API.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} username - The username of the authenticated user.
|
|
53
|
+
* @param {string} locale - The selected language locale code (e.g., 'en', 'es-419', 'ar', 'de-de').
|
|
54
|
+
* Should be a valid ISO language code supported by the platform. For reference:
|
|
55
|
+
* https://github.com/openedx/openedx-platform/blob/master/openedx/envs/common.py#L231
|
|
56
|
+
* @returns {Promise} - A promise that resolves when the API call completes successfully,
|
|
57
|
+
* or rejects if there's an error with the request.
|
|
58
|
+
*/
|
|
59
|
+
function patchUserPreferences(username, locale) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
const { lmsBaseUrl } = getSiteConfig();
|
|
62
|
+
yield getAuthenticatedHttpClient().patch(`${lmsBaseUrl}/api/user/v1/preferences/${username}`, {
|
|
63
|
+
'pref-lang': locale,
|
|
64
|
+
}, {
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/merge-patch+json',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Sets the language for the current session using the lang preference endpoint.
|
|
73
|
+
*
|
|
74
|
+
* This function sends a PATCH request to the LMS update_language endpoint to change
|
|
75
|
+
* the language for the current user session.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} locale - The selected language locale code (e.g., 'en', 'es-419', 'ar', 'de-de').
|
|
78
|
+
* Should be a valid ISO language code supported by the platform. For reference:
|
|
79
|
+
* https://github.com/openedx/openedx-platform/blob/master/openedx/envs/common.py#L231
|
|
80
|
+
* @returns {Promise} - A promise that resolves when the API call completes successfully,
|
|
81
|
+
* or rejects if there's an error with the request.
|
|
82
|
+
*/
|
|
83
|
+
function setSessionLanguage(locale) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const { lmsBaseUrl } = getSiteConfig();
|
|
86
|
+
// Use the authenticated HTTP client to ensure that the request includes the CSRF token.
|
|
87
|
+
// Works for both authenticated and anonymous users, since the endpoint is public.
|
|
88
|
+
yield getAuthenticatedHttpClient().patch(`${lmsBaseUrl}/lang_pref/update_language`, { 'pref-lang': locale }, { isPublic: true });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=updateSiteLanguage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updateSiteLanguage.js","sourceRoot":"","sources":["../../../runtime/i18n/updateSiteLanguage.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAgB,kBAAkB,CAAC,MAAc;;QACrD,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QAEpC,4EAA4E;QAC5E,gFAAgF;QAChF,YAAY,CAAC,MAAM,CAAC,CAAC;QAErB,MAAM,QAAQ,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;QAE9C,+CAA+C;QAC/C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,OAAO;aACrB,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC;aAChD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAE,MAAgC,CAAC,MAAM,CAAC,CAAC;QAE7D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,wCAAwC,MAAM,IAAI,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;CAAA;AAED;;;;;;;;;GASG;AACH,SAAe,oBAAoB,CAAC,QAAgB,EAAE,MAAc;;QAClE,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,CAAC;QACvC,MAAM,0BAA0B,EAAE,CAAC,KAAK,CACtC,GAAG,UAAU,4BAA4B,QAAQ,EAAE,EACnD;YACE,WAAW,EAAE,MAAM;SACpB,EACD;YACE,OAAO,EAAE;gBACP,cAAc,EAAE,8BAA8B;aAC/C;SACF,CACF,CAAC;IACJ,CAAC;CAAA;AAED;;;;;;;;;;;GAWG;AACH,SAAe,kBAAkB,CAAC,MAAc;;QAC9C,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,CAAC;QAEvC,wFAAwF;QACxF,kFAAkF;QAClF,MAAM,0BAA0B,EAAE,CAAC,KAAK,CACtC,GAAG,UAAU,4BAA4B,EACzC,EAAE,WAAW,EAAE,MAAM,EAAE,EACvB,EAAE,QAAQ,EAAE,IAAI,EAAE,CACnB,CAAC;IACJ,CAAC;CAAA","sourcesContent":["import {\n getAuthenticatedHttpClient,\n getAuthenticatedUser,\n} from '../auth';\nimport { getSiteConfig } from '../config';\nimport { updateLocale } from './lib';\n\n/**\n * Changes the user's site language. This is the supported way to switch languages.\n *\n * - Updates the UI locale immediately via updateLocale(), so the change is reflected\n * without waiting for the network requests to complete.\n * - For authenticated users, persists the preference to the LMS API.\n * - For all users (authenticated and anonymous), sets the language cookie via the LMS language preference endpoint.\n *\n * Both requests are attempted regardless of whether the other one fails, so a failed\n * preference save doesn't prevent the session cookie from being set, and vice versa.\n *\n * @param {string} locale The locale code to switch to (e.g. 'es-419', 'ar').\n * @returns {Promise<void>} Resolves when the switch is complete. Rejects with an\n * AggregateError of the failures if any request fails.\n * @memberof module:Internationalization\n */\nexport async function updateSiteLanguage(locale: string): Promise<void> {\n const user = getAuthenticatedUser();\n\n // Update the UI locale and RTL direction immediately, before waiting on any\n // network requests. This ensures that the UI reflects the change without delay.\n updateLocale(locale);\n\n const requests = [setSessionLanguage(locale)];\n\n // Save the preference for authenticated users.\n if (user !== null) {\n requests.push(patchUserPreferences(user.username, locale));\n }\n\n const results = await Promise.allSettled(requests);\n const failures = results\n .filter((result) => result.status === 'rejected')\n .map((result) => (result as PromiseRejectedResult).reason);\n\n if (failures.length > 0) {\n throw new AggregateError(failures, `Failed to persist the site language '${locale}'.`);\n }\n}\n\n/**\n * Updates user language preferences via the preferences API.\n *\n * @param {string} username - The username of the authenticated user.\n * @param {string} locale - The selected language locale code (e.g., 'en', 'es-419', 'ar', 'de-de').\n * Should be a valid ISO language code supported by the platform. For reference:\n * https://github.com/openedx/openedx-platform/blob/master/openedx/envs/common.py#L231\n * @returns {Promise} - A promise that resolves when the API call completes successfully,\n * or rejects if there's an error with the request.\n */\nasync function patchUserPreferences(username: string, locale: string) {\n const { lmsBaseUrl } = getSiteConfig();\n await getAuthenticatedHttpClient().patch(\n `${lmsBaseUrl}/api/user/v1/preferences/${username}`,\n {\n 'pref-lang': locale,\n },\n {\n headers: {\n 'Content-Type': 'application/merge-patch+json',\n },\n },\n );\n}\n\n/**\n * Sets the language for the current session using the lang preference endpoint.\n *\n * This function sends a PATCH request to the LMS update_language endpoint to change\n * the language for the current user session.\n *\n * @param {string} locale - The selected language locale code (e.g., 'en', 'es-419', 'ar', 'de-de').\n * Should be a valid ISO language code supported by the platform. For reference:\n * https://github.com/openedx/openedx-platform/blob/master/openedx/envs/common.py#L231\n * @returns {Promise} - A promise that resolves when the API call completes successfully,\n * or rejects if there's an error with the request.\n */\nasync function setSessionLanguage(locale: string) {\n const { lmsBaseUrl } = getSiteConfig();\n\n // Use the authenticated HTTP client to ensure that the request includes the CSRF token.\n // Works for both authenticated and anonymous users, since the endpoint is public.\n await getAuthenticatedHttpClient().patch(\n `${lmsBaseUrl}/lang_pref/update_language`,\n { 'pref-lang': locale },\n { isPublic: true },\n );\n}\n"]}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { configureAnalytics, getAnalyticsService, identifyAnonymousUser, identif
|
|
|
2
2
|
export { AUTHENTICATED_USER_CHANGED, AUTHENTICATED_USER_TOPIC, AxiosJwtAuthService, configureAuth, ensureAuthenticatedUser, fetchAuthenticatedUser, getAuthenticatedHttpClient, getAuthenticatedUser, getAuthService, getHttpClient, getLoginRedirectUrl, getLogoutRedirectUrl, hydrateAuthenticatedUser, MockAuthService, redirectToLogin, redirectToLogout, setAuthenticatedUser } from './auth';
|
|
3
3
|
export { getSiteConfig, setSiteConfig, mergeSiteConfig, addAppConfigs, getAppConfig, mergeAppConfig, setActiveRouteRoles, getActiveRouteRoles, addActiveWidgetRole, removeActiveWidgetRole, getActiveWidgetRoles, getActiveRoles, getExternalLinkUrl, getProvides, getProvidesAsStrings } from './config';
|
|
4
4
|
export * from './constants';
|
|
5
|
-
export { configureI18n, createIntl, defineMessages, FormattedDate, FormattedMessage, FormattedNumber, FormattedPlural, FormattedRelativeTime, FormattedTime, getLocale, getLocalizedLanguageName, getMessages, getPrimaryLanguageSubtag, getSupportedLanguageList, handleRtl, IntlProvider, isRtl, LOCALE_CHANGED, LOCALE_TOPIC, mergeMessages, updateLocale, useIntl, type IntlConfig, type ResolvedIntlConfig, type IntlShape, } from './i18n';
|
|
5
|
+
export { configureI18n, createIntl, defineMessages, FormattedDate, FormattedMessage, FormattedNumber, FormattedPlural, FormattedRelativeTime, FormattedTime, getLocale, getLocalizedLanguageName, getMessages, getPrimaryLanguageSubtag, getSupportedLanguageList, handleRtl, IntlProvider, isRtl, LOCALE_CHANGED, LOCALE_TOPIC, mergeMessages, updateLocale, useIntl, updateSiteLanguage, type IntlConfig, type ResolvedIntlConfig, type IntlShape, } from './i18n';
|
|
6
6
|
export { auth, getBasename, initError, initialize } from './initialize';
|
|
7
7
|
export { loadExternalScripts } from './scripts';
|
|
8
8
|
export { configureLogging, getLoggingService, logError, logInfo, MockLoggingService, NewRelicLoggingService, resetLoggingService } from './logging';
|
package/dist/runtime/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { configureAnalytics, getAnalyticsService, identifyAnonymousUser, identif
|
|
|
2
2
|
export { AUTHENTICATED_USER_CHANGED, AUTHENTICATED_USER_TOPIC, AxiosJwtAuthService, configureAuth, ensureAuthenticatedUser, fetchAuthenticatedUser, getAuthenticatedHttpClient, getAuthenticatedUser, getAuthService, getHttpClient, getLoginRedirectUrl, getLogoutRedirectUrl, hydrateAuthenticatedUser, MockAuthService, redirectToLogin, redirectToLogout, setAuthenticatedUser } from './auth';
|
|
3
3
|
export { getSiteConfig, setSiteConfig, mergeSiteConfig, addAppConfigs, getAppConfig, mergeAppConfig, setActiveRouteRoles, getActiveRouteRoles, addActiveWidgetRole, removeActiveWidgetRole, getActiveWidgetRoles, getActiveRoles, getExternalLinkUrl, getProvides, getProvidesAsStrings } from './config';
|
|
4
4
|
export * from './constants';
|
|
5
|
-
export { configureI18n, createIntl, defineMessages, FormattedDate, FormattedMessage, FormattedNumber, FormattedPlural, FormattedRelativeTime, FormattedTime, getLocale, getLocalizedLanguageName, getMessages, getPrimaryLanguageSubtag, getSupportedLanguageList, handleRtl, IntlProvider, isRtl, LOCALE_CHANGED, LOCALE_TOPIC, mergeMessages, updateLocale, useIntl, } from './i18n';
|
|
5
|
+
export { configureI18n, createIntl, defineMessages, FormattedDate, FormattedMessage, FormattedNumber, FormattedPlural, FormattedRelativeTime, FormattedTime, getLocale, getLocalizedLanguageName, getMessages, getPrimaryLanguageSubtag, getSupportedLanguageList, handleRtl, IntlProvider, isRtl, LOCALE_CHANGED, LOCALE_TOPIC, mergeMessages, updateLocale, useIntl, updateSiteLanguage, } from './i18n';
|
|
6
6
|
export { auth, getBasename, initError, initialize } from './initialize';
|
|
7
7
|
export { loadExternalScripts } from './scripts';
|
|
8
8
|
export { configureLogging, getLoggingService, logError, logInfo, MockLoggingService, NewRelicLoggingService, resetLoggingService } from './logging';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,EACb,uBAAuB,EACvB,sBAAsB,EACtB,0BAA0B,EAC1B,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACrB,MAAM,UAAU,CAAC;AAElB,cAAc,aAAa,CAAC;AAE5B,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,wBAAwB,EACxB,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,SAAS,EACT,YAAY,EACZ,KAAK,EACL,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,EACb,uBAAuB,EACvB,sBAAsB,EACtB,0BAA0B,EAC1B,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACrB,MAAM,UAAU,CAAC;AAElB,cAAc,aAAa,CAAC;AAE5B,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,wBAAwB,EACxB,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,SAAS,EACT,YAAY,EACZ,KAAK,EACL,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,OAAO,EACP,kBAAkB,GAInB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,IAAI,EACJ,WAAW,EACX,SAAS,EACT,UAAU,EACX,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,EACR,OAAO,EACP,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,OAAO,EACP,aAAa,EACb,SAAS,EACT,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,qBAAqB,EACrB,OAAO,EACP,SAAS,EACT,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,cAAc,SAAS,CAAC","sourcesContent":["export {\n configureAnalytics,\n getAnalyticsService,\n identifyAnonymousUser,\n identifyAuthenticatedUser,\n MockAnalyticsService,\n resetAnalyticsService,\n SegmentAnalyticsService,\n sendPageEvent,\n sendTrackEvent,\n sendTrackingLogEvent\n} from './analytics';\n\nexport {\n AUTHENTICATED_USER_CHANGED,\n AUTHENTICATED_USER_TOPIC,\n AxiosJwtAuthService,\n configureAuth,\n ensureAuthenticatedUser,\n fetchAuthenticatedUser,\n getAuthenticatedHttpClient,\n getAuthenticatedUser,\n getAuthService,\n getHttpClient,\n getLoginRedirectUrl,\n getLogoutRedirectUrl,\n hydrateAuthenticatedUser,\n MockAuthService,\n redirectToLogin,\n redirectToLogout,\n setAuthenticatedUser\n} from './auth';\n\nexport {\n getSiteConfig,\n setSiteConfig,\n mergeSiteConfig,\n addAppConfigs,\n getAppConfig,\n mergeAppConfig,\n setActiveRouteRoles,\n getActiveRouteRoles,\n addActiveWidgetRole,\n removeActiveWidgetRole,\n getActiveWidgetRoles,\n getActiveRoles,\n getExternalLinkUrl,\n getProvides,\n getProvidesAsStrings\n} from './config';\n\nexport * from './constants';\n\nexport {\n configureI18n,\n createIntl,\n defineMessages,\n FormattedDate,\n FormattedMessage,\n FormattedNumber,\n FormattedPlural,\n FormattedRelativeTime,\n FormattedTime,\n getLocale,\n getLocalizedLanguageName,\n getMessages,\n getPrimaryLanguageSubtag,\n getSupportedLanguageList,\n handleRtl,\n IntlProvider,\n isRtl,\n LOCALE_CHANGED,\n LOCALE_TOPIC,\n mergeMessages,\n updateLocale,\n useIntl,\n updateSiteLanguage,\n type IntlConfig,\n type ResolvedIntlConfig,\n type IntlShape,\n} from './i18n';\n\nexport {\n auth,\n getBasename,\n initError,\n initialize\n} from './initialize';\n\nexport {\n loadExternalScripts\n} from './scripts';\n\nexport {\n configureLogging,\n getLoggingService,\n logError,\n logInfo,\n MockLoggingService,\n NewRelicLoggingService,\n resetLoggingService\n} from './logging';\n\nexport {\n CurrentAppContext,\n CurrentAppProvider,\n SiteContext,\n SiteProvider,\n AuthenticatedPageRoute,\n Divider,\n ErrorBoundary,\n ErrorPage,\n LoginRedirect,\n PageWrap,\n useSiteEvent,\n useAuthenticatedUser,\n useSiteConfig,\n useAppConfig\n} from './react';\n\nexport {\n authenticatedLoader,\n getUrlByRouteRole,\n isRoleRouteObject\n} from './routing';\n\nexport {\n clearAllSubscriptions,\n publish,\n subscribe,\n unsubscribe\n} from './subscriptions';\n\nexport {\n initializeMockApp,\n mockMessages\n} from './testing';\n\nexport {\n camelCaseObject,\n convertKeyNames,\n getQueryParameters,\n isValidVariableName,\n modifyObjectKeys,\n snakeCaseObject\n} from './utils';\n\nexport * from './slots';\n"]}
|
|
@@ -1,16 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
|
+
import { Dropdown, Toast } from '@openedx/paragon';
|
|
12
|
+
import { useCallback, useContext, useState } from 'react';
|
|
13
|
+
import { SiteContext, getLocalizedLanguageName, getSupportedLanguageList, updateSiteLanguage, useIntl, } from '../../runtime';
|
|
5
14
|
import LanguageMenuItem from './LanguageMenuItem';
|
|
15
|
+
import messages from './messages';
|
|
6
16
|
export default function LanguageMenu() {
|
|
17
|
+
const { formatMessage } = useIntl();
|
|
7
18
|
const { locale } = useContext(SiteContext);
|
|
19
|
+
const [pendingLanguage, setPendingLanguage] = useState(null);
|
|
20
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
8
21
|
const languages = getSupportedLanguageList();
|
|
9
|
-
const
|
|
22
|
+
const handleSelect = useCallback((languageCode) => __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
setPendingLanguage(languageCode);
|
|
24
|
+
setErrorMessage(null);
|
|
25
|
+
try {
|
|
26
|
+
yield updateSiteLanguage(languageCode);
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
// The UI switch is optimistic and stays in the picked language; only the
|
|
30
|
+
// preference save failed, so surface that without reverting.
|
|
31
|
+
setErrorMessage(formatMessage(messages.languageSaveError));
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
setPendingLanguage(null);
|
|
35
|
+
}
|
|
36
|
+
}), [formatMessage]);
|
|
10
37
|
// Hide the menu if there's only one language.
|
|
11
38
|
if (languages.length === 1) {
|
|
12
39
|
return null;
|
|
13
40
|
}
|
|
14
|
-
|
|
41
|
+
const toggleLabel = pendingLanguage
|
|
42
|
+
? getLocalizedLanguageName(pendingLanguage)
|
|
43
|
+
: getLocalizedLanguageName(locale);
|
|
44
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Dropdown, { children: [_jsx(Dropdown.Toggle, { id: "language-menu-dropdown-trigger", variant: "outline-primary", size: "sm", children: toggleLabel }), _jsx(Dropdown.Menu, { className: "overflow-auto", style: { maxHeight: '320px' }, children: languages.map((language) => (_jsx(LanguageMenuItem, { language: language, disabled: pendingLanguage !== null, isActive: language.code === locale, onSelect: handleSelect }, language.code))) })] }), errorMessage && (_jsx(Toast, { show: true, onClose: () => setErrorMessage(null), children: errorMessage }))] }));
|
|
15
45
|
}
|
|
16
46
|
//# sourceMappingURL=LanguageMenu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LanguageMenu.js","sourceRoot":"","sources":["../../../shell/footer/LanguageMenu.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LanguageMenu.js","sourceRoot":"","sources":["../../../shell/footer/LanguageMenu.tsx"],"names":[],"mappings":";;;;;;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE1D,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,OAAO,GACR,MAAM,eAAe,CAAC;AAEvB,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAClD,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC,MAAM,CAAC,OAAO,UAAU,YAAY;IAClC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAE3C,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAEtE,MAAM,SAAS,GAAG,wBAAwB,EAAE,CAAC;IAE7C,MAAM,YAAY,GAAG,WAAW,CAAC,CAAO,YAAoB,EAAE,EAAE;QAC9D,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACjC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACzC,CAAC;QAAC,WAAM,CAAC;YACP,yEAAyE;YACzE,6DAA6D;YAC7D,eAAe,CAAC,aAAa,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC7D,CAAC;gBAAS,CAAC;YACT,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAA,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,8CAA8C;IAC9C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,eAAe;QACjC,CAAC,CAAC,wBAAwB,CAAC,eAAe,CAAC;QAC3C,CAAC,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAErC,OAAO,CACL,8BACE,MAAC,QAAQ,eACP,KAAC,QAAQ,CAAC,MAAM,IAAC,EAAE,EAAC,gCAAgC,EAAC,OAAO,EAAC,iBAAiB,EAAC,IAAI,EAAC,IAAI,YACrF,WAAW,GACI,EAClB,KAAC,QAAQ,CAAC,IAAI,IAAC,SAAS,EAAC,eAAe,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,YACnE,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAC3B,KAAC,gBAAgB,IAEf,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,eAAe,KAAK,IAAI,EAClC,QAAQ,EAAE,QAAQ,CAAC,IAAI,KAAK,MAAM,EAClC,QAAQ,EAAE,YAAY,IAJjB,QAAQ,CAAC,IAAI,CAKlB,CACH,CAAC,GACY,IACP,EACV,YAAY,IAAI,CACf,KAAC,KAAK,IACJ,IAAI,QACJ,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,YAEnC,YAAY,GACP,CACT,IACA,CACJ,CAAC;AACJ,CAAC","sourcesContent":["import { Dropdown, Toast } from '@openedx/paragon';\nimport { useCallback, useContext, useState } from 'react';\n\nimport {\n SiteContext,\n getLocalizedLanguageName,\n getSupportedLanguageList,\n updateSiteLanguage,\n useIntl,\n} from '../../runtime';\n\nimport LanguageMenuItem from './LanguageMenuItem';\nimport messages from './messages';\n\nexport default function LanguageMenu() {\n const { formatMessage } = useIntl();\n const { locale } = useContext(SiteContext);\n\n const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n const languages = getSupportedLanguageList();\n\n const handleSelect = useCallback(async (languageCode: string) => {\n setPendingLanguage(languageCode);\n setErrorMessage(null);\n try {\n await updateSiteLanguage(languageCode);\n } catch {\n // The UI switch is optimistic and stays in the picked language; only the\n // preference save failed, so surface that without reverting.\n setErrorMessage(formatMessage(messages.languageSaveError));\n } finally {\n setPendingLanguage(null);\n }\n }, [formatMessage]);\n\n // Hide the menu if there's only one language.\n if (languages.length === 1) {\n return null;\n }\n\n const toggleLabel = pendingLanguage\n ? getLocalizedLanguageName(pendingLanguage)\n : getLocalizedLanguageName(locale);\n\n return (\n <>\n <Dropdown>\n <Dropdown.Toggle id=\"language-menu-dropdown-trigger\" variant=\"outline-primary\" size=\"sm\">\n {toggleLabel}\n </Dropdown.Toggle>\n <Dropdown.Menu className=\"overflow-auto\" style={{ maxHeight: '320px' }}>\n {languages.map((language) => (\n <LanguageMenuItem\n key={language.code}\n language={language}\n disabled={pendingLanguage !== null}\n isActive={language.code === locale}\n onSelect={handleSelect}\n />\n ))}\n </Dropdown.Menu>\n </Dropdown>\n {errorMessage && (\n <Toast\n show\n onClose={() => setErrorMessage(null)}\n >\n {errorMessage}\n </Toast>\n )}\n </>\n );\n}\n"]}
|
|
@@ -3,6 +3,9 @@ interface LanguageMenuItemProps {
|
|
|
3
3
|
code: string;
|
|
4
4
|
name: string;
|
|
5
5
|
};
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
isActive?: boolean;
|
|
8
|
+
onSelect: (code: string) => void;
|
|
6
9
|
}
|
|
7
|
-
export default function LanguageMenuItem({ language }: LanguageMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export default function LanguageMenuItem({ language, disabled, isActive, onSelect, }: LanguageMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
8
11
|
export {};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Dropdown } from '@openedx/paragon';
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
-
|
|
5
|
-
export default function LanguageMenuItem({ language }) {
|
|
4
|
+
export default function LanguageMenuItem({ language, disabled, isActive, onSelect, }) {
|
|
6
5
|
const handleClick = useCallback(() => {
|
|
7
|
-
|
|
8
|
-
}, [language.code]);
|
|
9
|
-
return (_jsx(Dropdown.Item, { onClick: handleClick, children: language.name }, language.code));
|
|
6
|
+
onSelect(language.code);
|
|
7
|
+
}, [language.code, onSelect]);
|
|
8
|
+
return (_jsx(Dropdown.Item, { active: isActive, disabled: disabled, onClick: handleClick, children: language.name }, language.code));
|
|
10
9
|
}
|
|
11
10
|
//# sourceMappingURL=LanguageMenuItem.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LanguageMenuItem.js","sourceRoot":"","sources":["../../../shell/footer/LanguageMenuItem.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"LanguageMenuItem.js","sourceRoot":"","sources":["../../../shell/footer/LanguageMenuItem.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAYpC,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,GACc;IACtB,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE9B,OAAO,CACL,KAAC,QAAQ,CAAC,IAAI,IAEZ,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,WAAW,YAEnB,QAAQ,CAAC,IAAI,IALT,QAAQ,CAAC,IAAI,CAMJ,CACjB,CAAC;AACJ,CAAC","sourcesContent":["import { Dropdown } from '@openedx/paragon';\nimport { useCallback } from 'react';\n\ninterface LanguageMenuItemProps {\n language: {\n code: string;\n name: string;\n };\n disabled?: boolean;\n isActive?: boolean;\n onSelect: (code: string) => void;\n}\n\nexport default function LanguageMenuItem({\n language,\n disabled,\n isActive,\n onSelect,\n}: LanguageMenuItemProps) {\n const handleClick = useCallback(() => {\n onSelect(language.code);\n }, [language.code, onSelect]);\n\n return (\n <Dropdown.Item\n key={language.code}\n active={isActive}\n disabled={disabled}\n onClick={handleClick}\n >\n {language.name}\n </Dropdown.Item>\n );\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineMessages } from '../../runtime';
|
|
2
|
+
const messages = defineMessages({
|
|
3
|
+
languageSaveError: {
|
|
4
|
+
id: 'footer.languageMenu.error.languageSave',
|
|
5
|
+
defaultMessage: 'We could not save your language preference.',
|
|
6
|
+
description: 'Error shown when saving the site language preference fails.',
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
export default messages;
|
|
10
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../shell/footer/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,iBAAiB,EAAE;QACjB,EAAE,EAAE,wCAAwC;QAC5C,cAAc,EAAE,6CAA6C;QAC7D,WAAW,EAAE,6DAA6D;KAC3E;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '../../runtime';\n\nconst messages = defineMessages({\n languageSaveError: {\n id: 'footer.languageMenu.error.languageSave',\n defaultMessage: 'We could not save your language preference.',\n description: 'Error shown when saving the site language preference fails.',\n },\n});\n\nexport default messages;\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface App {
|
|
|
24
24
|
providers?: AppProvider[];
|
|
25
25
|
slots?: SlotOperation[];
|
|
26
26
|
externalScripts?: ExternalScriptLoaderClass[];
|
|
27
|
+
defaultConfig?: AppConfig;
|
|
27
28
|
config?: AppConfig;
|
|
28
29
|
provides?: Record<string, unknown>;
|
|
29
30
|
}
|
|
@@ -54,6 +55,8 @@ export interface OptionalSiteConfig {
|
|
|
54
55
|
commonAppConfig: AppConfig;
|
|
55
56
|
headerLogoImageUrl: string;
|
|
56
57
|
theme: Theme;
|
|
58
|
+
defaultLanguage: string;
|
|
59
|
+
supportedLanguages: string[];
|
|
57
60
|
accessTokenCookieName: string;
|
|
58
61
|
languagePreferenceCookieName: string;
|
|
59
62
|
userInfoCookieName: string;
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAkIA,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,6CAAyB,CAAA;IACzB,+CAA2B,CAAA;IAC3B,iCAAa,CAAA;AACf,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B","sourcesContent":["import { FC, ReactElement, ReactNode } from 'react';\nimport { MessageDescriptor } from 'react-intl';\nimport { RouteObject } from 'react-router';\nimport { SlotOperation } from './runtime/slots/types';\n\n// Apps\n\nexport interface ExternalRoute {\n role: string;\n url: string;\n}\n\nexport type RoleRouteObject = RouteObject & {\n handle?: {\n /**\n * Route roles identify the purpose(s) a route fulfills in the site.\n */\n roles?: string[];\n };\n};\n\nexport type AppConfig = Record<string, unknown>;\n\nexport type AppProvider = FC<{ children?: ReactNode }>;\n\nexport interface App {\n appId: string;\n routes?: RoleRouteObject[];\n providers?: AppProvider[];\n slots?: SlotOperation[];\n externalScripts?: ExternalScriptLoaderClass[];\n defaultConfig?: AppConfig;\n config?: AppConfig;\n provides?: Record<string, unknown>;\n}\n\n// External Scripts\n\nexport interface ExternalScriptLoader {\n loadScript(): void;\n}\n\nexport type ExternalScriptLoaderClass = new (data: { config: AppConfig }) => ExternalScriptLoader;\n\n// Site Config\n\nexport interface RequiredSiteConfig {\n siteId: string;\n siteName: string;\n baseUrl: string;\n\n // Backends\n lmsBaseUrl: string;\n\n // Frontends\n loginUrl: string;\n logoutUrl: string;\n}\n\nexport type LocalizedMessages = Record<string, Record<string, string>>;\nexport type SiteMessages = LocalizedMessages[];\n\nexport interface OptionalSiteConfig {\n // Site environment\n environment: EnvironmentTypes;\n\n // Backends\n cmsBaseUrl: string;\n\n // Apps, routes, and URLs\n apps: App[];\n basename: string;\n externalRoutes: ExternalRoute[];\n externalLinkUrlOverrides: string[];\n runtimeConfigJsonUrl: string | null;\n commonAppConfig: AppConfig;\n headerLogoImageUrl: string;\n\n // Theme\n theme: Theme;\n\n // i18n\n defaultLanguage: string;\n supportedLanguages: string[];\n\n // Cookies\n accessTokenCookieName: string;\n languagePreferenceCookieName: string;\n userInfoCookieName: string;\n\n // Paths\n csrfTokenApiPath: string;\n refreshAccessTokenApiPath: string;\n\n // Logging\n ignoredErrorRegex: RegExp | null;\n\n // Analytics\n segmentKey: string | null;\n}\n\nexport type SiteConfig = RequiredSiteConfig & Partial<OptionalSiteConfig>;\n\nexport interface ThemeVariant {\n url: string;\n}\n\nexport interface ThemeDefaults {\n light?: string;\n dark?: string;\n}\n\nexport type ThemeVariants = Record<string, ThemeVariant>;\n\nexport interface Theme {\n core?: ThemeVariant;\n defaults?: ThemeDefaults;\n variants?: ThemeVariants;\n}\n\nexport interface User {\n administrator: boolean;\n email: string;\n name: string;\n roles: string[];\n userId: number;\n username: string;\n avatar: string;\n}\n\nexport enum EnvironmentTypes {\n PRODUCTION = 'production',\n DEVELOPMENT = 'development',\n TEST = 'test',\n}\n\n// Menu Items\n\nexport type MenuItemName = string | MessageDescriptor | ReactElement;\n"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function updateSiteLanguage(locale: string): Promise<void>;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { getAuthenticatedHttpClient, getAuthenticatedUser, getSiteConfig, updateLocale } from '../../../runtime';
|
|
11
|
-
export function updateSiteLanguage(locale) {
|
|
12
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
13
|
-
const user = getAuthenticatedUser();
|
|
14
|
-
if (user !== null) {
|
|
15
|
-
const { username } = getAuthenticatedUser();
|
|
16
|
-
yield patchUserPreferences(username, locale);
|
|
17
|
-
}
|
|
18
|
-
yield postSetlang(locale);
|
|
19
|
-
updateLocale();
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function patchUserPreferences(username, locale) {
|
|
23
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
yield getAuthenticatedHttpClient().patch(`${getSiteConfig().lmsBaseUrl}/api/user/v1/preferences/${username}`, {
|
|
25
|
-
'pref-lang': locale
|
|
26
|
-
}, {
|
|
27
|
-
headers: {
|
|
28
|
-
'Content-Type': 'application/merge-patch+json'
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
function postSetlang(locale) {
|
|
34
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
const formData = new FormData();
|
|
36
|
-
formData.append('language', locale);
|
|
37
|
-
yield getAuthenticatedHttpClient().post(`${getSiteConfig().lmsBaseUrl}/i18n/setlang/`, formData, {
|
|
38
|
-
headers: {
|
|
39
|
-
Accept: 'application/json',
|
|
40
|
-
'X-Requested-With': 'XMLHttpRequest',
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=api.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../shell/footer/data/api.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,MAAM,UAAgB,kBAAkB,CAAC,MAAc;;QACrD,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QAEpC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,EAAE,QAAQ,EAAE,GAAG,oBAAoB,EAAE,CAAC;YAC5C,MAAM,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;QAE1B,YAAY,EAAE,CAAC;IACjB,CAAC;CAAA;AAED,SAAe,oBAAoB,CAAC,QAAgB,EAAE,MAAc;;QAClE,MAAM,0BAA0B,EAAE,CAAC,KAAK,CACtC,GAAG,aAAa,EAAE,CAAC,UAAU,4BAA4B,QAAQ,EAAE,EACnE;YACE,WAAW,EAAE,MAAM;SACpB,EACD;YACE,OAAO,EAAE;gBACP,cAAc,EAAE,8BAA8B;aAC/C;SACF,CACF,CAAC;IACJ,CAAC;CAAA;AAED,SAAe,WAAW,CAAC,MAAc;;QACvC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAEpC,MAAM,0BAA0B,EAAE,CAAC,IAAI,CACrC,GAAG,aAAa,EAAE,CAAC,UAAU,gBAAgB,EAC7C,QAAQ,EACR;YACE,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,kBAAkB,EAAE,gBAAgB;aACrC;SACF,CACF,CAAC;IACJ,CAAC;CAAA","sourcesContent":["import {\n getAuthenticatedHttpClient,\n getAuthenticatedUser,\n getSiteConfig,\n updateLocale\n} from '../../../runtime';\n\nexport async function updateSiteLanguage(locale: string) {\n const user = getAuthenticatedUser();\n\n if (user !== null) {\n const { username } = getAuthenticatedUser();\n await patchUserPreferences(username, locale);\n }\n await postSetlang(locale);\n\n updateLocale();\n}\n\nasync function patchUserPreferences(username: string, locale: string) {\n await getAuthenticatedHttpClient().patch(\n `${getSiteConfig().lmsBaseUrl}/api/user/v1/preferences/${username}`,\n {\n 'pref-lang': locale\n },\n {\n headers: {\n 'Content-Type': 'application/merge-patch+json'\n },\n }\n );\n}\n\nasync function postSetlang(locale: string) {\n const formData = new FormData();\n formData.append('language', locale);\n\n await getAuthenticatedHttpClient().post(\n `${getSiteConfig().lmsBaseUrl}/i18n/setlang/`,\n formData,\n {\n headers: {\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n },\n }\n );\n}\n"]}
|