diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7017237135..0ed39edfe1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,7 @@ forked repo, check that it meets these guidelines: * [Squash](http://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git) your commits into one for each PR. * Run `npm test` to make sure that your code style is OK and there are no any regression bugs. +* If you contributing to opt-in feature, prefix your PR title with `[feature/...]` tag. #### Style Guide diff --git a/README.md b/README.md index 9f0be03ecf..1c898d9107 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,23 @@ expenses via [OpenCollective](https://opencollective.com/react-starter-kit) or +### Feature branches + +There are feature branches. +You can just merge and it will give you desired functionality. +These branches should be in sync with master and themselves so there should not be merging conflicts. If not, please report it. + + * [feature/redux](https://github.com/kriasoft/react-starter-kit/tree/feature/redux) – isomorphic redux support. + Use [`connect()`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) + higher order component to access state in redux store and to get actions mapped on dispatch. + You can see [LanguageSwitcher](https://github.com/kriasoft/react-starter-kit/blob/86eadfd3d11d804cf858aa21f657022fcc098752/src/components/LanguageSwitcher/LanguageSwitcher.js) component how to use `connect()` +
+ [Read full recipe](./docs/recipes/feature-redux.md) + + * [feature/react-intl](https://github.com/kriasoft/react-starter-kit/tree/feature/react-intl) – [`react-intl`](https://github.com/yahoo/react-intl#react-intl) integration based on `feature/redux` +
+ [Read full recipe](./docs/recipes/feature-react-intl.md) + ### Learn More * [Getting Started with React.js](http://facebook.github.io/react/) diff --git a/docs/README.md b/docs/README.md index a3c529cf7a..c6ede3cc6a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,4 +15,6 @@ ### Recipes * [How to Implement Routing and Navigation](./recipes/how-to-implement-routing.md) +* [How to Integrate Redux](./recipes/feature-redux.md) +* [How to Integrate React-Intl](./recipes/feature-react-intl.md) * [How to Integrate Disqus](./recipes/how-to-integrate-disqus.md) diff --git a/docs/recipes/feature-react-intl.md b/docs/recipes/feature-react-intl.md new file mode 100644 index 0000000000..d561d17b62 --- /dev/null +++ b/docs/recipes/feature-react-intl.md @@ -0,0 +1,121 @@ +## Integrating [React-Intl](https://github.com/yahoo/react-intl#react-intl) + + 1. Merge `feature/react-intl` branch with git. + You will get `feature/redux` too because this is based on top of it. + + 2. Adjust `INTL_REQUIRE_DESCRIPTIONS` constant in `tools/webpack.config.js` around line 17: + ```js + const INTL_REQUIRE_DESCRIPTIONS = true; + ``` + It's boolean. When enabled, build will fail if `description` is not provided. + + 3. Adjust `locales` settings in `src/config.js`: + ```js + // default locale is the first one + export const locales = ['en-GB', 'cs-CZ']; + ``` + Note that you should follow + [BCP 47](https://tools.ietf.org/html/bcp47) + ([RFC 5646](https://tools.ietf.org/html/rfc5646)). + + 4. Add locale support in `src/client.js`: + ```js + import en from 'react-intl/locale-data/en'; + import cs from 'react-intl/locale-data/cs'; + ... + + [en, cs].forEach(addLocaleData); + ``` + + 5. Execute `npm run extractMessages` or `npm start` to strip out messages. + Message files are created in `src/messages` directory. + + 6. Edit `src/messages/*.json` files, change only `message` property. + + 7. Execute `npm run build`, + your translations should be copied to `build/messages/` directory. + + +## How to write localizable components + +Just import appropriate [component](https://github.com/yahoo/react-intl/wiki#the-react-intl-module) from `react-intl` + +- For localizable text use +[``](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage). +- You can also use it with +[`defineMessages()`](https://github.com/yahoo/react-intl/wiki/API#definemessages) helper. + +- For date and time: +[``](https://github.com/yahoo/react-intl/wiki/Components#formatteddate) +[``](https://github.com/yahoo/react-intl/wiki/Components#formattedtime) +[``](https://github.com/yahoo/react-intl/wiki/Components#formattedrelative) + +- For numbers and currencies: +[``](https://github.com/yahoo/react-intl/wiki/Components#formattednumber) +[``](https://github.com/yahoo/react-intl/wiki/Components#formattedplural) + +- Do not use `` if possible, see how to use *Rich Text Formatting* with +[``](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage) + +- When you need imperative formatting API, use [`injectIntl`](https://github.com/yahoo/react-intl/wiki/API#injectintl) High-Order Component. + +### Example + +```jsx +import { defineMessages, FormattedMessage, injectIntl, intlShape } from 'react-intl'; + +const messages = defineMessages({ + text: { + id: 'example.text', + defaultMessage: 'Example text', + description: 'Hi Pavel', + }, + textTemplate: { + id: 'example.text.template', + defaultMessage: 'Example text template', + description: 'Hi {name}', + }, +}); + +function Example(props) { + const text = props.intl.formatMessage(messages.textTemplate, { name: 'Pavel'}); + return ( +
+ + + Pavel + }} + /> +
+ ); +} + +Example.propTypes = { + intl: intlShape, +} + +export default injectIntl(Example); +``` + +## Updating translations + +When developing, every source file is watched and parsed for messages on change. + +Messages files are updated on the fly. +If new definition is found, this definition is added at the end of every used `src/messages/xx-XX.json` file so when commiting, new translations are at the tail of file. +When untranslated message is removed and if it's `message` field is empty too, message is deleted from all translation files. This is reason why `files` array is present. + +When developing and you edit translation file, it should be copied to `build/messages/` directory. + +## Other references + + * [`Intl documentation on MDN`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Intl) + * [express-request-language](https://github.com/tinganho/express-request-language#readme) + – for more details how initial language negotiation works. diff --git a/docs/recipes/feature-redux.md b/docs/recipes/feature-redux.md new file mode 100644 index 0000000000..ed81f0e913 --- /dev/null +++ b/docs/recipes/feature-redux.md @@ -0,0 +1,58 @@ +## Integrating [Redux](http://redux.js.org/index.html) + +Merge `feature/redux` branch with git. +If you are interrested into `feature/react-intl`, +merge that branch instead. +It contains this implementation of redux + +**If you don't know redux well, you should [read about it first](http://redux.js.org/docs/basics/index.html).** + + +## Creating Actions + + 1. Go to `src/constants/index.js` and define action name there. + + 2. Go to `src/actions/` and create file with appropriate name. + You can copy `src/actions/runtime.js` as a template. + + 3. If you need async action, [`redux-thunk`](https://github.com/gaearon/redux-thunk#readme) + is there for you. + For inspiration how to create async actions you can look at + [`setLocale`](https://github.com/kriasoft/react-starter-kit/blob/feature/react-intl/src/actions/intl.js) + action from `feature/react-intl`. + See [Async Flow](http://redux.js.org/docs/advanced/AsyncFlow.html) for more on this topic + + +## Creating Reducer (aka Store) + + 1. Go to [`src/reducers/`](https://github.com/kriasoft/react-starter-kit/tree/feature/redux/src/reducers) and create new file there. + + You can copy [`src/reducers/runtime.js`](https://github.com/kriasoft/react-starter-kit/tree/feature/redux/src/reducers/runtime.js) as a template. + + - Do not forget to always return `state`. + - Never mutate provided `state`. + If you mutate state, rendering of connected component will not be triggered because of `===` equality. + Always return new value if you perform state update. + You can use this construct: `{ ...state, updatedKey: action.payload.value, }` + - Keep in mind that store state *must* be repeatable by replaying actions on it. + For example, when you store timestamp, pass it into *action payload*. + If you call REST API, do it in action. Never do this things in reducer! + + 2. Edit [`src/reducers/index.js`](https://github.com/kriasoft/react-starter-kit/tree/feature/redux/src/reducers/index.js), import your reducer and add it to root reducer created by + [`combineReducers`](http://redux.js.org/docs/api/combineReducers.html) + + +## Connecting Components + +You can use [`connect()`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) High-Order Component from [`react-redux`](https://github.com/reactjs/react-redux#readme) package. + +See [Usage With React](http://redux.js.org/docs/basics/UsageWithReact.html) on redux.js.org. + +For example you can look at +[``](https://github.com/kriasoft/react-starter-kit/blob/feature/react-intl/src/components/LanguageSwitcher/LanguageSwitcher.js) +component from `feature/react-intl` branch. It demonstrates both, subscribing to store and dispathing actions. + + +## Dispatching Actions On Server + +See source of `src/server.js`