I18n example (#33)

* feat: i18n pt 1

* fix: prevent float calculations when updating dimensions
This commit is contained in:
Ben Warzeski
2022-03-17 14:08:46 -04:00
committed by GitHub
parent a79da4cb2d
commit 3b8a7780ac
18 changed files with 160 additions and 94 deletions

View File

@@ -28,7 +28,11 @@ exports[`EditorFooter snapshots Save Failed, error message raised 1`] = `
}
variant="tertiary"
>
Cancel
<FormattedMessage
defaultMessage="Cancel"
description="Label for cancel button"
id="authoring.editorfooter.cancelButton.label"
/>
</Button>
<Button
aria-label="Save Changes and Return to Learning Context"
@@ -72,7 +76,11 @@ exports[`EditorFooter snapshots not intialized, Spinner appears and button is di
}
variant="tertiary"
>
Cancel
<FormattedMessage
defaultMessage="Cancel"
description="Label for cancel button"
id="authoring.editorfooter.cancelButton.label"
/>
</Button>
<Button
aria-label="Save Changes and Return to Learning Context"
@@ -115,7 +123,11 @@ exports[`EditorFooter snapshots renders as expected with default behavior 1`] =
}
variant="tertiary"
>
Cancel
<FormattedMessage
defaultMessage="Cancel"
description="Label for cancel button"
id="authoring.editorfooter.cancelButton.label"
/>
</Button>
<Button
aria-label="Save Changes and Return to Learning Context"

View File

@@ -9,13 +9,13 @@ import {
ModalDialog,
Toast,
} from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { nullMethod, saveBlock, navigateCallback } from '../../hooks';
import { RequestKeys } from '../../data/constants/requests';
import { selectors, thunkActions } from '../../data/redux';
import messages from '../messages';
import messages from './messages';
import * as module from '.';
export const handleSaveClicked = (props) => () => saveBlock(props);
@@ -23,6 +23,8 @@ export const handleCancelClicked = ({ returnUrl }) => navigateCallback(returnUrl
export const EditorFooter = ({
editorRef,
// injected
intl,
// redux
isInitialized,
returnUrl,
@@ -38,14 +40,14 @@ export const EditorFooter = ({
<ActionRow>
<ActionRow.Spacer />
<Button
aria-label="Discard Changes and Return to Learning Context"
aria-label={intl.formatMessage(messages.cancelButtonAriaLabel)}
variant="tertiary"
onClick={module.handleCancelClicked({ returnUrl })}
>
Cancel
<FormattedMessage {...messages.cancelButtonLabel} />
</Button>
<Button
aria-label="Save Changes and Return to Learning Context"
aria-label={intl.formatMessage(messages.saveButtonAriaLabel)}
onClick={module.handleSaveClicked({
editorRef,
returnUrl,
@@ -70,6 +72,8 @@ EditorFooter.propTypes = {
PropTypes.func,
PropTypes.shape({ current: PropTypes.any }),
]),
// injected
intl: intlShape.isRequired,
// redux
isInitialized: PropTypes.bool.isRequired,
returnUrl: PropTypes.string,
@@ -88,4 +92,4 @@ export const mapDispatchToProps = {
saveBlockContent: thunkActions.app.saveBlock,
};
export default connect(mapStateToProps, mapDispatchToProps)(EditorFooter);
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(EditorFooter));

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { shallow } from 'enzyme';
import * as module from './index';
import { formatMessage } from '../../../testUtils';
import { selectors, thunkActions } from '../../data/redux';
import { RequestKeys } from '../../data/constants/requests';
import { saveBlock, navigateCallback } from '../../hooks';
import * as module from './index';
jest.mock('../../data/redux', () => ({
thunkActions: {
@@ -39,6 +41,7 @@ jest.mock('../../hooks', () => ({
describe('EditorFooter', () => {
const props = {
intl: { formatMessage },
editorRef: jest.fn().mockName('args.editorRef'),
isInitialized: true,
returnUrl: 'hocuspocus.ca',

View File

@@ -0,0 +1,29 @@
export const messages = {
contentSaveFailed: {
id: 'authoring.editorfooter.save.error',
defaultMessage: 'Error: Content save failed. Try again later.',
description: 'Error message displayed when content fails to save.',
},
cancelButtonAriaLabel: {
id: 'authoring.editorfooter.cancelButton.ariaLabel',
defaultMessage: 'Discard Changes and Return to Learning Context',
description: 'Aria label for cancel button',
},
cancelButtonLabel: {
id: 'authoring.editorfooter.cancelButton.label',
defaultMessage: 'Cancel',
description: 'Label for cancel button',
},
saveButtonAriaLabel: {
id: 'authoring.editorfooter.savebutton.ariaLabel',
defaultMessage: 'Save Changes and Return to Learning Context',
description: 'Aria label for save button',
},
addToCourse: {
id: 'authoring.editorfooter.savebutton.label',
defaultMessage: 'Save',
description: 'Label for Save button',
},
};
export default messages;

View File

@@ -4,20 +4,21 @@ import PropTypes from 'prop-types';
import { Icon, IconButton } from '@edx/paragon';
import { Edit } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { actions, selectors } from '../../data/redux';
import { localTitleHooks } from './hooks';
import messages from '../messages';
import messages from './messages';
import EditableHeader from './EditableHeader';
export const HeaderTitle = ({
editorRef,
intl,
isInitialized,
setBlockTitle,
typeHeader,
}) => {
if (!isInitialized) { return <FormattedMessage {...messages.loading} />; }
if (!isInitialized) { return intl.formatMessage(messages.loading); }
const {
inputRef,
isEditing,
@@ -51,8 +52,7 @@ export const HeaderTitle = ({
{localTitle}
</div>
<IconButton
alt="Edit"
aria-label="Edit Title"
alt={intl.formatMessage(messages.editTitleLabel)}
className="mr-2"
iconAs={Icon}
onClick={startEditing}
@@ -70,6 +70,8 @@ HeaderTitle.propTypes = {
PropTypes.func,
PropTypes.shape({ current: PropTypes.any }),
]),
// injected
intl: intlShape.isRequired,
// redux
isInitialized: PropTypes.bool.isRequired,
setBlockTitle: PropTypes.func.isRequired,
@@ -85,4 +87,4 @@ export const mapDispatchToProps = {
setBlockTitle: actions.app.setBlockTitle,
};
export default connect(mapStateToProps, mapDispatchToProps)(HeaderTitle);
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(HeaderTitle));

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import * as module from './HeaderTitle';
import { formatMessage } from '../../../testUtils';
import { actions, selectors } from '../../data/redux';
import { localTitleHooks } from './hooks';
import * as module from './HeaderTitle';
jest.mock('../../data/redux', () => ({
actions: {
@@ -25,6 +26,7 @@ jest.mock('./hooks', () => ({
describe('HeaderTitle', () => {
const props = {
intl: { formatMessage },
editorRef: jest.fn().mockName('args.editorRef'),
isInitialized: false,
setBlockTitle: jest.fn().mockName('args.setBlockTitle'),

View File

@@ -25,8 +25,7 @@ exports[`HeaderTitle snapshots initialized 1`] = `
TeST LocALtitLE
</div>
<IconButton
alt="Edit"
aria-label="Edit Title"
alt="Edit Title"
className="mr-2"
iconAs="Icon"
onClick={[MockFunction localTitleHooks.startEditing]}
@@ -36,10 +35,4 @@ exports[`HeaderTitle snapshots initialized 1`] = `
</div>
`;
exports[`HeaderTitle snapshots not initialized 1`] = `
<FormattedMessage
defaultMessage="Loading..."
description="Message displayed while loading content"
id="authoring.texteditor.title.loading"
/>
`;
exports[`HeaderTitle snapshots not initialized 1`] = `"Loading..."`;

View File

@@ -11,8 +11,7 @@ exports[` 1`] = `
</ModalDialog.Title>
<ActionRow.Spacer />
<IconButton
alt="Close"
aria-label="Cancel Changes and Return to Learning Context"
alt="Cancel Changes and Return to Learning Context"
className="mr-2"
iconAs="Icon"
src={[MockFunction icons.Close]}

View File

@@ -9,23 +9,24 @@ import {
ModalDialog,
} from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { selectors } from '../../data/redux';
import * as appHooks from '../../hooks';
import HeaderTitle from './HeaderTitle';
import messages from './messages';
export const EditorHeader = ({ returnUrl }) => (
export const EditorHeader = ({ intl, returnUrl }) => (
<div className="editor-header">
<ModalDialog.Header>
<ActionRow>
<ModalDialog.Title><HeaderTitle /></ModalDialog.Title>
<ActionRow.Spacer />
<IconButton
aria-label="Cancel Changes and Return to Learning Context"
alt={intl.formatMessage(messages.cancelChangesLabel)}
src={Close}
iconAs={Icon}
alt="Close"
onClick={appHooks.navigateCallback(returnUrl)}
variant="light"
className="mr-2"
@@ -35,10 +36,13 @@ export const EditorHeader = ({ returnUrl }) => (
</div>
);
EditorHeader.propTypes = {
// injected
intl: intlShape.isRequired,
// redux
returnUrl: PropTypes.string.isRequired,
};
export const mapStateToProps = (state) => ({
returnUrl: selectors.app.returnUrl(state),
});
export default connect(mapStateToProps)(EditorHeader);
export default injectIntl(connect(mapStateToProps)(EditorHeader));

View File

@@ -2,9 +2,11 @@ import React from 'react';
import { shallow } from 'enzyme';
import { IconButton } from '@edx/paragon';
import * as module from './index';
import { formatMessage } from '../../../testUtils';
import { selectors } from '../../data/redux';
import * as appHooks from '../../hooks';
import * as module from './index';
jest.mock('.', () => ({
__esModule: true, // Use it when dealing with esModules
@@ -28,6 +30,7 @@ jest.mock('./HeaderTitle', () => 'HeaderTitle');
describe('Editor Header index', () => {
const props = {
intl: { formatMessage },
returnUrl: 'TeST-ReTurNurL',
};
const { EditorHeader } = module;

View File

@@ -0,0 +1,19 @@
export const messages = {
loading: {
id: 'authoring.texteditor.title.loading',
description: 'Message displayed while loading content',
defaultMessage: 'Loading...',
},
cancelChangesLabel: {
id: 'authoring.texteditor.header.cancelChangesLabel',
defaultMessage: 'Cancel Changes and Return to Learning Context',
description: 'Aria label title for icon button to return to learning context',
},
editTitleLabel: {
id: 'authoring.texteditor.header.editTitleLabel',
defaultMessage: 'Edit Title',
description: 'Aria label title for icon button to edit the xblock title',
},
};
export default messages;

View File

@@ -1,19 +0,0 @@
export const messages = {
contentSaveFailed: {
id: 'authoring.editorfooter.save.error',
defaultMessage: 'Error: Content save failed. Try again later.',
description: 'Error message displayed when content fails to save.',
},
addToCourse: {
id: 'authoring.editorfooter.savebutton.label',
defaultMessage: 'Save',
description: 'Label for Save button',
},
loading: {
id: 'authoring.texteditor.title.loading',
description: 'Message displayed while loading content',
defaultMessage: 'Loading...',
},
};
export default messages;

View File

@@ -59,8 +59,8 @@ export const getValidDimensions = ({
// if closest valid iteration is current iteration, move one iteration in the change direction
if (iter === (dimensions[keys.changed] / minInc[keys.changed])) { iter += direction; }
out[keys.changed] = iter * minInc[keys.changed];
out[keys.other] = out[keys.changed] * (locked[keys.other] / locked[keys.changed]);
out[keys.changed] = Math.round(iter * minInc[keys.changed]);
out[keys.other] = Math.round(out[keys.changed] * (locked[keys.other] / locked[keys.changed]));
return out;
};