Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add i18n across the platform #19

Merged
merged 8 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 4 additions & 2 deletions src/ValidationPanelPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ const ValidationPanelPage = () => {
)
: (
<div className="container-lg">
<Stack direction="horizontal" className="my-5 justify-content-between">
<Stack direction="horizontal" className="mt-5 mb-4 justify-content-between align-items-end">
<div>
<h1>{intl.formatMessage(messages.heading)}</h1>
<h3>{isValidator ? 'Course validation processes' : 'My course validation processes' }</h3>
<h3 className="mt-5">{isValidator ? intl.formatMessage(messages.validatorTitle)
: intl.formatMessage(messages.courseAutorTitle)}
</h3>
</div>
{!isValidator && <Button onClick={open} variant="brand">{intl.formatMessage(messages.newRecordCreatorButton)}</Button>}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import * as Yup from 'yup';
import { useEffect, useState } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FormikProvider, useFormik } from 'formik';
import { Button, Stack } from '@edx/paragon';
import { useDispatch, useSelector } from 'react-redux';
Expand All @@ -15,8 +16,11 @@ import { SelectField } from '../SelectField';
import { ModalLayout } from '../ModalLayout';
import { getCourseValidationRequestForm } from './helpers';
import { getValidationBodies } from '../../data/api';
import globalMessages from '../../messages';
import messages from './messages';

const CourseValidationRequestForm = ({ isOpen, close }) => {
const intl = useIntl();
const dispatch = useDispatch();

const availableUserCourses = useSelector((state) => state.courses.availableUserCourses.data.results);
Expand All @@ -33,6 +37,7 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
availableUserCourses,
availableCourseCategories,
availableValidationBodies,
intl,
);

const initialValues = {
Expand All @@ -45,11 +50,11 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
};

const FormSchema = Yup.object().shape({
courseId: Yup.string().required('Please select a course!'),
validationBodyId: Yup.number().required('Please select a validation body!'),
courseId: Yup.string().required(intl.formatMessage(messages.feedbackSelectCourse)),
validationBodyId: Yup.number().required(intl.formatMessage(messages.feedbackSelectValidationBody)),
// When is needed category as array
// categoryIds: Yup.array().of(Yup.string()).min(1, 'Please select at least one category!'),
categoryId: Yup.number().required('Please select at least one category!'),
categoryId: Yup.number().required(intl.formatMessage(messages.feedbackSelectCategory)),
comment: Yup.string(),
});

Expand Down Expand Up @@ -99,7 +104,7 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
<FormikProvider value={formik}>
<ModalLayout isOpen={isOpen} onClose={handleClose}>
<Stack gap={3}>
<span className="lead">Submit a course for validation</span>
<span className="lead">{intl.formatMessage(messages.CourseValidationFormTitle)}</span>
<Stack gap={2}>
{
validationRequestFormFields?.map((field) => (
Expand All @@ -116,8 +121,8 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
}
</Stack>
<Stack gap={2} direction="horizontal" className="justify-content-end mb-4">
<Button variant="tertiary" onClick={handleClose} className="px-5">Cancel</Button>
<Button onClick={formik.handleSubmit} className="px-5">Submit</Button>
<Button variant="tertiary" onClick={handleClose} className="px-5">{intl.formatMessage(globalMessages.formActionCancel)}</Button>
<Button onClick={formik.handleSubmit} className="px-5">{intl.formatMessage(globalMessages.formActionSubmmit)}</Button>
</Stack>
</Stack>
</ModalLayout>
Expand Down
19 changes: 11 additions & 8 deletions src/components/CourseValidationRequestForm/helpers.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
/* eslint-disable import/prefer-default-export */
import { adaptOptions } from '../../utils/helpers';
import globalMessages from '../../messages';
import messages from './messages';

export const getCourseValidationRequestForm = (
availableUserCourses,
availableCourseCategories,
availableValidationBodies,
intl,
) => ([
{
name: 'courseId',
// select, autosuggest, textarea or input
as: 'autosuggest',
label: 'Course Name',
description: 'Please select one of your courses from the list below',
label: intl.formatMessage(globalMessages.courseName),
description: intl.formatMessage(messages.descriptionSelectCourse),
// prop when 'as' property is 'select'
// This properties come from API or Constants
options: adaptOptions(availableUserCourses)?.sort((a, b) => {
Expand All @@ -23,23 +26,23 @@ export const getCourseValidationRequestForm = (
{
name: 'validationBodyId',
as: 'autosuggest',
label: 'Validation Body',
description: 'Please select the applicable validation body for your course',
label: intl.formatMessage(globalMessages.validationBody),
description: intl.formatMessage(messages.descriptionSelectValidationBody),
options: adaptOptions(availableValidationBodies),
},
{
name: 'categoryId',
// When is needed category as array
// isArray: true,
as: 'autosuggest',
label: 'Category',
description: 'Please select the appropriate category for your course',
label: intl.formatMessage(globalMessages.categories),
description: intl.formatMessage(messages.descriptionSelectCategory),
options: adaptOptions(availableCourseCategories),
},
{
name: 'comment',
as: 'textarea',
label: 'Comments',
description: 'Type any comment or explanation for your course',
label: intl.formatMessage(globalMessages.comment),
description: intl.formatMessage(messages.descriptionComment),
},
]);
46 changes: 46 additions & 0 deletions src/components/CourseValidationRequestForm/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
CourseValidationFormTitle: {
id: 'course.validation.request.form.title',
defaultMessage: 'Submit a course for validation',
description: 'Title of the course validation request form',
},
descriptionSelectCourse: {
id: 'course.validation.request.form.description.course',
defaultMessage: 'Please select one of your courses from the list below',
description: 'Description display in the course select',
},
feedbackSelectCourse: {
id: 'course.validation.request.form.feedback.course',
defaultMessage: 'Please select a course',
description: 'Feedback display in the course select',
},
descriptionSelectValidationBody: {
id: 'course.validation.request.form.description.validation.body',
defaultMessage: 'Please select the applicable validation body for your course',
description: 'Description display in the validation body select',
},
feedbackSelectValidationBody: {
id: 'course.validation.request.form.feedback.validation.body',
defaultMessage: 'Please select a validation body',
description: 'Feedback display in the validation body select',
},
descriptionSelectCategory: {
id: 'course.validation.request.form.description.category',
defaultMessage: 'Please select the appropriate category for your course',
description: 'Description display in the category select',
},
feedbackSelectCategory: {
id: 'course.validation.request.form.feedback.category',
defaultMessage: 'Please select a category',
description: 'Feedback display in the category select',
},
descriptionComment: {
id: 'course.validation.request.form.description.comment',
defaultMessage: 'Type any comment or explanation for your course',
description: 'Description display in the description input',
},
});

export default messages;
19 changes: 7 additions & 12 deletions src/components/PopUpMessage/PopUpMessage.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
/* eslint-disable react/jsx-no-useless-fragment */
import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { useDispatch, useSelector } from 'react-redux';
import { resetPopUpMessage } from '../../data/slices';
import messages from './messages';

const codeMessages = {
409: 'The validation process for this course is already underway.',
400: 'This action cannot be completed at the moment. Please try refreshing the page and try again.',
401: 'You are not authorized to execute this action.',
404: 'There was an error trying to find the register you are looking for.',
500: 'An unknown error occurred. Please try again later.',
};

const getErrorMessage = (message) => {
const getErrorMessage = (message, intl) => {
if (message) {
const errCode = message?.match(/(\d+)/)?.[0];
return codeMessages[errCode];
return intl.formatMessage(messages[errCode]);
}

return '';
};

const PopUpMessage = () => {
const intl = useIntl();
const dispatch = useDispatch();
const { variant, message } = useSelector((state) => state.popUpMessage);

Expand All @@ -31,7 +26,7 @@ const PopUpMessage = () => {

return (
<>
{ !message
{!message
? null
: (
<Alert
Expand All @@ -44,7 +39,7 @@ const PopUpMessage = () => {
icon={Info}
style={{ position: 'fixed', bottom: 0, zIndex: 1 }}
>
{getErrorMessage(message)}
{getErrorMessage(message, intl)}
</Alert>
)}
</>
Expand Down
31 changes: 31 additions & 0 deletions src/components/PopUpMessage/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
409: {
id: 'pop.up.message.409.error',
defaultMessage: 'The validation process for this course is already underway.',
description: 'Message displayed when the API response with a 409 error',
},
400: {
id: 'pop.up.message.400.error',
defaultMessage: 'This action cannot be completed at the moment. Please try refreshing the page and try again.',
description: 'Message displayed when the API response with a 400 error',
},
401: {
id: 'pop.up.message.401.error',
defaultMessage: 'You are not authorized to execute this action.',
description: 'Message displayed when the API response with a 401 error',
},
404: {
id: 'pop.up.message.404.error',
defaultMessage: 'There was an error trying to find the register you are looking for.',
description: 'Message displayed when the API response with a 404 error',
},
500: {
id: 'pop.up.message.500.error',
defaultMessage: 'An unknown error occurred. Please try again later.',
description: 'Message displayed when the API response with a 500 error',
},
});

export default messages;
9 changes: 6 additions & 3 deletions src/components/SelectField/SelectField.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FieldArray } from 'formik';
import { Close } from '@edx/paragon/icons';
import { Chip, Form, Stack } from '@edx/paragon';
import { useState } from 'react';
import globalMessages from '../../messages';

const CustomOption = ({
className, onClick, value, optionId, label, children,
Expand Down Expand Up @@ -43,6 +45,7 @@ CustomOption.defaultProps = {
const SelectField = ({
label, description, name, as, options, handleChange, value, errorMessage, isArray, isLoading, disabled, setFieldValue,
}) => {
const intl = useIntl();
const [selected, setSelected] = useState('');
if (isArray) {
return (
Expand All @@ -53,7 +56,7 @@ const SelectField = ({
<span>{label}</span>
<span className="small">{description}</span>
<Form.Autosuggest
placeholder="Select at least one..."
placeholder={intl.formatMessage(globalMessages.multiselectPlaceholder)}
isInvalid={!!errorMessage}
onSelected={(newValue) => {
const valueFoundIndex = value.findIndex((el) => el === newValue);
Expand Down Expand Up @@ -99,7 +102,7 @@ const SelectField = ({
<span>{label}</span>
<span className="small">{description}</span>
<Form.Autosuggest
placeholder="Select one"
placeholder={intl.formatMessage(globalMessages.selectPlaceholder)}
isLoading={isLoading}
isInvalid={!!errorMessage}
value={value ? selected : ''}
Expand Down Expand Up @@ -147,7 +150,7 @@ const SelectField = ({
disabled={disabled}
size="sm"
>
<option hidden> Select one... </option>
<option hidden> {intl.formatMessage(globalMessages.multiselectPlaceholder)} </option>
{options?.map((optionInfo) => (
<option key={optionInfo.key} value={optionInfo.id}>{optionInfo.label}</option>
))}
Expand Down
28 changes: 14 additions & 14 deletions src/components/Timeline/RecordItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const RecordItem = ({
<FormattedMessage
id="record.item.validation.date"
defaultMessage="Validation date: {date}"
description="date when the record was created"
description="Date when the record was created"
values={{
date: (
<FormattedDate
Expand All @@ -50,7 +50,7 @@ const RecordItem = ({
<FormattedMessage
id="record.item.validation.status"
defaultMessage="<b>Status</b>: {status}"
description="status of the record"
description="Status of the record"
tagName="div"
values={{
b: bold,
Expand All @@ -60,7 +60,7 @@ const RecordItem = ({
<FormattedMessage
id="record.item.validation.reportedBy"
defaultMessage="<b>Reported by</b>: {user}"
description="reportedBy of the record"
description="Identify who reported the record"
tagName="div"
values={{
b: bold,
Expand All @@ -70,30 +70,30 @@ const RecordItem = ({
<FormattedMessage
id="record.item.validation.body"
defaultMessage="<b>Validator body</b>: {validationBody}"
description="identify who was in charge to validate the course"
description="Identify the validation body in charge to validate the course"
tagName="div"
values={{
b: bold,
validationBody,
}}
/>
{status === VALIDATION_STATUS.DISAPPROVED && (
<div>
<FormattedMessage
id="record.item.validation.reason"
defaultMessage="Reason: "
description="identify who was in charge to validate the course"
tagName="b"
/>
{reason}
</div>
<div>
<FormattedMessage
id="record.item.validation.reason"
defaultMessage="Reason: "
description="Identify the reason to reject the course"
tagName="b"
/>
{reason}
</div>
)}
</div>
<div className="mt-2">
<FormattedMessage
id="record.item.validation.comment"
defaultMessage="Info: "
description="Shown the label for additional information left for the validator"
description="Shown additional information left for the record reporter"
tagName="b"
/>
{comment}
Expand Down
Loading