- p5.js Web Editor | Feedback
+ {this.props.t('Feedback.Title')}
@@ -47,4 +48,4 @@ function Feedback(props) {
);
}
-export default Feedback;
+export default withTranslation()(Feedback);
diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx
index 0533091a..f4b8166d 100644
--- a/client/modules/IDE/components/FileNode.jsx
+++ b/client/modules/IDE/components/FileNode.jsx
@@ -67,7 +67,7 @@ FileName.propTypes = {
name: PropTypes.string.isRequired
};
-export class FileNode extends React.Component {
+class FileNode extends React.Component {
constructor(props) {
super(props);
@@ -419,4 +419,7 @@ const TranslatedFileNode = withTranslation()(FileNode);
const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(TranslatedFileNode);
-export default ConnectedFileNode;
+export {
+ TranslatedFileNode as FileNode,
+ ConnectedFileNode as default
+};
diff --git a/client/modules/IDE/components/FileNode.test.jsx b/client/modules/IDE/components/FileNode.test.jsx
index ddb8fec7..84b14aa5 100644
--- a/client/modules/IDE/components/FileNode.test.jsx
+++ b/client/modules/IDE/components/FileNode.test.jsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
+
+import { fireEvent, render, screen, waitFor, within } from '../../../test-utils';
import { FileNode } from './FileNode';
describe(' ', () => {
diff --git a/client/modules/IDE/components/QuickAddList/QuickAddList.jsx b/client/modules/IDE/components/QuickAddList/QuickAddList.jsx
index 32264230..b79e70fb 100644
--- a/client/modules/IDE/components/QuickAddList/QuickAddList.jsx
+++ b/client/modules/IDE/components/QuickAddList/QuickAddList.jsx
@@ -8,7 +8,7 @@ import Icons from './Icons';
const Item = ({
isAdded, onSelect, name, url, t
}) => {
- const buttonLabel = isAdded ? 'Remove from collection' : 'Add to collection';
+ const buttonLabel = isAdded ? t('QuickAddList.ButtonRemoveARIA') : t('QuickAddList.ButtonAddToCollectionARIA');
return (
{ /* eslint-disable-line */ }
diff --git a/client/modules/IDE/components/Searchbar/Collection.jsx b/client/modules/IDE/components/Searchbar/Collection.jsx
index c566ced2..a0a2b579 100644
--- a/client/modules/IDE/components/Searchbar/Collection.jsx
+++ b/client/modules/IDE/components/Searchbar/Collection.jsx
@@ -1,14 +1,16 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
+import i18next from 'i18next';
import * as SortingActions from '../../actions/sorting';
import Searchbar from './Searchbar';
+
const scope = 'collection';
function mapStateToProps(state) {
return {
- searchLabel: 'Search collections...',
+ searchLabel: i18next.t('Searchbar.SearchCollection'),
searchTerm: state.search[`${scope}SearchTerm`],
};
}
diff --git a/client/modules/IDE/components/Searchbar/Searchbar.jsx b/client/modules/IDE/components/Searchbar/Searchbar.jsx
index 765a240d..05a99e98 100644
--- a/client/modules/IDE/components/Searchbar/Searchbar.jsx
+++ b/client/modules/IDE/components/Searchbar/Searchbar.jsx
@@ -1,9 +1,11 @@
import PropTypes from 'prop-types';
import React from 'react';
import { throttle } from 'lodash';
-
+import { withTranslation } from 'react-i18next';
+import i18next from 'i18next';
import SearchIcon from '../../../../images/magnifyingglass.svg';
+
class Searchbar extends React.Component {
constructor(props) {
super(props);
@@ -50,7 +52,7 @@ class Searchbar extends React.Component {
clear
+ >{this.props.t('Searchbar.ClearTerm')}
);
@@ -62,10 +64,11 @@ Searchbar.propTypes = {
setSearchTerm: PropTypes.func.isRequired,
resetSearchTerm: PropTypes.func.isRequired,
searchLabel: PropTypes.string,
+ t: PropTypes.func.isRequired
};
Searchbar.defaultProps = {
- searchLabel: 'Search sketches...',
+ searchLabel: i18next.t('Searchbar.SearchSketch')
};
-export default Searchbar;
+export default withTranslation()(Searchbar);
diff --git a/client/modules/IDE/components/Searchbar/Sketch.jsx b/client/modules/IDE/components/Searchbar/Sketch.jsx
index bc12854e..cc995103 100644
--- a/client/modules/IDE/components/Searchbar/Sketch.jsx
+++ b/client/modules/IDE/components/Searchbar/Sketch.jsx
@@ -1,5 +1,6 @@
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
+import i18next from 'i18next';
import * as SortingActions from '../../actions/sorting';
import Searchbar from './Searchbar';
@@ -8,6 +9,7 @@ const scope = 'sketch';
function mapStateToProps(state) {
return {
+ searchLabel: i18next.t('Searchbar.SearchSketch'),
searchTerm: state.search[`${scope}SearchTerm`],
};
}
diff --git a/client/modules/IDE/components/Timer.jsx b/client/modules/IDE/components/Timer.jsx
index 25e4b247..c11530dd 100644
--- a/client/modules/IDE/components/Timer.jsx
+++ b/client/modules/IDE/components/Timer.jsx
@@ -3,6 +3,7 @@ import differenceInMilliseconds from 'date-fns/difference_in_milliseconds';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import PropTypes from 'prop-types';
import React from 'react';
+import { withTranslation } from 'react-i18next';
class Timer extends React.Component {
constructor(props) {
@@ -23,17 +24,19 @@ class Timer extends React.Component {
showSavedTime() {
const now = new Date();
if (Math.abs(differenceInMilliseconds(now, this.props.projectSavedTime) < 10000)) {
- return 'Saved: just now';
+ return this.props.t('Timer.SavedJustNow');
} else if (differenceInMilliseconds(now, this.props.projectSavedTime) < 20000) {
- return 'Saved: 15 seconds ago';
+ return this.props.t('Timer.Saved15Seconds');
} else if (differenceInMilliseconds(now, this.props.projectSavedTime) < 30000) {
- return 'Saved: 25 seconds ago';
+ return this.props.t('Timer.Saved25Seconds');
} else if (differenceInMilliseconds(now, this.props.projectSavedTime) < 46000) {
- return 'Saved: 35 seconds ago';
+ return this.props.t('Timer.Saved35Seconds');
}
- return `Saved: ${distanceInWordsToNow(this.props.projectSavedTime, {
+
+ const timeAgo = distanceInWordsToNow(this.props.projectSavedTime, {
includeSeconds: true
- })} ago`;
+ });
+ return this.props.t('Timer.SavedAgo', { timeAgo });
}
render() {
@@ -51,11 +54,12 @@ class Timer extends React.Component {
Timer.propTypes = {
projectSavedTime: PropTypes.string.isRequired,
- isUserOwner: PropTypes.bool
+ isUserOwner: PropTypes.bool,
+ t: PropTypes.func.isRequired
};
Timer.defaultProps = {
isUserOwner: false
};
-export default Timer;
+export default withTranslation()(Timer);
diff --git a/client/modules/IDE/components/Toolbar.jsx b/client/modules/IDE/components/Toolbar.jsx
index 5a126b35..5da38560 100644
--- a/client/modules/IDE/components/Toolbar.jsx
+++ b/client/modules/IDE/components/Toolbar.jsx
@@ -86,7 +86,7 @@ class Toolbar extends React.Component {
this.props.setTextOutput(true);
this.props.setGridOutput(true);
}}
- aria-label="Play sketch"
+ aria-label={this.props.t('Toolbar.PlaySketchARIA')}
disabled={this.props.infiniteLoop}
>
@@ -94,7 +94,7 @@ class Toolbar extends React.Component {
@@ -102,7 +102,7 @@ class Toolbar extends React.Component {
@@ -129,7 +129,7 @@ class Toolbar extends React.Component {
}
}}
disabled={!canEditProjectName}
- aria-label="Edit sketch name"
+ aria-label={this.props.t('Toolbar.EditSketchARIA')}
>
{this.props.project.name}
{
@@ -145,7 +145,7 @@ class Toolbar extends React.Component {
type="text"
maxLength="128"
className="toolbar__project-name-input"
- aria-label="New sketch name"
+ aria-label={this.props.t('Toolbar.NewSketchNameARIA')}
value={this.state.projectNameInputValue}
onChange={this.handleProjectNameChange}
ref={(element) => { this.projectNameInput = element; }}
@@ -165,7 +165,7 @@ class Toolbar extends React.Component {
@@ -200,6 +200,7 @@ Toolbar.propTypes = {
saveProject: PropTypes.func.isRequired,
currentUser: PropTypes.string,
t: PropTypes.func.isRequired
+
};
Toolbar.defaultProps = {
@@ -225,6 +226,5 @@ const mapDispatchToProps = {
...projectActions,
};
-export const ToolbarComponent = Toolbar;
-// export default connect(mapStateToProps, mapDispatchToProps)(Toolbar);
-export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(Toolbar));
+export const ToolbarComponent = withTranslation()(Toolbar);
+export default connect(mapStateToProps, mapDispatchToProps)(ToolbarComponent);
diff --git a/client/modules/IDE/components/Toolbar.test.jsx b/client/modules/IDE/components/Toolbar.test.jsx
index cd435302..79b0f9ee 100644
--- a/client/modules/IDE/components/Toolbar.test.jsx
+++ b/client/modules/IDE/components/Toolbar.test.jsx
@@ -1,8 +1,7 @@
import React from 'react';
-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import lodash from 'lodash';
-
+import { fireEvent, render, screen, waitFor } from '../../../test-utils';
import { ToolbarComponent } from './Toolbar';
const renderComponent = (extraProps = {}) => {
diff --git a/client/test-utils.js b/client/test-utils.js
new file mode 100644
index 00000000..b8e9e382
--- /dev/null
+++ b/client/test-utils.js
@@ -0,0 +1,40 @@
+/**
+ * This file re-exports @testing-library but ensures that
+ * any calls to render have translations available.
+ *
+ * This means tested components will be able to call
+ * `t()` and have the translations of the default
+ * language
+ *
+ * See: https://react.i18next.com/misc/testing#testing-without-stubbing
+ */
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { render } from '@testing-library/react';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+
+import { I18nextProvider } from 'react-i18next';
+import i18n from './i18n-test';
+
+// re-export everything
+// eslint-disable-next-line import/no-extraneous-dependencies
+export * from '@testing-library/react';
+
+const Providers = ({ children }) => (
+ // eslint-disable-next-line react/jsx-filename-extension
+
+ {children}
+
+);
+
+Providers.propTypes = {
+ children: PropTypes.element.isRequired,
+};
+
+const customRender = (ui, options) =>
+ render(ui, { wrapper: Providers, ...options });
+
+// override render method
+export { customRender as render };
diff --git a/client/utils/consoleUtils.js b/client/utils/consoleUtils.js
index a6013924..19e942e8 100644
--- a/client/utils/consoleUtils.js
+++ b/client/utils/consoleUtils.js
@@ -38,6 +38,22 @@ export const hijackConsoleErrorsScript = (offs) => {
}], '*');
return false;
};
+ // catch rejected promises
+ window.onunhandledrejection = function (event) {
+ if (event.reason && event.reason.message && event.reason.stack){
+ var errorNum = event.reason.stack.split('about:srcdoc:')[1].split(':')[0];
+ var fileInfo = getScriptOff(errorNum);
+ var data = event.reason.message + ' (' + fileInfo[1] + ': line ' + fileInfo[0] + ')';
+ window.parent.postMessage([{
+ log: [{
+ method: 'error',
+ data: [data],
+ id: Date.now().toString()
+ }],
+ source: fileInfo[1]
+ }], '*');
+ }
+ };
`;
return s;
};
@@ -46,7 +62,7 @@ export const startTag = '@fs-';
export const getAllScriptOffsets = (htmlFile) => {
const offs = [];
- const hijackConsoleErrorsScriptLength = 36;
+ const hijackConsoleErrorsScriptLength = 52;
const embeddedJSStart = 'script crossorigin=""';
let foundJSScript = true;
let foundEmbeddedJS = true;
diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json
index 8c548068..ab64c317 100644
--- a/translations/locales/en-US/translations.json
+++ b/translations/locales/en-US/translations.json
@@ -341,6 +341,30 @@
"Verified": "All done, your email address has been verified.",
"InvalidState": "Something went wrong."
},
+ "AssetList": {
+ "Title": "p5.js Web Editor | My assets",
+ "ToggleOpenCloseARIA": "Toggle Open/Close Asset Options",
+ "Delete": "Delete",
+ "OpenNewTab": "Open in New Tab",
+ "NoUploadedAssets": "No uploaded assets.",
+ "HeaderName": "Name",
+ "HeaderSize": "Size",
+ "HeaderSketch": "Sketch"
+ },
+ "Feedback": {
+ "Title": "p5.js Web Editor | Feedback",
+ "ViaGithubHeader": "Via Github Issues",
+ "ViaGithubDescription": "If you're familiar with Github, this is our preferred method for receiving bug reports and feedback.",
+ "GoToGithub": "Go to Github",
+ "ViaGoogleHeader": "Via Google Form",
+ "ViaGoogleDescription": "You can also submit this quick form.",
+ "GoToForm": "Go to Form"
+ },
+ "Searchbar": {
+ "SearchSketch": "Search sketches...",
+ "SearchCollection": "Search collections...",
+ "ClearTerm": "clear"
+ },
"UploadFileModal": {
"Title": "Upload File",
"CloseButtonARIA": "Close upload file modal",
@@ -444,8 +468,8 @@
"AriaLabel": "Close {{title}} overlay"
},
"QuickAddList":{
- "ButtonLabelRemove": "Remove from collection",
- "ButtonLabelAddToCollection": "Add to collection",
+ "ButtonRemoveARIA": "Remove from collection",
+ "ButtonAddToCollectionARIA": "Add to collection",
"View": "View"
},
"SketchList": {
@@ -474,5 +498,38 @@
"Title": "p5.js Web Editor | My sketches",
"AnothersTitle": "p5.js Web Editor | {{anotheruser}}'s sketches",
"NoCollections": "No collections."
+ },
+ "Editor": {
+ "OpenSketchARIA": "Open Sketch files navigation",
+ "CloseSketchARIA": "Close Sketch files navigation",
+ "UnsavedChangesARIA": "Sketch has unsaved changes",
+ "KeyUpLineNumber": "line {{lineNumber}}"
+ },
+ "EditorAccessibility": {
+ "NoLintMessages": "There are no lint messages ",
+ "CurrentLine": " Current line"
+ },
+ "Timer": {
+ "SavedJustNow": "Saved: just now",
+ "Saved15Seconds": "Saved: 15 seconds ago",
+ "Saved25Seconds": "Saved: 25 seconds ago",
+ "Saved35Seconds": "Saved: 35 seconds ago",
+ "SavedAgo": "Saved: {{timeAgo}} ago"
+ },
+ "AddRemoveButton": {
+ "AltAddARIA": "Add to collection",
+ "AltRemoveARIA": "Remove from collection"
+ },
+ "CopyableInput": {
+ "CopiedARIA": "Copied to Clipboard!",
+ "OpenViewTabARIA": "Open {{label}} view in new tab"
+ },
+ "EditableInput": {
+ "EditValue": "Edit {{display}} value",
+ "EmptyPlaceholder": "No value"
+ },
+ "PreviewNav": {
+ "EditSketchARIA": "Edit Sketch",
+ "ByUser": "by"
}
}
diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json
index 089c6768..08d9778a 100644
--- a/translations/locales/es-419/translations.json
+++ b/translations/locales/es-419/translations.json
@@ -341,6 +341,30 @@
"Verified": "Concluido, tu correo electrónico ha sido verificado.",
"InvalidState": "Algo salió mal."
},
+ "AssetList": {
+ "Title": "Editor Web p5.js | Mis assets",
+ "ToggleOpenCloseARIA": "Alternar Abrir/Cerrar Opciones de Asset",
+ "Delete": "Borrar",
+ "OpenNewTab": "Abrir en Nueva Pestaña",
+ "NoUploadedAssets": "No has subido archivos.",
+ "HeaderName": "Nombre",
+ "HeaderSize": "Tamaño",
+ "HeaderSketch": "Bosquejo"
+ },
+ "Feedback": {
+ "Title": "Editor Web p5.js | Retroalimentación",
+ "ViaGithubHeader": "Vía Github Issues",
+ "ViaGithubDescription": " Si estas familiarizado con Github, este es nuestro método favorito para recibir reporte de errores y retroalimentación.",
+ "GoToGithub": "Ir a Github",
+ "ViaGoogleHeader": "Vía Formulario de Google",
+ "ViaGoogleDescription": "También puedes enviar tu retroalimetnación usando esta vía rapida.",
+ "GoToForm": "Ir a Formulario"
+ },
+ "Searchbar": {
+ "SearchSketch": "Buscar en bosquejos...",
+ "SearchCollection": "Buscar en colecciones...",
+ "ClearTerm": "limpiar"
+ },
"UploadFileModal": {
"Title": "Subir Archivo",
"CloseButtonARIA": "Cerrar diálogo para subir archivo",
@@ -444,8 +468,8 @@
"AriaLabel": "Cerrar la capa {{title}}"
},
"QuickAddList":{
- "ButtonLabelRemove": "Remove from collection",
- "ButtonLabelAddToCollection": "Add to collection",
+ "ButtonRemoveARIA": "Remover de colección",
+ "ButtonAddToCollectionARIA": "Agregar a colección",
"View": "Ver"
},
"SketchList": {
@@ -474,5 +498,38 @@
"Title": "p5.js Web Editor | Mis bosquejos",
"AnothersTitle": "Editor Web p5.js | Bosquejos de {{anotheruser}}",
"NoCollections": "No hay colecciones."
+ },
+ "Editor": {
+ "OpenSketchARIA": "Abrir navegación de archivos de bosquejo",
+ "CloseSketchARIA": "Cerrar navegación de archivos de bosquejo",
+ "UnsavedChangesARIA": "El bosquejo tiene cambios sin guardar",
+ "KeyUpLineNumber": "línea {{lineNumber}}"
+ },
+ "EditorAccessibility": {
+ "NoLintMessages": "No hay mensajes de Lint",
+ "CurrentLine": " Línea actual"
+ },
+ "Timer": {
+ "SavedJustNow": "Guardado: justo ahora",
+ "Saved15Seconds": "Guardado: hace 15 segundos",
+ "Saved25Seconds": "Guardado: hace 25 segundos",
+ "Saved35Seconds": "Guardado: hace 35 segundos",
+ "SavedAgo": "Guardado: hace {{timeAgo}}"
+ },
+ "AddRemoveButton": {
+ "AltAddARIA": "Agregar a colección",
+ "AltRemoveARIA": "Remover de colección"
+ },
+ "CopyableInput": {
+ "CopiedARIA": "¡Copiado en el portapapeles!",
+ "OpenViewTabARIA": "Abrir la vista {{label}} en nueva pestaña"
+ },
+ "EditableInput": {
+ "EditValue": "Editar valor de {{display}}",
+ "EmptyPlaceholder": "Sin valor"
+ },
+ "PreviewNav": {
+ "EditSketchARIA": "Editar Bosquejo",
+ "ByUser": "por"
}
}