Skip to content

Commit 8dd28c1

Browse files
Merge pull request #1221 from openedx/ammar/mark-strings-for-i8n-on-lpr-page
feat: mark strings for i18n on lpr page
2 parents 3fcadf5 + 951ea91 commit 8dd28c1

26 files changed

+742
-197
lines changed

src/components/Admin/AdminSearchForm.jsx

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/* eslint-disable camelcase */
22
import React from 'react';
3-
import dayjs from 'dayjs';
43
import PropTypes from 'prop-types';
54

65
import { Form } from '@edx/paragon';
76
import { Info } from '@edx/paragon/icons';
87

8+
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
9+
910
import SearchBar from '../SearchBar';
10-
import { updateUrl } from '../../utils';
11+
import { formatTimestamp, updateUrl } from '../../utils';
1112
import IconWithTooltip from '../IconWithTooltip';
1213
import { withLocation, withNavigate } from '../../hoc';
1314

@@ -46,6 +47,7 @@ class AdminSearchForm extends React.Component {
4647

4748
render() {
4849
const {
50+
intl,
4951
tableData,
5052
searchParams: { searchCourseQuery, searchDateQuery, searchQuery },
5153
} = this.props;
@@ -58,14 +60,26 @@ class AdminSearchForm extends React.Component {
5860
<div className="row w-100 m-0">
5961
<div className="col-12 col-md-3 px-0 pl-0 pr-md-2 pr-lg-3">
6062
<Form.Group>
61-
<Form.Label className="search-label mb-2">Filter by course</Form.Label>
63+
<Form.Label className="search-label mb-2">
64+
<FormattedMessage
65+
id="admin.portal.lpr.filter.by.course.dropdown.label"
66+
defaultMessage="Filter by course"
67+
description="Label for the course filter dropdown in the admin portal LPR page."
68+
/>
69+
</Form.Label>
6270
<Form.Control
6371
className="w-100"
6472
as="select"
6573
value={searchCourseQuery}
6674
onChange={e => this.onCourseSelect(e)}
6775
>
68-
<option value="">All Courses</option>
76+
<option value="">
77+
{intl.formatMessage({
78+
id: 'admin.portal.lpr.filter.by.course.dropdown.option.all.courses',
79+
defaultMessage: 'All Courses',
80+
description: 'Label for the all courses option in the course filter dropdown in the admin portal LPR page.',
81+
})}
82+
</option>
6983
{courseTitles.map(title => (
7084
<option
7185
value={title}
@@ -80,11 +94,25 @@ class AdminSearchForm extends React.Component {
8094
<div className="col-12 col-md-3 px-0 pr-0 px-md-2 px-lg-3">
8195
<Form.Group>
8296
<Form.Label className="search-label mb-2 d-flex align-items-center">
83-
<span>Filter by start date</span>
97+
<span>
98+
<FormattedMessage
99+
id="admin.portal.lpr.filter.by.start.date.dropdown.label"
100+
defaultMessage="Filter by start date"
101+
description="Label for the start date filter dropdown in the admin portal LPR page."
102+
/>
103+
</span>
84104
<IconWithTooltip
85105
icon={Info}
86-
altText="More information"
87-
tooltipText="A start date can be selected after the course name is selected."
106+
altText={intl.formatMessage({
107+
id: 'admin.portal.lpr.filter.by.start.date.alt.text',
108+
defaultMessage: 'More information',
109+
description: 'Alt text for the info icon in the start date filter dropdown in the admin portal LPR page.',
110+
})}
111+
tooltipText={intl.formatMessage({
112+
id: 'admin.portal.lpr.filter.by.start.date.dropdown.tooltip',
113+
defaultMessage: 'A start date can be selected after the course name is selected.',
114+
description: 'Tooltip text for the start date filter dropdown in the admin portal LPR page.',
115+
})}
88116
/>
89117
</Form.Label>
90118
<Form.Control
@@ -97,22 +125,46 @@ class AdminSearchForm extends React.Component {
97125
})}
98126
disabled={!searchCourseQuery}
99127
>
100-
<option value="">{searchCourseQuery ? 'All Dates' : 'Choose a course'}</option>
128+
<option value="">
129+
{searchCourseQuery ? intl.formatMessage({
130+
id: 'admin.portal.lpr.filter.by.start.date.dropdown.option.all.dates',
131+
defaultMessage: 'All Dates',
132+
description: 'Label for the all dates option in the start date filter dropdown in the admin portal LPR page.',
133+
}) : intl.formatMessage({
134+
id: 'admin.portal.lpr.filter.by.start.date.dropdown.option.choose.course',
135+
defaultMessage: 'Choose a course',
136+
description: 'Label for the Choose a course option in the start date filter dropdown in the admin portal LPR page.',
137+
})}
138+
</option>
101139
{searchCourseQuery && courseDates.map(date => (
102140
<option
103141
value={date}
104142
key={date}
105143
>
106-
{dayjs(date).format('MMMM D, YYYY')}
144+
{intl.formatDate(formatTimestamp({ timestamp: date }), {
145+
year: 'numeric',
146+
month: 'long',
147+
day: 'numeric',
148+
})}
107149
</option>
108150
))}
109151
</Form.Control>
110152
</Form.Group>
111153
</div>
112154
<div className="col-12 col-md-6 my-2 my-md-0 px-0 px-md-2 px-lg-3">
113-
<Form.Label id="search-email-label" className="mb-2">Filter by email</Form.Label>
155+
<Form.Label id="search-email-label" className="mb-2">
156+
<FormattedMessage
157+
id="admin.portal.lpr.filter.by.email.input.label"
158+
defaultMessage="Filter by email"
159+
description="Label for the email filter dropdown in the admin portal LPR page"
160+
/>
161+
</Form.Label>
114162
<SearchBar
115-
placeholder="Search by email..."
163+
placeholder={intl.formatMessage({
164+
id: 'admin.portal.lpr.filter.by.email.input.placeholder',
165+
defaultMessage: 'Search by email...',
166+
description: 'Placeholder text for the email filter input in the admin portal LPR page.',
167+
})}
116168
onSearch={query => updateUrl(this.props.navigate, this.props.location.pathname, {
117169
search: query,
118170
page: 1,
@@ -147,6 +199,8 @@ AdminSearchForm.propTypes = {
147199
location: PropTypes.shape({
148200
pathname: PropTypes.string,
149201
}),
202+
// injected
203+
intl: intlShape.isRequired,
150204
};
151205

152-
export default withLocation(withNavigate(AdminSearchForm));
206+
export default withLocation(withNavigate(injectIntl(AdminSearchForm)));

src/components/Admin/AdminSearchForm.test.jsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { mount } from 'enzyme';
33
import { FormControl } from '@edx/paragon';
4+
import { IntlProvider } from '@edx/frontend-platform/i18n';
45

56
import AdminSearchForm from './AdminSearchForm';
67
import SearchBar from '../SearchBar';
@@ -17,9 +18,17 @@ const DEFAULT_PROPS = {
1718
tableData: [],
1819
};
1920

21+
const AdminSearchFormWrapper = props => (
22+
<IntlProvider locale="en">
23+
<AdminSearchForm {...props} />
24+
</IntlProvider>
25+
);
26+
2027
describe('<AdminSearchForm />', () => {
2128
it('displays three filters', () => {
22-
const wrapper = mount(<AdminSearchForm {...DEFAULT_PROPS} />);
29+
const wrapper = mount(
30+
<AdminSearchFormWrapper {...DEFAULT_PROPS} />,
31+
);
2332
expect(wrapper.find(FormControl)).toHaveLength(2);
2433
expect(wrapper.find(SearchBar)).toHaveLength(1);
2534
expect(wrapper.find(FormControl).at(1).text()).toContain('Choose a course');
@@ -32,7 +41,9 @@ describe('<AdminSearchForm />', () => {
3241
it(`calls searchEnrollmentsList when ${Object.keys(searchParams)[0]} changes`, () => {
3342
const spy = jest.fn();
3443
const props = { ...DEFAULT_PROPS, searchEnrollmentsList: spy };
35-
const wrapper = mount(<AdminSearchForm {...props} />);
44+
const wrapper = mount(
45+
<AdminSearchFormWrapper {...props} />,
46+
);
3647
wrapper.setProps({ searchParams });
3748
expect(spy).toHaveBeenCalledTimes(1);
3849
});

src/components/Admin/EmbeddedSubscription.jsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import dayjs from 'dayjs';
33
import { useParams, Link } from 'react-router-dom';
44
import { Form, Icon } from '@edx/paragon';
55
import { Lightbulb, ArrowOutward } from '@edx/paragon/icons';
6+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
67
import ConnectedSubscriptionDetailPage from './SubscriptionDetailPage';
78
import { SubscriptionContext } from '../subscriptions/SubscriptionData';
89
import { sortSubscriptionsByStatus } from '../subscriptions/data/utils';
@@ -36,11 +37,23 @@ const EmbeddedSubscription = () => {
3637
!loading && activeSubscriptions.length > 0
3738
&& (
3839
<>
39-
<h2 className="mt-4.5 mb-4">Manage Learners</h2>
40+
<h2 className="mt-4.5 mb-4">
41+
<FormattedMessage
42+
id="admin.portal.lpr.embedded.subscription.section.heading"
43+
defaultMessage="Manage Learners"
44+
description="Heading for the embedded subscription section on lpr page."
45+
/>
46+
</h2>
4047
{activeSubscriptions.length > 1
4148
? (
4249
<>
43-
<p className="ml-4 mt-3">Filter by subscription plan</p>
50+
<p className="ml-4 mt-3">
51+
<FormattedMessage
52+
id="admin.portal.lpr.embedded.subscription.section.filter.by.subscription.dropdown.label"
53+
defaultMessage="Filter by subscription plan"
54+
description="Label for the subscription plan filter dropdown in the embedded subscription section on lpr page."
55+
/>
56+
</p>
4457
<div className="ml-2 col-8 col-md-6">
4558
<Form.Control
4659
as="select"
@@ -59,19 +72,34 @@ const EmbeddedSubscription = () => {
5972
<ConnectedSubscriptionDetailPage enterpriseSlug={enterpriseSlug} match={match} />
6073
<div className="d-flex align-items-center">
6174
<Icon src={Lightbulb} className="text-danger mr-2" />
62-
<span> Help Center: Learners report that nudges have a positive impact on their motivation and
63-
performance.
75+
<span>
76+
<FormattedMessage
77+
id="admin.portal.lpr.embedded.subscription.section.info.nudges"
78+
defaultMessage="Help Center: Learners report that nudges have a positive impact on their motivation and performance."
79+
description="Nudges info message for the embedded subscription section on lpr page."
80+
/>
6481
</span>
6582
</div>
6683
<div className="align-items-center">
67-
<span className="ml-4.5"> Learn more helpful tips in </span>
84+
<span className="ml-4.5">
85+
<FormattedMessage
86+
id="admin.portal.lpr.embedded.subscription.section.info.learn.more"
87+
defaultMessage="Learn more helpful tips in <a>Best Practices</a>"
88+
description="Learn more helpful tips link in the embedded subscription section on lpr page."
89+
/* eslint-disable react/no-unstable-nested-components */
90+
values={{
91+
a: chunks => (
92+
<Link
93+
to={bestPracticesUrl}
94+
target="_blank"
95+
>{chunks}
96+
</Link>
97+
),
98+
}}
99+
/* eslint-disable react/no-unstable-nested-components */a
100+
/>
101+
</span>
68102
<span>
69-
<Link
70-
to={bestPracticesUrl}
71-
target="_blank"
72-
>
73-
Best Practices
74-
</Link>
75103
<Icon className="d-inline-flex ml-2" src={ArrowOutward} />
76104
</span>
77105
</div>

src/components/Admin/SubscriptionDetailPage.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import { connect } from 'react-redux';
44

5+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
6+
57
import SubscriptionExpirationModals from '../subscriptions/expiration/SubscriptionExpirationModals';
68
import SubscriptionDetails from './SubscriptionDetails';
79
import LicenseAllocationDetails from './licenses/LicenseAllocationDetails';
@@ -17,7 +19,13 @@ export const SubscriptionDetailPage = ({ enterpriseSlug, match }) => {
1719

1820
if (!subscription && !loadingSubscription) {
1921
return (
20-
<div>No subscription available</div>
22+
<div>
23+
<FormattedMessage
24+
id="admin.portal.lpr.embedded.subscription.section.no.subscription.available"
25+
defaultMessage="No subscription available"
26+
description="Message displayed when no subscription is available for the enterprise"
27+
/>
28+
</div>
2129
);
2230
}
2331

src/components/Admin/SubscriptionDetails.jsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import React, { useContext, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import { connect } from 'react-redux';
4-
import dayjs from 'dayjs';
54
import {
65
Row, Col, Toast, Button,
76
} from '@edx/paragon';
87

98
import { Link } from 'react-router-dom';
9+
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
1010
import { SubscriptionDetailContext } from '../subscriptions/SubscriptionDetailContextProvider';
1111
import InviteLearnersButton from '../subscriptions/buttons/InviteLearnersButton';
1212
import { SubscriptionContext } from '../subscriptions/SubscriptionData';
1313
import SubscriptionExpirationBanner from '../subscriptions/expiration/SubscriptionExpirationBanner';
1414
import { MANAGE_LEARNERS_TAB } from '../subscriptions/data/constants';
15+
import { i18nFormatTimestamp } from '../../utils';
1516

1617
const SubscriptionDetails = ({ enterpriseSlug }) => {
1718
const { forceRefresh } = useContext(SubscriptionContext);
@@ -26,8 +27,11 @@ const SubscriptionDetails = ({ enterpriseSlug }) => {
2627
const shouldShowInviteLearnersButton = (
2728
hasLicensesAllocatedOrRevoked && subscription.daysUntilExpiration > 0
2829
);
30+
const intl = useIntl();
2931

3032
const backToSubscriptionsPath = `/${enterpriseSlug}/admin/subscriptions/${MANAGE_LEARNERS_TAB}`;
33+
const subscriptionStartDate = i18nFormatTimestamp({ intl, timestamp: subscription.startDate });
34+
const subscriptionExpirationDate = i18nFormatTimestamp({ intl, timestamp: subscription.expirationDate });
3135

3236
return (
3337
<>
@@ -37,7 +41,7 @@ const SubscriptionDetails = ({ enterpriseSlug }) => {
3741
<div className="mt-3 d-flex align-items-center">
3842
<div className="mr-5">
3943
<span>
40-
{dayjs(subscription.startDate).format('MMMM D, YYYY')} - {dayjs(subscription.expirationDate).format('MMMM D, YYYY')}
44+
{subscriptionStartDate} - {subscriptionExpirationDate}
4145
</span>
4246
</div>
4347
</div>
@@ -46,14 +50,22 @@ const SubscriptionDetails = ({ enterpriseSlug }) => {
4650
<div className="text-md-right">
4751
<Link to={backToSubscriptionsPath}>
4852
<Button variant="outline-primary mr-2">
49-
Manage All Learners
53+
<FormattedMessage
54+
id="admin.portal.lpr.embedded.subscription.section.manage.all.learners.button.label"
55+
defaultMessage="Manage All Learners"
56+
description="Label for the manage all learners button in the embedded subscription section on lpr page."
57+
/>
5058
</Button>
5159
</Link>
5260
<InviteLearnersButton
5361
onSuccess={({ numAlreadyAssociated, numSuccessfulAssignments }) => {
5462
forceRefresh();
5563
forceRefreshDetailView();
56-
setToastMessage(`${numAlreadyAssociated} email addresses were previously assigned. ${numSuccessfulAssignments} email addresses were successfully added.`);
64+
setToastMessage(intl.formatMessage({
65+
id: 'admin.portal.lpr.embedded.subscription.section.invite.learners.toast.message',
66+
defaultMessage: '{numAlreadyAssociated} email addresses were previously assigned. {numSuccessfulAssignments} email addresses were successfully added.',
67+
description: 'Toast message displayed when learners are successfully invited to a subscription plan.',
68+
}, { numAlreadyAssociated, numSuccessfulAssignments }));
5769
setShowToast(true);
5870
}}
5971
disabled={subscription.isLockedForRenewalProcessing}

src/components/Admin/__snapshots__/Admin.test.jsx.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9860,8 +9860,7 @@ exports[`<Admin /> renders correctly with error state 1`] = `
98609860
Hey, nice to see you
98619861
</div>
98629862
<p>
9863-
Try refreshing your screen
9864-
Network Error
9863+
Try refreshing your screen Network Error
98659864
</p>
98669865
</div>
98679866
</div>

0 commit comments

Comments
 (0)