From 7cd73654371d70761a8b99d5501dfb57f1fd00f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Neac=C8=99u?= Date: Thu, 2 Aug 2018 00:35:12 +0300 Subject: [PATCH 1/6] set up basic i18n --- src/app/i18n/I18nProvider.jsx | 34 +++++++++++++++++++ src/app/i18n/i18n.js | 27 +++++++++++++++ src/app/i18n/index.js | 2 ++ src/app/i18n/reducer.js | 10 ++++++ src/index.js | 7 +++- .../ProposalsPage/ProposalsPage.jsx | 13 +++++-- 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/app/i18n/I18nProvider.jsx create mode 100644 src/app/i18n/i18n.js create mode 100644 src/app/i18n/index.js create mode 100644 src/app/i18n/reducer.js diff --git a/src/app/i18n/I18nProvider.jsx b/src/app/i18n/I18nProvider.jsx new file mode 100644 index 0000000..6e198fb --- /dev/null +++ b/src/app/i18n/I18nProvider.jsx @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { I18nextProvider } from 'react-i18next'; + +import i18n from './i18n'; + +class I18nProvider extends Component { + componentWillUpdate(nextProps) { + if (nextProps.preferredLanguage !== this.props.preferredLanguage) { + i18n.changeLanguage(nextProps.preferredLanguage); + } + } + + render() { + return ( + + {this.props.children} + + ); + } +} + +I18nProvider.propTypes = { + children: PropTypes.node, + // connect: + preferredLanguage: PropTypes.string +}; + +const mapStateToProps = state => ({ + preferredLanguage: state.i18n.preferredLanguage +}); + +export default connect(mapStateToProps)(I18nProvider); diff --git a/src/app/i18n/i18n.js b/src/app/i18n/i18n.js new file mode 100644 index 0000000..a32ef39 --- /dev/null +++ b/src/app/i18n/i18n.js @@ -0,0 +1,27 @@ +import i18next from 'i18next'; + +i18next.init({ + interpolation: { + // React already does escaping + escapeValue: false + }, + lng: 'ro', + resources: { + ro: { + translation: { + proposals: { + pageTitle: 'Pagina de propuneri' + } + } + }, + en: { + translation: { + proposals: { + pageTitle: 'Proposals page' + } + } + } + } +}); + +export default i18next; diff --git a/src/app/i18n/index.js b/src/app/i18n/index.js new file mode 100644 index 0000000..b3c62ae --- /dev/null +++ b/src/app/i18n/index.js @@ -0,0 +1,2 @@ +export { default as I18nProvider } from './I18nProvider'; +export { default as i18n } from './reducer'; diff --git a/src/app/i18n/reducer.js b/src/app/i18n/reducer.js new file mode 100644 index 0000000..5b2cdde --- /dev/null +++ b/src/app/i18n/reducer.js @@ -0,0 +1,10 @@ +const i18n = (state = { + preferredLanguage: 'ro', +}, action) => { + switch (action.type) { + default: + return state; + } +}; + +export default i18n; diff --git a/src/index.js b/src/index.js index e0e3045..7bca213 100644 --- a/src/index.js +++ b/src/index.js @@ -5,13 +5,16 @@ import { Provider } from 'react-redux'; import { combineReducers, createStore } from 'redux'; import { App } from './app'; +import { I18nProvider } from './app/i18n'; import registerServiceWorker from './registerServiceWorker'; // Import module reducers +import { i18n } from './app/i18n'; import { categorySelection } from './category-selection'; import { categories } from './categories'; const rootReducer = combineReducers({ + i18n, categorySelection, categories }); @@ -23,7 +26,9 @@ const store = createStore( ReactDOM.render( - + + + , document.getElementById('root') ); diff --git a/src/proposals/components/ProposalsPage/ProposalsPage.jsx b/src/proposals/components/ProposalsPage/ProposalsPage.jsx index b6f8066..f1e0577 100644 --- a/src/proposals/components/ProposalsPage/ProposalsPage.jsx +++ b/src/proposals/components/ProposalsPage/ProposalsPage.jsx @@ -1,7 +1,16 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { translate } from 'react-i18next'; -export default class ProposalsPage extends Component { +class ProposalsPage extends Component { render() { - return

Proposals page

; + return

{this.props.t('proposals.pageTitle')}

; } } + +ProposalsPage.propTypes = { + // translate HoC: + t: PropTypes.func +}; + +export default translate()(ProposalsPage); From 15a7cca9b6ed2da1c8bf05a0549d1b4c694ea514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Neac=C8=99u?= Date: Wed, 8 Aug 2018 23:22:38 +0300 Subject: [PATCH 2/6] fix missing package.json entries --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index b047de3..d66ca7c 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,12 @@ "@material-ui/core": "^1.0.0-rc.0", "@material-ui/icons": "1.0.0-beta.42", "eslint-plugin-flowtype": "2.46.3", + "i18next": "11.5.0", "material-ui": "1.0.0-beta.42", "mdi-material-ui": "^5.0.0", "react": "16.3.2", "react-dom": "16.3.2", + "react-i18next": "7.10.1", "react-redux": "5.0.7", "react-router-dom": "4.2.2", "react-scripts": "1.1.4", From 7b6bc0c06fa77d6752e0c728a40cfe4dda2d315b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Neac=C8=99u?= Date: Wed, 8 Aug 2018 23:32:19 +0300 Subject: [PATCH 3/6] set up redux-thunk --- package.json | 1 + src/index.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d66ca7c..0d9ea21 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "react-router-dom": "4.2.2", "react-scripts": "1.1.4", "redux": "4.0.0", + "redux-thunk": "2.3.0", "typeface-roboto": "^0.0.54" }, "scripts": { diff --git a/src/index.js b/src/index.js index 7bca213..9c1b985 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; -import { combineReducers, createStore } from 'redux'; +import { combineReducers, createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; import { App } from './app'; import { I18nProvider } from './app/i18n'; @@ -19,9 +20,10 @@ const rootReducer = combineReducers({ categories }); +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore( rootReducer, - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() + composeEnhancers(applyMiddleware(thunk)) ); ReactDOM.render( From 1612dac65889dd1d1231df1456230fc8fa9ec0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Neac=C8=99u?= Date: Wed, 8 Aug 2018 23:44:48 +0300 Subject: [PATCH 4/6] use default i18n provider --- src/app/i18n/I18nProvider.jsx | 34 ---------------------------------- src/app/i18n/index.js | 2 +- src/index.js | 7 ++++--- 3 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 src/app/i18n/I18nProvider.jsx diff --git a/src/app/i18n/I18nProvider.jsx b/src/app/i18n/I18nProvider.jsx deleted file mode 100644 index 6e198fb..0000000 --- a/src/app/i18n/I18nProvider.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { I18nextProvider } from 'react-i18next'; - -import i18n from './i18n'; - -class I18nProvider extends Component { - componentWillUpdate(nextProps) { - if (nextProps.preferredLanguage !== this.props.preferredLanguage) { - i18n.changeLanguage(nextProps.preferredLanguage); - } - } - - render() { - return ( - - {this.props.children} - - ); - } -} - -I18nProvider.propTypes = { - children: PropTypes.node, - // connect: - preferredLanguage: PropTypes.string -}; - -const mapStateToProps = state => ({ - preferredLanguage: state.i18n.preferredLanguage -}); - -export default connect(mapStateToProps)(I18nProvider); diff --git a/src/app/i18n/index.js b/src/app/i18n/index.js index b3c62ae..4433129 100644 --- a/src/app/i18n/index.js +++ b/src/app/i18n/index.js @@ -1,2 +1,2 @@ -export { default as I18nProvider } from './I18nProvider'; +export { default as i18nInstance } from './i18n'; export { default as i18n } from './reducer'; diff --git a/src/index.js b/src/index.js index 9c1b985..db08cfc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { combineReducers, createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import { App } from './app'; -import { I18nProvider } from './app/i18n'; +import { i18nInstance } from './app/i18n'; import registerServiceWorker from './registerServiceWorker'; // Import module reducers @@ -28,9 +29,9 @@ const store = createStore( ReactDOM.render( - + - + , document.getElementById('root') ); From 2f36f358b383d5445f35b87edde88fd80cda037b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Neac=C8=99u?= Date: Wed, 8 Aug 2018 23:45:42 +0300 Subject: [PATCH 5/6] add setLocale thunk --- src/app/i18n/actions.js | 10 ++++++++++ src/app/i18n/index.js | 1 + src/app/i18n/reducer.js | 14 +++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/app/i18n/actions.js diff --git a/src/app/i18n/actions.js b/src/app/i18n/actions.js new file mode 100644 index 0000000..7d55da0 --- /dev/null +++ b/src/app/i18n/actions.js @@ -0,0 +1,10 @@ +import i18nInstance from './i18n'; + +export const setLocale = locale => dispatch => { + dispatch({ + type: 'SET_LOCALE', + locale + }); + + i18nInstance.changeLanguage(locale); +}; diff --git a/src/app/i18n/index.js b/src/app/i18n/index.js index 4433129..b9332bb 100644 --- a/src/app/i18n/index.js +++ b/src/app/i18n/index.js @@ -1,2 +1,3 @@ export { default as i18nInstance } from './i18n'; export { default as i18n } from './reducer'; +export * from './actions'; diff --git a/src/app/i18n/reducer.js b/src/app/i18n/reducer.js index 5b2cdde..edc0853 100644 --- a/src/app/i18n/reducer.js +++ b/src/app/i18n/reducer.js @@ -1,7 +1,15 @@ -const i18n = (state = { - preferredLanguage: 'ro', -}, action) => { +const i18n = ( + state = { + locale: 'ro' + }, + action +) => { switch (action.type) { + case 'SET_LOCALE': + return { + // set to 'cimode' to have i18next render translation keys instead of values + locale: action.locale + }; default: return state; } From cf8c4eac56297348fcd2796ef1b2a10f4f37f192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Neac=C8=99u?= Date: Thu, 9 Aug 2018 00:16:08 +0300 Subject: [PATCH 6/6] split translation into files --- src/app/i18n/i18n.js | 19 +++++-------------- src/app/i18n/translations/en.json | 7 +++++++ src/app/i18n/translations/ro.json | 7 +++++++ 3 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 src/app/i18n/translations/en.json create mode 100644 src/app/i18n/translations/ro.json diff --git a/src/app/i18n/i18n.js b/src/app/i18n/i18n.js index a32ef39..eca5689 100644 --- a/src/app/i18n/i18n.js +++ b/src/app/i18n/i18n.js @@ -1,5 +1,8 @@ import i18next from 'i18next'; +import ro from './translations/ro.json'; +import en from './translations/en.json'; + i18next.init({ interpolation: { // React already does escaping @@ -7,20 +10,8 @@ i18next.init({ }, lng: 'ro', resources: { - ro: { - translation: { - proposals: { - pageTitle: 'Pagina de propuneri' - } - } - }, - en: { - translation: { - proposals: { - pageTitle: 'Proposals page' - } - } - } + ro, + en } }); diff --git a/src/app/i18n/translations/en.json b/src/app/i18n/translations/en.json new file mode 100644 index 0000000..f9acf87 --- /dev/null +++ b/src/app/i18n/translations/en.json @@ -0,0 +1,7 @@ +{ + "translation": { + "proposals": { + "pageTitle": "Proposals page" + } + } +} diff --git a/src/app/i18n/translations/ro.json b/src/app/i18n/translations/ro.json new file mode 100644 index 0000000..6b5159e --- /dev/null +++ b/src/app/i18n/translations/ro.json @@ -0,0 +1,7 @@ +{ + "translation": { + "proposals": { + "pageTitle": "Pagina de propuneri" + } + } +}