From af6973759077bf159b2fead7c215af3dccf9e1d5 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Wed, 19 Jul 2017 12:04:53 -0400 Subject: [PATCH 1/2] Add language selection widget to menubar Added language-selector component and container. Replaced lib/intl.jsx with reducers/intl.js and added updateIntl to handle language changes. Moved languages.json into src --- scripts/generate-locale-messages.js | 4 +- .../language-selector/language-icon.svg | 72 +++++++++++++++++++ .../language-selector/language-selector.css | 23 ++++++ .../language-selector/language-selector.jsx | 44 ++++++++++++ src/components/menu-bar/menu-bar.jsx | 2 + src/containers/language-selector.jsx | 20 ++++++ src/index.jsx | 18 +++-- languages.json => src/languages.json | 0 src/lib/intl.jsx | 19 ----- src/reducers/intl.js | 27 ++++++- 10 files changed, 195 insertions(+), 34 deletions(-) create mode 100644 src/components/language-selector/language-icon.svg create mode 100644 src/components/language-selector/language-selector.css create mode 100644 src/components/language-selector/language-selector.jsx create mode 100644 src/containers/language-selector.jsx rename languages.json => src/languages.json (100%) delete mode 100644 src/lib/intl.jsx diff --git a/scripts/generate-locale-messages.js b/scripts/generate-locale-messages.js index 1199b0dc5..6534d25e2 100755 --- a/scripts/generate-locale-messages.js +++ b/scripts/generate-locale-messages.js @@ -16,7 +16,7 @@ Translation files are in Chrome i18n json format: ''' They are named by locale, for example: 'fr.json' or 'zh-cn.json' -Current languages supported are listed in ../languages.json +Current languages supported are listed in ../src/languages.json Converts the collection of translation files to a single set of messages. Example output: @@ -42,7 +42,7 @@ Missing locales are ignored, react-intl will use the default messages for them. const fs = require('fs'); const path = require('path'); const mkdirp = require('mkdirp'); -const languages = require('../languages.json'); +const languages = require('../src/languages.json'); const LANG_DIR = './translations/'; const MSGS_DIR = './locale/'; diff --git a/src/components/language-selector/language-icon.svg b/src/components/language-selector/language-icon.svg new file mode 100644 index 000000000..3644ab6fd --- /dev/null +++ b/src/components/language-selector/language-icon.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css new file mode 100644 index 000000000..f5879335e --- /dev/null +++ b/src/components/language-selector/language-selector.css @@ -0,0 +1,23 @@ +@import "../../css/units.css"; + +.group { + display: inline-flex; + flex-direction: row; /* makes columns, for each label/form group */ + align-items: center; + vertical-align: middle; +} + +.language-icon { + height: 2rem; +} + +.language-select { + width: 100%; + height: 1.85rem; + border: 1px solid #4c97ff; + user-select: none; + outline: none; + background: rgba(255, 255, 255, 0.2); + color: white; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} diff --git a/src/components/language-selector/language-selector.jsx b/src/components/language-selector/language-selector.jsx new file mode 100644 index 000000000..78302de02 --- /dev/null +++ b/src/components/language-selector/language-selector.jsx @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +import Box from '../box/box.jsx'; +import languages from '../../languages.json'; +import languageIcon from './language-icon.svg'; +import styles from './language-selector.css'; + +const LanguageSelector = ({ + currentLocale, + onChange, + ...props +}) => ( + +
+ + +
+
+); + + +LanguageSelector.propTypes = { + currentLocale: PropTypes.string, + onChange: PropTypes.func +}; + +module.exports = LanguageSelector; diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index c22ba3636..35ced7049 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -4,6 +4,7 @@ import React from 'react'; import Box from '../box/box.jsx'; import LoadButton from '../../containers/load-button.jsx'; import SaveButton from '../../containers/save-button.jsx'; +import LanguageSelector from '../../containers/language-selector.jsx'; import styles from './menu-bar.css'; import scratchLogo from './scratch-logo.svg'; @@ -23,6 +24,7 @@ const MenuBar = function MenuBar () { + ); }; diff --git a/src/containers/language-selector.jsx b/src/containers/language-selector.jsx new file mode 100644 index 000000000..9412b0c53 --- /dev/null +++ b/src/containers/language-selector.jsx @@ -0,0 +1,20 @@ +import {connect} from 'react-redux'; +import {updateIntl} from '../reducers/intl.js'; + +import LanguageSelectorComponent from '../components/language-selector/language-selector.jsx'; + +const mapStateToProps = state => ({ + currentLocale: state.intl.locale +}); + +const mapDispatchToProps = dispatch => ({ + onChange: e => { + e.preventDefault(); + dispatch(updateIntl(e.target.value)); + } +}); + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(LanguageSelectorComponent); diff --git a/src/index.jsx b/src/index.jsx index 48f4a12da..35c761085 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,9 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import {Provider} from 'react-redux'; -import {createStore, applyMiddleware} from 'redux'; +import {createStore, applyMiddleware, compose} from 'redux'; import throttle from 'redux-throttle'; -import {intlInitialState, IntlProvider} from './lib/intl.jsx'; +import {intlInitialState, IntlProvider} from './reducers/intl.js'; import GUI from './containers/gui.jsx'; import log from './lib/log'; @@ -61,16 +61,14 @@ class App extends React.Component { const appTarget = document.createElement('div'); appTarget.className = styles.app; document.body.appendChild(appTarget); -const store = applyMiddleware( - throttle(300, {leading: true, trailing: true}) -)(createStore)( - reducer, - Object.assign( - {}, - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), - intlInitialState + +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; +const enhancer = composeEnhancers( + applyMiddleware( + throttle(300, {leading: true, trailing: true}) ) ); +const store = createStore(reducer, intlInitialState, enhancer); ReactDOM.render(( diff --git a/languages.json b/src/languages.json similarity index 100% rename from languages.json rename to src/languages.json diff --git a/src/lib/intl.jsx b/src/lib/intl.jsx deleted file mode 100644 index 2c39f2ce3..000000000 --- a/src/lib/intl.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import {addLocaleData} from 'react-intl'; -import languages from '../../languages.json'; -import messages from '../../locale/messages.json'; - -export {IntlProvider, intlReducer} from 'react-intl-redux'; - -Object.keys(languages).forEach(locale => { - // TODO: will need to handle locales not in the default intl - see www/custom-locales - const data = require(`react-intl/locale-data/${locale}`); - addLocaleData(data); -}); - -export const intlInitialState = { - intl: { - defaultLocale: 'en', - locale: 'en', - messages: messages.en - } -}; diff --git a/src/reducers/intl.js b/src/reducers/intl.js index 12c4b35b5..228417057 100644 --- a/src/reducers/intl.js +++ b/src/reducers/intl.js @@ -1,5 +1,26 @@ +import {addLocaleData} from 'react-intl'; import {updateIntl as superUpdateIntl} from 'react-intl-redux'; -import {messages} from '../../locale/messages.json'; +import languages from '../languages.json'; +import messages from '../../locale/messages.json'; -export {intlReducer as default} from 'react-intl-redux'; -export const updateIntl = locale => superUpdateIntl(locale, messages[locale] || messages.en); +export {IntlProvider, intlReducer as default} from 'react-intl-redux'; + +Object.keys(languages).forEach(locale => { + // TODO: will need to handle locales not in the default intl - see www/custom-locales + const data = require(`react-intl/locale-data/${locale}`); + addLocaleData(data); +}); + +// start with English +export const intlInitialState = { + intl: { + defaultLocale: 'en', + locale: 'en', + messages: messages.en + } +}; + +export const updateIntl = locale => superUpdateIntl({ + locale: locale, + messages: messages[locale] || messages.en +}); From 2dd378274614159087364009e4648d3693c7d082 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Wed, 19 Jul 2017 17:06:56 -0400 Subject: [PATCH 2/2] handle messages better MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dynamic import didn’t do what we wanted, so just added `npm run i18n:msgs` to the start command. This is just temporary. Other changes as requested: * exports in one place * loc=> locale Also fixed use of require for localeData to use dynamic import (this one works), so the babelrc changes are needed, otherwise import as a function is not recognized. --- .babelrc | 1 + package.json | 3 ++- .../language-selector/language-selector.jsx | 8 ++++---- src/reducers/intl.js | 13 ++++++------- webpack.config.js | 10 +--------- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.babelrc b/.babelrc index 194df44f1..97242d46f 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,6 @@ { "plugins": [ + "syntax-dynamic-import", "transform-object-rest-spread", ["react-intl", { "messagesDir": "./translations/messages/" diff --git a/package.json b/package.json index 1a5d5ef54..f4bf916b1 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "i18n:msgs": "./scripts/generate-locale-messages.js", "i18n:src": "babel src > tmp.js && rimraf tmp.js && ./scripts/build-i18n-source.js ./translations/messages/ ./translations/", "lint": "eslint . --ext .js,.jsx", - "start": "webpack-dev-server", + "start": "npm run i18n:msgs && webpack-dev-server", "test": "npm run lint && npm run build", "watch": "webpack --progress --colors --watch" }, @@ -32,6 +32,7 @@ "babel-eslint": "^7.1.1", "babel-loader": "^7.0.0", "babel-plugin-react-intl": "2.3.1", + "babel-plugin-syntax-dynamic-import": "6.18.0", "babel-plugin-transform-object-rest-spread": "^6.22.0", "babel-preset-es2015": "^6.22.0", "babel-preset-react": "^6.22.0", diff --git a/src/components/language-selector/language-selector.jsx b/src/components/language-selector/language-selector.jsx index 78302de02..6c6ffd829 100644 --- a/src/components/language-selector/language-selector.jsx +++ b/src/components/language-selector/language-selector.jsx @@ -22,12 +22,12 @@ const LanguageSelector = ({ value={currentLocale} onChange={onChange} > - {Object.keys(languages).map(loc => ( + {Object.keys(languages).map(locale => ( ))} diff --git a/src/reducers/intl.js b/src/reducers/intl.js index 228417057..14fa64ef5 100644 --- a/src/reducers/intl.js +++ b/src/reducers/intl.js @@ -2,17 +2,14 @@ import {addLocaleData} from 'react-intl'; import {updateIntl as superUpdateIntl} from 'react-intl-redux'; import languages from '../languages.json'; import messages from '../../locale/messages.json'; - -export {IntlProvider, intlReducer as default} from 'react-intl-redux'; +import {IntlProvider, intlReducer} from 'react-intl-redux'; Object.keys(languages).forEach(locale => { // TODO: will need to handle locales not in the default intl - see www/custom-locales - const data = require(`react-intl/locale-data/${locale}`); - addLocaleData(data); + import(`react-intl/locale-data/${locale}`).then(data => addLocaleData(data)); }); -// start with English -export const intlInitialState = { +const intlInitialState = { intl: { defaultLocale: 'en', locale: 'en', @@ -20,7 +17,9 @@ export const intlInitialState = { } }; -export const updateIntl = locale => superUpdateIntl({ +const updateIntl = locale => superUpdateIntl({ locale: locale, messages: messages[locale] || messages.en }); + +export {intlReducer as default, IntlProvider, intlInitialState, updateIntl}; diff --git a/webpack.config.js b/webpack.config.js index afb092497..44595ff2c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,15 +33,7 @@ module.exports = { rules: [{ test: /\.jsx?$/, loader: 'babel-loader', - include: path.resolve(__dirname, 'src'), - options: { - plugins: [ - 'transform-object-rest-spread', - ['react-intl', { - messagesDir: './translations/messages/' - }]], - presets: ['es2015', 'react'] - } + include: path.resolve(__dirname, 'src') }, { test: /\.css$/,