diff --git a/client/jest.setup.js b/client/jest.setup.js index 79652c74..233c76db 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; + }, +})); diff --git a/client/modules/IDE/components/AddToCollectionList.jsx b/client/modules/IDE/components/AddToCollectionList.jsx index e9417d3b..e003a896 100644 --- a/client/modules/IDE/components/AddToCollectionList.jsx +++ b/client/modules/IDE/components/AddToCollectionList.jsx @@ -14,7 +14,7 @@ import Loader from '../../App/components/loader'; import QuickAddList from './QuickAddList'; const projectInCollection = (project, collection) => - collection.items.find(item => item.project.id === project.id) != null; + collection.items.find(item => item.projectId === project.id) != null; class CollectionList extends React.Component { constructor(props) { @@ -81,12 +81,14 @@ class CollectionList extends React.Component { } return ( -
- - {this.getTitle()} - +
+
+ + {this.getTitle()} + - {content} + {content} +
); } diff --git a/client/modules/IDE/components/AddToCollectionSketchList.jsx b/client/modules/IDE/components/AddToCollectionSketchList.jsx index d4173b4b..6abb9aad 100644 --- a/client/modules/IDE/components/AddToCollectionSketchList.jsx +++ b/client/modules/IDE/components/AddToCollectionSketchList.jsx @@ -72,11 +72,13 @@ class SketchList extends React.Component { } return ( -
- - {this.getSketchesTitle()} - - {content} +
+
+ + {this.getSketchesTitle()} + + {content} +
); } diff --git a/client/modules/IDE/components/CollectionList/CollectionList.jsx b/client/modules/IDE/components/CollectionList/CollectionList.jsx index 1007ca97..3d4e1aa6 100644 --- a/client/modules/IDE/components/CollectionList/CollectionList.jsx +++ b/client/modules/IDE/components/CollectionList/CollectionList.jsx @@ -170,12 +170,10 @@ class CollectionList extends React.Component { closeOverlay={this.hideAddSketches} isFixedHeight > -
- -
+ ) } diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx index ddb6d55e..eaee9492 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); @@ -98,19 +100,19 @@ const Console = () => { return (
-

Console

+

{t('Console.Title')}

- -
@@ -140,5 +142,9 @@ const Console = () => { ); }; +Console.propTypes = { + t: PropTypes.func.isRequired, +}; -export default Console; + +export default withTranslation()(Console); diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index d0a10ef8..0533091a 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'; @@ -152,7 +154,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); @@ -237,6 +241,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 && @@ -252,14 +258,14 @@ export class FileNode extends React.Component { @@ -286,7 +292,7 @@ export class FileNode extends React.Component { />
  • { this.props.authenticated &&
  • } @@ -342,7 +348,7 @@ export class FileNode extends React.Component { onFocus={this.onFocusComponent} className="sidebar__file-item-option" > - Rename + {t('FileNode.Rename')}
  • @@ -352,7 +358,7 @@ export class FileNode extends React.Component { onFocus={this.onFocusComponent} className="sidebar__file-item-option" > - Delete + {t('FileNode.Delete')}
  • @@ -388,6 +394,7 @@ FileNode.propTypes = { canEdit: PropTypes.bool.isRequired, openUploadFileModal: PropTypes.func.isRequired, authenticated: PropTypes.bool.isRequired, + t: PropTypes.func.isRequired, onClickFile: PropTypes.func }; @@ -408,5 +415,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 97c8c0ec..5d480278 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/client/modules/User/components/Collection.jsx b/client/modules/User/components/Collection.jsx index 54e87518..abcd9ce5 100644 --- a/client/modules/User/components/Collection.jsx +++ b/client/modules/User/components/Collection.jsx @@ -398,9 +398,10 @@ class Collection extends React.Component { closeOverlay={this.hideAddSketches} isFixedHeight > -
    - -
    + ) } diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index 80c924b1..d0f62b27 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -88,6 +88,7 @@ $themes: ( nav-border-color: $middle-light, error-color: $p5js-pink, table-row-stripe-color: $medium-light, + table-row-stripe-color-alternate: $medium-light, codefold-icon-open: url(../images/triangle-arrow-down.svg?byUrl), codefold-icon-closed: url(../images/triangle-arrow-right.svg?byUrl), @@ -163,6 +164,7 @@ $themes: ( nav-border-color: $middle-dark, error-color: $p5js-pink, table-row-stripe-color: $dark, + table-row-stripe-color-alternate: $darker, codefold-icon-open: url(../images/triangle-arrow-down-white.svg?byUrl), codefold-icon-closed: url(../images/triangle-arrow-right-white.svg?byUrl), @@ -236,6 +238,7 @@ $themes: ( nav-border-color: $middle-dark, error-color: $p5-contrast-pink, table-row-stripe-color: $dark, + table-row-stripe-color-alternate: $darker, codefold-icon-open: url(../images/triangle-arrow-down-white.svg?byUrl), codefold-icon-closed: url(../images/triangle-arrow-right-white.svg?byUrl), diff --git a/client/styles/components/_quick-add.scss b/client/styles/components/_quick-add.scss index 838a1d65..75f93773 100644 --- a/client/styles/components/_quick-add.scss +++ b/client/styles/components/_quick-add.scss @@ -1,11 +1,16 @@ .quick-add-wrapper { min-width: #{600 / $base-font-size}rem; - overflow-y: scroll; + padding: #{24 / $base-font-size}rem; + height: 100%; } .quick-add { width: auto; - padding: #{24 / $base-font-size}rem; + overflow-y: scroll; + height: 100%; + @include themify() { + border: 1px solid getThemifyVariable('modal-border-color'); + } } .quick-add__item { @@ -23,7 +28,7 @@ .quick-add__item:nth-child(odd) { @include themify() { - background: getThemifyVariable('table-row-stripe-color'); + background: getThemifyVariable('table-row-stripe-color-alternate'); } } diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 9ff6b536..39c5dcfb 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -174,15 +174,33 @@ } }, "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"