diff --git a/client/modules/User/components/APIKeyForm.jsx b/client/modules/User/components/APIKeyForm.jsx
index cb99dcde..73c24c81 100644
--- a/client/modules/User/components/APIKeyForm.jsx
+++ b/client/modules/User/components/APIKeyForm.jsx
@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
-
import Button from '../../../common/Button';
import { PlusIcon } from '../../../common/icons';
import CopyableInput from '../../IDE/components/CopyableInput';
@@ -12,7 +11,7 @@ export const APIKeyPropType = PropTypes.shape({
token: PropTypes.object,
label: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
- lastUsedAt: PropTypes.string,
+ lastUsedAt: PropTypes.string
});
class APIKeyForm extends React.Component {
@@ -39,7 +38,7 @@ class APIKeyForm extends React.Component {
}
removeKey(key) {
- const message = `Are you sure you want to delete "${key.label}"?`;
+ const message = this.props.t('APIKeyForm.ConfirmDelete', { key_label: key.label });
if (window.confirm(message)) {
this.props.removeApiKey(key.id);
@@ -51,10 +50,10 @@ class APIKeyForm extends React.Component {
if (hasApiKeys) {
return (
-
+
);
}
- return
You have no exsiting tokens.
;
+ return {this.props.t('APIKeyForm.NoTokens')}
;
}
render() {
@@ -63,20 +62,18 @@ class APIKeyForm extends React.Component {
return (
- Personal Access Tokens act like your password to allow automated
- scripts to access the Editor API. Create a token for each script
- that needs access.
+ {this.props.t('APIKeyForm.Summary')}
-
Create new token
+
{this.props.t('APIKeyForm.CreateToken')}
{
keyWithToken && (
-
Your new access token
+
{this.props.t('APIKeyForm.NewTokenTitle')}
- Make sure to copy your new personal access token now.
- You won’t be able to see it again!
+ {this.props.t('APIKeyForm.NewTokenInfo')}
@@ -105,7 +101,7 @@ class APIKeyForm extends React.Component {
-
Existing tokens
+ {this.props.t('APIKeyForm.ExistingTokensTitle')}
{this.renderApiKeys()}
@@ -117,6 +113,7 @@ APIKeyForm.propTypes = {
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
createApiKey: PropTypes.func.isRequired,
removeApiKey: PropTypes.func.isRequired,
+ t: PropTypes.func.isRequired
};
export default APIKeyForm;
diff --git a/client/modules/User/components/APIKeyList.jsx b/client/modules/User/components/APIKeyList.jsx
index 9201aa4b..2c2e39ac 100644
--- a/client/modules/User/components/APIKeyList.jsx
+++ b/client/modules/User/components/APIKeyList.jsx
@@ -8,22 +8,22 @@ import { APIKeyPropType } from './APIKeyForm';
import TrashCanIcon from '../../../images/trash-can.svg';
-function APIKeyList({ apiKeys, onRemove }) {
+function APIKeyList({ apiKeys, onRemove, t }) {
return (
- Name
- Created on
- Last used
- Actions
+ {t('APIKeyList.Name')}
+ {t('APIKeyList.Created')}
+ {t('APIKeyList.LastUsed')}
+ {t('APIKeyList.Actions')}
{orderBy(apiKeys, ['createdAt'], ['desc']).map((key) => {
const lastUsed = key.lastUsedAt ?
distanceInWordsToNow(new Date(key.lastUsedAt), { addSuffix: true }) :
- 'Never';
+ t('APIKeyList.Never');
return (
@@ -34,7 +34,7 @@ function APIKeyList({ apiKeys, onRemove }) {
onRemove(key)}
- aria-label="Delete API Key"
+ aria-label={t('APIKeyList.DeleteARIA')}
>
@@ -50,6 +50,7 @@ function APIKeyList({ apiKeys, onRemove }) {
APIKeyList.propTypes = {
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
onRemove: PropTypes.func.isRequired,
+ t: PropTypes.func.isRequired
};
export default APIKeyList;
diff --git a/client/modules/User/components/AccountForm.jsx b/client/modules/User/components/AccountForm.jsx
index e032448b..829beb0d 100644
--- a/client/modules/User/components/AccountForm.jsx
+++ b/client/modules/User/components/AccountForm.jsx
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
+import { withTranslation } from 'react-i18next';
import { domOnlyProps } from '../../../utils/reduxFormUtils';
import Button from '../../../common/Button';
@@ -13,7 +14,8 @@ function AccountForm(props) {
initiateVerification,
submitting,
invalid,
- pristine
+ pristine,
+ t
} = props;
const handleInitiateVerification = (evt) => {
@@ -24,10 +26,10 @@ function AccountForm(props) {
return (
);
@@ -118,6 +120,7 @@ AccountForm.propTypes = {
submitting: PropTypes.bool,
invalid: PropTypes.bool,
pristine: PropTypes.bool,
+ t: PropTypes.func.isRequired
};
AccountForm.defaultProps = {
@@ -126,4 +129,4 @@ AccountForm.defaultProps = {
invalid: false
};
-export default AccountForm;
+export default withTranslation()(AccountForm);
diff --git a/client/modules/User/components/NewPasswordForm.jsx b/client/modules/User/components/NewPasswordForm.jsx
index 59240908..a08a2832 100644
--- a/client/modules/User/components/NewPasswordForm.jsx
+++ b/client/modules/User/components/NewPasswordForm.jsx
@@ -1,20 +1,21 @@
import PropTypes from 'prop-types';
import React from 'react';
-
+import { withTranslation } from 'react-i18next';
import { domOnlyProps } from '../../../utils/reduxFormUtils';
import Button from '../../../common/Button';
function NewPasswordForm(props) {
const {
- fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine
+ fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine,
+ t
} = props;
return (
);
}
@@ -54,6 +55,7 @@ NewPasswordForm.propTypes = {
params: PropTypes.shape({
reset_password_token: PropTypes.string,
}).isRequired,
+ t: PropTypes.func.isRequired
};
NewPasswordForm.defaultProps = {
@@ -62,4 +64,4 @@ NewPasswordForm.defaultProps = {
submitting: false
};
-export default NewPasswordForm;
+export default withTranslation()(NewPasswordForm);
diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx
index fff8d331..f9f46cc8 100644
--- a/client/modules/User/pages/AccountView.jsx
+++ b/client/modules/User/pages/AccountView.jsx
@@ -4,6 +4,7 @@ import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { Helmet } from 'react-helmet';
+import { withTranslation } from 'react-i18next';
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
import AccountForm from '../components/AccountForm';
import apiClient from '../../../utils/apiClient';
@@ -16,9 +17,11 @@ function SocialLoginPanel(props) {
return (
- Social Login
+ {/* eslint-disable-next-line react/prop-types */}
+ {props.t('AccountView.SocialLogin')}
- Use your GitHub or Google account to log into the p5.js Web Editor.
+ {/* eslint-disable-next-line react/prop-types */}
+ {props.t('AccountView.SocialLoginDescription')}
@@ -39,21 +42,21 @@ class AccountView extends React.Component {
return (
- p5.js Web Editor | Account Settings
+ {this.props.t('AccountView.Title')}
- Account Settings
+ {this.props.t('AccountView.Settings')}
{accessTokensUIEnabled &&
- Account
- {accessTokensUIEnabled && Access Tokens }
+ {this.props.t('AccountView.AccountTab')}
+ {accessTokensUIEnabled && {this.props.t('AccountView.AccessTokensTab')} }
@@ -107,13 +110,14 @@ function asyncValidate(formProps, dispatch, props) {
AccountView.propTypes = {
previousPath: PropTypes.string.isRequired,
- theme: PropTypes.string.isRequired
+ theme: PropTypes.string.isRequired,
+ t: PropTypes.func.isRequired
};
-export default reduxForm({
+export default withTranslation()(reduxForm({
form: 'updateAllSettings',
fields: ['username', 'email', 'currentPassword', 'newPassword'],
validate: validateSettings,
asyncValidate,
asyncBlurFields: ['username', 'email', 'currentPassword']
-}, mapStateToProps, mapDispatchToProps)(AccountView);
+}, mapStateToProps, mapDispatchToProps)(AccountView));
diff --git a/client/modules/User/pages/NewPasswordView.jsx b/client/modules/User/pages/NewPasswordView.jsx
index 1760cc32..d1b27106 100644
--- a/client/modules/User/pages/NewPasswordView.jsx
+++ b/client/modules/User/pages/NewPasswordView.jsx
@@ -4,6 +4,8 @@ import { reduxForm } from 'redux-form';
import classNames from 'classnames';
import { bindActionCreators } from 'redux';
import { Helmet } from 'react-helmet';
+import { withTranslation } from 'react-i18next';
+import i18next from 'i18next';
import NewPasswordForm from '../components/NewPasswordForm';
import * as UserActions from '../actions';
import Nav from '../../../components/Nav';
@@ -20,13 +22,13 @@ function NewPasswordView(props) {
- p5.js Web Editor | New Password
+ {props.t('NewPasswordView.Title')}
-
Set a New Password
+
{props.t('NewPasswordView.Description')}
- The password reset token is invalid or has expired.
+ {props.t('NewPasswordView.TokenInvalidOrExpired')}
@@ -41,21 +43,22 @@ NewPasswordView.propTypes = {
validateResetPasswordToken: PropTypes.func.isRequired,
user: PropTypes.shape({
resetPasswordInvalid: PropTypes.bool
- }).isRequired
+ }).isRequired,
+ t: PropTypes.func.isRequired
};
function validate(formProps) {
const errors = {};
if (!formProps.password) {
- errors.password = 'Please enter a password';
+ errors.password = i18next.t('NewPasswordView.EmptyPassword');
}
if (!formProps.confirmPassword) {
- errors.confirmPassword = 'Please enter a password confirmation';
+ errors.confirmPassword = i18next.t('NewPasswordView.PasswordConfirmation');
}
if (formProps.password !== formProps.confirmPassword) {
- errors.password = 'Passwords must match';
+ errors.password = i18next.t('NewPasswordView.PasswordMismatch');
}
return errors;
@@ -71,8 +74,8 @@ function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
-export default reduxForm({
+export default withTranslation()(reduxForm({
form: 'new-password',
fields: ['password', 'confirmPassword'],
validate
-}, mapStateToProps, mapDispatchToProps)(NewPasswordView);
+}, mapStateToProps, mapDispatchToProps)(NewPasswordView));
diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json
index aa8638c2..c12d097c 100644
--- a/translations/locales/en-US/translations.json
+++ b/translations/locales/en-US/translations.json
@@ -232,5 +232,62 @@
"errorEmptyUsername": "Please enter a username.",
"errorLongUsername": "Username must be less than 20 characters.",
"errorValidUsername": "Username must only consist of numbers, letters, periods, dashes, and underscores."
+ },
+ "NewPasswordView": {
+ "Title": "p5.js Web Editor | New Password",
+ "Description": "Set a New Password",
+ "TokenInvalidOrExpired": "The password reset token is invalid or has expired.",
+ "EmptyPassword": "Please enter a password",
+ "PasswordConfirmation": "Please enter a password confirmation",
+ "PasswordMismatch": "Passwords must match"
+ },
+ "AccountForm": {
+ "Email": "Email",
+ "EmailARIA": "email",
+ "Unconfirmed": "Unconfirmed.",
+ "EmailSent": "Confirmation sent, check your email.",
+ "Resend": "Resend confirmation email",
+ "UserName": "User Name",
+ "UserNameARIA": "Username",
+ "CurrentPassword": "Current Password",
+ "CurrentPasswordARIA": "Current Password",
+ "NewPassword": "New Password",
+ "NewPasswordARIA": "New Password",
+ "SubmitSaveAllSettings": "Save All Settings"
+ },
+ "AccountView": {
+ "SocialLogin": "Social Login",
+ "SocialLoginDescription": "Use your GitHub or Google account to log into the p5.js Web Editor.",
+ "Title": "p5.js Web Editor | Account Settings",
+ "Settings": "Account Settings",
+ "AccountTab": "Account",
+ "AccessTokensTab": "Access Tokens"
+ },
+ "APIKeyForm": {
+ "ConfirmDelete": "Are you sure you want to delete {{key_label}}?",
+ "Summary": "Personal Access Tokens act like your password to allow automated\n scripts to access the Editor API. Create a token for each script\n that needs access.",
+ "CreateToken": "Create new token",
+ "TokenLabel": "What is this token for?",
+ "TokenPlaceholder": "What is this token for? e.g. Example import script",
+ "CreateTokenSubmit": "Create",
+ "NoTokens": "You have no existing tokens.",
+ "NewTokenTitle": "Your new access token",
+ "NewTokenInfo": "Make sure to copy your new personal access token now.\n You won’t be able to see it again!",
+ "ExistingTokensTitle": "Existing tokens"
+ },
+ "APIKeyList": {
+ "Name": "Name",
+ "Created": "Created on",
+ "LastUsed": "Last used",
+ "Actions": "Actions",
+ "Never": "Never",
+ "DeleteARIA": "Delete API Key"
+ },
+ "NewPasswordForm": {
+ "Title": "Password",
+ "TitleARIA": "Password",
+ "ConfirmPassword": "Confirm Password",
+ "ConfirmPasswordARIA": "Confirm Password",
+ "SubmitSetNewPassword": "Set New Password"
}
}
diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json
index 672dccb9..0104b718 100644
--- a/translations/locales/es-419/translations.json
+++ b/translations/locales/es-419/translations.json
@@ -232,8 +232,62 @@
"errorEmptyUsername": "Por favor introduce tu identificación",
"errorLongUsername": "La identificación debe ser menor a 20 caracteres.",
"errorValidUsername": "La identificación debe consistir solamente de números, letras, puntos, guiones y guiones bajos."
-
+ },
+ "NewPasswordView": {
+ "Title": "Editor Web p5.js | Nueva Contraseña",
+ "Description": "Define una nueva contraseña",
+ "TokenInvalidOrExpired": "El token para regenerar la contraseña es inválido o ha expirado.",
+ "EmptyPassword": "Por favor introduce una contraseña",
+ "PasswordConfirmation": "Por favor confirma la contraseña",
+ "PasswordMismatch": "Las contraseña deben coincidir"
+ },
+ "AccountForm": {
+ "Email": "Correo Electrónico",
+ "EmailARIA": "correo electrónico",
+ "Unconfirmed": "Sin confirmar.",
+ "EmailSent": "Confirmación enviada, revisa tu correo electrónico.",
+ "Resend": "Reenviar correo de confirmación",
+ "UserName": "Nombre de Identificación",
+ "UserNameARIA": "Nombre de identificación",
+ "CurrentPassword": "Contraseña Actual",
+ "CurrentPasswordARIA": "Contraseña Actual",
+ "NewPassword": "Nueva Contraseña",
+ "NewPasswordARIA": "Nueva Contraseña",
+ "SubmitSaveAllSettings": "Guardar Todas Las Configuraciones"
+ },
+ "AccountView": {
+ "SocialLogin": "Identificacion usando redes sociales",
+ "SocialLoginDescription": "Usa tu cuenta de GitHub o Google para acceder al Editor Web de p5.js .",
+ "Title": "Editor Web p5.js | Configuración Cuenta",
+ "Settings": "Configuración de la Cuenta",
+ "AccountTab": "Cuenta",
+ "AccessTokensTab": "Tokens de acceso"
+ },
+ "APIKeyForm": {
+ "ConfirmDelete": "¿Estas seguro que quieres borrar {{key_label}}?",
+ "Summary": " Los Tokens de acceso personal actuan como tu contraseña para permitir\n a scripts automáticos acceder al API del Editor. Crea un token por cada script \n que necesite acceso.",
+ "CreateToken": "Crear nuevo token",
+ "TokenLabel": "¿Para que es este token?",
+ "TokenPlaceholder": "¿Para que es este token? p.e. Ejemplo para Importar un Archivo",
+ "CreateTokenSubmit": "Crear",
+ "NoTokens": "No tienes tokens.",
+ "NewTokenTitle": "Tu nuevo token de acceso",
+ "NewTokenInfo": "Asegurate de copiar tu token ahora mismo.\n ¡No podras verlo de nuevo!",
+ "ExistingTokensTitle": "Tokens existentes"
+ },
+ "APIKeyList": {
+ "Name": "Nombre",
+ "Created": "Creado en",
+ "LastUsed": "Usado por última vez",
+ "Actions": "Acciones",
+ "Never": "Nunca",
+ "DeleteARIA": "Borrar clave de API"
+ },
+ "NewPasswordForm": {
+ "Title": "Contraseña",
+ "TitleARIA": "Contraseña",
+ "ConfirmPassword": "Confirmar Contraseña",
+ "ConfirmPasswordARIA": "Confirmar contraseña",
+ "SubmitSetNewPassword": "Crear Nueva Contraseña"
}
}
-
-