Adding i18next integration
This commit is contained in:
parent
7d7916baf0
commit
e1c4cb9068
@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as Sentry from '@sentry/browser';
|
import * as Sentry from '@sentry/browser';
|
||||||
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
|
||||||
|
import i18n from 'i18n';
|
||||||
import Layout from 'components/Layout';
|
import Layout from 'components/Layout';
|
||||||
|
|
||||||
import 'site.css';
|
import 'site.css';
|
||||||
@ -14,3 +16,8 @@ export const onClientEntry = () => {
|
|||||||
export const wrapPageElement = ({ element }) => {
|
export const wrapPageElement = ({ element }) => {
|
||||||
return <Layout>{ element }</Layout>;
|
return <Layout>{ element }</Layout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
export const wrapRootElement = ({ element }) => {
|
||||||
|
return <I18nextProvider i18n={ i18n }>{ element }</I18nextProvider>;
|
||||||
|
};
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
|
||||||
|
import i18n from 'i18n';
|
||||||
import Layout from 'components/Layout';
|
import Layout from 'components/Layout';
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
export const wrapPageElement = ({ element }) => {
|
export const wrapPageElement = ({ element }) => {
|
||||||
return <Layout>{ element }</Layout>;
|
return <Layout>{ element }</Layout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
export const wrapRootElement = ({ element }) => {
|
||||||
|
return <I18nextProvider i18n={ i18n }>{ element }</I18nextProvider>;
|
||||||
|
};
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
"jest": {
|
"jest": {
|
||||||
"clearMocks": true,
|
"clearMocks": true,
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"src/**/*.js"
|
"src/**/*.js",
|
||||||
|
"!src/i18n.js"
|
||||||
],
|
],
|
||||||
"coverageReporters": [
|
"coverageReporters": [
|
||||||
"text-summary",
|
"text-summary",
|
||||||
@ -77,6 +78,9 @@
|
|||||||
"gatsby-plugin-postcss": "^2.0.2",
|
"gatsby-plugin-postcss": "^2.0.2",
|
||||||
"gatsby-plugin-react-helmet": "^3.0.5",
|
"gatsby-plugin-react-helmet": "^3.0.5",
|
||||||
"gatsby-plugin-sentry": "^1.0.0",
|
"gatsby-plugin-sentry": "^1.0.0",
|
||||||
|
"i18next": "^13.1.0",
|
||||||
|
"i18next-browser-languagedetector": "^2.2.4",
|
||||||
|
"i18next-xhr-backend": "^1.5.1",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^23.6.0",
|
"jest": "^23.6.0",
|
||||||
"postcss-cssnext": "^3.1.0",
|
"postcss-cssnext": "^3.1.0",
|
||||||
@ -85,6 +89,7 @@
|
|||||||
"react": "^16.7.0",
|
"react": "^16.7.0",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"react-feather": "^1.1.5",
|
"react-feather": "^1.1.5",
|
||||||
"react-helmet": "^5.2.0"
|
"react-helmet": "^5.2.0",
|
||||||
|
"react-i18next": "^9.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
src/__mocks__/i18n.js
Normal file
15
src/__mocks__/i18n.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const i18n = jest.requireActual('i18n');
|
||||||
|
|
||||||
|
// Load empty resource bundle to reduce logging output
|
||||||
|
i18n.default.addResourceBundle('dev', 'translation', {});
|
||||||
|
i18n.default.addResourceBundle('en', 'translation', {});
|
||||||
|
i18n.default.addResourceBundle('other', 'translation', {});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...i18n,
|
||||||
|
locales: [
|
||||||
|
{ code: 'en', name: 'English' },
|
||||||
|
{ code: 'other', name: 'Other' }
|
||||||
|
],
|
||||||
|
mockT: str => `TRANSLATE(${ str })`
|
||||||
|
};
|
@ -56,6 +56,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>
|
||||||
</ul>,
|
</ul>,
|
||||||
],
|
],
|
||||||
"className": "header",
|
"className": "header",
|
||||||
@ -115,6 +118,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>,
|
</li>,
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>,
|
||||||
],
|
],
|
||||||
"className": "list",
|
"className": "list",
|
||||||
},
|
},
|
||||||
@ -200,6 +206,25 @@ ShallowWrapper {
|
|||||||
},
|
},
|
||||||
"type": "li",
|
"type": "li",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": <LoadNamespace(LocaleSwitcherImpl) />,
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "class",
|
||||||
|
"props": Object {},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
"type": "li",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"type": "ul",
|
"type": "ul",
|
||||||
},
|
},
|
||||||
@ -243,6 +268,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>
|
||||||
</ul>,
|
</ul>,
|
||||||
],
|
],
|
||||||
"className": "header",
|
"className": "header",
|
||||||
@ -302,6 +330,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>,
|
</li>,
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>,
|
||||||
],
|
],
|
||||||
"className": "list",
|
"className": "list",
|
||||||
},
|
},
|
||||||
@ -387,6 +418,25 @@ ShallowWrapper {
|
|||||||
},
|
},
|
||||||
"type": "li",
|
"type": "li",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": <LoadNamespace(LocaleSwitcherImpl) />,
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "class",
|
||||||
|
"props": Object {},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
"type": "li",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"type": "ul",
|
"type": "ul",
|
||||||
},
|
},
|
||||||
@ -470,6 +520,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>
|
||||||
</ul>,
|
</ul>,
|
||||||
],
|
],
|
||||||
"className": "header",
|
"className": "header",
|
||||||
@ -529,6 +582,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>,
|
</li>,
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>,
|
||||||
],
|
],
|
||||||
"className": "list",
|
"className": "list",
|
||||||
},
|
},
|
||||||
@ -614,6 +670,25 @@ ShallowWrapper {
|
|||||||
},
|
},
|
||||||
"type": "li",
|
"type": "li",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": <LoadNamespace(LocaleSwitcherImpl) />,
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "class",
|
||||||
|
"props": Object {},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
"type": "li",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"type": "ul",
|
"type": "ul",
|
||||||
},
|
},
|
||||||
@ -657,6 +732,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>
|
||||||
</ul>,
|
</ul>,
|
||||||
],
|
],
|
||||||
"className": "header",
|
"className": "header",
|
||||||
@ -716,6 +794,9 @@ ShallowWrapper {
|
|||||||
Privacy Policy
|
Privacy Policy
|
||||||
</mockConstructor>
|
</mockConstructor>
|
||||||
</li>,
|
</li>,
|
||||||
|
<li>
|
||||||
|
<LoadNamespace(LocaleSwitcherImpl) />
|
||||||
|
</li>,
|
||||||
],
|
],
|
||||||
"className": "list",
|
"className": "list",
|
||||||
},
|
},
|
||||||
@ -801,6 +882,25 @@ ShallowWrapper {
|
|||||||
},
|
},
|
||||||
"type": "li",
|
"type": "li",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": <LoadNamespace(LocaleSwitcherImpl) />,
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "class",
|
||||||
|
"props": Object {},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
"type": "li",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"type": "ul",
|
"type": "ul",
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,8 @@ import { Link, StaticQuery, graphql } from 'gatsby';
|
|||||||
|
|
||||||
import GitlabIcon from 'react-feather/dist/icons/gitlab';
|
import GitlabIcon from 'react-feather/dist/icons/gitlab';
|
||||||
|
|
||||||
|
import LocaleSwitcher from 'components/LocaleSwitcher';
|
||||||
|
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
const query = graphql`
|
const query = graphql`
|
||||||
@ -36,6 +38,9 @@ export const HeaderImpl = ({ site: { siteMetadata } }) => (
|
|||||||
<li>
|
<li>
|
||||||
<Link to="/privacy">Privacy Policy</Link>
|
<Link to="/privacy">Privacy Policy</Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<LocaleSwitcher />
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
323
src/components/LocaleSwitcher/__snapshots__/test.js.snap
Normal file
323
src/components/LocaleSwitcher/__snapshots__/test.js.snap
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`LocaleSwitcher rendering 1`] = `
|
||||||
|
ShallowWrapper {
|
||||||
|
Symbol(enzyme.__root__): [Circular],
|
||||||
|
Symbol(enzyme.__unrendered__): <LocaleSwitcherImpl />,
|
||||||
|
Symbol(enzyme.__renderer__): Object {
|
||||||
|
"batchedUpdates": [Function],
|
||||||
|
"getNode": [Function],
|
||||||
|
"render": [Function],
|
||||||
|
"simulateError": [Function],
|
||||||
|
"simulateEvent": [Function],
|
||||||
|
"unmount": [Function],
|
||||||
|
},
|
||||||
|
Symbol(enzyme.__node__): Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": Array [
|
||||||
|
<WithMergedOptions(TransComponent)>
|
||||||
|
Language
|
||||||
|
</WithMergedOptions(TransComponent)>,
|
||||||
|
<div
|
||||||
|
className="switcher"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
onChange={[Function]}
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="other"
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<ChevronsDown
|
||||||
|
color="currentColor"
|
||||||
|
size="24"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "class",
|
||||||
|
"props": Object {
|
||||||
|
"children": "Language",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": "Language",
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": Array [
|
||||||
|
<select
|
||||||
|
onChange={[Function]}
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="other"
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</option>
|
||||||
|
</select>,
|
||||||
|
<ChevronsDown
|
||||||
|
color="currentColor"
|
||||||
|
size="24"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
"className": "switcher",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": Array [
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>,
|
||||||
|
<option
|
||||||
|
value="other"
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</option>,
|
||||||
|
],
|
||||||
|
"onChange": [Function],
|
||||||
|
"value": "en",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": "en",
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": "English",
|
||||||
|
"value": "en",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": "English",
|
||||||
|
"type": "option",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": "other",
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": "Other",
|
||||||
|
"value": "other",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": "Other",
|
||||||
|
"type": "option",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "function",
|
||||||
|
"props": Object {
|
||||||
|
"color": "currentColor",
|
||||||
|
"size": "24",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "div",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "label",
|
||||||
|
},
|
||||||
|
Symbol(enzyme.__nodes__): Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": Array [
|
||||||
|
<WithMergedOptions(TransComponent)>
|
||||||
|
Language
|
||||||
|
</WithMergedOptions(TransComponent)>,
|
||||||
|
<div
|
||||||
|
className="switcher"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
onChange={[Function]}
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="other"
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<ChevronsDown
|
||||||
|
color="currentColor"
|
||||||
|
size="24"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "class",
|
||||||
|
"props": Object {
|
||||||
|
"children": "Language",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": "Language",
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": Array [
|
||||||
|
<select
|
||||||
|
onChange={[Function]}
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="other"
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</option>
|
||||||
|
</select>,
|
||||||
|
<ChevronsDown
|
||||||
|
color="currentColor"
|
||||||
|
size="24"
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
"className": "switcher",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": Array [
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>,
|
||||||
|
<option
|
||||||
|
value="other"
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</option>,
|
||||||
|
],
|
||||||
|
"onChange": [Function],
|
||||||
|
"value": "en",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": Array [
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": "en",
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": "English",
|
||||||
|
"value": "en",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": "English",
|
||||||
|
"type": "option",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": "other",
|
||||||
|
"nodeType": "host",
|
||||||
|
"props": Object {
|
||||||
|
"children": "Other",
|
||||||
|
"value": "other",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": "Other",
|
||||||
|
"type": "option",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"instance": null,
|
||||||
|
"key": undefined,
|
||||||
|
"nodeType": "function",
|
||||||
|
"props": Object {
|
||||||
|
"color": "currentColor",
|
||||||
|
"size": "24",
|
||||||
|
},
|
||||||
|
"ref": null,
|
||||||
|
"rendered": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "div",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "label",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Symbol(enzyme.__options__): Object {
|
||||||
|
"adapter": ReactSixteenAdapter {
|
||||||
|
"options": Object {
|
||||||
|
"enableComponentDidUpdateOnSetState": true,
|
||||||
|
"lifecycles": Object {
|
||||||
|
"componentDidUpdate": Object {
|
||||||
|
"onSetState": true,
|
||||||
|
},
|
||||||
|
"getDerivedStateFromProps": true,
|
||||||
|
"getSnapshotBeforeUpdate": true,
|
||||||
|
"setState": Object {
|
||||||
|
"skipsComponentDidUpdateOnNullish": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
67
src/components/LocaleSwitcher/index.js
Normal file
67
src/components/LocaleSwitcher/index.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withNamespaces, Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
import ExpandIcon from 'react-feather/dist/icons/chevrons-down';
|
||||||
|
|
||||||
|
import i18n, { locales } from 'i18n';
|
||||||
|
|
||||||
|
import style from './style.module.css';
|
||||||
|
|
||||||
|
const localeToAvailable = (locale, available, defaultLocale) => {
|
||||||
|
if (available.includes(locale)) {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = locale.split('-');
|
||||||
|
|
||||||
|
if (parts.length > 0 && available.includes(parts[0])) {
|
||||||
|
return parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultLocale;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class LocaleSwitcherImpl extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
current: localeToAvailable(
|
||||||
|
i18n.language || '',
|
||||||
|
locales.map(l => l.code),
|
||||||
|
'en')
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
i18n.on('languageChanged', this.handleLanguageChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
i18n.off('languageChanged', this.handleLanguageChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectChange = ({ target }) => {
|
||||||
|
i18n.changeLanguage(target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLanguageChange = lang => {
|
||||||
|
this.setState({ current: lang });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { current } = this.state;
|
||||||
|
|
||||||
|
return <label>
|
||||||
|
<Trans>Language</Trans>
|
||||||
|
<div className={ style.switcher }>
|
||||||
|
<select value={ current } onChange={ this.handleSelectChange }>
|
||||||
|
{ locales.map(locale => (
|
||||||
|
<option value={ locale.code } key={ locale.code }>
|
||||||
|
{ locale.name }
|
||||||
|
</option>
|
||||||
|
)) }
|
||||||
|
</select>
|
||||||
|
<ExpandIcon />
|
||||||
|
</div>
|
||||||
|
</label>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withNamespaces()(LocaleSwitcherImpl);
|
11
src/components/LocaleSwitcher/style.module.css
Normal file
11
src/components/LocaleSwitcher/style.module.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@import url('../../globals.module.css');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--control-gradient: var(--color-tan) var(--gradient-tan);
|
||||||
|
--select-height: 2.4rem;
|
||||||
|
--select-width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher {
|
||||||
|
composes: fancy-select;
|
||||||
|
}
|
27
src/components/LocaleSwitcher/test.js
Normal file
27
src/components/LocaleSwitcher/test.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import i18n from 'i18n';
|
||||||
|
import { LocaleSwitcherImpl } from 'components/LocaleSwitcher';
|
||||||
|
|
||||||
|
describe('LocaleSwitcher', () => {
|
||||||
|
test('rendering', () => {
|
||||||
|
const component = shallow(
|
||||||
|
<LocaleSwitcherImpl />
|
||||||
|
);
|
||||||
|
expect(component).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('changing language', () => {
|
||||||
|
jest.spyOn(i18n, 'changeLanguage');
|
||||||
|
|
||||||
|
const component = shallow(
|
||||||
|
<LocaleSwitcherImpl />
|
||||||
|
);
|
||||||
|
const selectInput = component.find('select');
|
||||||
|
selectInput.value = 'other';
|
||||||
|
selectInput.simulate('change', { target: { value: 'other' } });
|
||||||
|
|
||||||
|
expect(i18n.changeLanguage).toHaveBeenCalledWith('other');
|
||||||
|
});
|
||||||
|
});
|
@ -11,12 +11,20 @@
|
|||||||
--gradient-green: linear-gradient(to bottom,
|
--gradient-green: linear-gradient(to bottom,
|
||||||
var(--color-green) 0%,
|
var(--color-green) 0%,
|
||||||
color(var(--color-green) shade(40%)) 100%);
|
color(var(--color-green) shade(40%)) 100%);
|
||||||
|
--gradient-tan: linear-gradient(to bottom,
|
||||||
|
var(--color-tan) 0%,
|
||||||
|
color(var(--color-tan) shade(40%)) 100%);
|
||||||
|
|
||||||
--header-height: 6rem;
|
--header-height: 6rem;
|
||||||
--content-margin: 2rem;
|
--content-margin: 2rem;
|
||||||
--spacing-margin: 1rem;
|
--spacing-margin: 1rem;
|
||||||
|
|
||||||
--list-separator-width: 2ex;
|
--list-separator-width: 2ex;
|
||||||
|
|
||||||
|
--control-gradient: var(--color-green) var(--gradient-green);
|
||||||
|
--select-height: 2.8rem;
|
||||||
|
--select-width: 12rem;
|
||||||
|
--select-separator-inset: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-list {
|
.inline-list {
|
||||||
@ -80,3 +88,42 @@
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fancy-select {
|
||||||
|
margin-left: 1ex;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: bottom;
|
||||||
|
display: inline-block;
|
||||||
|
height: var(--select-height);
|
||||||
|
width: var(--select-width);
|
||||||
|
background: var(--control-gradient);
|
||||||
|
color: var(--color-black);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& select {
|
||||||
|
background: transparent;
|
||||||
|
border: 0 none;
|
||||||
|
font-size: inherit;
|
||||||
|
height: var(--select-height);
|
||||||
|
width: calc(var(--select-width) + 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: var(--select-height);
|
||||||
|
width: var(--select-height);
|
||||||
|
pointer-events: none;
|
||||||
|
background: var(--control-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: var(--select-separator-inset);
|
||||||
|
right: var(--select-height);
|
||||||
|
height: calc(var(--select-height) - var(--select-separator-inset) * 2);
|
||||||
|
border-right: 0.1rem solid color(var(--color-black) alpha(0.2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
51
src/i18n.js
Normal file
51
src/i18n.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import Backend from 'i18next-xhr-backend';
|
||||||
|
|
||||||
|
export const locales = [
|
||||||
|
{ code: 'en', name: 'English' },
|
||||||
|
{ code: 'en-AC', name: 'English (ALL CAPS)' }
|
||||||
|
];
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(Backend)
|
||||||
|
.init({
|
||||||
|
fallbackLng: 'en',
|
||||||
|
debug: false,
|
||||||
|
keySeparator: null,
|
||||||
|
nsSeparator: null,
|
||||||
|
saveMissing: true,
|
||||||
|
saveMissingTo: 'current',
|
||||||
|
missingKeyHandler: (lng, ns, key, fallback) => {
|
||||||
|
const escapedKey = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
if (console && console.group && console.log) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.group(`Missing key in ${ lng }`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`"${ escapedKey }": |\n ${ fallback }`);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
loadPath: '{{lng}}',
|
||||||
|
parse: data => data,
|
||||||
|
ajax: (lng, options, callback) => {
|
||||||
|
try {
|
||||||
|
import(`locales/${ lng }.yaml`).then(
|
||||||
|
locale => callback(locale, { status: '200' }),
|
||||||
|
() => callback(null, { status: '404' }));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
callback(null, { status: '404' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
2
src/locales/en-AC.yaml
Normal file
2
src/locales/en-AC.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"Language": |
|
||||||
|
LANGUAGE
|
2
src/locales/en.yaml
Normal file
2
src/locales/en.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"Language": |
|
||||||
|
Language
|
45
yarn.lock
45
yarn.lock
@ -5649,6 +5649,13 @@ hoek@4.x.x:
|
|||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||||
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
|
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
|
||||||
|
|
||||||
|
hoist-non-react-statics@3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz#fba3e7df0210eb9447757ca1a7cb607162f0a364"
|
||||||
|
integrity sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==
|
||||||
|
dependencies:
|
||||||
|
react-is "^16.3.2"
|
||||||
|
|
||||||
hoist-non-react-statics@^2.5.0:
|
hoist-non-react-statics@^2.5.0:
|
||||||
version "2.5.5"
|
version "2.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||||
@ -5711,6 +5718,13 @@ html-entities@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
|
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
|
||||||
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
|
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
|
||||||
|
|
||||||
|
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"
|
||||||
|
integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=
|
||||||
|
dependencies:
|
||||||
|
void-elements "^2.0.1"
|
||||||
|
|
||||||
htmlparser2@^3.9.1:
|
htmlparser2@^3.9.1:
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
|
||||||
@ -5797,6 +5811,21 @@ https-browserify@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||||
|
|
||||||
|
i18next-browser-languagedetector@^2.2.4:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.4.tgz#b02412d7ab15d7d74e1b1317d67d8a244b219ee3"
|
||||||
|
integrity sha512-wPbtH18FdOuB245I8Bhma5/XSDdN/HpYlX+wga1eMy+slhaFQSnrWX6fp+aYSL2eEuj0RlfHeEVz6Fo/lxAj6A==
|
||||||
|
|
||||||
|
i18next-xhr-backend@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next-xhr-backend/-/i18next-xhr-backend-1.5.1.tgz#50282610780c6a696d880dfa7f4ac1d01e8c3ad5"
|
||||||
|
integrity sha512-9OLdC/9YxDvTFcgsH5t2BHCODHEotHCa6h7Ly0EUlUC7Y2GS09UeoHOGj3gWKQ3HCqXz8NlH4gOrK3NNc9vPuw==
|
||||||
|
|
||||||
|
i18next@^13.1.0:
|
||||||
|
version "13.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next/-/i18next-13.1.0.tgz#d8656bbc53216ffcd428364fbfb75f75647846a5"
|
||||||
|
integrity sha512-BuDhYRFReCXJiUJ8GdC2m0wXw4vC/BS6e7UJO+wTrkE3K+92VmJn5p/wxqEE+pdffquh9GYYAMfK9rUlb48pcg==
|
||||||
|
|
||||||
iconv-lite@0.4.23:
|
iconv-lite@0.4.23:
|
||||||
version "0.4.23"
|
version "0.4.23"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||||
@ -9542,7 +9571,16 @@ react-hot-loader@^4.6.2:
|
|||||||
shallowequal "^1.0.2"
|
shallowequal "^1.0.2"
|
||||||
source-map "^0.7.3"
|
source-map "^0.7.3"
|
||||||
|
|
||||||
react-is@^16.6.1, react-is@^16.7.0:
|
react-i18next@^9.0.2:
|
||||||
|
version "9.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-9.0.2.tgz#6c524c01402549ca010e90539dbe40996e213702"
|
||||||
|
integrity sha512-gR+GelJzwDUWYyOEnIGnbBA96ZHqkWgHg9t0wVyBAiYVhTrMIyOWX6tBL7zpETwodAXcQKj/Gd4NlgRZqfQnzA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.1.2"
|
||||||
|
hoist-non-react-statics "3.0.1"
|
||||||
|
html-parse-stringify2 "2.0.1"
|
||||||
|
|
||||||
|
react-is@^16.3.2, react-is@^16.6.1, react-is@^16.7.0:
|
||||||
version "16.7.0"
|
version "16.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.7.0.tgz#c1bd21c64f1f1364c6f70695ec02d69392f41bfa"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.7.0.tgz#c1bd21c64f1f1364c6f70695ec02d69392f41bfa"
|
||||||
integrity sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==
|
integrity sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==
|
||||||
@ -11419,6 +11457,11 @@ vm-browserify@0.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
indexof "0.0.1"
|
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"
|
||||||
|
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
|
||||||
|
|
||||||
w3c-hr-time@^1.0.1:
|
w3c-hr-time@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
|
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
|
||||||
|
Loading…
Reference in New Issue
Block a user