Skip to content

Commit

Permalink
feat: add standard MFE i18n with atlas (#973)
Browse files Browse the repository at this point in the history
Refs: FC-0012 OEP-58
  • Loading branch information
OmarIthawi authored Jun 5, 2023
1 parent 6456910 commit 53b3d39
Show file tree
Hide file tree
Showing 42 changed files with 1,812 additions and 3,491 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
coverage/*
dist/
node_modules/
src/i18n/
src/segment.js
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ dist/

# edx
.env.private
src/i18n/
temp/
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
transifex_utils = ./node_modules/.bin/transifex-utils.js
intl_imports = ./node_modules/frontend-platform-shim/node_modules/.bin/intl-imports.js

i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl

shell: ## run a shell on the cookie-cutter container
docker exec -it /bin/bash

Expand Down Expand Up @@ -30,3 +38,26 @@ restart-detached:

validate-no-uncommitted-package-lock-changes:
git diff --name-only --exit-code package-lock.json

requirements:
npm install

i18n.extract:
# Pulling display strings from .jsx files into .json files...
rm -rf $(transifex_temp)
npm run-script i18n_extract

i18n.concat:
# Gathering JSON messages into one file...
$(transifex_utils) $(transifex_temp) $(transifex_input)

extract_translations: | requirements i18n.extract i18n.concat

pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-app-admin-portal/src/i18n/messages:frontend-app-admin-portal
$(intl_imports) paragon frontend-app-admin-portal
5 changes: 5 additions & 0 deletions docs/how_tos/i18n.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
####################
React App i18n HOWTO
####################

This document has moved to the frontend-platform repo: https://github.com/openedx/frontend-platform/blob/master/docs/how_tos/i18n.rst
4,415 changes: 1,218 additions & 3,197 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"dash-embedded-component": "file:packages/dash-embedded-component-2.0.2.tgz",
"file-saver": "1.3.8",
"font-awesome": "4.7.0",
"frontend-platform-shim": "file:packages/frontend-platform-shim",
"history": "4.10.1",
"html-react-parser": "3.0.7",
"jest-environment-jsdom": "26.6.1",
Expand Down
11 changes: 11 additions & 0 deletions packages/frontend-platform-shim/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "frontend-platform-shim",
"version": "1.0.0",
"description": "Shim package to install the `intl-imports.js` script from [email protected]+ until the `frontend-app-admin-portal` upgrades from [email protected] . This package should be removed once the `frontend-app-admin-portal` is upgraded therefore the `peerDependencies` pin to ensure the TODO is coded into the dependency tree.",
"dependencies": {
"@edx/frontend-platform": "4.5.1"
},
"peerDependencies": {
"@edx/frontend-platform": "<4.1.0"
}
}
43 changes: 23 additions & 20 deletions src/components/Admin/Admin.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { MemoryRouter, Link } from 'react-router-dom';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import { IntlProvider } from '@edx/frontend-platform/i18n';

import EnterpriseDataApiService from '../../data/services/EnterpriseDataApiService';
import Admin from './index';
Expand Down Expand Up @@ -40,26 +41,28 @@ const store = mockStore({
const AdminWrapper = props => (
<MemoryRouter>
<Provider store={store}>
<Admin
enterpriseId="test-enterprise"
enterpriseSlug="test-enterprise"
clearDashboardAnalytics={() => {}}
fetchDashboardAnalytics={() => {}}
fetchPortalConfiguration={() => {}}
fetchCsv={() => {}}
searchEnrollmentsList={() => {}}
tableData={[
{
course_title: 'Bears 101',
course_start: Date.now(),
},
]}
match={{
params: {},
url: '/',
}}
{...props}
/>
<IntlProvider locale="en">
<Admin
enterpriseId="test-enterprise"
enterpriseSlug="test-enterprise"
clearDashboardAnalytics={() => {}}
fetchDashboardAnalytics={() => {}}
fetchPortalConfiguration={() => {}}
fetchCsv={() => {}}
searchEnrollmentsList={() => {}}
tableData={[
{
course_title: 'Bears 101',
course_start: Date.now(),
},
]}
match={{
params: {},
url: '/',
}}
{...props}
/>
</IntlProvider>
</Provider>
</MemoryRouter>
);
Expand Down
62 changes: 50 additions & 12 deletions src/components/Admin/AdminCards.jsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,76 @@
import React from 'react';
import PropTypes from 'prop-types';

import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';

import NumberCard from '../NumberCard';

class AdminCards extends React.Component {
constructor(props) {
super(props);
const { intl } = this.props;

this.cards = {
numberOfUsers: {
ref: React.createRef(),
description: 'total number of learners registered',
description: intl.formatMessage({
id: 'adminPortal.cards.registeredLearners',
defaultMessage: 'total number of learners registered',
}),
iconClassName: 'fa fa-users',
actions: [{
label: 'Which learners are registered but not yet enrolled in any courses?',
label: intl.formatMessage({
id: 'adminPortal.cards.registeredUnenrolledLearners',
defaultMessage: 'Which learners are registered but not yet enrolled in any courses?',
}),
slug: 'registered-unenrolled-learners',
}],
},
enrolledLearners: {
ref: React.createRef(),
description: 'learners enrolled in at least one course',
description: intl.formatMessage({
id: 'adminPortal.cards.enrolledOneCourse',
defaultMessage: 'learners enrolled in at least one course',
}),
iconClassName: 'fa fa-check',
actions: [{
label: 'How many courses are learners enrolled in?',
label: intl.formatMessage({
id: 'adminPortal.cards.enrolledLearners',
defaultMessage: 'How many courses are learners enrolled in?',
}),
slug: 'enrolled-learners',
}, {
label: 'Who is no longer enrolled in a current course?',
label: intl.formatMessage({
id: 'adminPortal.cards.enrolledLearnersInactiveCourses',
defaultMessage: 'Who is no longer enrolled in a current course?',
}),
slug: 'enrolled-learners-inactive-courses',
}],
},
activeLearners: {
ref: React.createRef(),
description: 'active learners in the past week',
description: intl.formatMessage({
id: 'adminPortal.cards.activeLearnersPastWeek',
defaultMessage: 'active learners in the past week',
}),
iconClassName: 'fa fa-eye',
actions: [{
label: 'Who are my top active learners?',
label: intl.formatMessage({
id: 'adminPortal.cards.learnersActiveWeek',
defaultMessage: 'Who are my top active learners?',
}),
slug: 'learners-active-week',
}, {
label: 'Who has not been active for over a week?',
label: intl.formatMessage({
id: 'adminPortal.cards.learnersInactiveWeek',
defaultMessage: 'Who has not been active for over a week?',
}),
slug: 'learners-inactive-week',
}, {
label: 'Who has not been active for over a month?',
label: intl.formatMessage({
id: 'adminPortal.cards.learnersInactiveMonth',
defaultMessage: 'Who has not been active for over a month?',
}),
slug: 'learners-inactive-month',
}],
},
Expand All @@ -49,10 +79,16 @@ class AdminCards extends React.Component {
description: 'course completions',
iconClassName: 'fa fa-trophy',
actions: [{
label: 'How many courses have been completed by learners?',
label: intl.formatMessage({
id: 'adminPortal.cards.completedLearners',
defaultMessage: 'How many courses have been completed by learners?',
}),
slug: 'completed-learners',
}, {
label: 'Who completed a course in the past week?',
label: intl.formatMessage({
id: 'adminPortal.cards.completedLearnersWeek',
defaultMessage: 'Who completed a course in the past week?',
}),
slug: 'completed-learners-week',
}],
},
Expand Down Expand Up @@ -110,6 +146,8 @@ AdminCards.propTypes = {
numberOfUsers: PropTypes.number.isRequired,
courseCompletions: PropTypes.number.isRequired,
enrolledLearners: PropTypes.number.isRequired,
// injected
intl: intlShape.isRequired,
};

export default AdminCards;
export default injectIntl(AdminCards);
34 changes: 22 additions & 12 deletions src/components/CodeAssignmentModal/CodeAssignmentModal.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React from 'react';
import { Provider } from 'react-redux';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import { screen, render } from '@testing-library/react';
// import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/extend-expect';
Expand All @@ -11,13 +11,12 @@ import thunk from 'redux-thunk';
import { MemoryRouter } from 'react-router-dom';
import remindEmailTemplate from './emailTemplate';
import CodeAssignmentModal, { BaseCodeAssignmentModal } from '.';
import { ASSIGNMENT_MODAL_FIELDS, NOTIFY_LEARNERS_CHECKBOX_TEST_ID } from './constants';
import { getAssignmentModalFields, NOTIFY_LEARNERS_CHECKBOX_TEST_ID } from './constants';
import { displayCode, displaySelectedCodes } from '../CodeModal/codeModalHelpers';

import {
EMAIL_TEMPLATE_SOURCE_NEW_EMAIL,
} from '../../data/constants/emailTemplate';
import { EMAIL_FORM_NAME } from '../EmailTemplateForm';

jest.mock('redux-form', () => ({
...jest.requireActual('redux-form'),
Expand Down Expand Up @@ -122,14 +121,20 @@ const initialState = {
},
};

const mockIntl = {
formatMessage: message => message.defaultMessage,
};

/* eslint-disable react/prop-types */
const CodeAssignmentModalWrapper = (props) => (
<MemoryRouter>
<Provider store={mockStore(initialState)}>
<CodeAssignmentModal
{...initialProps}
{...props}
/>
<IntlProvider locale="en">
<CodeAssignmentModal
{...initialProps}
{...props}
/>
</IntlProvider>
</Provider>
</MemoryRouter>
);
Expand All @@ -148,9 +153,12 @@ describe('CodeAssignmentModal component', () => {
render(
<MemoryRouter>
<Provider store={mockStore(initialState)}>
<BaseCodeAssignmentModal
{...props}
/>
<IntlProvider locale="en">
<BaseCodeAssignmentModal
{...props}
intl={mockIntl}
/>
</IntlProvider>
</Provider>
</MemoryRouter>,
);
Expand All @@ -167,11 +175,13 @@ describe('CodeAssignmentModal component', () => {
});
it('renders an email template form', () => {
render(<CodeAssignmentModalWrapper />);
expect(screen.getByText(EMAIL_FORM_NAME)).toBeInTheDocument();
expect(screen.getByText('Email Template')).toBeInTheDocument();
});
it('renders a auto-reminder checkbox', () => {
const formatMessageMock = message => message.defaultMessage;
const assignmentModalFields = getAssignmentModalFields(formatMessageMock);
render(<CodeAssignmentModalWrapper />);
expect(screen.getByText(ASSIGNMENT_MODAL_FIELDS['enable-nudge-emails'].label)).toBeInTheDocument();
expect(screen.getByText(assignmentModalFields['enable-nudge-emails'].label)).toBeInTheDocument();
});
it('renders notify learners toggle checkbox', () => {
render(<CodeAssignmentModalWrapper />);
Expand Down
33 changes: 19 additions & 14 deletions src/components/CodeAssignmentModal/constants.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { MODAL_TYPES } from '../EmailTemplateForm/constants';
import { EMAIL_TEMPLATE_FIELDS } from '../EmailTemplateForm';
import { getTemplateEmailFields } from '../EmailTemplateForm';
import CheckboxWithTooltip from '../ReduxFormCheckbox/CheckboxWithTooltip';

import messages from './messages';

export const ASSIGNMENT_ERROR_TITLES = {
[MODAL_TYPES.assign]: 'Unable to assign codes',
[MODAL_TYPES.save]: 'Unable to save template',
};
export const EMAIL_TEMPLATE_NUDGE_EMAIL_ID = 'enable-nudge-emails';

export const ASSIGNMENT_MODAL_FIELDS = {
...EMAIL_TEMPLATE_FIELDS,
[EMAIL_TEMPLATE_NUDGE_EMAIL_ID]: {
name: EMAIL_TEMPLATE_NUDGE_EMAIL_ID,
id: EMAIL_TEMPLATE_NUDGE_EMAIL_ID,
component: CheckboxWithTooltip,
className: 'auto-reminder-wrapper',
icon: faInfoCircle,
altText: 'More information',
tooltipText: 'edX will remind learners to redeem their code 3, 10, and 19 days after you assign it.',
label: 'Automate reminders',
defaultChecked: true,
},
export const getAssignmentModalFields = formatMessage => {
const emailTemplateFields = getTemplateEmailFields(formatMessage);
return {
...emailTemplateFields,
[EMAIL_TEMPLATE_NUDGE_EMAIL_ID]: {
name: EMAIL_TEMPLATE_NUDGE_EMAIL_ID,
id: EMAIL_TEMPLATE_NUDGE_EMAIL_ID,
component: CheckboxWithTooltip,
className: 'auto-reminder-wrapper',
icon: faInfoCircle,
altText: formatMessage(messages.modalAltText),
tooltipText: formatMessage(messages.modalTooltipText),
label: formatMessage(messages.modalFieldLabel),
defaultChecked: true,
},
};
};

export const NOTIFY_LEARNERS_CHECKBOX_TEST_ID = 'notify-learners-checkbox';
Expand Down
Loading

0 comments on commit 53b3d39

Please sign in to comment.