Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/i18n/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,7 @@ export {
getLanguageList,
getLanguageMessages,
} from './languages';

export {
changeUserSessionLanguage,
} from './languageManager';
101 changes: 101 additions & 0 deletions src/i18n/languageManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* LanguageManager.js
*
* Provides utility functions for updating the session language preferences for users.
*/

import { getConfig } from '../config';
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '../auth';
import { convertKeyNames, snakeCaseObject } from '../utils';
import { getCookies, handleRtl, LOCALE_CHANGED } from './lib';
import { logError } from '../logging';
import { publish } from '../pubSub';

/**
* Updates user language preferences via the preferences API.
*
* This function converts preference data to snake_case and formats specific keys
* according to backend requirements before sending the PATCH request.
*
* @param {string} username - The username of the user whose preferences to update.
* @param {Object} preferenceData - The preference parameters to update (e.g., { prefLang: 'en' }).
* @returns {Promise} - A promise that resolves when the API call completes successfully,
* or rejects if there's an error with the request.
*/
export async function updateUserPreferences(username, preferenceData) {
const snakeCaseData = snakeCaseObject(preferenceData);
const formattedData = convertKeyNames(snakeCaseData, {

Check warning on line 27 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L25-L27

Added lines #L25 - L27 were not covered by tests
pref_lang: 'pref-lang',
});

return getAuthenticatedHttpClient().patch(

Check warning on line 31 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L31

Added line #L31 was not covered by tests
`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`,
formattedData,
{ headers: { 'Content-Type': 'application/merge-patch+json' } },
);
}

/**
* Sets the language for the current session using the setlang endpoint.
*
* This function sends a POST request to the LMS setlang endpoint to change
* the language for the current user session.
*
* @param {string} languageCode - The language code to set (e.g., 'en', 'es', 'ar').
* Should be a valid ISO language code supported by the platform.
* @returns {Promise} - A promise that resolves when the API call completes successfully,
* or rejects if there's an error with the request.
*/
export async function setSessionLanguage(languageCode) {
const formData = new FormData();
formData.append('language', languageCode);

Check warning on line 51 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L49-L51

Added lines #L49 - L51 were not covered by tests

const url = `${getConfig().LMS_BASE_URL}/i18n/setlang/`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was digging a little bit in edx-platform to see how the mako templates manage the logic and I found this endpoint, it is used for update no-authenticated users as you can see in the mako template.

The endpoint request a CSRF token as you can see here. I just want to make sure the following line of code is correct as a suggested change from the frontend-platform perspective (getAuthenticatedHttpClient is the right service to use? -@arbrandes could you help me with this doubt?-).

getAuthenticatedHttpClient().patch(`${getConfig().LMS_BASE_URL}/lang_pref/update_language`, {"pref-lang": languageCode}, { isPublic: true })

In this way we can avoid setting the language cookie (as currently happen with the lines 81-83 of the this PR), and it will be 100% managed for the backend in both authenticated and unauthenticated users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I create a sandbox for testing in learning, and the current endpoint can manage the authenticated and unauthenticated case (I have the problem in my dev environment only)

https://app.pr-1741-1395ab.sandboxes.opencraft.hosting/learning/course/course-v1:Demo+CT01+CT01/home

c.c @arbrandes

return getAuthenticatedHttpClient().post(url, formData, {

Check warning on line 54 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L53-L54

Added lines #L53 - L54 were not covered by tests
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
});
}

/**
* Changes the user's language preference and applies it to the current session.
*
* This comprehensive function handles the complete language change process:
* 1. Sets the language cookie with the selected language code
* 2. If a user is authenticated, updates their server-side preference in the backend
* 3. Updates the session language through the setlang endpoint
* 4. Publishes a locale change event to notify other parts of the application
*
* @param {string} languageCode - The selected language locale code (e.g., 'en', 'es', 'ar').
* Should be a valid ISO language code supported by the platform.
* @param {boolean} [forceReload=false] - Whether to force a page reload after changing the language.
* @returns {Promise} - A promise that resolves when all operations complete.
*
*/
export async function changeUserSessionLanguage(
languageCode,
forceReload = false,
) {
const cookies = getCookies();
const cookieName = getConfig().LANGUAGE_PREFERENCE_COOKIE_NAME;
cookies.set(cookieName, languageCode);

Check warning on line 83 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L79-L83

Added lines #L79 - L83 were not covered by tests

try {
const user = getAuthenticatedUser();

Check warning on line 86 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L85-L86

Added lines #L85 - L86 were not covered by tests
if (user) {
await updateUserPreferences(user.username, { prefLang: languageCode });

Check warning on line 88 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L88

Added line #L88 was not covered by tests
}

await setSessionLanguage(languageCode);
handleRtl(languageCode);
publish(LOCALE_CHANGED, languageCode);

Check warning on line 93 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L91-L93

Added lines #L91 - L93 were not covered by tests
} catch (error) {
logError(error);

Check warning on line 95 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L95

Added line #L95 was not covered by tests
}

if (forceReload) {
window.location.reload();

Check warning on line 99 in src/i18n/languageManager.js

View check run for this annotation

Codecov / codecov/patch

src/i18n/languageManager.js#L99

Added line #L99 was not covered by tests
}
}