Commit 618b21bb authored by Jeff Avallone's avatar Jeff Avallone

Implementing translations

parent e1c4cb90
import React from 'react';
import PropTypes from 'prop-types';
import { StaticQuery, graphql } from 'gatsby';
import { withNamespaces, Trans } from 'react-i18next';
import style from './style.module.css';
......@@ -14,20 +15,21 @@ const query = graphql`
}
`;
export const FooterImpl = ({ site: { siteMetadata } }) => (
export const FooterImpl = ({ t, site: { siteMetadata } }) => (
<footer className={ style.footer }>
<ul className={ style.list }>
<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
<Trans>Generated images licensed: <a
href="http://creativecommons.org/licenses/by/3.0/"
rel="license external noopener noreferrer"
target="_blank">
<img src="https://licensebuttons.net/l/by/3.0/80x15.png"
alt="Creative Commons CC-BY-3.0 License" />
</a>
alt={ t('Creative Commons CC-BY-3.0 License') } />
</a></Trans>
</li>
</ul>
<div className={ style.buildId }>
......@@ -37,6 +39,7 @@ export const FooterImpl = ({ site: { siteMetadata } }) => (
);
FooterImpl.propTypes = {
t: PropTypes.func.isRequired,
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
buildId: PropTypes.string.isRequired
......@@ -44,8 +47,9 @@ FooterImpl.propTypes = {
}).isRequired
};
const TranslatedFooter = withNamespaces()(FooterImpl);
const Footer = () => <StaticQuery query={ query } render={ data => (
<FooterImpl { ...data } />
<TranslatedFooter { ...data } />
) } />;
export default Footer;
import React from 'react';
import { shallow } from 'enzyme';
import { mockT } from 'i18n';
import Footer, { FooterImpl } from 'components/Footer';
describe('Footer', () => {
......@@ -13,7 +14,7 @@ describe('Footer', () => {
test('rendering implementation', () => {
const component = shallow(
<FooterImpl site={{ siteMetadata: { buildId: 'abc-123' } }} />
<FooterImpl site={{ siteMetadata: { buildId: 'abc-123' } }} t={ mockT } />
);
expect(component).toMatchSnapshot();
});
......
import React from 'react';
import PropTypes from 'prop-types';
import { Link, StaticQuery, graphql } from 'gatsby';
import { withNamespaces, Trans } from 'react-i18next';
import GitlabIcon from 'react-feather/dist/icons/gitlab';
......@@ -32,11 +33,13 @@ export const HeaderImpl = ({ site: { siteMetadata } }) => (
rel="external noopener noreferrer"
target="_blank">
<GitlabIcon />
Source on GitLab
<Trans>Source on GitLab</Trans>
</a>
</li>
<li>
<Link to="/privacy">Privacy Policy</Link>
<Link to="/privacy">
<Trans>Privacy Policy</Trans>
</Link>
</li>
<li>
<LocaleSwitcher />
......@@ -56,8 +59,9 @@ HeaderImpl.propTypes = {
}).isRequired
};
const TranslatedHeader = withNamespaces()(HeaderImpl);
const Header = () => <StaticQuery query={ query } render={ data => (
<HeaderImpl { ...data } />
<TranslatedHeader { ...data } />
) } />;
export default Header;
......@@ -3,7 +3,9 @@
exports[`SentryError rendering 1`] = `
ShallowWrapper {
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <SentryError />,
Symbol(enzyme.__unrendered__): <SentryErrorImpl
t={[Function]}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
......@@ -18,64 +20,27 @@ ShallowWrapper {
"nodeType": "function",
"props": Object {
"children": <p>
This error has been logged. You may also
<a
href="#error-report"
onClick={[Function]}
>
fill out a report
</a>
.
</p>,
"heading": "An error has occurred",
"type": "error",
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": Array [
"This error has been logged. You may also ",
<WithMergedOptions(TransComponent)>
This error has been logged. You may also
<a
href="#error-report"
onClick={[Function]}
>
fill out a report
</a>,
".",
],
},
"ref": null,
"rendered": Array [
"This error has been logged. You may also ",
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "fill out a report",
"href": "#error-report",
"onClick": [Function],
},
"ref": null,
"rendered": "fill out a report",
"type": "a",
},
".",
],
"type": "p",
</a>
.
</WithMergedOptions(TransComponent)>
</p>,
"heading": "TRANSLATE(An error has occurred)",
"type": "error",
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"nodeType": "host",
"props": Object {
"children": <p>
"children": <WithMergedOptions(TransComponent)>
This error has been logged. You may also
<a
href="#error-report"
......@@ -84,15 +49,13 @@ ShallowWrapper {
fill out a report
</a>
.
</p>,
"heading": "An error has occurred",
"type": "error",
</WithMergedOptions(TransComponent)>,
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"nodeType": "class",
"props": Object {
"children": Array [
"This error has been logged. You may also ",
......@@ -123,6 +86,87 @@ ShallowWrapper {
},
".",
],
"type": [Function],
},
"type": "p",
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {
"children": <p>
<WithMergedOptions(TransComponent)>
This error has been logged. You may also
<a
href="#error-report"
onClick={[Function]}
>
fill out a report
</a>
.
</WithMergedOptions(TransComponent)>
</p>,
"heading": "TRANSLATE(An error has occurred)",
"type": "error",
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": <WithMergedOptions(TransComponent)>
This error has been logged. You may also
<a
href="#error-report"
onClick={[Function]}
>
fill out a report
</a>
.
</WithMergedOptions(TransComponent)>,
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": Array [
"This error has been logged. You may also ",
<a
href="#error-report"
onClick={[Function]}
>
fill out a report
</a>,
".",
],
},
"ref": null,
"rendered": Array [
"This error has been logged. You may also ",
Object {
"instance": null,
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "fill out a report",
"href": "#error-report",
"onClick": [Function],
},
"ref": null,
"rendered": "fill out a report",
"type": "a",
},
".",
],
"type": [Function],
},
"type": "p",
},
"type": [Function],
......
import React from 'react';
import PropTypes from 'prop-types';
import * as Sentry from '@sentry/browser';
import { withNamespaces, Trans } from 'react-i18next';
import Message from 'components/Message';
class SentryError extends React.Component {
export class SentryErrorImpl extends React.Component {
reportError = event => {
event.preventDefault();
......@@ -13,11 +15,19 @@ class SentryError extends React.Component {
}
render() {
return <Message type="error" heading="An error has occurred">
<p>This error has been logged. You may also <a href="#error-report"
onClick={ this.reportError }>fill out a report</a>.</p>
const { t } = this.props;
return <Message type="error" heading={ t('An error has occurred') }>
<p>
<Trans>This error has been logged. You may also <a href="#error-report"
onClick={ this.reportError }>fill out a report</a>.</Trans>
</p>
</Message>;
}
}
export default SentryError;
SentryErrorImpl.propTypes = {
t: PropTypes.func.isRequired
};
export default withNamespaces()(SentryErrorImpl);
......@@ -4,12 +4,13 @@ import React from 'react';
import { shallow } from 'enzyme';
import * as Sentry from '@sentry/browser';
import SentryError from 'components/SentryError';
import { mockT } from 'i18n';
import { SentryErrorImpl } from 'components/SentryError';
describe('SentryError', () => {
test('rendering', () => {
const component = shallow(
<SentryError />
<SentryErrorImpl t={ mockT }/>
);
expect(component).toMatchSnapshot();
});
......@@ -18,7 +19,7 @@ describe('SentryError', () => {
test('fill out a report when an event has been logged', () => {
Sentry.lastEventId.mockReturnValue(1);
const component = shallow(
<SentryError />
<SentryErrorImpl t={ mockT } />
);
const eventObj = { preventDefault: jest.fn() };
component.find('a').simulate('click', eventObj);
......@@ -30,7 +31,7 @@ describe('SentryError', () => {
test('fill out a report when an event has not been logged', () => {
Sentry.lastEventId.mockReturnValue(false);
const component = shallow(
<SentryError />
<SentryErrorImpl t={ mockT } />
);
const eventObj = { preventDefault: jest.fn() };
component.find('a').simulate('click', eventObj);
......
"Language": |
LANGUAGE
"Source on GitLab": |
SOURCE ON GITLAB
"Privacy Policy": |
PRIVACY POLICY
"Created by <1>Jeff Avallone</1>": |
CREATED BY <1>JEFF AVALLONE</1>
"Generated images licensed: <1><0></0></1>": |
GENERATED IMAGES LICENSED: <1><0></0></1>
"Creative Commons CC-BY-3.0 License": |
CREATIVE COMMONS CC-BY-3.0 LICENSE
"An error has occurred": |
AN ERROR HAS OCCURRED
"This error has been logged. You may also <1>fill out a report</1>.": |
THIS ERROR HAS BEEN LOGGED. YOU MAY ALSO <1>FILL OUT A REPORT</1>.
"Hello world": |
HELLO WORLD
"Privacy policy copy": |
<0>REGEXPER AND THE TOOLS USED TO CREATE IT ARE ALL OPEN SOURCE. If you are
concerned that the JavaScript being delivered is in any way malicious, please
inspect the source in the <1>GitLab repository</1>.</0>
<1>There are two data collection tools integrated in the app. These tools are
not used to collect personal information:</1>
<2>
<0><0>Google Analytics</0> is used to track browser usage data and
application performance. It is configured to anonymize the client IP
address.</0>
<1><0>Sentry.io</0> is a tool used to capture and report client-side
JavaScript errors. It is configured to not store the client IP address.</1>
</2>
<3>Regexper honors the browser <1>“Do Not Track”</1> setting and will not
enable these data collection tools if that setting is enabled. Also, most
popular ad blockers will prevent these tools from sending any tracking data.
Disabling or blocking these data collection tools will <3>not</3> impact the
performance of this app. The information collected by these tools is used to
monitor application performance, determine browser support, and collect error
reports.</3>
<4>Regexper is not supported by ad revenue or sales of any kind.</4>
"Page Not Found": |
PAGE NOT FOUND
"404 Page Not Found": |
404 PAGE NOT FOUND
"The page you have requested could not be found.": |
THE PAGE YOU HAVE REQUESTED COULD NOT BE FOUND.
"Language": |
Language
"Source on GitLab": |
Source on GitLab
"Privacy Policy": |
Privacy Policy
"Created by <1>Jeff Avallone</1>": |
Created by <1>Jeff Avallone</1>
"Generated images licensed: <1><0></0></1>": |
Generated images licensed: <1><0></0></1>
"Creative Commons CC-BY-3.0 License": |
Creative Commons CC-BY-3.0 License
"An error has occurred": |
An error has occurred
"This error has been logged. You may also <1>fill out a report</1>.": |
This error has been logged. You may also <1>fill out a report</1>.
"Hello world": |
Hello world
"Privacy policy copy": |
<0>Regexper and the tools used to create it are all open source. If you are
concerned that the JavaScript being delivered is in any way malicious, please
inspect the source in the <1>GitLab repository</1>.</0>
<1>There are two data collection tools integrated in the app. These tools are
not used to collect personal information:</1>
<2>
<0><0>Google Analytics</0> is used to track browser usage data and
application performance. It is configured to anonymize the client IP
address.</0>
<1><0>Sentry.io</0> is a tool used to capture and report client-side
JavaScript errors. It is configured to not store the client IP address.</1>
</2>
<3>Regexper honors the browser <1>“Do Not Track”</1> setting and will not
enable these data collection tools if that setting is enabled. Also, most
popular ad blockers will prevent these tools from sending any tracking data.
Disabling or blocking these data collection tools will <3>not</3> impact the
performance of this app. The information collected by these tools is used to
monitor application performance, determine browser support, and collect error
reports.</3>
<4>Regexper is not supported by ad revenue or sales of any kind.</4>
"Page Not Found": |
Page Not Found
"404 Page Not Found": |
404 Page Not Found
"The page you have requested could not be found.": |
The page you have requested could not be found.
import React from 'react';
import PropTypes from 'prop-types';
import { withNamespaces, Trans } from 'react-i18next';
import Metadata from 'components/Metadata';
import Message from 'components/Message';
const ErrorPage = () => <>
<Metadata title="Page Not Found" />
<Message type="error" heading="404 Page Not Found">
<p>The page you have requrested could not be found.</p>
export const ErrorPageImpl = ({ t }) => <>
<Metadata title={ t('Page Not Found') } />
<Message type="error" heading={ t('404 Page Not Found') }>
<p><Trans>The page you have requested could not be found.</Trans></p>
</Message>
</>;
export default ErrorPage;
ErrorPageImpl.propTypes = {
t: PropTypes.func.isRequired
};
export default withNamespaces()(ErrorPageImpl);
import React from 'react';
import { shallow } from 'enzyme';
import ErrorPage from 'pages/404';
import { mockT } from 'i18n';
import { ErrorPageImpl } from 'pages/404';
describe('Error Page', () => {
test('rendering', () => {
const component = shallow(
<ErrorPage />
<ErrorPageImpl t={ mockT } />
);
expect(component).toMatchSnapshot();
});
......
......@@ -3,7 +3,9 @@
exports[`Error Page rendering 1`] = `
ShallowWrapper {
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <ErrorPage />,
Symbol(enzyme.__unrendered__): <ErrorPageImpl
t={[Function]}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
......@@ -19,14 +21,16 @@ ShallowWrapper {
"props": Object {
"children": Array [
<Metadata
title="Page Not Found"
title="TRANSLATE(Page Not Found)"
/>,
<Message
heading="404 Page Not Found"
heading="TRANSLATE(404 Page Not Found)"
type="error"
>
<p>
The page you have requrested could not be found.
<WithMergedOptions(TransComponent)>
The page you have requested could not be found.
</WithMergedOptions(TransComponent)>
</p>
</Message>,
],
......@@ -38,7 +42,7 @@ ShallowWrapper {
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "Page Not Found",
"title": "TRANSLATE(Page Not Found)",
},
"ref": null,
"rendered": null,
......@@ -50,9 +54,11 @@ ShallowWrapper {
"nodeType": "function",
"props": Object {
"children": <p>
The page you have requrested could not be found.
<WithMergedOptions(TransComponent)>
The page you have requested could not be found.
</WithMergedOptions(TransComponent)>
</p>,
"heading": "404 Page Not Found",
"heading": "TRANSLATE(404 Page Not Found)",
"type": "error",
},
"ref": null,
......@@ -61,10 +67,22 @@ ShallowWrapper {
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "The page you have requrested could not be found.",
"children": <WithMergedOptions(TransComponent)>
The page you have requested could not be found.
</WithMergedOptions(TransComponent)>,
},
"ref": null,
"rendered": "The page you have requrested could not be found.",
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": "The page you have requested could not be found.",
},
"ref": null,
"rendered": "The page you have requested could not be found.",
"type": [Function],
},
"type": "p",
},
"type": [Function],
......@@ -80,14 +98,16 @@ ShallowWrapper {
"props": Object {
"children": Array [
<Metadata
title="Page Not Found"
title="TRANSLATE(Page Not Found)"
/>,
<Message
heading="404 Page Not Found"
heading="TRANSLATE(404 Page Not Found)"
type="error"
>
<p>
The page you have requrested could not be found.
<WithMergedOptions(TransComponent)>
The page you have requested could not be found.
</WithMergedOptions(TransComponent)>
</p>
</Message>,
],
......@@ -99,7 +119,7 @@ ShallowWrapper {
"key": undefined,
"nodeType": "function",
"props": Object {
"title": "Page Not Found",
"title": "TRANSLATE(Page Not Found)",
},
"ref": null,
"rendered": null,
......@@ -111,9 +131,11 @@ ShallowWrapper {
"nodeType": "function",
"props": Object {
"children": <p>
The page you have requrested could not be found.
<WithMergedOptions(TransComponent)>
The page you have requested could not be found.
</WithMergedOptions(TransComponent)>
</p>,
"heading": "404 Page Not Found",
"heading": "TRANSLATE(404 Page Not Found)",
"type": "error",
},
"ref": null,
......@@ -122,10 +144,22 @@ ShallowWrapper {
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "The page you have requrested could not be found.",
"children": <WithMergedOptions(TransComponent)>
The page you have requested could not be found.
</WithMergedOptions(TransComponent)>,
},
"ref": null,
"rendered": "The page you have requrested could not be found.",
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": "The page you have requested could not be found.",
},
"ref": null,
"rendered": "The page you have requested could not be found.",
"type": [Function],
},
"type": "p",
},
"type": [Function],
......
......@@ -3,7 +3,7 @@
exports[`Index Page rendering 1`] = `
ShallowWrapper {
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <IndexPage />,
Symbol(enzyme.__unrendered__): <IndexPageImpl />,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
......@@ -39,7 +39,9 @@ ShallowWrapper {
</Message>
</noscript>,
<div>
Hello world
<WithMergedOptions(TransComponent)>
Hello world
</WithMergedOptions(TransComponent)>
</div>,
],
},
......@@ -157,10 +159,22 @@ ShallowWrapper {
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "Hello world",
"children": <WithMergedOptions(TransComponent)>
Hello world
</WithMergedOptions(TransComponent)>,
},
"ref": null,
"rendered": "Hello world",
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": "Hello world",
},
"ref": null,
"rendered": "Hello world",
"type": [Function],
},
"type": "div",
},
],
......@@ -194,7 +208,9 @@ ShallowWrapper {
</Message>
</noscript>,
<div>
Hello world
<WithMergedOptions(TransComponent)>
Hello world
</WithMergedOptions(TransComponent)>
</div>,
],
},
......@@ -312,10 +328,22 @@ ShallowWrapper {
"key": undefined,
"nodeType": "host",
"props": Object {
"children": "Hello world",
"children": <WithMergedOptions(TransComponent)>
Hello world
</WithMergedOptions(TransComponent)>,
},
"ref": null,
"rendered": "Hello world",
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": "Hello world",
},
"ref": null,
"rendered": "Hello world",
"type": [Function],
},
"type": "div",
},
],
......
import React from 'react';
import { Link } from 'gatsby';
import { withNamespaces, Trans } from 'react-i18next';
import Metadata from 'components/Metadata';
import Message from 'components/Message';
const IndexPage = () => <>
export const IndexPageImpl = () => <>
<Metadata/>