Skip to content

Commit

Permalink
feat: add header lang selector
Browse files Browse the repository at this point in the history
  • Loading branch information
dcoa committed Dec 11, 2024
1 parent 2b9f341 commit 41c56fe
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 3 deletions.
16 changes: 13 additions & 3 deletions src/DesktopHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';

// Local Components
import { Menu, MenuTrigger, MenuContent } from './Menu';
Expand All @@ -13,6 +14,7 @@ import messages from './Header.messages';

// Assets
import { CaretIcon } from './Icons';
import LanguageSelector from './LanguageSelector';

class DesktopHeader extends React.Component {
constructor(props) { // eslint-disable-line no-useless-constructor
Expand Down Expand Up @@ -136,7 +138,7 @@ class DesktopHeader extends React.Component {
} = this.props;
const logoProps = { src: logo, alt: logoAltText, href: logoDestination };
const logoClasses = getConfig().AUTHN_MINIMAL_HEADER ? 'mw-100' : null;

console.log(loggedIn)

Check failure on line 141 in src/DesktopHeader.jsx

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 4 spaces but found 0

Check warning on line 141 in src/DesktopHeader.jsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement

Check failure on line 141 in src/DesktopHeader.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing semicolon
return (
<header className="site-header-desktop">
<a className="nav-skip sr-only sr-only-focusable" href="#main">{intl.formatMessage(messages['header.label.skip.nav'])}</a>
Expand All @@ -145,13 +147,21 @@ class DesktopHeader extends React.Component {
{logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} />}
<nav
aria-label={intl.formatMessage(messages['header.label.main.nav'])}
className="nav main-nav"
className="nav main-nav mr-auto"
>
{this.renderMainMenu()}
</nav>
{getConfig().ENABLE_HEADER_LANG_SELECTOR && (
<div className="mx-2">
<LanguageSelector
options={getConfig().SITE_SUPPORTED_LENGUAGES}
authenticatedUser={getAuthenticatedUser()}
/>
</div>
)}
<nav
aria-label={intl.formatMessage(messages['header.label.secondary.nav'])}
className="nav secondary-menu-container align-items-center ml-auto"
className="nav secondary-menu-container align-items-center"
>
{loggedIn
? (
Expand Down
32 changes: 32 additions & 0 deletions src/LanguageSelector/data/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { convertKeyNames, snakeCaseObject } from '@edx/frontend-platform/utils';

export async function patchPreferences(username, params) {
let processedParams = snakeCaseObject(params);
processedParams = convertKeyNames(processedParams, {
pref_lang: 'pref-lang',
});

await getAuthenticatedHttpClient()
.patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, {
headers: { 'Content-Type': 'application/merge-patch+json' },
});

return params;
}

export async function postSetLang(code) {
const formData = new FormData();
const requestConfig = {
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
};
const url = `${getConfig().LMS_BASE_URL}/i18n/setlang/`;
formData.append('language', code);

await getAuthenticatedHttpClient()
.post(url, formData, requestConfig);
}
84 changes: 84 additions & 0 deletions src/LanguageSelector/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import { publish } from '@edx/frontend-platform';
import {
getLocale, injectIntl, intlShape, FormattedMessage, LOCALE_CHANGED, handleRtl,
} from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';

import { patchPreferences, postSetLang } from './data/api';

const onLanguageSelected = async (username, selectedLanguageCode) => {
try {
if (username) {
await patchPreferences(username, { prefLang: selectedLanguageCode });
await postSetLang(selectedLanguageCode);
}
publish(LOCALE_CHANGED, getLocale());
handleRtl();
} catch (error) {
logError(error);
}
};

const LanguageSelector = ({
intl, options, authenticatedUser, ...props
}) => {
const handleSubmit = (e) => {
e.preventDefault();
const previousSiteLanguage = getLocale();
const languageCode = e.target.elements['site-header-language-select'].value;
console.debug(previousSiteLanguage, languageCode, authenticatedUser);

Check warning on line 31 in src/LanguageSelector/index.jsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected console statement

if (previousSiteLanguage !== languageCode) {
onLanguageSelected(authenticatedUser?.username, languageCode);
}
};

return (
<form
className="form-inline"
onSubmit={handleSubmit}
{...props}
>
<div className="form-group d-wrap">
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label htmlFor="site-header-language-select" className="d-inline-block m-0 small">
<FormattedMessage
id="footer.languageForm.select.label"
defaultMessage="Choose Language"
description="The label for the laguage select part of the language selection form."
/>
</label>
<select
id="site-header-language-select"
className="form-control-sm mx-2"
name="site-header-language-select"
defaultValue={intl.locale}
>
{options.map(({ value, label }) => <option key={value} value={value}>{label}</option>)}
</select>
<button data-testid="site-header-submit-btn" className="btn btn-outline-primary btn-sm" type="submit">
<FormattedMessage
id="footer.languageForm.submit.label"
defaultMessage="Apply"
description="The label for button to submit the language selection form."
/>
</button>
</div>
</form>
);
};

LanguageSelector.propTypes = {
authenticatedUser: PropTypes.shape({
username: PropTypes.string,
}).isRequired,
intl: intlShape.isRequired,
options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string,
label: PropTypes.string,
})).isRequired,
};

export default injectIntl(LanguageSelector);
9 changes: 9 additions & 0 deletions src/learning-header/LearningHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import AnonymousUserMenu from './AnonymousUserMenu';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import messages from './messages';
import getCourseLogoOrg from './data/api';
import LanguageSelector from '../LanguageSelector';

const LinkedLogo = ({
href,
Expand Down Expand Up @@ -66,6 +67,14 @@ const LearningHeader = ({
</span>
</div>
</div>
{getConfig().ENABLE_HEADER_LANG_SELECTOR && (
<div className="mx-2 d-none d-md-inline-flex">
<LanguageSelector
options={getConfig().SITE_SUPPORTED_LENGUAGES}
authenticatedUser={authenticatedUser}
/>
</div>
)}
{showUserDropdown && authenticatedUser && (
<AuthenticatedUserDropdown
username={authenticatedUser.username}
Expand Down

0 comments on commit 41c56fe

Please sign in to comment.