From 78d202fcfdda3d3d8cfc982a393dc12da9f58a74 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Wed, 12 Aug 2020 19:14:26 +0100 Subject: [PATCH 1/3] Convert Console component to use translations --- client/modules/IDE/components/Console.jsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx index 749caaaadf..a2494ecbe6 100644 --- a/client/modules/IDE/components/Console.jsx +++ b/client/modules/IDE/components/Console.jsx @@ -1,4 +1,6 @@ import React, { useRef } from 'react'; +import PropTypes from 'prop-types'; +import { withTranslation } from 'react-i18next'; import { bindActionCreators } from 'redux'; @@ -72,7 +74,7 @@ const getConsoleFeedStyle = (theme, times, fontSize) => { } }; -const Console = () => { +const Console = ({ t }) => { const consoleEvents = useSelector(state => state.console); const isExpanded = useSelector(state => state.ide.consoleIsExpanded); const { theme, fontSize } = useSelector(state => state.preferences); @@ -96,19 +98,19 @@ const Console = () => { return (
-

Console

+

{t('Console.Title')}

- -
@@ -138,5 +140,9 @@ const Console = () => { ); }; +Console.propTypes = { + t: PropTypes.func.isRequired, +}; + -export default Console; +export default withTranslation()(Console); From 1d451fd6287681187e396532c5a6751745917ffb Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Thu, 13 Aug 2020 10:54:58 +0100 Subject: [PATCH 2/3] Extracts Sidebar/FileNode to use English translations - Fixes typo with "Close file contents", should be "folder" - Creates Common.DeleteConfirmation as it's used in many places in the app --- client/modules/IDE/components/FileNode.jsx | 38 ++++++++++++-------- client/modules/IDE/components/Sidebar.jsx | 23 ++++++------ translations/locales/en-US/translations.json | 35 +++++++++++++----- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index e7869f26b4..15b4103a41 100644 --- a/client/modules/IDE/components/FileNode.jsx +++ b/client/modules/IDE/components/FileNode.jsx @@ -3,6 +3,8 @@ import React from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import classNames from 'classnames'; +import { withTranslation } from 'react-i18next'; + import * as IDEActions from '../actions/ide'; import * as FileActions from '../actions/files'; import DownArrowIcon from '../../../images/down-filled-triangle.svg'; @@ -147,7 +149,9 @@ export class FileNode extends React.Component { } handleClickDelete = () => { - if (window.confirm(`Are you sure you want to delete ${this.props.name}?`)) { + const prompt = this.props.t('Common.DeleteConfirmation', { name: this.props.name }); + + if (window.confirm(prompt)) { this.setState({ isDeleting: true }); this.props.resetSelectedFile(this.props.id); setTimeout(() => this.props.deleteFile(this.props.id, this.props.parentId), 100); @@ -232,6 +236,8 @@ export class FileNode extends React.Component { const isFolder = this.props.fileType === 'folder'; const isRoot = this.props.name === 'root'; + const { t } = this.props; + return (
{ !isRoot && @@ -247,14 +253,14 @@ export class FileNode extends React.Component { @@ -281,7 +287,7 @@ export class FileNode extends React.Component { />
  • { this.props.authenticated &&
  • } @@ -337,7 +343,7 @@ export class FileNode extends React.Component { onFocus={this.onFocusComponent} className="sidebar__file-item-option" > - Rename + {t('FileNode.Rename')}
  • @@ -347,7 +353,7 @@ export class FileNode extends React.Component { onFocus={this.onFocusComponent} className="sidebar__file-item-option" > - Delete + {t('FileNode.Delete')}
  • @@ -382,7 +388,8 @@ FileNode.propTypes = { hideFolderChildren: PropTypes.func.isRequired, canEdit: PropTypes.bool.isRequired, openUploadFileModal: PropTypes.func.isRequired, - authenticated: PropTypes.bool.isRequired + authenticated: PropTypes.bool.isRequired, + t: PropTypes.func.isRequired, }; FileNode.defaultProps = { @@ -401,5 +408,8 @@ function mapDispatchToProps(dispatch) { return bindActionCreators(Object.assign(FileActions, IDEActions), dispatch); } -const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(FileNode); +const TranslatedFileNode = withTranslation()(FileNode); + +const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(TranslatedFileNode); + export default ConnectedFileNode; diff --git a/client/modules/IDE/components/Sidebar.jsx b/client/modules/IDE/components/Sidebar.jsx index 97c8c0ecc1..5d4802788f 100644 --- a/client/modules/IDE/components/Sidebar.jsx +++ b/client/modules/IDE/components/Sidebar.jsx @@ -1,6 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; +import { withTranslation } from 'react-i18next'; + import ConnectedFileNode from './FileNode'; import DownArrowIcon from '../../../images/down-filled-triangle.svg'; @@ -71,11 +73,11 @@ class Sidebar extends React.Component {

    - Sketch Files + {this.props.t('Sidebar.Title')}

  • { this.props.user.authenticated &&
  • } @@ -159,11 +161,12 @@ Sidebar.propTypes = { user: PropTypes.shape({ id: PropTypes.string, authenticated: PropTypes.bool.isRequired - }).isRequired + }).isRequired, + t: PropTypes.func.isRequired, }; Sidebar.defaultProps = { owner: undefined }; -export default Sidebar; +export default withTranslation()(Sidebar); diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 652eece467..0455b6cd95 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -174,30 +174,47 @@ } }, "Sidebar": { - "Create": "Create", - "EnterName": "enter a name", - "Add": "Add", - "Folder": "Folder" + "Title": "Sketch Files", + "ToggleARIA": "Toggle open/close sketch file options", + "AddFolder": "Create folder", + "AddFolderARIA": "add folder", + "AddFile": "Create file", + "AddFileARIA": "add file", + "UploadFile": "Upload file", + "UploadFileARIA": "upload file" + }, + "FileNode": { + "OpenFolderARIA": "Open folder contents", + "CloseFolderARIA": "Close folder contents", + "ToggleFileOptionsARIA": "Toggle open/close file options", + "AddFolder": "Create folder", + "AddFolderARIA": "add folder", + "AddFile": "Create file", + "AddFileARIA": "add file", + "UploadFile": "Upload file", + "UploadFileARIA": "upload file", + "Rename": "Rename", + "Delete": "Delete" }, "Common": { "Error": "Error", "Save": "Save", - "p5logoARIA": "p5.js Logo" - + "p5logoARIA": "p5.js Logo", + "DeleteConfirmation": "Are you sure you want to delete {{name}}?" }, "IDEView": { "SubmitFeedback": "Submit Feedback" }, "NewFileModal": { "Title": "Create File", - "CloseButtonARIA": "Close New File Modal", + "CloseButtonARIA": "Close New File Modal", "EnterName": "Please enter a name", "InvalidType": "Invalid file type. Valid extensions are .js, .css, .json, .txt, .csv, .tsv, .frag, and .vert." }, "NewFileForm": { - "AddFileSubmit": "Add File", + "AddFileSubmit": "Add File", "Placeholder": "Name" -}, + }, "NewFolderModal": { "Title": "Create Folder", "CloseButtonARIA": "Close New Folder Modal", From ed8f9637dce0894250e0c655223bcea8214dddf2 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Thu, 13 Aug 2020 12:09:56 +0100 Subject: [PATCH 3/3] Mock react-i18next with real English translations --- client/jest.setup.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/client/jest.setup.js b/client/jest.setup.js index 79652c74c9..233c76dbfd 100644 --- a/client/jest.setup.js +++ b/client/jest.setup.js @@ -3,3 +3,21 @@ import '@babel/polyfill'; // See: https://github.com/testing-library/jest-dom // eslint-disable-next-line import/no-extraneous-dependencies import '@testing-library/jest-dom'; + +import lodash from 'lodash'; + +// For testing, we use en-US and provide a mock implementation +// of t() that finds the correct translation +import translations from '../translations/locales/en-US/translations.json'; + +// This function name needs to be prefixed with "mock" so that Jest doesn't +// complain that it's out-of-scope in the mock below +const mockTranslate = key => lodash.get(translations, key); + +jest.mock('react-i18next', () => ({ + // this mock makes sure any components using the translate HoC receive the t function as a prop + withTranslation: () => (Component) => { + Component.defaultProps = { ...Component.defaultProps, t: mockTranslate }; + return Component; + }, +}));