diff --git a/frontend/client/components/Home/index.tsx b/frontend/client/components/Home/index.tsx index e65448fe..22834559 100644 --- a/frontend/client/components/Home/index.tsx +++ b/frontend/client/components/Home/index.tsx @@ -1,18 +1,16 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { withNamespaces, WithNamespaces } from 'react-i18next'; import HeaderDetails from 'components/HeaderDetails'; import Rocket from 'static/images/rocket.svg'; import './style.less'; -export default class Home extends React.Component { +class Home extends React.Component { render() { + const { t } = this.props; return (
- +
@@ -24,15 +22,15 @@ export default class Home extends React.Component {

- Decentralized funding for
Blockchain ecosystem improvements + {t('home.heroTitle1')}
{t('home.heroTitle2')}

- Propose a Project + {t('home.createButton')} - Explore Projects + {t('home.exploreButton')}
@@ -41,3 +39,5 @@ export default class Home extends React.Component { ); } } + +export default withNamespaces()(Home); diff --git a/frontend/client/i18n.ts b/frontend/client/i18n.ts new file mode 100644 index 00000000..0b72b229 --- /dev/null +++ b/frontend/client/i18n.ts @@ -0,0 +1,17 @@ +import i18n from 'i18next'; + +// NOTE: maintain parity with server/i18n.ts +i18n.init({ + whitelist: ['en'], + fallbackLng: 'en', + + ns: ['common'], + defaultNS: 'common', + + interpolation: { + // not needed for react + escapeValue: false, + }, +}); + +export default i18n; diff --git a/frontend/client/index.tsx b/frontend/client/index.tsx index 6d6871e4..a256f3cc 100644 --- a/frontend/client/index.tsx +++ b/frontend/client/index.tsx @@ -6,20 +6,27 @@ import { loadComponents } from 'loadable-components'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import { PersistGate } from 'redux-persist/integration/react'; +import { I18nextProvider } from 'react-i18next'; import { configureStore } from 'store/configure'; import Routes from './Routes'; +import i18n from './i18n'; const initialState = window && (window as any).__PRELOADED_STATE__; const { store, persistor } = configureStore(initialState); +const i18nLanguage = window && (window as any).__PRELOADED_I18N__; +i18n.changeLanguage(i18nLanguage.locale); +i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true); const App = hot(module)(() => ( - - - - - - - + + + + + + + + + )); loadComponents().then(() => { diff --git a/frontend/client/static/locales/en/common.json b/frontend/client/static/locales/en/common.json new file mode 100644 index 00000000..368261a3 --- /dev/null +++ b/frontend/client/static/locales/en/common.json @@ -0,0 +1,10 @@ +{ + "home": { + "title": "Home", + "description": "Grant.io organizes creators and community members to incentivize ecosystem improvements", + "heroTitle1": "Decentralized funding for", + "heroTitle2": "Blockchain ecosystem improvements", + "createButton": "Propose a Project", + "exploreButton": "Explore Projects" + } +} diff --git a/frontend/config/webpack.config.js/plugins.js b/frontend/config/webpack.config.js/plugins.js index ae73457e..64dbc072 100644 --- a/frontend/config/webpack.config.js/plugins.js +++ b/frontend/config/webpack.config.js/plugins.js @@ -5,6 +5,7 @@ const { StatsWriterPlugin } = require('webpack-stats-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const ModuleDependencyWarning = require('./module-dependency-warning'); const WebappWebpackPlugin = require('webapp-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); const env = require('../env')(); const paths = require('../paths'); @@ -44,6 +45,15 @@ const client = [ theme_color: '#ffffff', }, }), + new CopyWebpackPlugin([ + { + from: 'client/static/locales/**/*.json', + transformPath(targetPath, absolutePath) { + const match = targetPath.match(/locales\/(.+)\/(.+\.json)$/); + return `locales/${match[1]}/${match[2]}`; + }, + }, + ]), // this allows the server access to the dependency graph // so it can find which js/css to add to initial page new StatsWriterPlugin({ diff --git a/frontend/package.json b/frontend/package.json index dafc361a..b640e0eb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,6 +46,8 @@ "@types/dotenv": "^4.0.3", "@types/express": "^4.16.0", "@types/express-winston": "^3.0.0", + "@types/i18next-express-middleware": "^0.0.33", + "@types/i18next-node-fs-backend": "^0.0.30", "@types/js-cookie": "2.1.0", "@types/jwt-decode": "^2.2.1", "@types/lodash": "^4.14.112", @@ -77,6 +79,7 @@ "chalk": "^2.4.1", "classnames": "^2.2.6", "cookie-parser": "^1.4.3", + "copy-webpack-plugin": "^4.6.0", "core-js": "^2.5.7", "cors": "^2.8.4", "cross-env": "^5.2.0", @@ -97,6 +100,9 @@ "http-proxy-middleware": "^0.18.0", "https-proxy": "0.0.2", "husky": "^1.0.0-rc.8", + "i18next": "^12.0.0", + "i18next-express-middleware": "^1.4.1", + "i18next-node-fs-backend": "^2.1.0", "js-cookie": "^2.2.0", "less": "^3.7.1", "less-loader": "^4.1.0", @@ -115,6 +121,7 @@ "react-dom": "16.5.2", "react-helmet": "^5.2.0", "react-hot-loader": "^4.3.8", + "react-i18next": "^8.3.5", "react-mde": "^5.8.0", "react-redux": "^5.0.7", "react-router": "^4.3.1", diff --git a/frontend/server/components/HTML.tsx b/frontend/server/components/HTML.tsx index f2bed5ee..db2d3ce3 100644 --- a/frontend/server/components/HTML.tsx +++ b/frontend/server/components/HTML.tsx @@ -8,6 +8,7 @@ export interface Props { linkTags: Array>; metaTags: Array>; state: string; + i18n: string; loadableStateScript: string; } @@ -16,6 +17,7 @@ const HTML: React.SFC = ({ scripts, css, state, + i18n, linkTags, metaTags, loadableStateScript, @@ -63,6 +65,11 @@ const HTML: React.SFC = ({ __html: `window.__PRELOADED_STATE__ = ${state}`, }} /> +