Skip to content

Commit 7432578

Browse files
authored
Merge branch 'develop' into add-replace
2 parents 5c7b497 + 1eddeef commit 7432578

File tree

13 files changed

+114
-34
lines changed

13 files changed

+114
-34
lines changed

client/components/Nav.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import i18next from 'i18next';
99
import * as IDEActions from '../modules/IDE/actions/ide';
1010
import * as toastActions from '../modules/IDE/actions/toast';
1111
import * as projectActions from '../modules/IDE/actions/project';
12-
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
12+
import { setAllAccessibleOutput, setLanguage } from '../modules/IDE/actions/preferences';
1313
import { logoutUser } from '../modules/User/actions';
1414

1515
import getConfig from '../utils/getConfig';
@@ -74,7 +74,6 @@ class Nav extends React.PureComponent {
7474
document.removeEventListener('mousedown', this.handleClick, false);
7575
document.removeEventListener('keydown', this.closeDropDown, false);
7676
}
77-
7877
setDropdown(dropdown) {
7978
this.setState({
8079
dropdownOpen: dropdown
@@ -182,7 +181,7 @@ class Nav extends React.PureComponent {
182181
}
183182

184183
handleLangSelection(event) {
185-
i18next.changeLanguage(event.target.value);
184+
this.props.setLanguage(event.target.value);
186185
this.props.showToast(1500);
187186
this.props.setToastText('Toast.LangChange');
188187
this.setDropdown('none');
@@ -842,8 +841,8 @@ Nav.propTypes = {
842841
params: PropTypes.shape({
843842
username: PropTypes.string
844843
}),
845-
t: PropTypes.func.isRequired
846-
844+
t: PropTypes.func.isRequired,
845+
setLanguage: PropTypes.func.isRequired,
847846
};
848847

849848
Nav.defaultProps = {
@@ -873,7 +872,8 @@ const mapDispatchToProps = {
873872
...projectActions,
874873
...toastActions,
875874
logoutUser,
876-
setAllAccessibleOutput
875+
setAllAccessibleOutput,
876+
setLanguage
877877
};
878878

879879
export default withTranslation()(withRouter(connect(mapStateToProps, mapDispatchToProps)(Nav)));

client/components/__test__/Nav.test.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ describe('Nav', () => {
4747
rootFile: {
4848
id: 'root-file'
4949
},
50-
t: jest.fn()
50+
t: jest.fn(),
51+
setLanguage: jest.fn()
5152
};
5253

5354
it('renders correctly', () => {

client/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export const SHOW_TOAST = 'SHOW_TOAST';
9393
export const HIDE_TOAST = 'HIDE_TOAST';
9494
export const SET_TOAST_TEXT = 'SET_TOAST_TEXT';
9595
export const SET_THEME = 'SET_THEME';
96+
export const SET_LANGUAGE = 'SET_LANGUAGE';
9697

9798
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
9899
export const SET_AUTOREFRESH = 'SET_AUTOREFRESH';

client/modules/App/App.jsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
44
import getConfig from '../../utils/getConfig';
55
import DevTools from './components/DevTools';
66
import { setPreviousPath } from '../IDE/actions/ide';
7+
import { setLanguage } from '../IDE/actions/preferences';
78

89
class App extends React.Component {
910
constructor(props, context) {
@@ -23,6 +24,10 @@ class App extends React.Component {
2324
if (locationWillChange && !shouldSkipRemembering) {
2425
this.props.setPreviousPath(this.props.location.pathname);
2526
}
27+
28+
if (this.props.language !== nextProps.language) {
29+
this.props.setLanguage(nextProps.language, { persistPreference: false });
30+
}
2631
}
2732

2833
componentDidUpdate(prevProps) {
@@ -50,18 +55,22 @@ App.propTypes = {
5055
}),
5156
}).isRequired,
5257
setPreviousPath: PropTypes.func.isRequired,
58+
setLanguage: PropTypes.func.isRequired,
59+
language: PropTypes.string,
5360
theme: PropTypes.string,
5461
};
5562

5663
App.defaultProps = {
5764
children: null,
65+
language: null,
5866
theme: 'light'
5967
};
6068

6169
const mapStateToProps = state => ({
6270
theme: state.preferences.theme,
71+
language: state.preferences.language,
6372
});
6473

65-
const mapDispatchToProps = { setPreviousPath };
74+
const mapDispatchToProps = { setPreviousPath, setLanguage };
6675

6776
export default connect(mapStateToProps, mapDispatchToProps)(App);

client/modules/IDE/actions/preferences.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import i18next from 'i18next';
12
import apiClient from '../../../utils/apiClient';
23
import * as ActionTypes from '../../../constants';
34

@@ -210,3 +211,22 @@ export function setAllAccessibleOutput(value) {
210211
};
211212
}
212213

214+
export function setLanguage(value, { persistPreference = true } = {}) {
215+
return (dispatch, getState) => {
216+
i18next.changeLanguage(value);
217+
dispatch({
218+
type: ActionTypes.SET_LANGUAGE,
219+
language: value
220+
});
221+
const state = getState();
222+
if (persistPreference && state.user.authenticated) {
223+
const formParams = {
224+
preferences: {
225+
language: value
226+
}
227+
};
228+
updatePreferences(formParams, dispatch);
229+
}
230+
};
231+
}
232+

client/modules/IDE/pages/IDEView.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import AddToCollectionList from '../components/AddToCollectionList';
3535
import Feedback from '../components/Feedback';
3636
import { CollectionSearchbar } from '../components/Searchbar';
3737

38+
3839
function getTitle(props) {
3940
const { id } = props.project;
4041
return id ? `p5.js Web Editor | ${props.project.name}` : 'p5.js Web Editor';
@@ -144,13 +145,11 @@ class IDEView extends React.Component {
144145
this.props.router.setRouteLeaveHook(this.props.route, () => warnIfUnsavedChanges(this.props));
145146
}
146147
}
147-
148148
componentWillUnmount() {
149149
document.removeEventListener('keydown', this.handleGlobalKeydown, false);
150150
clearTimeout(this.autosaveInterval);
151151
this.autosaveInterval = null;
152152
}
153-
154153
handleGlobalKeydown(e) {
155154
// 83 === s
156155
if (e.keyCode === 83 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) {
@@ -367,6 +366,7 @@ class IDEView extends React.Component {
367366
expandConsole={this.props.expandConsole}
368367
clearConsole={this.props.clearConsole}
369368
cmController={this.cmController}
369+
language={this.props.preferences.language}
370370
/>
371371
</div>
372372
</section>
@@ -527,7 +527,8 @@ IDEView.propTypes = {
527527
gridOutput: PropTypes.bool.isRequired,
528528
soundOutput: PropTypes.bool.isRequired,
529529
theme: PropTypes.string.isRequired,
530-
autorefresh: PropTypes.bool.isRequired
530+
autorefresh: PropTypes.bool.isRequired,
531+
language: PropTypes.string.isRequired
531532
}).isRequired,
532533
closePreferences: PropTypes.func.isRequired,
533534
setFontSize: PropTypes.func.isRequired,

client/modules/IDE/reducers/preferences.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import i18next from 'i18next';
12
import * as ActionTypes from '../../../constants';
23

4+
35
const initialState = {
46
fontSize: 18,
57
autosave: true,
@@ -10,7 +12,8 @@ const initialState = {
1012
gridOutput: false,
1113
soundOutput: false,
1214
theme: 'light',
13-
autorefresh: false
15+
autorefresh: false,
16+
language: 'en-US'
1417
};
1518

1619
const preferences = (state = initialState, action) => {
@@ -37,6 +40,8 @@ const preferences = (state = initialState, action) => {
3740
return Object.assign({}, state, { autorefresh: action.value });
3841
case ActionTypes.SET_LINE_NUMBERS:
3942
return Object.assign({}, state, { lineNumbers: action.value });
43+
case ActionTypes.SET_LANGUAGE:
44+
return Object.assign({}, state, { language: action.language });
4045
default:
4146
return state;
4247
}

client/modules/User/actions.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { browserHistory } from 'react-router';
22
import * as ActionTypes from '../../constants';
33
import apiClient from '../../utils/apiClient';
44
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
5+
import { setLanguage } from '../IDE/actions/preferences';
56
import { showToast, setToastText } from '../IDE/actions/toast';
67

78
export function authError(error) {
@@ -59,6 +60,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
5960
type: ActionTypes.SET_PREFERENCES,
6061
preferences: response.data.preferences
6162
});
63+
setLanguage(response.data.preferences.language, { persistPreference: false });
6264
dispatch(justOpenedProject());
6365
browserHistory.push(previousPath);
6466
resolve();
@@ -80,8 +82,8 @@ export function getUser() {
8082
type: ActionTypes.SET_PREFERENCES,
8183
preferences: response.data.preferences
8284
});
83-
})
84-
.catch((error) => {
85+
setLanguage(response.data.preferences.language, { persistPreference: false });
86+
}).catch((error) => {
8587
const { response } = error;
8688
const message = response.message || response.data.error;
8789
dispatch(authError(message));

client/modules/User/components/SignupForm.jsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
3+
import { withTranslation } from 'react-i18next';
34

45
import { domOnlyProps } from '../../../utils/reduxFormUtils';
56
import Button from '../../../common/Button';
@@ -13,44 +14,44 @@ function SignupForm(props) {
1314
return (
1415
<form className="form" onSubmit={handleSubmit(props.signUpUser.bind(this, props.previousPath))}>
1516
<p className="form__field">
16-
<label htmlFor="username" className="form__label">User Name</label>
17+
<label htmlFor="username" className="form__label">{props.t('SignupForm.Title')}</label>
1718
<input
1819
className="form__input"
19-
aria-label="username"
20+
aria-label={props.t('SignupForm.TitleARIA')}
2021
type="text"
2122
id="username"
2223
{...domOnlyProps(username)}
2324
/>
2425
{username.touched && username.error && <span className="form-error">{username.error}</span>}
2526
</p>
2627
<p className="form__field">
27-
<label htmlFor="email" className="form__label">Email</label>
28+
<label htmlFor="email" className="form__label">{props.t('SignupForm.Email')}</label>
2829
<input
2930
className="form__input"
30-
aria-label="email"
31+
aria-label={props.t('SignupForm.EmailARIA')}
3132
type="text"
3233
id="email"
3334
{...domOnlyProps(email)}
3435
/>
3536
{email.touched && email.error && <span className="form-error">{email.error}</span>}
3637
</p>
3738
<p className="form__field">
38-
<label htmlFor="password" className="form__label">Password</label>
39+
<label htmlFor="password" className="form__label">{props.t('SignupForm.Password')}</label>
3940
<input
4041
className="form__input"
41-
aria-label="password"
42+
aria-label={props.t('SignupForm.PasswordARIA')}
4243
type="password"
4344
id="password"
4445
{...domOnlyProps(password)}
4546
/>
4647
{password.touched && password.error && <span className="form-error">{password.error}</span>}
4748
</p>
4849
<p className="form__field">
49-
<label htmlFor="confirm password" className="form__label">Confirm Password</label>
50+
<label htmlFor="confirm password" className="form__label">{props.t('SignupForm.ConfirmPassword')}</label>
5051
<input
5152
className="form__input"
5253
type="password"
53-
aria-label="confirm password"
54+
aria-label={props.t('SignupForm.ConfirmPasswordARIA')}
5455
id="confirm password"
5556
{...domOnlyProps(confirmPassword)}
5657
/>
@@ -63,7 +64,7 @@ function SignupForm(props) {
6364
<Button
6465
type="submit"
6566
disabled={submitting || invalid || pristine}
66-
>Sign Up
67+
>{props.t('SignupForm.SubmitSignup')}
6768
</Button>
6869
</form>
6970
);
@@ -81,7 +82,8 @@ SignupForm.propTypes = {
8182
submitting: PropTypes.bool,
8283
invalid: PropTypes.bool,
8384
pristine: PropTypes.bool,
84-
previousPath: PropTypes.string.isRequired
85+
previousPath: PropTypes.string.isRequired,
86+
t: PropTypes.func.isRequired
8587
};
8688

8789
SignupForm.defaultProps = {
@@ -90,4 +92,4 @@ SignupForm.defaultProps = {
9092
invalid: false
9193
};
9294

93-
export default SignupForm;
95+
export default withTranslation()(SignupForm);

client/modules/User/pages/SignupView.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
44
import { Link, browserHistory } from 'react-router';
55
import { Helmet } from 'react-helmet';
66
import { reduxForm } from 'redux-form';
7+
import { withTranslation } from 'react-i18next';
78
import * as UserActions from '../actions';
89
import SignupForm from '../components/SignupForm';
910
import apiClient from '../../../utils/apiClient';
@@ -26,19 +27,19 @@ class SignupView extends React.Component {
2627
<Nav layout="dashboard" />
2728
<main className="form-container">
2829
<Helmet>
29-
<title>p5.js Web Editor | Signup</title>
30+
<title>{this.props.t('SignupView.Title')}</title>
3031
</Helmet>
3132
<div className="form-container__content">
32-
<h2 className="form-container__title">Sign Up</h2>
33+
<h2 className="form-container__title">{this.props.t('SignupView.Description')}</h2>
3334
<SignupForm {...this.props} />
34-
<h2 className="form-container__divider">Or</h2>
35+
<h2 className="form-container__divider">{this.props.t('SignupView.Or')}</h2>
3536
<div className="form-container__stack">
3637
<SocialAuthButton service={SocialAuthButton.services.github} />
3738
<SocialAuthButton service={SocialAuthButton.services.google} />
3839
</div>
3940
<p className="form__navigation-options">
40-
Already have an account?&nbsp;
41-
<Link className="form__login-button" to="/login">Log In</Link>
41+
{this.props.t('SignupView.AlreadyHave')}
42+
<Link className="form__login-button" to="/login">{this.props.t('SignupView.Login')}</Link>
4243
</p>
4344
</div>
4445
</main>
@@ -108,7 +109,8 @@ SignupView.propTypes = {
108109
previousPath: PropTypes.string.isRequired,
109110
user: PropTypes.shape({
110111
authenticated: PropTypes.bool
111-
})
112+
}),
113+
t: PropTypes.func.isRequired
112114
};
113115

114116
SignupView.defaultProps = {
@@ -117,11 +119,11 @@ SignupView.defaultProps = {
117119
}
118120
};
119121

120-
export default reduxForm({
122+
export default withTranslation()(reduxForm({
121123
form: 'signup',
122124
fields: ['username', 'email', 'password', 'confirmPassword'],
123125
onSubmitFail,
124126
validate: validateSignup,
125127
asyncValidate,
126128
asyncBlurFields: ['username', 'email']
127-
}, mapStateToProps, mapDispatchToProps)(SignupView);
129+
}, mapStateToProps, mapDispatchToProps)(SignupView));

server/models/user.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ const userSchema = new Schema({
6565
gridOutput: { type: Boolean, default: false },
6666
soundOutput: { type: Boolean, default: false },
6767
theme: { type: String, default: 'light' },
68-
autorefresh: { type: Boolean, default: false }
68+
autorefresh: { type: Boolean, default: false },
69+
language: { type: String, default: 'en-US' }
6970
},
7071
totalSize: { type: Number, default: 0 }
7172
}, { timestamps: true, usePushEach: true });

0 commit comments

Comments
 (0)