diff --git a/gatsby-browser.js b/gatsby-browser.js
index 34e0714..05aba14 100644
--- a/gatsby-browser.js
+++ b/gatsby-browser.js
@@ -1,6 +1,8 @@
import React from 'react';
import * as Sentry from '@sentry/browser';
+import { I18nextProvider } from 'react-i18next';
+import i18n from 'i18n';
import Layout from 'components/Layout';
import 'site.css';
@@ -14,3 +16,8 @@ export const onClientEntry = () => {
export const wrapPageElement = ({ element }) => {
return { element };
};
+
+// eslint-disable-next-line react/prop-types
+export const wrapRootElement = ({ element }) => {
+ return { element };
+};
diff --git a/gatsby-ssr.js b/gatsby-ssr.js
index 75ec661..384d0a5 100644
--- a/gatsby-ssr.js
+++ b/gatsby-ssr.js
@@ -1,8 +1,15 @@
import React from 'react';
+import { I18nextProvider } from 'react-i18next';
+import i18n from 'i18n';
import Layout from 'components/Layout';
// eslint-disable-next-line react/prop-types
export const wrapPageElement = ({ element }) => {
return { element };
};
+
+// eslint-disable-next-line react/prop-types
+export const wrapRootElement = ({ element }) => {
+ return { element };
+};
diff --git a/package.json b/package.json
index f4bc970..0e496b7 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,8 @@
"jest": {
"clearMocks": true,
"collectCoverageFrom": [
- "src/**/*.js"
+ "src/**/*.js",
+ "!src/i18n.js"
],
"coverageReporters": [
"text-summary",
@@ -77,6 +78,9 @@
"gatsby-plugin-postcss": "^2.0.2",
"gatsby-plugin-react-helmet": "^3.0.5",
"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",
"jest": "^23.6.0",
"postcss-cssnext": "^3.1.0",
@@ -85,6 +89,7 @@
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-feather": "^1.1.5",
- "react-helmet": "^5.2.0"
+ "react-helmet": "^5.2.0",
+ "react-i18next": "^9.0.2"
}
}
diff --git a/src/__mocks__/i18n.js b/src/__mocks__/i18n.js
new file mode 100644
index 0000000..87ced19
--- /dev/null
+++ b/src/__mocks__/i18n.js
@@ -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 })`
+};
diff --git a/src/components/Header/__snapshots__/test.js.snap b/src/components/Header/__snapshots__/test.js.snap
index 1f54fc3..e7e19f7 100644
--- a/src/components/Header/__snapshots__/test.js.snap
+++ b/src/components/Header/__snapshots__/test.js.snap
@@ -56,6 +56,9 @@ ShallowWrapper {
Privacy Policy
+
+
+
,
],
"className": "header",
@@ -115,6 +118,9 @@ ShallowWrapper {
Privacy Policy
,
+
+
+ ,
],
"className": "list",
},
@@ -200,6 +206,25 @@ ShallowWrapper {
},
"type": "li",
},
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": ,
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": "li",
+ },
],
"type": "ul",
},
@@ -243,6 +268,9 @@ ShallowWrapper {
Privacy Policy
+
+
+
,
],
"className": "header",
@@ -302,6 +330,9 @@ ShallowWrapper {
Privacy Policy
,
+
+
+ ,
],
"className": "list",
},
@@ -387,6 +418,25 @@ ShallowWrapper {
},
"type": "li",
},
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": ,
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": "li",
+ },
],
"type": "ul",
},
@@ -470,6 +520,9 @@ ShallowWrapper {
Privacy Policy
+
+
+
,
],
"className": "header",
@@ -529,6 +582,9 @@ ShallowWrapper {
Privacy Policy
,
+
+
+ ,
],
"className": "list",
},
@@ -614,6 +670,25 @@ ShallowWrapper {
},
"type": "li",
},
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": ,
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": "li",
+ },
],
"type": "ul",
},
@@ -657,6 +732,9 @@ ShallowWrapper {
Privacy Policy
+
+
+
,
],
"className": "header",
@@ -716,6 +794,9 @@ ShallowWrapper {
Privacy Policy
,
+
+
+ ,
],
"className": "list",
},
@@ -801,6 +882,25 @@ ShallowWrapper {
},
"type": "li",
},
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": ,
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": "li",
+ },
],
"type": "ul",
},
diff --git a/src/components/Header/index.js b/src/components/Header/index.js
index 506e42b..d4703e2 100644
--- a/src/components/Header/index.js
+++ b/src/components/Header/index.js
@@ -4,6 +4,8 @@ import { Link, StaticQuery, graphql } from 'gatsby';
import GitlabIcon from 'react-feather/dist/icons/gitlab';
+import LocaleSwitcher from 'components/LocaleSwitcher';
+
import style from './style.module.css';
const query = graphql`
@@ -36,6 +38,9 @@ export const HeaderImpl = ({ site: { siteMetadata } }) => (
Privacy Policy
+
+
+
);
diff --git a/src/components/LocaleSwitcher/__snapshots__/test.js.snap b/src/components/LocaleSwitcher/__snapshots__/test.js.snap
new file mode 100644
index 0000000..8def336
--- /dev/null
+++ b/src/components/LocaleSwitcher/__snapshots__/test.js.snap
@@ -0,0 +1,323 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`LocaleSwitcher rendering 1`] = `
+ShallowWrapper {
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__): ,
+ 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 [
+
+ Language
+ ,
+
+
+
+
,
+ ],
+ },
+ "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 [
+ ,
+ ,
+ ],
+ "className": "switcher",
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": Array [
+ ,
+ ,
+ ],
+ "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 [
+
+ Language
+ ,
+
+
+
+
,
+ ],
+ },
+ "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 [
+ ,
+ ,
+ ],
+ "className": "switcher",
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": Array [
+ ,
+ ,
+ ],
+ "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,
+ },
+ },
+ },
+ },
+ },
+}
+`;
diff --git a/src/components/LocaleSwitcher/index.js b/src/components/LocaleSwitcher/index.js
new file mode 100644
index 0000000..c3255ce
--- /dev/null
+++ b/src/components/LocaleSwitcher/index.js
@@ -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 ;
+ }
+}
+
+export default withNamespaces()(LocaleSwitcherImpl);
diff --git a/src/components/LocaleSwitcher/style.module.css b/src/components/LocaleSwitcher/style.module.css
new file mode 100644
index 0000000..2e2f3dc
--- /dev/null
+++ b/src/components/LocaleSwitcher/style.module.css
@@ -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;
+}
diff --git a/src/components/LocaleSwitcher/test.js b/src/components/LocaleSwitcher/test.js
new file mode 100644
index 0000000..6ec1231
--- /dev/null
+++ b/src/components/LocaleSwitcher/test.js
@@ -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(
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+
+ test('changing language', () => {
+ jest.spyOn(i18n, 'changeLanguage');
+
+ const component = shallow(
+
+ );
+ const selectInput = component.find('select');
+ selectInput.value = 'other';
+ selectInput.simulate('change', { target: { value: 'other' } });
+
+ expect(i18n.changeLanguage).toHaveBeenCalledWith('other');
+ });
+});
diff --git a/src/globals.module.css b/src/globals.module.css
index aa230f7..1c00488 100644
--- a/src/globals.module.css
+++ b/src/globals.module.css
@@ -11,12 +11,20 @@
--gradient-green: linear-gradient(to bottom,
var(--color-green) 0%,
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;
--content-margin: 2rem;
--spacing-margin: 1rem;
--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 {
@@ -80,3 +88,42 @@
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));
+ }
+}
diff --git a/src/i18n.js b/src/i18n.js
new file mode 100644
index 0000000..4aac2c3
--- /dev/null
+++ b/src/i18n.js
@@ -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;
diff --git a/src/locales/en-AC.yaml b/src/locales/en-AC.yaml
new file mode 100644
index 0000000..a0213f6
--- /dev/null
+++ b/src/locales/en-AC.yaml
@@ -0,0 +1,2 @@
+"Language": |
+ LANGUAGE
diff --git a/src/locales/en.yaml b/src/locales/en.yaml
new file mode 100644
index 0000000..b3bad5d
--- /dev/null
+++ b/src/locales/en.yaml
@@ -0,0 +1,2 @@
+"Language": |
+ Language
diff --git a/yarn.lock b/yarn.lock
index 49171bd..b8b985b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5649,6 +5649,13 @@ hoek@4.x.x:
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
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:
version "2.5.5"
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"
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:
version "3.10.0"
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"
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:
version "0.4.23"
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"
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"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.7.0.tgz#c1bd21c64f1f1364c6f70695ec02d69392f41bfa"
integrity sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==
@@ -11419,6 +11457,11 @@ 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"
+ integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+
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"