diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 8a89fb1324cc5e6186cf32813a5577844c5a9f91..ffc38e07f9fa5a5109d53e0005c2ce3c8209c91b 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -4,7 +4,7 @@ import {compose} from 'redux'; import {connect} from 'react-redux'; import ReactModal from 'react-modal'; import VM from 'scratch-vm'; -import {injectIntl, intlShape} from 'react-intl'; +import {defineMessages, injectIntl, intlShape} from 'react-intl'; import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; import {openExtensionLibrary} from '../reducers/modals'; @@ -25,14 +25,22 @@ import { } from '../reducers/modals'; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; +import LocalizationHOC from '../lib/localization-hoc.jsx'; import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx'; import ProjectSaverHOC from '../lib/project-saver-hoc.jsx'; import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; import vmManagerHOC from '../lib/vm-manager-hoc.jsx'; -import {defaultProjectTitleMessages} from '../reducers/project-title'; import GUIComponent from '../components/gui/gui.jsx'; +const messages = defineMessages({ + defaultProjectTitle: { + id: 'gui.gui.defaultProjectTitle', + description: 'Default title for project', + defaultMessage: 'Scratch Project' + } +}); + class GUI extends React.Component { componentDidMount () { this.setReduxTitle(this.props.projectTitle); @@ -48,7 +56,7 @@ class GUI extends React.Component { setReduxTitle (newTitle) { if (newTitle === null || typeof newTitle === 'undefined') { this.props.onUpdateReduxProjectTitle( - this.props.intl.formatMessage(defaultProjectTitleMessages.defaultProjectTitle) + this.props.intl.formatMessage(messages.defaultProjectTitle) ); } else { this.props.onUpdateReduxProjectTitle(newTitle); @@ -163,6 +171,7 @@ const ConnectedGUI = injectIntl(connect( // the hierarchy of HOC constructor calls clearer here; it has nothing to do with redux's // ability to compose reducers. const WrappedGui = compose( + LocalizationHOC, ErrorBoundaryHOC('Top Level App'), FontLoaderHOC, ProjectFetcherHOC, diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx index 93d97180ef38526ac05feaf019338aa32ed4496b..3c9eb9266d115b38d2eb0e791131c001ed078de1 100644 --- a/src/lib/app-state-hoc.jsx +++ b/src/lib/app-state-hoc.jsx @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Provider} from 'react-redux'; import {createStore, combineReducers, compose} from 'redux'; -import ConnectedIntlProvider from './connected-intl-provider.jsx'; import localesReducer, {initLocale, localesInitialState} from '../reducers/locales'; @@ -108,9 +107,7 @@ const AppStateHOC = function (WrappedComponent, localesOnly) { } = this.props; return ( <Provider store={this.store}> - <ConnectedIntlProvider> - <WrappedComponent {...componentProps} /> - </ConnectedIntlProvider> + <WrappedComponent {...componentProps} /> </Provider> ); } diff --git a/src/lib/localization-hoc.jsx b/src/lib/localization-hoc.jsx new file mode 100644 index 0000000000000000000000000000000000000000..2df5158e838d95f3ad7c649178f7843a2690d253 --- /dev/null +++ b/src/lib/localization-hoc.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {connect} from 'react-redux'; + +import ConnectedIntlProvider from './connected-intl-provider.jsx'; + +/* + * Higher Order Component to provide localiztion state. Creates a nested IntlProvider + * to handle Gui intl context. The component accepts an onSetLanguage callback that is + * called when the locale chagnes. + * @param {React.Component} WrappedComponent - component to provide state for + * @returns {React.Component} component with intl state provided from redux + */ +const LocalizationHOC = function (WrappedComponent) { + class LocalizationWrapper extends React.Component { + componentDidUpdate (prevProps) { + if (prevProps.locale !== this.props.locale) { + this.props.onSetLanguage(this.props.locale); + } + } + render () { + const { + locale, // eslint-disable-line no-unused-vars + onSetLanguage, // eslint-disable-line no-unused-vars + ...componentProps + } = this.props; + return ( + <ConnectedIntlProvider> + <WrappedComponent {...componentProps} /> + </ConnectedIntlProvider> + ); + } + } + LocalizationWrapper.propTypes = { + locale: PropTypes.string, + onSetLanguage: PropTypes.func + }; + + LocalizationWrapper.defaultProps = { + onSetLanguage: () => {} + }; + + const mapStateToProps = state => ({ + locale: state.locales.locale + }); + + const mapDispatchToProps = () => ({}); + + return connect( + mapStateToProps, + mapDispatchToProps + )(LocalizationWrapper); +}; + +export default LocalizationHOC; diff --git a/src/lib/titled-hoc.jsx b/src/lib/titled-hoc.jsx index 2cc3e59209ead4289927f52d591096a615baf7bf..fea6a9388a812868114f9440ab5bafada9f85c8e 100644 --- a/src/lib/titled-hoc.jsx +++ b/src/lib/titled-hoc.jsx @@ -1,7 +1,5 @@ import React from 'react'; import bindAll from 'lodash.bindall'; -import {intlShape, injectIntl} from 'react-intl'; -import {defaultProjectTitleMessages} from '../reducers/project-title'; /* Higher Order Component to get and set the project title * @param {React.Component} WrappedComponent component to receive project title related props @@ -15,34 +13,24 @@ const TitledHOC = function (WrappedComponent) { 'handleUpdateProjectTitle' ]); this.state = { - projectTitle: this.props.intl.formatMessage(defaultProjectTitleMessages.defaultProjectTitle) + projectTitle: null }; } handleUpdateProjectTitle (newTitle) { this.setState({projectTitle: newTitle}); } render () { - const { - /* eslint-disable no-unused-vars */ - intl, - /* eslint-enable no-unused-vars */ - ...componentProps - } = this.props; return ( <WrappedComponent projectTitle={this.state.projectTitle} onUpdateProjectTitle={this.handleUpdateProjectTitle} - {...componentProps} + {...this.props} /> ); } } - TitledComponent.propTypes = { - intl: intlShape.isRequired - }; - - return injectIntl(TitledComponent); + return TitledComponent; }; export { diff --git a/src/reducers/project-title.js b/src/reducers/project-title.js index 389687ceb10b23c8f59964abdf8b78bb48c2fb6f..09bf6c4eab417e626b8a719326ac685c25d46cdd 100644 --- a/src/reducers/project-title.js +++ b/src/reducers/project-title.js @@ -1,19 +1,9 @@ -import {defineMessages} from 'react-intl'; - const SET_PROJECT_TITLE = 'projectTitle/SET_PROJECT_TITLE'; // we are initializing to a blank string instead of an actual title, // because it would be hard to localize here const initialState = ''; -const defaultProjectTitleMessages = defineMessages({ - defaultProjectTitle: { - id: 'gui.gui.defaultProjectTitle', - description: 'Default title for project', - defaultMessage: 'Scratch Project' - } -}); - const reducer = function (state, action) { if (typeof state === 'undefined') state = initialState; switch (action.type) { @@ -31,6 +21,5 @@ const setProjectTitle = title => ({ export { reducer as default, initialState as projectTitleInitialState, - defaultProjectTitleMessages, setProjectTitle };