feat: Persian language support added (#553)

* fix: corrected typos

justify-contnt-center -> justify-content-center
Visiblity -> Visibility
Wraper -> Wrapper
closeAssetinfo -> closeAssetInfo
RestictDatesInput -> RestrictDatesInput
isOnSmallcreen -> isOnSmallScreen
Repsonse -> Response
configuation -> configuration
seconary -> secondary
comparesion -> comparison

* feat: Persian language (fa_IR) added

* refactor: better variable name for languages

* chore: sort languages alphabetically
This commit is contained in:
Emad Rad
2023-11-16 07:01:08 +03:30
committed by GitHub
parent 2804f38d4f
commit 91019b4a51
14 changed files with 61 additions and 58 deletions

View File

@@ -1,6 +1,6 @@
transifex_resource = frontend-app-course-authoring
export TRANSIFEX_RESOURCE = ${transifex_resource}
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA,it_IT,pt_PT,de_DE"
transifex_langs = "ar,de,de_DE,es_419,fa_IR,fr,fr_CA,hi,it,it_IT,pt,pt_PT,ru,uk,zh_CN"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js

View File

@@ -85,7 +85,7 @@ const AdvancedSettings = ({ intl, courseId }) => {
}
if (loadingSettingsStatus === RequestStatus.DENIED) {
return (
<div className="row justify-contnt-center m-6">
<div className="row justify-content-center m-6">
<Placeholder />
</div>
);

View File

@@ -41,7 +41,7 @@ const CustomPageCard = ({
}));
};
const toggleVisibilty = () => {
const toggleVisibility = () => {
dispatch(updateCustomPageVisibility({
blockId: page.id,
metadata: { course_staff_only: !page.courseStaffOnly },
@@ -87,7 +87,7 @@ const CustomPageCard = ({
src={page.courseStaffOnly ? VisibilityOff : Visibility}
iconAs={Icon}
alt={intl.formatMessage(messages.visibilityTooltipContent)}
onClick={toggleVisibilty}
onClick={toggleVisibility}
data-testid="visibility-toggle-icon"
/>
<IconButtonWithTooltip

View File

@@ -19,7 +19,7 @@ import { executeThunk } from '../utils';
import { RequestStatus } from '../data/constants';
import CustomPageCard from './CustomPageCard';
import {
generateUpdateVisiblityApiResponse,
generateUpdateVisibilityApiResponse,
courseId,
initialState,
generateXblockData,
@@ -64,7 +64,7 @@ const mockStore = async ({
const xblockEditUrl = `${getApiBaseUrl()}/xblock/${blockId}`;
axiosMock.onDelete(xblockEditUrl).reply(204);
axiosMock.onPut(xblockEditUrl).reply(200, generateUpdateVisiblityApiResponse(blockId, visibility));
axiosMock.onPut(xblockEditUrl).reply(200, generateUpdateVisibilityApiResponse(blockId, visibility));
axiosMock.onGet(xblockEditUrl).reply(200, generateXblockData(blockId));
await executeThunk(deleteSingleCustomPage({

View File

@@ -105,7 +105,7 @@ const CustomPages = ({
}
if (loadingStatus === RequestStatus.DENIED) {
return (
<div data-testid="under-construction-placeholder" className="row justify-contnt-center m-6">
<div data-testid="under-construction-placeholder" className="row justify-content-center m-6">
<Placeholder />
</div>
);

View File

@@ -52,7 +52,7 @@ export const generateXblockData = (
data: '<p>test</p>',
});
export const generateUpdateVisiblityApiResponse = (
export const generateUpdateVisibilityApiResponse = (
blockId,
visibility,
) => ({

View File

@@ -33,8 +33,8 @@ export function fetchAssets(courseId) {
try {
const { totalCount } = await getAssets(courseId);
const { assets } = await getAssets(courseId, totalCount);
const parsedAssests = updateFileValues(assets);
dispatch(addModels({ modelType: 'assets', models: parsedAssests }));
const parsedAssets = updateFileValues(assets);
dispatch(addModels({ modelType: 'assets', models: parsedAssets }));
dispatch(setAssetIds({
assetIds: assets.map(asset => asset.id),
}));
@@ -79,10 +79,10 @@ export function addAssetFile(courseId, file) {
try {
const { asset } = await addAsset(courseId, file);
const [parsedAssest] = updateFileValues([asset]);
const [parsedAssets] = updateFileValues([asset]);
dispatch(addModel({
modelType: 'assets',
model: { ...parsedAssest },
model: { ...parsedAssets },
}));
dispatch(addAssetSuccess({
assetId: asset.id,

View File

@@ -144,7 +144,7 @@ const messages = defineMessages({
'header.label.secondary.nav': {
id: 'header.label.secondary.nav',
defaultMessage: 'Secondary',
description: 'The aria label for the seconary nav',
description: 'The aria label for the secondary nav',
},
'header.label.courseOutline': {
id: 'header.label.courseOutline',

View File

@@ -1,36 +1,38 @@
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as paragonMessages } from '@edx/paragon';
import arMessages from './messages/ar.json';
import frMessages from './messages/fr.json';
import es419Messages from './messages/es_419.json';
import zhcnMessages from './messages/zh_CN.json';
import ptMessages from './messages/pt.json';
import itMessages from './messages/it.json';
import ukMessages from './messages/uk.json';
import deMessages from './messages/de.json';
import ruMessages from './messages/ru.json';
import hiMessages from './messages/hi.json';
import deDEMessages from './messages/de_DE.json';
import es419Messages from './messages/es_419.json';
import faIRMessages from './messages/fa_IR.json';
import frMessages from './messages/fr.json';
import frCAMessages from './messages/fr_CA.json';
import dedeMessages from './messages/de_DE.json';
import ititMessages from './messages/it_IT.json';
import ptptMessages from './messages/pt_PT.json';
import hiMessages from './messages/hi.json';
import itMessages from './messages/it.json';
import itITMessages from './messages/it_IT.json';
import ptMessages from './messages/pt.json';
import ptPTMessages from './messages/pt_PT.json';
import ruMessages from './messages/ru.json';
import ukMessages from './messages/uk.json';
import zhCNMessages from './messages/zh_CN.json';
// no need to import en messages-- they are in the defaultMessage field
const appMessages = {
ar: arMessages,
'es-419': es419Messages,
fr: frMessages,
'zh-cn': zhcnMessages,
pt: ptMessages,
it: itMessages,
de: deMessages,
hi: hiMessages,
'de-de': deDEMessages,
'es-419': es419Messages,
'fa-ir': faIRMessages,
fr: frMessages,
'fr-ca': frCAMessages,
hi: hiMessages,
it: itMessages,
'it-it': itITMessages,
ru: ruMessages,
pt: ptMessages,
'pt-pt': ptPTMessages,
uk: ukMessages,
'de-de': dedeMessages,
'it-it': ititMessages,
'pt-pt': ptptMessages,
'zh-cn': zhCNMessages,
};
export default [

View File

@@ -0,0 +1 @@
{}

View File

@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import FieldFeedback from '../../../../../../generic/FieldFeedback';
const RestictDatesInput = ({
const RestrictDatesInput = ({
value,
type,
label,
@@ -64,7 +64,7 @@ const RestictDatesInput = ({
);
};
RestictDatesInput.propTypes = {
RestrictDatesInput.propTypes = {
value: PropTypes.string.isRequired,
fieldName: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
@@ -77,7 +77,7 @@ RestictDatesInput.propTypes = {
dataTestId: PropTypes.string,
};
RestictDatesInput.defaultProps = {
RestrictDatesInput.defaultProps = {
fieldClasses: '',
helpText: '',
feedbackClasses: '',
@@ -85,4 +85,4 @@ RestictDatesInput.defaultProps = {
dataTestId: null,
};
export default React.memo(RestictDatesInput);
export default React.memo(RestrictDatesInput);

View File

@@ -33,7 +33,7 @@ const AppList = ({ intl }) => {
const features = useModels('features', featureIds);
const isGlobalStaff = getAuthenticatedUser().administrator;
const ltiProvider = !['openedx', 'legacy'].includes(activeAppId);
const isOnSmallcreen = useIsOnSmallScreen();
const isOnSmallScreen = useIsOnSmallScreen();
const showOneEdxProvider = useMemo(() => apps.filter(app => (
activeAppId === 'openedx' ? app.id !== 'legacy' : app.id !== 'openedx'
@@ -122,8 +122,8 @@ const AppList = ({ intl }) => {
return (
<div className="my-sm-4" data-testid="appList">
<div className={!isOnSmallcreen ? 'd-flex flex-row justify-content-between align-items-center' : 'mb-4'}>
<h3 className={isOnSmallcreen ? 'mb-3' : 'm-0'}>
<div className={!isOnSmallScreen ? 'd-flex flex-row justify-content-between align-items-center' : 'mb-4'}>
<h3 className={isOnSmallScreen ? 'mb-3' : 'm-0'}>
{intl.formatMessage(messages.heading)}
</h3>
<Form.Switch
@@ -144,7 +144,7 @@ const AppList = ({ intl }) => {
lg: 4,
xl: 4,
}}
className={!isOnSmallcreen && 'mt-5'}
className={!isOnSmallScreen && 'mt-5'}
>
{(isGlobalStaff || ltiProvider) ? showAppCard(apps) : showAppCard(showOneEdxProvider)}
</CardGrid>

View File

@@ -47,7 +47,7 @@ function renderComponent() {
container = wrapper.container;
}
function generateCourseLevelAPIRepsonse({
function generateCourseLevelAPIResponse({
success, enabled,
}) {
return {
@@ -96,7 +96,7 @@ describe('XpertUnitSummarySettings', () => {
describe('with successful network connections', () => {
beforeEach(() => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: true,
}));
@@ -111,7 +111,7 @@ describe('XpertUnitSummarySettings', () => {
test('Shows switch on if disabled from backend', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: false,
}));
@@ -129,7 +129,7 @@ describe('XpertUnitSummarySettings', () => {
test('Shows disable radio selected if enabled from backend', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: false,
}));
@@ -143,7 +143,7 @@ describe('XpertUnitSummarySettings', () => {
describe('first time course configuration', () => {
beforeEach(() => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(400, generateCourseLevelAPIRepsonse({
.reply(400, generateCourseLevelAPIResponse({
success: false,
enabled: undefined,
}));
@@ -151,7 +151,7 @@ describe('XpertUnitSummarySettings', () => {
renderComponent();
});
test('Does not show as enabled if configuation does not exist', async () => {
test('Does not show as enabled if configuration does not exist', async () => {
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(container.querySelector('#enable-xpert-unit-summary-toggle').checked).not.toBeTruthy();
expect(queryByTestId(container, 'enable-badge')).not.toBeTruthy();
@@ -161,13 +161,13 @@ describe('XpertUnitSummarySettings', () => {
describe('saving configuration changes', () => {
beforeEach(() => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: false,
}));
axiosMock.onPost(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: true,
}));
@@ -190,7 +190,7 @@ describe('XpertUnitSummarySettings', () => {
describe('testing configurable gating', () => {
beforeEach(async () => {
axiosMock.onGet(API.getXpertConfigurationStatusUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: true,
}));
@@ -207,13 +207,13 @@ describe('XpertUnitSummarySettings', () => {
describe('removing course configuration', () => {
beforeEach(() => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: true,
}));
axiosMock.onDelete(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: undefined,
}));
@@ -235,13 +235,13 @@ describe('XpertUnitSummarySettings', () => {
describe('resetting course units', () => {
test('reset all units to be enabled', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: true,
}));
axiosMock.onPost(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: true,
}));
@@ -257,13 +257,13 @@ describe('XpertUnitSummarySettings', () => {
test('reset all units to be disabled', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: false,
}));
axiosMock.onPost(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
.reply(200, generateCourseLevelAPIResponse({
success: true,
enabled: false,
}));

View File

@@ -181,8 +181,8 @@ export function setupYupExtensions() {
Yup.addMethod(Yup.string, 'compare', function compare(message, type) {
return this.test('isGreater', message, function isGreater() {
// This function compare 2 dates or 2 times. It return no error if dateInstance/timeInstance is empty
// of if startTime or endTime is not present for time comparesion
// or startDate or endDate is not present for date comparesion
// of if startTime or endTime is not present for time comparison
// or startDate or endDate is not present for date comparison
if (!this.parent
|| (!(this.parent.startTime && this.parent.endTime) && type === 'time')