Skip to content

Commit 2aa49d5

Browse files
committed
🔀 merge from develop
2 parents fe81240 + 1eddeef commit 2aa49d5

File tree

13 files changed

+115
-41
lines changed

13 files changed

+115
-41
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';
@@ -72,7 +72,6 @@ class Nav extends React.PureComponent {
7272
document.removeEventListener('mousedown', this.handleClick, false);
7373
document.removeEventListener('keydown', this.closeDropDown, false);
7474
}
75-
7675
setDropdown(dropdown) {
7776
this.setState({
7877
dropdownOpen: dropdown
@@ -170,7 +169,7 @@ class Nav extends React.PureComponent {
170169
}
171170

172171
handleLangSelection(event) {
173-
i18next.changeLanguage(event.target.value);
172+
this.props.setLanguage(event.target.value);
174173
this.props.showToast(1500);
175174
this.props.setToastText('Toast.LangChange');
176175
this.setDropdown('none');
@@ -808,8 +807,8 @@ Nav.propTypes = {
808807
params: PropTypes.shape({
809808
username: PropTypes.string
810809
}),
811-
t: PropTypes.func.isRequired
812-
810+
t: PropTypes.func.isRequired,
811+
setLanguage: PropTypes.func.isRequired,
813812
};
814813

815814
Nav.defaultProps = {
@@ -839,7 +838,8 @@ const mapDispatchToProps = {
839838
...projectActions,
840839
...toastActions,
841840
logoutUser,
842-
setAllAccessibleOutput
841+
setAllAccessibleOutput,
842+
setLanguage
843843
};
844844

845845
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
@@ -45,7 +45,8 @@ describe('Nav', () => {
4545
rootFile: {
4646
id: 'root-file'
4747
},
48-
t: jest.fn()
48+
t: jest.fn(),
49+
setLanguage: jest.fn()
4950
};
5051

5152
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: 3 additions & 2 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';
@@ -167,13 +168,11 @@ class IDEView extends React.Component {
167168
warnIfUnsavedChanges(this.props));
168169
}
169170
}
170-
171171
componentWillUnmount() {
172172
document.removeEventListener('keydown', this.handleGlobalKeydown, false);
173173
clearTimeout(this.autosaveInterval);
174174
this.autosaveInterval = null;
175175
}
176-
177176
handleGlobalKeydown(e) {
178177
// 83 === s
179178
if (
@@ -428,6 +427,7 @@ class IDEView extends React.Component {
428427
expandConsole={this.props.expandConsole}
429428
clearConsole={this.props.clearConsole}
430429
cmController={this.cmController}
430+
language={this.props.preferences.language}
431431
/>
432432
</div>
433433
</section>
@@ -585,6 +585,7 @@ IDEView.propTypes = {
585585
soundOutput: PropTypes.bool.isRequired,
586586
theme: PropTypes.string.isRequired,
587587
autorefresh: PropTypes.bool.isRequired,
588+
language: PropTypes.string.isRequired
588589
}).isRequired,
589590
closePreferences: PropTypes.func.isRequired,
590591
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: 15 additions & 19 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';
@@ -20,12 +21,10 @@ function SignupForm(props) {
2021
onSubmit={handleSubmit(props.signUpUser.bind(this, props.previousPath))}
2122
>
2223
<p className="form__field">
23-
<label htmlFor="username" className="form__label">
24-
User Name
25-
</label>
24+
<label htmlFor="username" className="form__label">{props.t('SignupForm.Title')}</label>
2625
<input
2726
className="form__input"
28-
aria-label="username"
27+
aria-label={props.t('SignupForm.TitleARIA')}
2928
type="text"
3029
id="username"
3130
{...domOnlyProps(username)}
@@ -35,12 +34,10 @@ function SignupForm(props) {
3534
)}
3635
</p>
3736
<p className="form__field">
38-
<label htmlFor="email" className="form__label">
39-
Email
40-
</label>
37+
<label htmlFor="email" className="form__label">{props.t('SignupForm.Email')}</label>
4138
<input
4239
className="form__input"
43-
aria-label="email"
40+
aria-label={props.t('SignupForm.EmailARIA')}
4441
type="text"
4542
id="email"
4643
{...domOnlyProps(email)}
@@ -50,12 +47,10 @@ function SignupForm(props) {
5047
)}
5148
</p>
5249
<p className="form__field">
53-
<label htmlFor="password" className="form__label">
54-
Password
55-
</label>
50+
<label htmlFor="password" className="form__label">{props.t('SignupForm.Password')}</label>
5651
<input
5752
className="form__input"
58-
aria-label="password"
53+
aria-label={props.t('SignupForm.PasswordARIA')}
5954
type="password"
6055
id="password"
6156
{...domOnlyProps(password)}
@@ -65,22 +60,22 @@ function SignupForm(props) {
6560
)}
6661
</p>
6762
<p className="form__field">
68-
<label htmlFor="confirm password" className="form__label">
69-
Confirm Password
70-
</label>
63+
<label htmlFor="confirm password" className="form__label">{props.t('SignupForm.ConfirmPassword')}</label>
7164
<input
7265
className="form__input"
7366
type="password"
74-
aria-label="confirm password"
67+
aria-label={props.t('SignupForm.ConfirmPasswordARIA')}
7568
id="confirm password"
7669
{...domOnlyProps(confirmPassword)}
7770
/>
7871
{confirmPassword.touched && confirmPassword.error && (
7972
<span className="form-error">{confirmPassword.error}</span>
8073
)}
8174
</p>
82-
<Button type="submit" disabled={submitting || invalid || pristine}>
83-
Sign Up
75+
<Button
76+
type="submit"
77+
disabled={submitting || invalid || pristine}
78+
>{props.t('SignupForm.SubmitSignup')}
8479
</Button>
8580
</form>
8681
);
@@ -99,6 +94,7 @@ SignupForm.propTypes = {
9994
invalid: PropTypes.bool,
10095
pristine: PropTypes.bool,
10196
previousPath: PropTypes.string.isRequired,
97+
t: PropTypes.func.isRequired
10298
};
10399

104100
SignupForm.defaultProps = {
@@ -107,4 +103,4 @@ SignupForm.defaultProps = {
107103
invalid: false,
108104
};
109105

110-
export default SignupForm;
106+
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)