Skip to content

Commit 3333dd4

Browse files
oruburosandrewn
andauthored
Persistence Language Functionality to Store Language in User Preferences (#1536)
* Entry points to introduce persistence in language selection * setLanguage action changes both the state and the i18next language * Ensure language change applies to all pages on load Co-authored-by: Andrew Nicolaou <[email protected]>
1 parent 236cdac commit 3333dd4

File tree

9 files changed

+55
-15
lines changed

9 files changed

+55
-15
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: 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));

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)