Skip to content

Commit 7997ef4

Browse files
committed
refactor: make the lang selector a slot
fix: update test and linter docs: add slot readme example test: update snapshoots vv
1 parent c40231f commit 7997ef4

File tree

13 files changed

+146
-402
lines changed

13 files changed

+146
-402
lines changed

README.rst

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,6 @@ This library has the following exports:
8686
* ``messages``: Internationalization messages suitable for use with `@edx/frontend-platform/i18n <https://edx.github.io/frontend-platform/module-Internationalization.html>`_
8787
* ``dist/footer.scss``: A SASS file which contains style information for the component. It should be imported into the micro-frontend's own SCSS file.
8888

89-
Language Selector
90-
-----------------
91-
92-
The language selector dropdown is optional and can be enabled by setting the MFE configuration variable ``ENABLE_FOOTER_LANG_SELECTOR`` to ``true``.
93-
Secondly, configue the languages that should be displayed in the dropdown by setting the MFE configuration variable ``SITE_SUPPORTED_LANGUAGES`` to an array of locale languages.
94-
Example:
95-
96-
.. code-block:: python
97-
98-
MFE_CONFIG["EDX_FRONTEND_APP_CONFIG"] = {
99-
"ENABLE_FOOTER_LANG_SELECTOR": True,
100-
"SITE_SUPPORTED_LANGUAGES": ['en', 'es', 'fr', 'pt-br'],
101-
}
102-
10389
Plugin
10490
======
10591
The footer can be replaced or modified using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.

env.config.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/components/Footer.jsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { ensureConfig } from '@edx/frontend-platform';
66
import { AppContext } from '@edx/frontend-platform/react';
77

88
import messages from './Footer.messages';
9-
import LanguageSelector from './LanguageSelector';
9+
10+
import LanguageSelectorSlot from '../plugin-slots/LanguageSelectorSlot';
1011

1112
ensureConfig([
1213
'LMS_BASE_URL',
@@ -38,7 +39,7 @@ class SiteFooter extends React.Component {
3839
logo,
3940
intl,
4041
} = this.props;
41-
const { config, authenticatedUser } = this.context;
42+
const { config } = this.context;
4243
return (
4344
<footer
4445
role="contentinfo"
@@ -57,15 +58,9 @@ class SiteFooter extends React.Component {
5758
/>
5859
</a>
5960
<div className="flex-grow-1" />
60-
{config.ENABLE_FOOTER_LANG_SELECTOR && (
61-
<div className="mb-2">
62-
<LanguageSelector
63-
options={config.SITE_SUPPORTED_LANGUAGES}
64-
username={authenticatedUser?.username}
65-
langCookieName={config.LANGUAGE_PREFERENCE_COOKIE_NAME}
66-
/>
67-
</div>
68-
)}
61+
<div className="mb-2">
62+
<LanguageSelectorSlot />
63+
</div>
6964
</div>
7065
</footer>
7166
);

src/components/Footer.test.jsx

Lines changed: 20 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ import React, { useMemo } from 'react';
33
import renderer from 'react-test-renderer';
44
import { render, screen } from '@testing-library/react';
55
import userEvent from '@testing-library/user-event';
6-
import { IntlProvider } from '@edx/frontend-platform/i18n';
6+
import { IntlProvider, changeUserSessionLanguage } from '@edx/frontend-platform/i18n';
77
import { AppContext } from '@edx/frontend-platform/react';
88
import { initializeMockApp } from '@edx/frontend-platform/testing';
99

10-
import Footer from './Footer';
1110
import FooterSlot from '../plugin-slots/FooterSlot';
1211
import StudioFooterHelpSectionSlot from '../plugin-slots/StudioFooterHelpSectionSlot';
1312

14-
const FooterWithContext = ({ locale = 'es' }) => {
13+
jest.mock('@edx/frontend-platform/i18n', () => ({
14+
...jest.requireActual('@edx/frontend-platform/i18n'),
15+
changeUserSessionLanguage: jest.fn(),
16+
}));
17+
18+
const FooterWithContext = ({ locale = 'en' }) => {
1519
const contextValue = useMemo(() => ({
1620
authenticatedUser: null,
1721
config: {
@@ -31,64 +35,24 @@ const FooterWithContext = ({ locale = 'es' }) => {
3135
);
3236
};
3337

34-
const { LANGUAGE_PREFERENCE_COOKIE_NAME } = process.env;
35-
const FooterWithLanguageSelector = ({ authenticatedUser = null }) => {
36-
const contextValue = useMemo(() => ({
37-
authenticatedUser,
38-
config: {
39-
ENABLE_FOOTER_LANG_SELECTOR: true,
40-
LANGUAGE_PREFERENCE_COOKIE_NAME,
41-
LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
42-
LMS_BASE_URL: process.env.LMS_BASE_URL,
43-
SITE_SUPPORTED_LANGUAGES: ['es', 'en'],
44-
},
45-
}), [authenticatedUser]);
46-
47-
return (
48-
<IntlProvider locale="en">
49-
<AppContext.Provider
50-
value={contextValue}
51-
>
52-
<Footer />
53-
</AppContext.Provider>
54-
</IntlProvider>
55-
);
56-
};
57-
5838
describe('<Footer />', () => {
59-
describe('renders correctly', () => {
60-
it('renders without a language selector', () => {
61-
const tree = renderer
62-
.create(<FooterWithContext locale="en" />)
63-
.toJSON();
64-
expect(tree).toMatchSnapshot();
65-
});
66-
it('renders without a language selector in es', () => {
67-
const tree = renderer
68-
.create(<FooterWithContext locale="es" />)
69-
.toJSON();
70-
expect(tree).toMatchSnapshot();
71-
});
72-
it('renders with a language selector', () => {
73-
initializeMockApp();
74-
const tree = renderer
75-
.create(<FooterWithLanguageSelector />)
76-
.toJSON();
77-
expect(tree).toMatchSnapshot();
78-
});
39+
beforeEach(() => { initializeMockApp(); });
40+
41+
it('renders correctly', () => {
42+
const tree = renderer
43+
.create(<FooterWithContext />)
44+
.toJSON();
45+
expect(tree).toMatchSnapshot();
7946
});
8047

81-
describe('handles language switching', () => {
82-
it('calls onLanguageSelected prop when a language is changed', async () => {
83-
const user = userEvent.setup();
84-
const mockHandleLanguageSelected = jest.fn();
85-
render(<FooterWithLanguageSelector languageSelected={mockHandleLanguageSelected} />);
48+
it('handles language switching', async () => {
49+
const user = userEvent.setup();
50+
render(<FooterWithContext />);
8651

87-
await user.selectOptions(screen.getByRole('combobox'), 'es');
88-
await user.click(screen.getByTestId('site-footer-submit-btn'));
52+
await user.click(screen.getByRole('button'), 'English');
53+
await user.click(screen.getByRole('button', { name: 'Español (Latinoamérica)' }));
8954

90-
expect(mockHandleLanguageSelected).toHaveBeenCalledWith('es');
91-
});
55+
expect(changeUserSessionLanguage).toHaveBeenCalledWith('es-419');
9256
});
9357
});
9458

src/components/LanguageSelector.jsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useMemo, useState } from 'react';
2+
import PropTypes from 'prop-types';
3+
import {
4+
changeUserSessionLanguage, getPrimaryLanguageSubtag, getSupportedLocaleList, getLocale,
5+
} from '@edx/frontend-platform/i18n';
6+
import { Dropdown, Scrollable, useWindowSize } from '@openedx/paragon';
7+
import { Language } from '@openedx/paragon/icons';
8+
9+
const getLocaleName = (locale) => {
10+
const langName = new Intl.DisplayNames([locale], { type: 'language', languageDisplay: 'standard' }).of(locale);
11+
return langName.replace(/^\w/, (c) => c.toUpperCase());
12+
};
13+
14+
const LanguageSelector = ({
15+
supportedLanguages = [],
16+
}) => {
17+
const [currentLocale, setLocale] = useState(getLocale());
18+
const { width } = useWindowSize();
19+
const options = supportedLanguages.length > 0 ? supportedLanguages : getSupportedLocaleList();
20+
21+
const handleSelect = async (selectedLocale) => {
22+
if (currentLocale !== selectedLocale) {
23+
await changeUserSessionLanguage(selectedLocale);
24+
}
25+
setLocale(selectedLocale);
26+
};
27+
28+
const currentLocaleLabel = useMemo(() => {
29+
if (width < 576) {
30+
return '';
31+
}
32+
if (width < 768) {
33+
return getPrimaryLanguageSubtag(currentLocale).toUpperCase();
34+
}
35+
return getLocaleName(currentLocale);
36+
}, [currentLocale, width]);
37+
38+
return (
39+
<Dropdown onSelect={handleSelect}>
40+
<Dropdown.Toggle
41+
id="lang-selector-dropdown"
42+
iconBefore={Language}
43+
variant="outline-primary"
44+
size="sm"
45+
>
46+
{currentLocaleLabel}
47+
</Dropdown.Toggle>
48+
<Dropdown.Menu>
49+
<Scrollable style={{ maxHeight: '40vh' }}>
50+
{options.map((locale) => (
51+
<Dropdown.Item key={`lang-selector-${locale}`} eventKey={locale}>
52+
{getLocaleName(locale)}
53+
</Dropdown.Item>
54+
))}
55+
</Scrollable>
56+
</Dropdown.Menu>
57+
</Dropdown>
58+
);
59+
};
60+
61+
LanguageSelector.propTypes = {
62+
supportedLanguages: PropTypes.arrayOf(PropTypes.string),
63+
};
64+
65+
export default LanguageSelector;

src/components/LanguageSelector/data.js

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/components/LanguageSelector/index.jsx

Lines changed: 0 additions & 82 deletions
This file was deleted.

0 commit comments

Comments
 (0)