Skip to content

Commit

Permalink
Merge pull request #609 from langpavel/feature/react-intl
Browse files Browse the repository at this point in the history
react-intl
  • Loading branch information
koistya committed Apr 22, 2016
2 parents 5e4bacb + 86eadfd commit 1ab3873
Show file tree
Hide file tree
Showing 39 changed files with 963 additions and 35 deletions.
26 changes: 18 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
"express": "4.13.4",
"express-graphql": "0.5.1",
"express-jwt": "3.3.0",
"express-request-language": "^1.1.4",
"fastclick": "1.0.6",
"fbjs": "0.8.1",
"fbjs": "0.8.0",
"front-matter": "2.0.7",
"graphiql": "0.7.0",
"graphql": "0.5.0",
"history": "2.1.0",
"history": "2.0.2",
"intl": "^1.1.0",
"intl-locales-supported": "^1.0.0",
"isomorphic-style-loader": "1.0.0",
"jade": "1.11.0",
"jsonwebtoken": "5.7.0",
Expand All @@ -33,6 +36,10 @@
"pretty-error": "2.0.0",
"react": "15.0.1",
"react-dom": "15.0.1",
"react-intl": "^2.1.0",
"react-redux": "^4.4.5",
"redux": "^3.4.0",
"redux-thunk": "^2.0.1",
"sequelize": "^3.21.0",
"source-map-support": "0.4.0",
"sqlite3": "^3.1.3",
Expand All @@ -42,10 +49,11 @@
"devDependencies": {
"assets-webpack-plugin": "^3.4.0",
"autoprefixer": "^6.3.6",
"babel-cli": "^6.7.7",
"babel-core": "^6.7.7",
"babel-eslint": "^6.0.3",
"babel-cli": "^6.7.5",
"babel-core": "^6.7.6",
"babel-eslint": "^6.0.2",
"babel-loader": "^6.2.4",
"babel-plugin-react-intl": "^2.1.2",
"babel-plugin-react-transform": "^2.0.2",
"babel-plugin-rewire": "^1.0.0-rc-2",
"babel-plugin-transform-react-constant-elements": "^6.5.0",
Expand All @@ -58,7 +66,7 @@
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.7.2",
"babel-template": "^6.7.0",
"babel-types": "^6.7.7",
"babel-types": "^6.7.2",
"browser-sync": "^2.12.3",
"chai": "^3.5.0",
"css-loader": "^0.23.1",
Expand Down Expand Up @@ -90,9 +98,10 @@
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.4",
"redbox-react": "^1.2.3",
"redux-logger": "^2.6.1",
"sinon": "^2.0.0-pre",
"stylelint": "^6.0.3",
"stylelint-config-standard": "^6.0.0",
"stylelint": "^5.4.0",
"stylelint-config-standard": "^5.0.0",
"url-loader": "^0.5.7",
"webpack": "^1.13.0",
"webpack-hot-middleware": "^2.10.0",
Expand Down Expand Up @@ -153,6 +162,7 @@
"test:watch": "mocha src/**/*.test.js --require test/setup.js --compilers js:babel-register --reporter min --watch",
"clean": "babel-node tools/run clean",
"copy": "babel-node tools/run copy",
"extractMessages": "babel-node tools/run extractMessages",
"bundle": "babel-node tools/run bundle",
"build": "babel-node tools/run build",
"deploy": "babel-node tools/run deploy",
Expand Down
3 changes: 3 additions & 0 deletions src/actions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Action creators

Action Creators should go there
55 changes: 55 additions & 0 deletions src/actions/intl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fetch from '../core/fetch';
import {
SET_LOCALE_START,
SET_LOCALE_SUCCESS,
SET_LOCALE_ERROR,
} from '../constants';

export function setLocale({ locale }) {
return async (dispatch) => {
dispatch({
type: SET_LOCALE_START,
payload: {
locale,
},
});

try {
const resp = await fetch('/graphql', {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `query{intl(locale:${JSON.stringify(locale)}){id,message}}`,
}),
credentials: 'include',
});
if (resp.status !== 200) throw new Error(resp.statusText);
const { data } = await resp.json();
const messages = data.intl.reduce((msgs, msg) => {
msgs[msg.id] = msg.message; // eslint-disable-line no-param-reassign
return msgs;
}, {});
dispatch({
type: SET_LOCALE_SUCCESS,
payload: {
locale,
messages,
},
});
} catch (error) {
dispatch({
type: SET_LOCALE_ERROR,
payload: {
locale,
error,
},
});
return false;
}

return true;
};
}
11 changes: 11 additions & 0 deletions src/actions/runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SET_RUNTIME_VARIABLE } from '../constants';

export function setRuntimeVariable({ name, value }) {
return {
type: SET_RUNTIME_VARIABLE,
payload: {
name,
value,
},
};
}
34 changes: 31 additions & 3 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,25 @@
*/

import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import FastClick from 'fastclick';
import { match } from 'universal-router';
import routes from './routes';
import history from './core/history';
import configureStore from './store/configureStore';
import { addEventListener, removeEventListener } from './core/DOMUtils';
import Provide from './components/Provide';

import { addLocaleData } from 'react-intl';

import en from 'react-intl/locale-data/en';
import cs from 'react-intl/locale-data/cs';

[en, cs].forEach(addLocaleData);

const context = {
store: null,
insertCss: styles => styles._insertCss(),
setTitle: value => (document.title = value),
setMeta: (name, content) => {
Expand Down Expand Up @@ -60,11 +71,20 @@ let renderComplete = (state, callback) => {
};
};

function render(container, state, component) {
function render(container, state, config, component) {
return new Promise((resolve, reject) => {
if (process.env.NODE_ENV === 'development') {
console.log(// eslint-disable-line no-console
'React rendering. State:',
config.store.getState()
);
}

try {
ReactDOM.render(
component,
<Provide {...config}>
{component}
</Provide>,
container,
renderComplete.bind(undefined, state, resolve)
);
Expand All @@ -77,10 +97,18 @@ function render(container, state, component) {
function run() {
let currentLocation = null;
const container = document.getElementById('app');
const initialState = JSON.parse(
document.
getElementById('source').
getAttribute('data-initial-state')
);

// Make taps on links and buttons work fast on mobiles
FastClick.attach(document.body);

const store = configureStore(initialState);
context.store = store;

// Re-render the app when window.location changes
const removeHistoryListener = history.listen(location => {
currentLocation = location;
Expand All @@ -89,7 +117,7 @@ function run() {
query: location.query,
state: location.state,
context,
render: render.bind(undefined, container, location.state),
render: render.bind(undefined, container, location.state, { store }),
}).catch(err => console.error(err)); // eslint-disable-line no-console
});

Expand Down
10 changes: 7 additions & 3 deletions src/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class App extends Component {
insertCss: PropTypes.func,
setTitle: PropTypes.func,
setMeta: PropTypes.func,
}),
}).isRequired,
children: PropTypes.element.isRequired,
error: PropTypes.object,
};
Expand Down Expand Up @@ -51,14 +51,18 @@ class App extends Component {
}

render() {
return !this.props.error ? (
if (this.props.error) {
return this.props.children;
}

return (
<div>
<Header />
{this.props.children}
<Feedback />
<Footer />
</div>
) : this.props.children;
);
}

}
Expand Down
33 changes: 29 additions & 4 deletions src/components/Header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,30 @@
*/

import React from 'react';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './Header.scss';
import Link from '../Link';
import Navigation from '../Navigation';
import LanguageSwitcher from '../LanguageSwitcher';

const messages = defineMessages({
brand: {
id: 'header.brand',
defaultMessage: 'Your Company Brand',
description: 'Brand name displayed in header',
},
bannerTitle: {
id: 'header.banner.title',
defaultMessage: 'React',
description: 'Title in page header',
},
bannerDesc: {
id: 'header.banner.desc',
defaultMessage: 'Complex web apps made easy',
description: 'Description in header',
},
});

function Header() {
return (
Expand All @@ -20,15 +40,20 @@ function Header() {
<Navigation className={s.nav} />
<Link className={s.brand} to="/">
<img src={require('./logo-small.png')} width="38" height="38" alt="React" />
<span className={s.brandTxt}>Your Company</span>
<span className={s.brandTxt}>
<FormattedMessage {...messages.brand} />
</span>
</Link>
<LanguageSwitcher />
<div className={s.banner}>
<h1 className={s.bannerTitle}>React</h1>
<p className={s.bannerDesc}>Complex web apps made easy</p>
<h1 className={s.bannerTitle}>
<FormattedMessage {...messages.bannerTitle} />
</h1>
<FormattedMessage tagName="p" {...messages.bannerDesc} />
</div>
</div>
</div>
);
}

export default withStyles(s)(Header);
export default injectIntl(withStyles(s)(Header));
42 changes: 42 additions & 0 deletions src/components/LanguageSwitcher/LanguageSwitcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable no-shadow */

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { setLocale } from '../../actions/intl';

function LanguageSwitcher({ currentLocale, availableLocales, setLocale }) {
const isSelected = locale => locale === currentLocale;
return (
<div>
{availableLocales.map(locale => (
<span key={locale}>
{isSelected(locale) ? (
<span>{locale}</span>
) : (
<a
href={`?locale=${locale}`}
onClick={(e) => {
setLocale({ locale });
e.preventDefault();
}}
>{locale}</a>
)}
{' '}
</span>
))}
</div>
);
}

LanguageSwitcher.propTypes = {
currentLocale: PropTypes.string.isRequired,
availableLocales: PropTypes.arrayOf(PropTypes.string).isRequired,
setLocale: PropTypes.func.isRequired,
};

export default connect(state => ({
availableLocales: state.runtime.availableLocales,
currentLocale: state.intl.locale,
}), {
setLocale,
})(LanguageSwitcher);
6 changes: 6 additions & 0 deletions src/components/LanguageSwitcher/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "LanguageSwitcher",
"version": "0.0.0",
"private": true,
"main": "./LanguageSwitcher.js"
}
Loading

0 comments on commit 1ab3873

Please sign in to comment.