Merge pull request #551 from chrisgarrity/feature/gh511-language-selector

Add language selection widget to menubar
This commit is contained in:
chrisgarrity authored and GitHub committed 2017-07-20 09:53:16 -04:00
commit c68653a38e
13 files changed
+198 -44

No files matched your search

+1
View File
@@ -1,5 +1,6 @@
{
"plugins": [
"syntax-dynamic-import",
"transform-object-rest-spread",
["react-intl", {
"messagesDir": "./translations/messages/"
+2 -1
View File
@@ -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",
+2 -2
View File
@@ -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/';
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve">
<g>
<polygon fill="#999999" points="148.7,59.1 68,30.6 68,206.1 148.7,180 "/>
<polygon fill="#FFFFFF" points="146.7,59 230.5,30.5 230.5,206 146.7,180 "/>
<polygon fill="#FFFFFF" points="47.1,236.9 146.7,203.7 146.7,58.9 47.1,92.2 "/>
<polygon fill="#999999" points="192.1,239.1 206.2,262.3 213.7,240.7 "/>
<g>
<path fill="#999999" d="M81,108.8c-0.5-0.5,0.7,4.2,2.4,6c3,3,5.4,3.4,6.6,3.5c2.8,0.1,6.2-0.7,8.2-1.5c2-0.8,5.4-2.6,6.7-5.2
c0.3-0.5,1-1.5,0.6-3.7c-0.4-1.7-1.5-2.4-2.8-2.3c-1.4,0.1-5.5,1.2-7.4,1.8c-2,0.6-6.1,1.8-7.9,2.2C85.5,110,81.6,109.4,81,108.8z
"/>
<path fill="#999999" d="M131.2,165.8c-0.8-0.3-17-7-19.3-8.1c-1.9-0.9-6.5-2.9-8.7-3.7c6.1-9.4,10-16.6,10.5-17.7
c1-2,7.4-14.7,7.6-15.4c0.1-0.8,0.3-3.7,0.2-4.4c-0.1-0.7-2.5,0.7-5.7,1.7c-3.2,1.1-9.4,5.1-11.7,5.6c-2.4,0.5-10,3.4-13.9,4.7
c-3.9,1.3-11.3,3.5-14.3,4.4c-3,0.8-5.7,0.9-7.4,1.4c0,0,0.2,2.4,0.7,3.1c0.4,0.7,2.1,2.5,3.9,2.9c1.9,0.5,5,0.3,6.4,0
c1.4-0.3,3.8-1.5,4.2-2c0.3-0.5-0.2-2.2,0.4-2.6c0.6-0.5,8-2.2,10.8-3.1c2.8-0.9,13.5-4.6,15-4.4c-0.5,1.5-9.1,18.6-11.9,23.7
c-2.8,5.1-18.9,27.5-22.3,31.4c-2.6,3-8.9,10.7-11.1,12.4c0.6,0.2,4.5-0.2,5.2-0.6c4.4-2.7,11.8-11.9,14.2-14.7
c7.1-8.3,13.3-17,18.2-24.5h0c1,0.4,8.7,6.7,10.8,8.1c2,1.4,10,5.9,11.8,6.6c1.7,0.7,8.4,3.8,8.7,2.8
C133.5,172.2,132,166.1,131.2,165.8z"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#999999" d="M93.7,250.4c1.6,1,3,1.7,4.7,2.5c3.3,1.6,7,3.4,10.5,4.7
c4.8,1.8,9.7,3.3,14.5,4.4c2.7,0.6,5.6,1.1,8.5,1.6c0.3,0,8,1,9.5,1h7.8c3-0.3,5.9-0.4,8.9-0.9c2.4-0.3,5.1-0.8,7.7-1.4
c1.9-0.4,3.9-0.9,5.8-1.5c1.8-0.5,3.9-1.2,5.9-1.9c1.3-0.4,2.7-1,4.1-1.6c1.1-0.5,2.5-1.1,3.8-1.6c1.6-0.7,3.4-1.6,5.1-2.5
c1.4-0.7,2.9-1.6,4.4-2.4c1.1-0.6,3.7-2.6,5.1-2.6c1.6,0,2.6,1.4,2.6,2.6c0,2.5-3.4,3.3-4.9,4.4c-1.6,1.1-3.6,2-5.4,2.9
c-3.5,1.8-7,3.4-10.4,4.7c-4.4,1.6-9.3,3.2-13.6,4.2c-1.6,0.3-3.3,0.8-4.9,1c-0.9,0.2-9.9,1.6-12.4,1.6h-11.4c-3-0.3-6.2-0.6-9.3-1
c-2.7-0.4-5.5-1-8.2-1.6c-2.1-0.4-4.3-1-6.3-1.6c-3.5-1-6.8-2.2-10.1-3.5c-6-2.2-12.2-5.2-18.1-9.1c-1-0.7-1.1-1.4-1.1-2.2
c0-1.3,1-2.5,2.5-2.5C90.4,248.2,93.2,250.1,93.7,250.4z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#999999" d="M144,201.7l-94.3,31.4v-139L144,62.7V201.7z M149.3,58.6v145.3
c-0.1,0.4-0.3,0.9-0.6,1.3c-0.2,0.3-0.5,0.6-0.8,0.7c-2.2,0.9-99.5,33.6-100.8,33.6c-1,0-2-0.7-2.5-1.8c0-0.1-0.1-0.2-0.1-0.3V91.9
c0.2-0.4,0.3-1,0.6-1.4c0.7-1,1.9-1.1,2.7-1.4c1.5-0.5,97.4-32.7,98.8-32.7C147.4,56.4,149.3,57.1,149.3,58.6z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#999999" d="M227.8,202.4L149.4,178V60.9l78.4-26.6V202.4z M233.1,30.8v174.9
c-0.1,2-1.5,2.9-2.8,2.9c-1.1,0-9.3-2.8-10.6-3.2c-10.9-3.4-21.9-6.7-32.7-10.1c-2.4-0.8-4.9-1.6-7.3-2.3c-2.1-0.6-4.3-1.3-6.4-2
c-9.3-2.9-18.7-5.7-27.9-8.8c-0.3-0.1-1.2-1.3-1.2-1.6V58.4c0.2-0.4,0.3-1,0.8-1.3c0.7-0.8,30.4-10.6,42-14.5
c3.1-1.1,42.1-14.5,43.3-14.5C231.9,28,233.1,29.1,233.1,30.8z"/>
<polygon fill="#999999" points="253,239.1 148.2,205.7 148.7,60.3 253,93.5 "/>
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M200.2,125.2l-10.3,24.9l18.9,5.7L200.2,125.2z M194,101.8
l13.5,4.1l24.6,88.6l-13.9-4.2l-5-18.2l-28.7-8.7l-6.2,14.8l-13.9-4.2L194,101.8z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -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;
}
@@ -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
}) => (
<Box {...props}>
<div className={styles.group}>
<img
className={styles.languageIcon}
src={languageIcon}
/>
<select
className={styles.languageSelect}
value={currentLocale}
onChange={onChange}
>
{Object.keys(languages).map(locale => (
<option
key={locale}
value={locale}
>
{languages[locale]}
</option>
))}
</select>
</div>
</Box>
);
LanguageSelector.propTypes = {
currentLocale: PropTypes.string,
onChange: PropTypes.func
};
module.exports = LanguageSelector;
+2
View File
@@ -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 () {
</div>
<SaveButton className={styles.menuItem} />
<LoadButton className={styles.menuItem} />
<LanguageSelector className={styles.menuItem} />
</Box>
);
};
+20
View File
@@ -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);
+8 -10
View File
@@ -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((
<Provider store={store}>
File renamed without changes.
-19
View File
@@ -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
}
};
+23 -3
View File
@@ -1,5 +1,25 @@
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';
import {IntlProvider, intlReducer} from 'react-intl-redux';
export {intlReducer as default} from 'react-intl-redux';
export const updateIntl = locale => superUpdateIntl(locale, messages[locale] || messages.en);
Object.keys(languages).forEach(locale => {
// TODO: will need to handle locales not in the default intl - see www/custom-locales
import(`react-intl/locale-data/${locale}`).then(data => addLocaleData(data));
});
const intlInitialState = {
intl: {
defaultLocale: 'en',
locale: 'en',
messages: messages.en
}
};
const updateIntl = locale => superUpdateIntl({
locale: locale,
messages: messages[locale] || messages.en
});
export {intlReducer as default, IntlProvider, intlInitialState, updateIntl};
+1 -9
View File
@@ -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$/,