From 8136ac4fddbf6e1821439ed874b41486dc39b398 Mon Sep 17 00:00:00 2001
From: chrisgarrity <chrisg@media.mit.edu>
Date: Thu, 18 Oct 2018 20:57:17 -0400
Subject: [PATCH] Move IntlProvider into GUI to support integration with www
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is taking advantage of the fact that react-intl can support nested IntlProviders. GUI will use it’s own ConnectedIntlProvider, that maintains the translations in redux, while www will continue to have it’s own IntlProvider that is initialized when the page loads.

Moving the IntlProvider into GUI meant that nothing in the playground can use injectIntl, so the default project name needed to move into the component instead of being in the reducer.

The Localization HOC is added, it takes an optional onSetLanguage handler, so www can pass a function that will set the cookie that www needs.
---
 .../menu-bar/project-title-input.jsx          | 10 +++-
 src/containers/gui.jsx                        |  2 +
 src/lib/app-state-hoc.jsx                     |  5 +-
 src/lib/localization-hoc.jsx                  | 55 +++++++++++++++++++
 src/lib/titled-hoc.jsx                        | 18 +-----
 src/reducers/project-title.js                 | 11 ----
 6 files changed, 70 insertions(+), 31 deletions(-)
 create mode 100644 src/lib/localization-hoc.jsx

diff --git a/src/components/menu-bar/project-title-input.jsx b/src/components/menu-bar/project-title-input.jsx
index b9b5aa6d8..a07c3fe29 100644
--- a/src/components/menu-bar/project-title-input.jsx
+++ b/src/components/menu-bar/project-title-input.jsx
@@ -16,6 +16,11 @@ const messages = defineMessages({
         id: 'gui.gui.projectTitlePlaceholder',
         description: 'Placeholder for project title when blank',
         defaultMessage: 'Project title here'
+    },
+    defaultProjectTitle: {
+        id: 'gui.gui.defaultProjectTitle',
+        description: 'Default title for project',
+        defaultMessage: 'Scratch Project'
     }
 });
 
@@ -41,7 +46,10 @@ class ProjectTitleInput extends React.Component {
                 placeholder={this.props.intl.formatMessage(messages.projectTitlePlaceholder)}
                 tabIndex="0"
                 type="text"
-                value={this.props.projectTitle}
+                value={this.props.projectTitle ?
+                    this.props.projectTitle :
+                    this.props.intl.formatMessage(messages.defaultProjectTitle)
+                }
                 onSubmit={this.handleUpdateProjectTitle}
             />
         );
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 8a89fb132..9abec8733 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -25,6 +25,7 @@ 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';
@@ -163,6 +164,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 93d97180e..3c9eb9266 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 000000000..2df5158e8
--- /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 2cc3e5920..a2150814f 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: ''
             };
         }
         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 389687ceb..09bf6c4ea 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
 };
-- 
GitLab