Adding i18next

This commit is contained in:
Jeff Avallone 2018-02-11 18:37:07 -05:00
parent 63766e84e9
commit 355ef79d20
24 changed files with 195 additions and 66 deletions

View File

@ -65,7 +65,9 @@
"favicons-webpack-plugin-cesco": "^0.0.6",
"feather-icons": "^4.5.0",
"html-webpack-plugin": "^2.30.1",
"i18next": "^10.3.0",
"jest": "^22.2.2",
"json-loader": "^0.5.7",
"npm-run-all": "^4.1.2",
"postcss-cssnext": "^3.1.0",
"postcss-loader": "^2.1.0",
@ -73,12 +75,14 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-ga": "^2.4.1",
"react-i18next": "^7.3.6",
"style-loader": "^0.20.1",
"svg-react-loader": "^0.4.5",
"uglifyjs-webpack-plugin": "^1.1.8",
"webpack": "^3.10.0",
"webpack-merge": "^4.1.1",
"workbox-webpack-plugin": "^2.1.2"
"workbox-webpack-plugin": "^2.1.2",
"yaml-loader": "^0.5.0"
},
"devDependencies": {
"http-server": "^0.11.1",

View File

@ -1,20 +1,24 @@
import React from 'react';
import { translate, Trans } from 'react-i18next';
const Footer = () => (
<footer>
<ul className="inline with-separator">
<li>
Created by <a href="mailto:jeff.avallone@gmail.com">Jeff Avallone</a>
<Trans>Created by <a href="mailto:jeff.avallone@gmail.com">Jeff Avallone</a></Trans>
</li>
<li>
Generated images licensed: <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">
<img
alt="Creative Commons CC-BY-3.0 License"
src="https://licensebuttons.net/l/by/3.0/80x15.png" />
</a>
<Trans i18nKey="Generated images licensed">
Generated images licensed: <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">
<img
alt="Creative Commons CC-BY-3.0 License"
src="https://licensebuttons.net/l/by/3.0/80x15.png" />
</a>
</Trans>
</li>
</ul>
</footer>
);
export default Footer;
export default translate()(Footer);
export { Footer };

View File

@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import Footer from './Footer';
import { Footer } from './Footer';
test('Footer rendering', () => {
const component = shallow(

View File

@ -1,4 +1,5 @@
import React from 'react';
import { translate, Trans } from 'react-i18next';
import GithubIcon from 'feather-icons/dist/icons/github.svg';
@ -10,10 +11,11 @@ const Header = () => (
<ul className="inline">
<li><a href="https://github.com/javallone/regexper-static">
<GithubIcon/>Source on GitHub
<GithubIcon/><Trans>Source on GitHub</Trans>
</a></li>
</ul>
</header>
);
export default Header;
export default translate()(Header);
export { Header };

View File

@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import Header from './Header';
import { Header } from './Header';
const env = { ...process.env };

View File

@ -4,9 +4,6 @@ import PropTypes from 'prop-types';
import pkg from '../../package.json';
import Header from '../components/Header';
import Footer from '../components/Footer';
const PageTemplate = ({ title, children }) => (
<html>
<head>
@ -20,9 +17,7 @@ const PageTemplate = ({ title, children }) => (
</head>
<body data-build-id={ process.env.BUILD_ID }>
<div id="root">
<Header/>
{ children }
<Footer/>
</div>
</body>
</html>

View File

@ -21,7 +21,6 @@ class RavenBoundary extends React.Component {
if (error) {
const errorProps = {
heading: 'An error has occurred.',
details: { extra: errorInfo },
error
};

View File

@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate, Trans } from 'react-i18next';
import { Raven } from '../sentry';
import Message from './Message';
@ -20,10 +21,12 @@ class RavenError extends React.Component {
}
render() {
const { heading } = this.props;
const { t } = this.props;
return <Message className="error" icon={ AlertIcon } heading={ heading }>
<p>This error has been logged. You may also <a href="#error-report" onClick={ this.reportError }>fill out a report</a>.</p>
return <Message className="error" icon={ AlertIcon } heading={ t('An error has occurred') }>
<p><Trans i18nKey="This error has been logged">
This error has been logged. You may also <a href="#error-report" onClick={ this.reportError }>fill out a report</a>.
</Trans></p>
</Message>;
}
}
@ -31,7 +34,8 @@ class RavenError extends React.Component {
RavenError.propTypes = {
error: PropTypes.object.isRequired,
details: PropTypes.object.isRequired,
heading: PropTypes.string.isRequired
t: PropTypes.func
};
export default RavenError;
export default translate()(RavenError);
export { RavenError };

View File

@ -3,11 +3,12 @@ import { shallow } from 'enzyme';
jest.mock('../sentry');
import RavenError from './RavenError';
import { RavenError } from './RavenError';
import { Raven } from '../sentry';
const testError = { error: 'test error' };
const testDetails = { details: 'test details' };
const translate = v => `translate(${ v })`;
describe('RavenError', () => {
test('rendering', () => {
@ -15,7 +16,7 @@ describe('RavenError', () => {
<RavenError
error={ testError }
details={ testDetails }
heading="Test error"/>
t={ translate }/>
);
expect(component).toMatchSnapshot();
});
@ -25,7 +26,7 @@ describe('RavenError', () => {
<RavenError
error={ testError }
details={ testDetails }
heading="Test error"/>
t={ translate }/>
);
expect(Raven.captureException).toHaveBeenCalledWith(testError, testDetails);
});
@ -36,7 +37,7 @@ describe('RavenError', () => {
<RavenError
error={ testError }
details={ testDetails }
heading="Test error"/>
t={ translate }/>
);
const eventObj = { preventDefault: jest.fn() };
component.find('a').simulate('click', eventObj);

View File

@ -6,24 +6,30 @@ exports[`Footer rendering 1`] = `
className="inline with-separator"
>
<li>
Created by
<a
href="mailto:jeff.avallone@gmail.com"
>
Jeff Avallone
</a>
<Trans>
Created by
<a
href="mailto:jeff.avallone@gmail.com"
>
Jeff Avallone
</a>
</Trans>
</li>
<li>
Generated images licensed:
<a
href="http://creativecommons.org/licenses/by/3.0/"
rel="license"
<Trans
i18nKey="Generated images licensed"
>
<img
alt="Creative Commons CC-BY-3.0 License"
src="https://licensebuttons.net/l/by/3.0/80x15.png"
/>
</a>
Generated images licensed:
<a
href="http://creativecommons.org/licenses/by/3.0/"
rel="license"
>
<img
alt="Creative Commons CC-BY-3.0 License"
src="https://licensebuttons.net/l/by/3.0/80x15.png"
/>
</a>
</Trans>
</li>
</ul>
</footer>

View File

@ -20,7 +20,9 @@ exports[`Header rendering 1`] = `
href="https://github.com/javallone/regexper-static"
>
<Component />
Source on GitHub
<Trans>
Source on GitHub
</Trans>
</a>
</li>
</ul>

View File

@ -28,11 +28,9 @@ exports[`PageTemplate rendering 1`] = `
<div
id="root"
>
<Header />
<p>
Content
</p>
<Footer />
</div>
</body>
</html>
@ -67,11 +65,9 @@ exports[`PageTemplate rendering with title 1`] = `
<div
id="root"
>
<Header />
<p>
Content
</p>
<Footer />
</div>
</body>
</html>

View File

@ -3,7 +3,7 @@
exports[`RavenBoundary rendering (with error) 1`] = `<Child />`;
exports[`RavenBoundary rendering (with error) 2`] = `
<RavenError
<Translate(RavenError)
details={
Object {
"extra": Object {
@ -16,7 +16,6 @@ exports[`RavenBoundary rendering (with error) 2`] = `
"error": "test error",
}
}
heading="An error has occurred."
/>
`;

View File

@ -3,18 +3,22 @@
exports[`RavenError rendering 1`] = `
<Message
className="error"
heading="Test error"
heading="translate(An error has occurred)"
icon={[Function]}
>
<p>
This error has been logged. You may also
<a
href="#error-report"
onClick={[Function]}
<Trans
i18nKey="This error has been logged"
>
fill out a report
</a>
.
This error has been logged. You may also
<a
href="#error-report"
onClick={[Function]}
>
fill out a report
</a>
.
</Trans>
</p>
</Message>
`;

14
src/i18n.js Normal file
View File

@ -0,0 +1,14 @@
import i18n from 'i18next';
import { reactI18nextModule } from 'react-i18next';
import locales from './locales';
i18n
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
debug: (process.env.NODE_ENV !== 'production'),
resources: locales
});
export default i18n;

View File

@ -0,0 +1,7 @@
404 Page Not Found: 404 Page Not Found
An error has occurred: An error has occurred
Created by <1>Jeff Avallone</1>: Created by <1>Jeff Avallone</1>
Generated images licensed: "Generated images licensed: <1><0></0></1>"
Source on GitHub: Source on GitHub
The page you have requested could not be found: The page you have requested could not be found
This error has been logged: This error has been logged. You may also <1>fill out a report</1>.

5
src/locales/index.js Normal file
View File

@ -0,0 +1,5 @@
export default {
en: {
translation: require('./en/translation.yaml')
}
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import { translate, Trans } from 'react-i18next';
import Message from '../../components/Message';
import AlertIcon from 'feather-icons/dist/icons/alert-octagon.svg';
import Header from '../../components/Header';
import Footer from '../../components/Footer';
const Component = ({ t }) => ( // eslint-disable-line react/prop-types
<React.Fragment>
<Header/>
<Message className="error" icon={ AlertIcon } heading={ t('404 Page Not Found') }>
<p><Trans>The page you have requested could not be found</Trans></p>
</Message>
<Footer/>
</React.Fragment>
);
export default translate()(Component);

View File

@ -1,6 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Component from './Component';
import RavenBoundary from '../../components/RavenBoundary';
import '../../style.css';
import '../../i18n';
import { setupGA } from '../../analytics';
import { setupRaven } from '../../sentry';
import { Raven, setupRaven } from '../../sentry';
setupRaven();
setupGA();
try {
setupGA();
ReactDOM.render(
<RavenBoundary>
<Component/>
</RavenBoundary>,
document.getElementById('root'));
}
catch (e) {
Raven.captureException(e);
}

View File

@ -1,14 +1,13 @@
import 'babel-register';
import React from 'react';
import '../../i18n';
import PageTemplate, { renderToString } from '../../components/PageTemplate';
import Message from '../../components/Message';
import AlertIcon from 'feather-icons/dist/icons/alert-octagon.svg';
import Component from './Component';
export default renderToString(
<PageTemplate>
<Message className="error" icon={ AlertIcon } heading="404 Page Not Found">
<p>The page you have requested could not be found</p>
</Message>
<Component/>
</PageTemplate>
);

View File

@ -5,6 +5,7 @@ import App from '../../components/App';
import RavenBoundary from '../../components/RavenBoundary';
import '../../style.css';
import '../../i18n';
import { setupServiceWorker } from '../../service-worker';
import { setupGA } from '../../analytics';
import { Raven, setupRaven } from '../../sentry';

View File

@ -1,12 +1,17 @@
import 'babel-register';
import React from 'react';
import '../../i18n';
import PageTemplate, { renderToString } from '../../components/PageTemplate';
import Message from '../../components/Message';
import AlertIcon from 'feather-icons/dist/icons/alert-octagon.svg';
import Header from '../../components/Header';
import Footer from '../../components/Footer';
export default renderToString(
<PageTemplate>
<Header/>
<noscript>
<Message className="error" icon={ AlertIcon } heading="JavaScript Required">
<p>You need to enable JavaScript to use Regexper.</p>
@ -18,5 +23,6 @@ export default renderToString(
<p>Most popular ad blockers will prevent these tools from sending any tracking data, and doing so will <b>not</b> impact the performance of this app. Regexper is not supported by ad revenue or sales of any kind. The information collected by these tools is used to monitor application performance, determine browser support, and collect error reports.</p>
</Message>
</noscript>
<Footer/>
</PageTemplate>
);

View File

@ -139,6 +139,17 @@ module.exports = {
{
test: /\.svg$/,
loader: 'svg-react-loader'
},
{
test: /\.yaml$/,
use: [
{
loader: 'json-loader'
},
{
loader: 'yaml-loader'
}
]
}
]
}

View File

@ -3486,6 +3486,10 @@ hoek@4.x.x:
version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
hoist-non-react-statics@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@ -3533,6 +3537,12 @@ html-minifier@^3.2.3:
relateurl "0.2.x"
uglify-js "3.3.x"
html-parse-stringify2@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
dependencies:
void-elements "^2.0.1"
html-webpack-plugin@^2.30.1:
version "2.30.1"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5"
@ -3652,6 +3662,10 @@ husky@^0.14.3:
normalize-path "^1.0.0"
strip-indent "^2.0.0"
i18next@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-10.3.0.tgz#6866d14f6ae7b3629921f78bc028d04914f0654c"
iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
@ -4437,7 +4451,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1:
js-yaml@^3.4.3, js-yaml@^3.5.2, js-yaml@^3.7.0, js-yaml@^3.9.1:
version "3.10.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
dependencies:
@ -4494,7 +4508,7 @@ jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
json-loader@^0.5.4:
json-loader@^0.5.4, json-loader@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
@ -6543,6 +6557,14 @@ react-ga@^2.4.1:
prop-types "^15.6.0"
react "^15.6.2 || ^16.0"
react-i18next@^7.3.6:
version "7.3.6"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.3.6.tgz#3e4fdd0be7735aa53343720684247ba7f6cfd51a"
dependencies:
hoist-non-react-statics "2.3.1"
html-parse-stringify2 "2.0.1"
prop-types "^15.6.0"
react-reconciler@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d"
@ -8070,6 +8092,10 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
void-elements@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
w3c-hr-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
@ -8415,6 +8441,12 @@ yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yaml-loader@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/yaml-loader/-/yaml-loader-0.5.0.tgz#86b1982d84a8e429e6647d93de9a0169e1c15827"
dependencies:
js-yaml "^3.5.2"
yargs-parser@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"