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 <me@andrewnicolaou.co.uk>
This commit is contained in:
parent
236cdac7ed
commit
3333dd41fa
9 changed files with 55 additions and 15 deletions
|
@ -9,7 +9,7 @@ import i18next from 'i18next';
|
|||
import * as IDEActions from '../modules/IDE/actions/ide';
|
||||
import * as toastActions from '../modules/IDE/actions/toast';
|
||||
import * as projectActions from '../modules/IDE/actions/project';
|
||||
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
|
||||
import { setAllAccessibleOutput, setLanguage } from '../modules/IDE/actions/preferences';
|
||||
import { logoutUser } from '../modules/User/actions';
|
||||
|
||||
import getConfig from '../utils/getConfig';
|
||||
|
@ -72,7 +72,6 @@ class Nav extends React.PureComponent {
|
|||
document.removeEventListener('mousedown', this.handleClick, false);
|
||||
document.removeEventListener('keydown', this.closeDropDown, false);
|
||||
}
|
||||
|
||||
setDropdown(dropdown) {
|
||||
this.setState({
|
||||
dropdownOpen: dropdown
|
||||
|
@ -170,7 +169,7 @@ class Nav extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleLangSelection(event) {
|
||||
i18next.changeLanguage(event.target.value);
|
||||
this.props.setLanguage(event.target.value);
|
||||
this.props.showToast(1500);
|
||||
this.props.setToastText('Toast.LangChange');
|
||||
this.setDropdown('none');
|
||||
|
@ -808,8 +807,8 @@ Nav.propTypes = {
|
|||
params: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
}),
|
||||
t: PropTypes.func.isRequired
|
||||
|
||||
t: PropTypes.func.isRequired,
|
||||
setLanguage: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Nav.defaultProps = {
|
||||
|
@ -839,7 +838,8 @@ const mapDispatchToProps = {
|
|||
...projectActions,
|
||||
...toastActions,
|
||||
logoutUser,
|
||||
setAllAccessibleOutput
|
||||
setAllAccessibleOutput,
|
||||
setLanguage
|
||||
};
|
||||
|
||||
export default withTranslation()(withRouter(connect(mapStateToProps, mapDispatchToProps)(Nav)));
|
||||
|
|
|
@ -45,7 +45,8 @@ describe('Nav', () => {
|
|||
rootFile: {
|
||||
id: 'root-file'
|
||||
},
|
||||
t: jest.fn()
|
||||
t: jest.fn(),
|
||||
setLanguage: jest.fn()
|
||||
};
|
||||
|
||||
it('renders correctly', () => {
|
||||
|
|
|
@ -93,6 +93,7 @@ export const SHOW_TOAST = 'SHOW_TOAST';
|
|||
export const HIDE_TOAST = 'HIDE_TOAST';
|
||||
export const SET_TOAST_TEXT = 'SET_TOAST_TEXT';
|
||||
export const SET_THEME = 'SET_THEME';
|
||||
export const SET_LANGUAGE = 'SET_LANGUAGE';
|
||||
|
||||
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
|
||||
export const SET_AUTOREFRESH = 'SET_AUTOREFRESH';
|
||||
|
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
|||
import getConfig from '../../utils/getConfig';
|
||||
import DevTools from './components/DevTools';
|
||||
import { setPreviousPath } from '../IDE/actions/ide';
|
||||
import { setLanguage } from '../IDE/actions/preferences';
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props, context) {
|
||||
|
@ -23,6 +24,10 @@ class App extends React.Component {
|
|||
if (locationWillChange && !shouldSkipRemembering) {
|
||||
this.props.setPreviousPath(this.props.location.pathname);
|
||||
}
|
||||
|
||||
if (this.props.language !== nextProps.language) {
|
||||
this.props.setLanguage(nextProps.language, { persistPreference: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
@ -50,18 +55,22 @@ App.propTypes = {
|
|||
}),
|
||||
}).isRequired,
|
||||
setPreviousPath: PropTypes.func.isRequired,
|
||||
setLanguage: PropTypes.func.isRequired,
|
||||
language: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
};
|
||||
|
||||
App.defaultProps = {
|
||||
children: null,
|
||||
language: null,
|
||||
theme: 'light'
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
theme: state.preferences.theme,
|
||||
language: state.preferences.language,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = { setPreviousPath };
|
||||
const mapDispatchToProps = { setPreviousPath, setLanguage };
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import i18next from 'i18next';
|
||||
import apiClient from '../../../utils/apiClient';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
|
@ -210,3 +211,22 @@ export function setAllAccessibleOutput(value) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setLanguage(value, { persistPreference = true } = {}) {
|
||||
return (dispatch, getState) => {
|
||||
i18next.changeLanguage(value);
|
||||
dispatch({
|
||||
type: ActionTypes.SET_LANGUAGE,
|
||||
language: value
|
||||
});
|
||||
const state = getState();
|
||||
if (persistPreference && state.user.authenticated) {
|
||||
const formParams = {
|
||||
preferences: {
|
||||
language: value
|
||||
}
|
||||
};
|
||||
updatePreferences(formParams, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import AddToCollectionList from '../components/AddToCollectionList';
|
|||
import Feedback from '../components/Feedback';
|
||||
import { CollectionSearchbar } from '../components/Searchbar';
|
||||
|
||||
|
||||
function getTitle(props) {
|
||||
const { id } = props.project;
|
||||
return id ? `p5.js Web Editor | ${props.project.name}` : 'p5.js Web Editor';
|
||||
|
@ -144,13 +145,11 @@ class IDEView extends React.Component {
|
|||
this.props.router.setRouteLeaveHook(this.props.route, () => warnIfUnsavedChanges(this.props));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.handleGlobalKeydown, false);
|
||||
clearTimeout(this.autosaveInterval);
|
||||
this.autosaveInterval = null;
|
||||
}
|
||||
|
||||
handleGlobalKeydown(e) {
|
||||
// 83 === s
|
||||
if (e.keyCode === 83 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) {
|
||||
|
@ -367,6 +366,7 @@ class IDEView extends React.Component {
|
|||
expandConsole={this.props.expandConsole}
|
||||
clearConsole={this.props.clearConsole}
|
||||
cmController={this.cmController}
|
||||
language={this.props.preferences.language}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -527,7 +527,8 @@ IDEView.propTypes = {
|
|||
gridOutput: PropTypes.bool.isRequired,
|
||||
soundOutput: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.string.isRequired,
|
||||
autorefresh: PropTypes.bool.isRequired
|
||||
autorefresh: PropTypes.bool.isRequired,
|
||||
language: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
closePreferences: PropTypes.func.isRequired,
|
||||
setFontSize: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import i18next from 'i18next';
|
||||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
|
||||
const initialState = {
|
||||
fontSize: 18,
|
||||
autosave: true,
|
||||
|
@ -10,7 +12,8 @@ const initialState = {
|
|||
gridOutput: false,
|
||||
soundOutput: false,
|
||||
theme: 'light',
|
||||
autorefresh: false
|
||||
autorefresh: false,
|
||||
language: 'en-US'
|
||||
};
|
||||
|
||||
const preferences = (state = initialState, action) => {
|
||||
|
@ -37,6 +40,8 @@ const preferences = (state = initialState, action) => {
|
|||
return Object.assign({}, state, { autorefresh: action.value });
|
||||
case ActionTypes.SET_LINE_NUMBERS:
|
||||
return Object.assign({}, state, { lineNumbers: action.value });
|
||||
case ActionTypes.SET_LANGUAGE:
|
||||
return Object.assign({}, state, { language: action.language });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { browserHistory } from 'react-router';
|
|||
import * as ActionTypes from '../../constants';
|
||||
import apiClient from '../../utils/apiClient';
|
||||
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
|
||||
import { setLanguage } from '../IDE/actions/preferences';
|
||||
import { showToast, setToastText } from '../IDE/actions/toast';
|
||||
|
||||
export function authError(error) {
|
||||
|
@ -59,6 +60,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
|
|||
type: ActionTypes.SET_PREFERENCES,
|
||||
preferences: response.data.preferences
|
||||
});
|
||||
setLanguage(response.data.preferences.language, { persistPreference: false });
|
||||
dispatch(justOpenedProject());
|
||||
browserHistory.push(previousPath);
|
||||
resolve();
|
||||
|
@ -80,8 +82,8 @@ export function getUser() {
|
|||
type: ActionTypes.SET_PREFERENCES,
|
||||
preferences: response.data.preferences
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setLanguage(response.data.preferences.language, { persistPreference: false });
|
||||
}).catch((error) => {
|
||||
const { response } = error;
|
||||
const message = response.message || response.data.error;
|
||||
dispatch(authError(message));
|
||||
|
|
|
@ -65,7 +65,8 @@ const userSchema = new Schema({
|
|||
gridOutput: { type: Boolean, default: false },
|
||||
soundOutput: { type: Boolean, default: false },
|
||||
theme: { type: String, default: 'light' },
|
||||
autorefresh: { type: Boolean, default: false }
|
||||
autorefresh: { type: Boolean, default: false },
|
||||
language: { type: String, default: 'en-US' }
|
||||
},
|
||||
totalSize: { type: Number, default: 0 }
|
||||
}, { timestamps: true, usePushEach: true });
|
||||
|
|
Loading…
Reference in a new issue