├── .browserslistrc
├── packages
├── gatsby-i18n
│ ├── plugin.js
│ ├── src
│ │ ├── utils.js
│ │ ├── index.js
│ │ ├── plugin
│ │ │ ├── index.js
│ │ │ ├── onPreBootstrap.js
│ │ │ ├── onPreExtractQueries.js
│ │ │ ├── onCreatePage.js
│ │ │ └── onCreateNode.js
│ │ ├── I18nContext.js
│ │ ├── Language.js
│ │ ├── Link.js
│ │ ├── Redirect.js
│ │ └── Head.js
│ ├── README.md
│ ├── .babelrc
│ ├── LICENSE
│ └── package.json
├── gatsby-i18n-plugin
│ ├── src
│ │ ├── utils.js
│ │ ├── gatsby-node.js
│ │ ├── index.js
│ │ ├── plugin
│ │ │ ├── index.js
│ │ │ ├── onPreBootstrap.js
│ │ │ ├── onPreExtractQueries.js
│ │ │ ├── onCreatePage.js
│ │ │ └── onCreateNode.js
│ │ ├── I18nContext.js
│ │ ├── Link.js
│ │ ├── Language.js
│ │ ├── Redirect.js
│ │ └── Head.js
│ ├── README.md
│ ├── .babelrc
│ ├── LICENSE
│ ├── package.json
│ ├── .npmignore
│ └── .gitignore
└── gatsby-plugin-i18next
│ ├── src
│ ├── gatsby-node.js
│ ├── index.js
│ ├── setupI18next.js
│ └── withI18next.js
│ ├── .babelrc
│ ├── LICENSE
│ ├── README.md
│ ├── .npmignore
│ ├── package.json
│ └── .gitignore
├── .prettierrc
├── lerna.json
├── starters
└── gatsby-starter-i18next
│ ├── .gitignore
│ ├── src
│ ├── images
│ │ └── gatsby-icon.png
│ ├── pages
│ │ ├── 404.js
│ │ ├── page-2.js
│ │ └── index.js
│ └── components
│ │ ├── switcher.js
│ │ ├── layout.js
│ │ ├── header.js
│ │ └── layout.css
│ ├── gatsby-node.js
│ ├── gatsby-browser.js
│ ├── gatsby-ssr.js
│ ├── .eslintrc.js
│ ├── locale
│ ├── en
│ │ └── messages.json
│ └── de
│ │ └── messages.json
│ ├── README.md
│ ├── package.json
│ ├── gatsby-config.js
│ └── LICENSE
├── .eslintrc.js
├── scripts
└── deploy.js
├── README.md
├── LICENSE
├── package.json
└── .gitignore
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # Browsers that we support
2 |
3 | defaults
4 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/plugin');
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "singleQuote": true,
4 | "trailingComma": "all"
5 | }
6 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/utils.js:
--------------------------------------------------------------------------------
1 | export function isBrowser() {
2 | return typeof window === 'undefined';
3 | }
4 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/utils.js:
--------------------------------------------------------------------------------
1 | export function isBrowser() {
2 | return typeof window === 'undefined';
3 | }
4 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "independent",
3 | "npmClient": "npm",
4 | "useWorkspaces": true,
5 | "packages": [
6 | "packages/*"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/gatsby-node.js:
--------------------------------------------------------------------------------
1 | export {
2 | onPreBootstrap,
3 | onCreateNode,
4 | onPreExtractQueries,
5 | onCreatePage,
6 | } from './plugin';
7 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/.gitignore:
--------------------------------------------------------------------------------
1 | # Project dependencies
2 | .cache
3 | node_modules
4 | yarn-error.log
5 |
6 | # Build directory
7 | /public
8 | .DS_Store
9 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/images/gatsby-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikhudo/gatsby-i18n-plugin/HEAD/starters/gatsby-starter-i18next/src/images/gatsby-icon.png
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/src/gatsby-node.js:
--------------------------------------------------------------------------------
1 | export {
2 | onPreBootstrap,
3 | onCreateNode,
4 | onPreExtractQueries,
5 | onCreatePage,
6 | } from 'gatsby-i18n/plugin';
7 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/src/index.js:
--------------------------------------------------------------------------------
1 | export { Head, Redirect, Language, Link, I18nProvider } from 'gatsby-i18n';
2 |
3 | export { default as withI18next } from './withI18next';
4 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/gatsby-node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's Node APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/node-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's Browser APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/browser-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "react-app",
5 | "prettier"
6 | ],
7 | "plugins": ["prettier"],
8 | "rules": {
9 | "prettier/prettier": "error"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "react-app",
5 | "prettier"
6 | ],
7 | "plugins": ["prettier"],
8 | "rules": {
9 | "prettier/prettier": "error"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-i18n
2 | > [Gatsby](https://github.com/gatsbyjs/gatsby) plugin that provides i18n support.
3 |
4 | ## Installation
5 | ```sh
6 | yarn add gatsby-i18n
7 | # npm install --save gatsby-i18n
8 | ```
9 |
10 | ## License
11 | [MIT](LICENSE)
12 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-i18n
2 | > [Gatsby](https://github.com/gatsbyjs/gatsby) plugin that provides i18n support.
3 |
4 | ## Installation
5 | ```sh
6 | yarn add gatsby-i18n
7 | # npm install --save gatsby-i18n
8 | ```
9 |
10 | ## License
11 | [MIT](LICENSE)
12 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as Head } from './Head';
2 | export { default as Redirect } from './Redirect';
3 | export { default as Language } from './Language';
4 | export { default as Link } from './Link';
5 | export { I18nProvider, I18nConsumer } from './I18nContext';
6 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as Head } from './Head';
2 | export { default as Redirect } from './Redirect';
3 | export { default as Language } from './Language';
4 | export { default as Link } from './Link';
5 | export { I18nProvider, I18nConsumer } from './I18nContext';
6 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/react"
5 | ],
6 | "plugins": [
7 | "@babel/plugin-transform-runtime",
8 | "@babel/plugin-proposal-class-properties",
9 | "@babel/plugin-proposal-object-rest-spread"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/plugin/index.js:
--------------------------------------------------------------------------------
1 | export { default as onPreBootstrap } from './onPreBootstrap';
2 | export { default as onCreateNode } from './onCreateNode';
3 | export { default as onPreExtractQueries } from './onPreExtractQueries';
4 | export { default as onCreatePage } from './onCreatePage';
5 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/react"
5 | ],
6 | "plugins": [
7 | "@babel/plugin-transform-runtime",
8 | "@babel/plugin-proposal-class-properties",
9 | "@babel/plugin-proposal-object-rest-spread"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/plugin/index.js:
--------------------------------------------------------------------------------
1 | export { default as onPreBootstrap } from './onPreBootstrap';
2 | export { default as onCreateNode } from './onCreateNode';
3 | export { default as onPreExtractQueries } from './onPreExtractQueries';
4 | export { default as onCreatePage } from './onCreatePage';
5 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/react"
5 | ],
6 | "plugins": [
7 | "@babel/plugin-transform-runtime",
8 | "@babel/plugin-proposal-class-properties",
9 | "@babel/plugin-proposal-object-rest-spread"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/src/setupI18next.js:
--------------------------------------------------------------------------------
1 | import i18next from 'i18next';
2 |
3 | function setupI18next({ fallbackLng, debug }, cb) {
4 | i18next.init(
5 | {
6 | debug,
7 | defaultNS: 'messages',
8 | wait: true,
9 | fallbackLng,
10 | },
11 | cb,
12 | );
13 |
14 | return i18next;
15 | }
16 |
17 | export default setupI18next;
18 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/I18nContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const I18nContext = React.createContext();
5 |
6 | export function I18nProvider({ children, ...rest }) {
7 | return (
8 | {children}
9 | );
10 | }
11 |
12 | export const I18nConsumer = I18nContext.Consumer;
13 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/I18nContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const I18nContext = React.createContext();
5 |
6 | export function I18nProvider({ children, ...rest }) {
7 | return (
8 | {children}
9 | );
10 | }
11 |
12 | export const I18nConsumer = I18nContext.Consumer;
13 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/locale/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "Gatsby Starter I18next": "Gatsby Starter I18next (EN)",
3 | "Go back to the homepage": "Go back to the homepage",
4 | "Go to page 2": "Go to page 2",
5 | "Hi from the second page": "Hi from the second page",
6 | "Hi people": "Hi people",
7 | "Now go build something great.": "Now go build something great.",
8 | "Welcome to page 2": "Welcome to page 2",
9 | "Welcome to your new Gatsby site.": "Welcome to your new Gatsby site."
10 | }
11 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/locale/de/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "Gatsby Starter I18next": "Gatsby Starter I18next (DE)",
3 | "Go back to the homepage": "Gehe zurück zur Startseite",
4 | "Go to page 2": "Gehe zu Seite 2",
5 | "Hi from the second page": "Hi von Seite 2",
6 | "Hi people": "Hallo Leute",
7 | "Now go build something great.": "Jetzt mach etwas Großartiges.",
8 | "Welcome to page 2": "Willkommen auf Seite 2",
9 | "Welcome to your new Gatsby site.": "Willkommen auf Deiner neuen Gatsby-Site."
10 | }
11 |
--------------------------------------------------------------------------------
/scripts/deploy.js:
--------------------------------------------------------------------------------
1 | // const fs = require('fs-extra');
2 | // const path = require('path');
3 | // const ghpages = require('gh-pages');
4 | //
5 | // const STARTER_DIR = 'starters';
6 | //
7 | // fs.readdir(STARTER_DIR, (err, files) => {
8 | // files
9 | // .filter(file => file.startsWith('gatsby-starter'))
10 | // .forEach(async starter => {
11 | // const dist = path.join(STARTER_DIR, starter, 'public');
12 | // const dest = path.join(STARTER_DIR, starter);
13 | // ghpages.publish(dist, { dest: starter }, function(err) {
14 | // console.error(err);
15 | // });
16 | // });
17 | // });
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-i18n
2 | > [Gatsby2](https://github.com/gatsbyjs/gatsby) plugin that provides i18n support.
3 |
4 | ## Packages
5 | This repository is a monorepo managed with [Lerna](https://github.com/lerna/lerna). [Several packages](/packages) are published to npm from the same codebase.
6 |
7 | ## Starters
8 | ### gatsby-starter-i18next
9 | [Demo](https://hupe1980.github.io/gatsby-i18n/gatsby-starter-i18next) [Source](/starters/gatsby-starter-i18next)
10 | - i18next integration
11 | - automatic browser-language detection and redirection
12 | - language switcher
13 | - hreflang
14 | - ...
15 |
16 |
17 | ## License
18 | [MIT](LICENSE)
19 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/Link.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link as GatsbyLink } from 'gatsby';
4 |
5 | import { I18nConsumer } from './I18nContext';
6 |
7 | const Link = ({ to, lng, children, ...rest }) => {
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
15 | Link.propTypes = {
16 | to: PropTypes.string.isRequired,
17 | children: PropTypes.node.isRequired,
18 | };
19 |
20 | export default props => (
21 | {value => }
22 | );
23 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/Language.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { navigate } from 'gatsby'
3 |
4 | import { I18nConsumer } from './I18nContext'
5 |
6 | class Language extends Component {
7 | handleChangeLng = newLng => {
8 | const { originalPath } = this.props
9 | const newUrl = `/${newLng}${originalPath}`
10 | navigate(newUrl)
11 | }
12 |
13 | render() {
14 | const { availableLngs, children, lng } = this.props
15 | return children({ lng, changeLng: this.handleChangeLng, availableLngs })
16 | }
17 | }
18 |
19 | export default props => (
20 |
21 | {lngProps => }
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/Language.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { navigate } from 'gatsby'
3 |
4 | import { I18nConsumer } from './I18nContext'
5 |
6 | class Language extends Component {
7 | handleChangeLng = newLng => {
8 | const { originalPath } = this.props
9 | const newUrl = `/${newLng}${originalPath}`
10 | navigate(newUrl)
11 | }
12 |
13 | render() {
14 | const { availableLngs, children, lng } = this.props
15 | return children({ lng, changeLng: this.handleChangeLng, availableLngs })
16 | }
17 | }
18 |
19 | export default props => (
20 |
21 | {lngProps => }
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/plugin/onPreBootstrap.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 |
3 | export const onPreBootstrap = ({ store, reporter }) => {
4 | const activity = reporter.activityTimer('@igorko: copy redirect component');
5 | activity.start();
6 |
7 | const program = store.getState().program;
8 |
9 | const module = `
10 | const { Redirect } = require('gatsby-i18n');
11 | module.exports = Redirect;
12 | `;
13 |
14 | const dir = `${program.directory}/.cache/@igorko`;
15 |
16 | if (!fs.existsSync(dir)) {
17 | fs.mkdirSync(dir);
18 | }
19 |
20 | fs.writeFileSync(`${dir}/redirect.js`, module);
21 |
22 | activity.end();
23 | };
24 |
25 | export default onPreBootstrap;
26 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import { I18n } from 'react-i18next';
4 | import { withI18next } from 'gatsby-plugin-i18next';
5 |
6 | import Layout from '../components/layout';
7 |
8 | const NotFoundPage = () => (
9 |
10 | NOT FOUND
11 | You just hit a route that doesn't exist... the sadness.
12 |
13 | );
14 |
15 | export default withI18next()(NotFoundPage);
16 |
17 | export const query = graphql`
18 | query($lng: String!) {
19 | locales: allLocale(filter: { lng: { eq: $lng }, ns: { eq: "messages" } }) {
20 | ...TranslationFragment
21 | }
22 | }
23 | `;
24 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/plugin/onPreBootstrap.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 |
3 | export const onPreBootstrap = ({ store, reporter }) => {
4 | const activity = reporter.activityTimer('@igorko: copy redirect component');
5 | activity.start();
6 |
7 | const program = store.getState().program;
8 |
9 | const module = `
10 | const { Redirect } = require('gatsby-i18n');
11 | module.exports = Redirect;
12 | `;
13 |
14 | const dir = `${program.directory}/.cache/@igorko`;
15 |
16 | if (!fs.existsSync(dir)) {
17 | fs.mkdirSync(dir);
18 | }
19 |
20 | fs.writeFileSync(`${dir}/redirect.js`, module);
21 |
22 | activity.end();
23 | };
24 |
25 | export default onPreBootstrap;
26 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/Link.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link as GatsbyLink } from 'gatsby';
4 |
5 | import { I18nConsumer } from './I18nContext';
6 |
7 | const Link = ({ to, lng, children, ...rest }) => {
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
15 | Link.propTypes = {
16 | to: PropTypes.string.isRequired,
17 | children: PropTypes.node.isRequired,
18 | };
19 |
20 | export default props => (
21 |
22 | {value => {
23 | console.log('!!! value', value);
24 |
25 | return ;
26 | }}
27 |
28 | );
29 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-starter-default
2 | The default Gatsby starter.
3 |
4 | For an overview of the project structure please refer to the [Gatsby documentation - Building with Components](https://www.gatsbyjs.org/docs/building-with-components/).
5 |
6 | ## Install
7 |
8 | Make sure that you have the Gatsby CLI program installed:
9 | ```sh
10 | npm install --global gatsby-cli
11 | ```
12 |
13 | And run from your CLI:
14 | ```sh
15 | gatsby new gatsby-example-site
16 | ```
17 |
18 | Then you can run it by:
19 | ```sh
20 | cd gatsby-example-site
21 | gatsby develop
22 | ```
23 |
24 | ## Deploy
25 |
26 | [](https://app.netlify.com/start/deploy?repository=https://github.com/gatsbyjs/gatsby-starter-default)
27 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/plugin/onPreExtractQueries.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 |
3 | const onPreExtractQueries = async ({ store, getNodes }, pluginOptions) => {
4 | const program = store.getState().program;
5 | const fragment = `
6 | import { graphql } from 'gatsby';
7 |
8 | export const localeFragment = graphql\`
9 | fragment TranslationFragment on ${pluginOptions.translationsConnections ||
10 | `Locale`}Connection {
11 | edges {
12 | node {
13 | id
14 | lng
15 | ns
16 | data
17 | }
18 | }
19 | }
20 | \`;
21 | `;
22 | const file = `${program.directory}/.cache/fragments/@igorko/fragments.js`;
23 |
24 | await fs.outputFile(file, fragment);
25 | };
26 |
27 | export default onPreExtractQueries;
28 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/components/switcher.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Language } from 'gatsby-plugin-i18next';
3 |
4 | const Switcher = ({ changeLng, lng, availableLngs }) => (
5 |
6 | {availableLngs.map(value => (
7 | -
8 |
18 |
19 | ))}
20 |
21 | );
22 |
23 | export default props => (
24 | {lngProps => }
25 | );
26 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/plugin/onPreExtractQueries.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 |
3 | const onPreExtractQueries = async ({ store, getNodes }, pluginOptions) => {
4 | const program = store.getState().program;
5 | const fragment = `
6 | import { graphql } from 'gatsby';
7 |
8 | export const localeFragment = graphql\`
9 | fragment TranslationFragment on ${pluginOptions.translationsConnections ||
10 | `Locale`}Connection {
11 | edges {
12 | node {
13 | id
14 | lng
15 | ns
16 | data
17 | }
18 | }
19 | }
20 | \`;
21 | `;
22 | const file = `${program.directory}/.cache/fragments/@igorko/fragments.js`;
23 |
24 | await fs.outputFile(file, fragment);
25 | };
26 |
27 | export default onPreExtractQueries;
28 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/pages/page-2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import { I18n } from 'react-i18next';
4 | import { Link, withI18next } from 'gatsby-plugin-i18next';
5 |
6 | import Layout from '../components/layout';
7 |
8 | const SecondPage = () => (
9 |
10 | {t => (
11 |
12 | {t('Hi from the second page')}
13 | {t('Welcome to page 2')}
14 | {t('Go back to the homepage')}
15 |
16 | )}
17 |
18 | );
19 |
20 | export default withI18next()(SecondPage);
21 |
22 | export const query = graphql`
23 | query($lng: String!) {
24 | locales: allLocale(filter: { lng: { eq: $lng }, ns: { eq: "messages" } }) {
25 | ...TranslationFragment
26 | }
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import { I18n } from 'react-i18next';
4 | import { Link, withI18next } from 'gatsby-plugin-i18next';
5 |
6 | import Layout from '../components/layout';
7 |
8 | const IndexPage = () => (
9 |
10 | {t => (
11 |
12 | {t('Hi people')}
13 | {t('Welcome to your new Gatsby site.')}
14 | {t('Now go build something great.')}
15 | {t('Go to page 2')}
16 |
17 | )}
18 |
19 | );
20 |
21 | export default withI18next()(IndexPage);
22 |
23 | export const query = graphql`
24 | query($lng: String!) {
25 | locales: allLocale(filter: { lng: { eq: $lng }, ns: { eq: "messages" } }) {
26 | ...TranslationFragment
27 | }
28 | }
29 | `;
30 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-starter-i18next",
3 | "private": true,
4 | "description": "Gatsby i18next starter",
5 | "version": "1.1.4",
6 | "author": "igorko82@me.com",
7 | "repository": "ikhudo/gatsby-i18n-plugin",
8 | "license": "MIT",
9 | "scripts": {
10 | "build": "gatsby build",
11 | "develop": "gatsby develop",
12 | "serve": "gatsby serve",
13 | "deploy": "gatsby build --prefix-paths && gh-pages -d public -e gatsby-starter-i18next"
14 | },
15 | "dependencies": {
16 | "gatsby": "^2.0.40",
17 | "gatsby-plugin-i18next": "^1.1.5",
18 | "gatsby-plugin-layout": "^1.0.7",
19 | "gatsby-plugin-manifest": "^2.0.7",
20 | "gatsby-plugin-offline": "^2.0.12",
21 | "gatsby-plugin-react-helmet": "^3.0.1",
22 | "gatsby-source-filesystem": "^2.0.7",
23 | "react": "^16.6.0",
24 | "react-dom": "^16.6.0",
25 | "react-helmet": "^5.2.0"
26 | },
27 | "gitHead": "1bf52e4b2a459af17526d03ebaf34cb1a87b5ab4"
28 | }
29 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/components/layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { translate } from 'react-i18next';
4 | import { Head } from 'gatsby-plugin-i18next';
5 |
6 | import Header from './header';
7 | import './layout.css';
8 |
9 | const Layout = ({ children, data, t }) => (
10 | <>
11 |
12 | {t('Gatsby Starter I18next')}
13 |
14 |
15 |
16 |
17 |
25 | {children}
26 |
27 | >
28 | );
29 |
30 | Layout.propTypes = {
31 | children: PropTypes.node.isRequired,
32 | };
33 |
34 | export default translate()(Layout);
35 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/Redirect.js:
--------------------------------------------------------------------------------
1 | import { PureComponent } from 'react';
2 | import { navigate } from 'gatsby';
3 | import { lookup, navigatorLanguages } from '@wapps/langtag-utils';
4 |
5 | import { isBrowser } from './utils';
6 |
7 | class Redirect extends PureComponent {
8 | componentDidMount() {
9 | this.perform();
10 | }
11 |
12 | perform = () => {
13 | const { fallbackLng, availableLngs, redirectPage } = this.props.pageContext;
14 |
15 | const detectedLng =
16 | window.localStorage.getItem('@igorkoLng') ||
17 | lookup(availableLngs, navigatorLanguages(), fallbackLng);
18 |
19 | window.localStorage.setItem('@igorkoLng', detectedLng);
20 | const { hash, search } = window.location;
21 |
22 | const newUrl = `/${detectedLng}${redirectPage}${search ? search : ''}${
23 | hash ? hash : ''
24 | }`;
25 | navigate(newUrl, { replace: true });
26 | };
27 |
28 | render() {
29 | return null;
30 | }
31 | }
32 |
33 | export default Redirect;
34 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby-plugin-i18next';
3 |
4 | import Switcher from './switcher';
5 |
6 | const Header = ({ siteTitle }) => (
7 |
13 |
20 |
21 |
28 | {siteTitle}
29 |
30 |
31 |
32 |
39 |
40 |
41 |
42 | );
43 |
44 | export default Header;
45 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/Redirect.js:
--------------------------------------------------------------------------------
1 | import { PureComponent } from 'react';
2 | import { navigate } from 'gatsby';
3 | import { lookup, navigatorLanguages } from '@wapps/langtag-utils';
4 |
5 | import { isBrowser } from './utils';
6 |
7 | class Redirect extends PureComponent {
8 | componentDidMount() {
9 | if (isBrowser()) this.perform();
10 | }
11 |
12 | perform = () => {
13 | const { fallbackLng, availableLngs, redirectPage } = this.props.pageContext;
14 |
15 | const detectedLng =
16 | window.localStorage.getItem('@igorkoLng') ||
17 | lookup(availableLngs, navigatorLanguages(), fallbackLng);
18 |
19 | window.localStorage.setItem('@igorkoLng', detectedLng);
20 | const { hash, search } = window.location;
21 |
22 | const newUrl = `/${detectedLng}${redirectPage}${search ? search : ''}${
23 | hash ? hash : ''
24 | }`;
25 | navigate(newUrl, { replace: true });
26 | };
27 |
28 | render() {
29 | return null;
30 | }
31 | }
32 |
33 | export default Redirect;
34 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pathPrefix: '/gatsby-i18n/gatsby-starter-i18next',
3 | plugins: [
4 | 'gatsby-plugin-react-helmet',
5 | {
6 | resolve: `gatsby-plugin-manifest`,
7 | options: {
8 | name: 'gatsby-starter-lingui',
9 | short_name: 'starter',
10 | start_url: '/gatsby-i18n/gatsby-starter-i18next/',
11 | background_color: '#663399',
12 | theme_color: '#663399',
13 | display: 'minimal-ui',
14 | icon: 'src/images/gatsby-icon.png', // This path is relative to the root of the site.
15 | },
16 | },
17 | {
18 | resolve: `gatsby-source-filesystem`,
19 | options: {
20 | path: `${__dirname}/locale`,
21 | name: `locale`,
22 | },
23 | },
24 | {
25 | resolve: `gatsby-plugin-i18next`,
26 | options: {
27 | availableLngs: ['en', 'de'],
28 | fallbackLng: 'en',
29 | debug: true,
30 | },
31 | },
32 | 'gatsby-plugin-offline',
33 | ],
34 | };
35 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/Head.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Helmet from 'react-helmet';
4 |
5 | import { I18nConsumer } from './I18nContext';
6 |
7 | const defaultProps = {
8 | hreflang: true, // TODO https://github.com/nfl/react-helmet/issues/342
9 | };
10 |
11 | function Head({
12 | availableLngs,
13 | children,
14 | lng,
15 | originalPath,
16 | siteUrl,
17 | hreflang,
18 | }) {
19 | return (
20 | <>
21 |
22 |
23 |
24 | {availableLngs.map(value => (
25 |
31 | ))}
32 | {children}
33 |
34 | >
35 | );
36 | }
37 |
38 | Head.defaultProps = defaultProps;
39 |
40 | export default props => (
41 | {lngProps => }
42 | );
43 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/Head.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Helmet from 'react-helmet';
4 |
5 | import { I18nConsumer } from './I18nContext';
6 |
7 | const defaultProps = {
8 | hreflang: true, // TODO https://github.com/nfl/react-helmet/issues/342
9 | };
10 |
11 | function Head({
12 | availableLngs,
13 | children,
14 | lng,
15 | originalPath,
16 | siteUrl,
17 | hreflang,
18 | }) {
19 | return (
20 | <>
21 |
22 |
23 |
24 | {availableLngs.map(value => (
25 |
31 | ))}
32 | {children}
33 |
34 | >
35 | );
36 | }
37 |
38 | Head.defaultProps = defaultProps;
39 |
40 | export default props => (
41 | {lngProps => }
42 | );
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 gatsbyjs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "scripts": {
5 | "build": "lerna run build --scope @igorko/*",
6 | "build:watch": "lerna run build:watch --parallel --scope @igorko/*",
7 | "deploy": "lerna run deploy --concurrency 1 --scope gatsby-starter*"
8 | },
9 | "devDependencies": {
10 | "@babel/cli": "^7.1.2",
11 | "@babel/core": "^7.1.2",
12 | "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1",
13 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
14 | "@babel/plugin-transform-runtime": "^7.0.0-rc.1",
15 | "@babel/preset-env": "^7.0.0-rc.1",
16 | "@babel/preset-react": "^7.0.0-rc.1",
17 | "babel-eslint": "^10.0.1",
18 | "eslint": "^5.2.0",
19 | "eslint-config-prettier": "^3.1.0",
20 | "eslint-config-react-app": "^3.0.5",
21 | "eslint-plugin-flowtype": "^3.2.0",
22 | "eslint-plugin-import": "^2.13.0",
23 | "eslint-plugin-jsx-a11y": "^6.1.2",
24 | "eslint-plugin-prettier": "^3.0.0",
25 | "eslint-plugin-react": "^7.8.2",
26 | "gh-pages": "^2.0.1",
27 | "jest": "^23.4.2",
28 | "lerna": "^3.0.2",
29 | "prettier": "^1.13.7"
30 | },
31 | "workspaces": [
32 | "packages/*",
33 | "starters/*"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-i18n-plugin",
3 | "description": "Gatsby plugin that provides i18n support",
4 | "version": "0.1.0",
5 | "author": "igorko82@me.com",
6 | "repository": "ikhudo/gatsby-i18n-plugin",
7 | "keywords": [
8 | "react",
9 | "gatsby",
10 | "gatsby-plugin",
11 | "gatsby-component",
12 | "lingui",
13 | "i18n"
14 | ],
15 | "license": "MIT",
16 | "main": "lib/index.js",
17 | "scripts": {
18 | "build": "babel src --out-dir . --ignore __tests__",
19 | "build:watch": "babel -w src --out-dir . --ignore __tests__",
20 | "prepare": "yarn run build"
21 | },
22 | "dependencies": {
23 | "@babel/runtime": "^7.1.2",
24 | "@wapps/langtag-utils": "^1.0.2",
25 | "react-helmet": "^5.2.0"
26 | },
27 | "peerDependencies": {
28 | "gatsby": ">2.0.0-alpha"
29 | },
30 | "devDependencies": {
31 | "@babel/plugin-transform-runtime": "^7.1.0",
32 | "babel-core": "^6.4.0",
33 | "gatsby": "^2.0.0-beta.105",
34 | "react": "^16.4.1",
35 | "react-dom": "^16.4.1",
36 | "rimraf": "^2.6.2"
37 | },
38 | "publishConfig": {
39 | "access": "public"
40 | },
41 | "gitHead": "725ae5554e989002c0143a663f35877972580b20"
42 | }
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | yarn.lock
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 | *.pid.lock
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # Bower dependency directory (https://bower.io/)
28 | bower_components
29 |
30 | # node-waf configuration
31 | .lock-wscript
32 |
33 | # Compiled binary addons (https://nodejs.org/api/addons.html)
34 | build/Release
35 |
36 | # Dependency directories
37 | node_modules/
38 | jspm_packages/
39 |
40 | # TypeScript v1 declaration files
41 | typings/
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Optional REPL history
50 | .node_repl_history
51 |
52 | # Output of 'npm pack'
53 | *.tgz
54 |
55 | # Yarn Integrity file
56 | .yarn-integrity
57 |
58 | # dotenv environment variables file
59 | .env
60 |
61 | # next.js build output
62 | .next
63 |
64 | # Lib folder
65 | lib
66 |
67 | .DS_Store
68 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-i18n",
3 | "description": "Gatsby plugin that provides i18n support",
4 | "version": "1.1.8",
5 | "author": "igorko82@me.com",
6 | "repository": "ikhudo/gatsby-i18n-plugin",
7 | "keywords": [
8 | "react",
9 | "gatsby",
10 | "gatsby-plugin",
11 | "gatsby-component",
12 | "lingui",
13 | "i18n"
14 | ],
15 | "license": "MIT",
16 | "main": "lib/index.js",
17 | "scripts": {
18 | "build": "rimraf lib && babel src --out-dir lib --ignore __tests__",
19 | "build:watch": "rimraf lib && babel -w src --out-dir lib --ignore __tests__",
20 | "prepare": "yarn run build"
21 | },
22 | "dependencies": {
23 | "@babel/runtime": "^7.1.2",
24 | "@wapps/langtag-utils": "^1.0.2",
25 | "react-helmet": "^5.2.0"
26 | },
27 | "peerDependencies": {
28 | "gatsby": ">2.0.0-alpha"
29 | },
30 | "devDependencies": {
31 | "@babel/plugin-transform-runtime": "^7.1.0",
32 | "babel-core": "^6.4.0",
33 | "gatsby": "^2.0.0-beta.105",
34 | "react": "^16.4.1",
35 | "react-dom": "^16.4.1",
36 | "rimraf": "^2.6.2"
37 | },
38 | "publishConfig": {
39 | "access": "public"
40 | },
41 | "gitHead": "725ae5554e989002c0143a663f35877972580b20"
42 | }
43 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-plugin-i18next
2 | > [Gatsby](https://github.com/gatsbyjs/gatsby) plugin that provides i18n support.
3 |
4 | ## Installation
5 | ```sh
6 | yarn add @igorko/gatsby-plugin-i18next
7 | # npm install --save @igorko/gatsby-plugin-i18next
8 | ```
9 |
10 | ## Usage
11 | Edit `gatsby-config.js`
12 |
13 | ```javascript
14 | module.exports = {
15 | plugins: [
16 | {
17 | resolve: `@igorko/gatsby-plugin-i18next`,
18 | options: {
19 | // Add any options here
20 | },
21 | },
22 | ],
23 | }
24 | ```
25 |
26 | # Options
27 | You can pass options to the plugin:
28 | - availableLngs (Array [required])
29 | - fallbackLng (String [required])
30 | - siteUrl (String [optional])
31 |
32 | For example:
33 |
34 | ```js
35 | options: {
36 | availableLngs: ['en', 'de'],
37 | fallbackLng: 'en',
38 | siteUrl: 'https://www.example.com/',
39 | }
40 | ```
41 |
42 | ## Starters
43 | ### gatsby-starter-i18next
44 | [Demo](https://hupe1980.github.io/gatsby-i18n/gatsby-starter-i18next) [Source](/starters/gatsby-starter-i18next)
45 | - i18next integration
46 | - automatic browser-language detection and redirection
47 | - language switcher
48 | - hreflang
49 | - ...
50 |
51 | ## License
52 | [MIT](LICENSE)
53 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # Lib folder
64 | lib
65 |
66 | .DS_Store
67 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # Lib folder
64 | lib
65 |
66 | .DS_Store
67 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-plugin-i18next",
3 | "description": "Gatsby plugin that provides i18n support",
4 | "version": "1.1.10",
5 | "author": "igorko82@me.com",
6 | "repository": "ikhudo/gatsby-i18n-plugin",
7 | "keywords": [
8 | "react",
9 | "gatsby",
10 | "gatsby-plugin",
11 | "gatsby-component",
12 | "i18next",
13 | "i18n"
14 | ],
15 | "license": "MIT",
16 | "main": "index.js",
17 | "scripts": {
18 | "build": "babel src --out-dir . --ignore __tests__",
19 | "build:watch": "babel -w src --out-dir . --ignore __tests__",
20 | "prepare": "yarn run build"
21 | },
22 | "dependencies": {
23 | "@babel/runtime": "^7.1.2",
24 | "gatsby-i18n": "^1.1.8",
25 | "i18next": "11.6.0",
26 | "i18next-xhr-backend": "1.5.1",
27 | "react-i18next": "7.10.1"
28 | },
29 | "peerDependencies": {
30 | "gatsby": ">2.0.0-alpha"
31 | },
32 | "devDependencies": {
33 | "@babel/plugin-transform-runtime": "^7.1.0",
34 | "babel-core": "^6.4.0",
35 | "gatsby": "^2.0.0-beta.105",
36 | "react": "^16.4.1",
37 | "react-dom": "^16.4.1"
38 | },
39 | "publishConfig": {
40 | "access": "public"
41 | },
42 | "gitHead": "725ae5554e989002c0143a663f35877972580b20"
43 | }
44 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # Lib folder
64 | lib
65 |
66 | .DS_Store
67 |
68 | /gatsby-node.js
69 | /index.js
70 | /setupI18next.js
71 | /withI18next.js
72 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/plugin/onCreatePage.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | const onCreatePage = ({ page, actions }, pluginOptions) => {
4 | const { createPage, deletePage } = actions;
5 | const { fallbackLng, availableLngs, siteUrl, debug } = pluginOptions;
6 |
7 | if (page.path.includes('dev-404')) {
8 | return Promise.resolve();
9 | }
10 |
11 | return new Promise(resolve => {
12 | const redirect = path.resolve('./.cache/@igorko/redirect.js');
13 | const redirectPage = {
14 | ...page,
15 | component: redirect,
16 | context: {
17 | ...page.context,
18 | availableLngs,
19 | fallbackLng,
20 | debug,
21 | lng: null,
22 | routed: false,
23 | redirectPage: page.path,
24 | siteUrl,
25 | },
26 | };
27 |
28 | deletePage(page);
29 | createPage(redirectPage);
30 |
31 | availableLngs.forEach(lng => {
32 | const localePage = {
33 | ...page,
34 | path: `/${lng}${page.path}`,
35 | context: {
36 | ...page.context,
37 | availableLngs,
38 | fallbackLng,
39 | lng,
40 | routed: true,
41 | originalPath: page.path,
42 | siteUrl,
43 | debug,
44 | },
45 | };
46 |
47 | createPage(localePage);
48 | });
49 |
50 | resolve();
51 | });
52 | };
53 |
54 | export default onCreatePage;
55 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # Lib folder
64 | lib
65 |
66 | .DS_Store
67 |
68 | /gatsby-node.js
69 | /index.js
70 | /plugin/*
71 | /Head.js
72 | /I18nContext.js
73 | /Language.js
74 | /Link.js
75 | /Redirect.js
76 | /utils.js
77 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/plugin/onCreatePage.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import slash from 'slash';
3 |
4 | const onCreatePage = ({ page, actions }, pluginOptions) => {
5 | const { createPage, deletePage } = actions;
6 | const { fallbackLng, availableLngs, siteUrl, debug } = pluginOptions;
7 |
8 | if (page.path.includes('dev-404')) {
9 | return Promise.resolve();
10 | }
11 |
12 | return new Promise(resolve => {
13 | const redirect = path.resolve('./.cache/@igorko/redirect.js');
14 | const redirectPage = {
15 | ...page,
16 | component: slash(redirect),
17 | context: {
18 | ...page.context,
19 | availableLngs,
20 | fallbackLng,
21 | debug,
22 | lng: null,
23 | routed: false,
24 | redirectPage: page.path,
25 | siteUrl,
26 | },
27 | };
28 |
29 | deletePage(page);
30 | createPage(redirectPage);
31 |
32 | availableLngs.forEach(lng => {
33 | const localePage = {
34 | ...page,
35 | path: `/${lng}${page.path}`,
36 | context: {
37 | ...page.context,
38 | availableLngs,
39 | fallbackLng,
40 | lng,
41 | routed: true,
42 | originalPath: page.path,
43 | siteUrl,
44 | debug,
45 | },
46 | };
47 |
48 | createPage(localePage);
49 | });
50 |
51 | resolve();
52 | });
53 | };
54 |
55 | export default onCreatePage;
56 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-i18next/src/withI18next.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { I18nextProvider } from 'react-i18next';
3 | import { I18nProvider } from 'gatsby-i18n';
4 |
5 | import setupI18next from './setupI18next';
6 |
7 | const withI18next = (options = {}) => Comp => {
8 | class I18n extends Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | const { pageContext } = props;
13 |
14 | this.i18n = setupI18next(pageContext, this.activateLng);
15 |
16 | //console.log('@@@ this.i18n', this.i18n);
17 |
18 | //this.activateLng();
19 | }
20 |
21 | activateLng = () => {
22 | const { data, pageContext } = this.props;
23 |
24 | if (data && data.locales) {
25 | data.locales.edges.forEach(({ node }) => {
26 | const { lng, ns = 'messages', data } = node;
27 | const parsedData = JSON.parse(data);
28 |
29 | if (!this.i18n.hasResourceBundle(lng, 'messages')) {
30 | this.i18n.addResources(lng, 'messages', parsedData);
31 | }
32 | });
33 | }
34 |
35 | this.i18n.changeLanguage(pageContext.lng);
36 | };
37 |
38 | componentDidUpdate(prevProps) {
39 | if (this.props.pageContext.lng !== prevProps.pageContext.lng) {
40 | this.activateLng();
41 | }
42 | }
43 |
44 | render() {
45 | return (
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | return I18n;
56 | };
57 |
58 | export default withI18next;
59 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n/src/plugin/onCreateNode.js:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | const onCreateNode = async ({
4 | node,
5 | actions,
6 | loadNodeContent,
7 | getNode,
8 | reporter,
9 | }) => {
10 | const {
11 | absolutePath,
12 | internal: { mediaType, type },
13 | sourceInstanceName,
14 | relativeDirectory,
15 | name,
16 | id,
17 | } = node;
18 |
19 | const { createNode, createParentChildLink } = actions;
20 |
21 | if (type !== 'File' || sourceInstanceName !== `locale`) return;
22 |
23 |
24 | //Ignore _build folder
25 | if (/^_build/.test(relativeDirectory)) return;
26 | //Ignore .DS_Store
27 | if (/^.DS/.test(relativeDirectory)) return;
28 |
29 | // process.env.NODE_ENV === `production`;
30 | // if (mediaType === `application/json`) return;
31 |
32 | const activity = reporter.activityTimer(
33 | `@igorko: create node: ${relativeDirectory}_${name}`,
34 | );
35 | activity.start();
36 |
37 | const content = await loadNodeContent(node);
38 |
39 | // const compiledContent = requireFromString(content);
40 | // const data = JSON.stringify({ ...compiledContent }, function(key, value) {
41 | // if (typeof value === 'function') {
42 | // return '/Function(' + value.toString() + ')/';
43 | // }
44 | // return value;
45 | // });
46 | const data = JSON.stringify(JSON.parse(content), undefined, '');
47 |
48 | const contentDigest = crypto
49 | .createHash(`md5`)
50 | .update(data)
51 | .digest(`hex`);
52 |
53 | const localeNode = {
54 | id: `${id} >>> Locale`,
55 | children: [],
56 | parent: id,
57 | internal: { content, contentDigest, type: `Locale` },
58 | lng: relativeDirectory,
59 | ns: name,
60 | data,
61 | fileAbsolutePath: absolutePath,
62 | };
63 |
64 | createNode(localeNode);
65 |
66 | createParentChildLink({ parent: node, child: localeNode });
67 |
68 | activity.end();
69 | };
70 |
71 | export default onCreateNode;
72 |
--------------------------------------------------------------------------------
/packages/gatsby-i18n-plugin/src/plugin/onCreateNode.js:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | const onCreateNode = async ({
4 | node,
5 | actions,
6 | loadNodeContent,
7 | getNode,
8 | reporter,
9 | }) => {
10 | const {
11 | absolutePath,
12 | internal: { mediaType, type },
13 | sourceInstanceName,
14 | relativeDirectory,
15 | name,
16 | id,
17 | } = node;
18 |
19 | const { createNode, createParentChildLink } = actions;
20 |
21 | if (type !== 'File' || sourceInstanceName !== `locale`) return;
22 |
23 |
24 | //Ignore _build folder
25 | if (/^_build/.test(relativeDirectory)) return;
26 | //Ignore .DS_Store
27 | if (/^.DS/.test(relativeDirectory)) return;
28 |
29 | // process.env.NODE_ENV === `production`;
30 | // if (mediaType === `application/json`) return;
31 |
32 | const activity = reporter.activityTimer(
33 | `@igorko: create node: ${relativeDirectory}_${name}`,
34 | );
35 | activity.start();
36 |
37 | const content = await loadNodeContent(node);
38 |
39 | // const compiledContent = requireFromString(content);
40 | // const data = JSON.stringify({ ...compiledContent }, function(key, value) {
41 | // if (typeof value === 'function') {
42 | // return '/Function(' + value.toString() + ')/';
43 | // }
44 | // return value;
45 | // });
46 | const data = JSON.stringify(JSON.parse(content), undefined, '');
47 |
48 | const contentDigest = crypto
49 | .createHash(`md5`)
50 | .update(data)
51 | .digest(`hex`);
52 |
53 | const localeNode = {
54 | id: `${id} >>> Locale`,
55 | children: [],
56 | parent: id,
57 | internal: { content, contentDigest, type: `Locale` },
58 | lng: relativeDirectory,
59 | ns: name,
60 | data,
61 | fileAbsolutePath: absolutePath,
62 | };
63 |
64 | createNode(localeNode);
65 |
66 | createParentChildLink({ parent: node, child: localeNode });
67 |
68 | activity.end();
69 | };
70 |
71 | export default onCreateNode;
72 |
--------------------------------------------------------------------------------
/starters/gatsby-starter-i18next/src/components/layout.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: sans-serif;
3 | -ms-text-size-adjust: 100%;
4 | -webkit-text-size-adjust: 100%;
5 | }
6 | body {
7 | margin: 0;
8 | }
9 | article,
10 | aside,
11 | details,
12 | figcaption,
13 | figure,
14 | footer,
15 | header,
16 | main,
17 | menu,
18 | nav,
19 | section,
20 | summary {
21 | display: block;
22 | }
23 | audio,
24 | canvas,
25 | progress,
26 | video {
27 | display: inline-block;
28 | }
29 | audio:not([controls]) {
30 | display: none;
31 | height: 0;
32 | }
33 | progress {
34 | vertical-align: baseline;
35 | }
36 | [hidden],
37 | template {
38 | display: none;
39 | }
40 | a {
41 | background-color: transparent;
42 | -webkit-text-decoration-skip: objects;
43 | }
44 | a:active,
45 | a:hover {
46 | outline-width: 0;
47 | }
48 | abbr[title] {
49 | border-bottom: none;
50 | text-decoration: underline;
51 | text-decoration: underline dotted;
52 | }
53 | b,
54 | strong {
55 | font-weight: inherit;
56 | font-weight: bolder;
57 | }
58 | dfn {
59 | font-style: italic;
60 | }
61 | h1 {
62 | font-size: 2em;
63 | margin: .67em 0;
64 | }
65 | mark {
66 | background-color: #ff0;
67 | color: #000;
68 | }
69 | small {
70 | font-size: 80%;
71 | }
72 | sub,
73 | sup {
74 | font-size: 75%;
75 | line-height: 0;
76 | position: relative;
77 | vertical-align: baseline;
78 | }
79 | sub {
80 | bottom: -.25em;
81 | }
82 | sup {
83 | top: -.5em;
84 | }
85 | img {
86 | border-style: none;
87 | }
88 | svg:not(:root) {
89 | overflow: hidden;
90 | }
91 | code,
92 | kbd,
93 | pre,
94 | samp {
95 | font-family: monospace, monospace;
96 | font-size: 1em;
97 | }
98 | figure {
99 | margin: 1em 40px;
100 | }
101 | hr {
102 | box-sizing: content-box;
103 | height: 0;
104 | overflow: visible;
105 | }
106 | button,
107 | input,
108 | optgroup,
109 | select,
110 | textarea {
111 | font: inherit;
112 | margin: 0;
113 | }
114 | optgroup {
115 | font-weight: 700;
116 | }
117 | button,
118 | input {
119 | overflow: visible;
120 | }
121 | button,
122 | select {
123 | text-transform: none;
124 | }
125 | [type=reset],
126 | [type=submit],
127 | button,
128 | html [type=button] {
129 | -webkit-appearance: button;
130 | }
131 | [type=button]::-moz-focus-inner,
132 | [type=reset]::-moz-focus-inner,
133 | [type=submit]::-moz-focus-inner,
134 | button::-moz-focus-inner {
135 | border-style: none;
136 | padding: 0;
137 | }
138 | [type=button]:-moz-focusring,
139 | [type=reset]:-moz-focusring,
140 | [type=submit]:-moz-focusring,
141 | button:-moz-focusring {
142 | outline: 1px dotted ButtonText;
143 | }
144 | fieldset {
145 | border: 1px solid silver;
146 | margin: 0 2px;
147 | padding: .35em .625em .75em;
148 | }
149 | legend {
150 | box-sizing: border-box;
151 | color: inherit;
152 | display: table;
153 | max-width: 100%;
154 | padding: 0;
155 | white-space: normal;
156 | }
157 | textarea {
158 | overflow: auto;
159 | }
160 | [type=checkbox],
161 | [type=radio] {
162 | box-sizing: border-box;
163 | padding: 0;
164 | }
165 | [type=number]::-webkit-inner-spin-button,
166 | [type=number]::-webkit-outer-spin-button {
167 | height: auto;
168 | }
169 | [type=search] {
170 | -webkit-appearance: textfield;
171 | outline-offset: -2px;
172 | }
173 | [type=search]::-webkit-search-cancel-button,
174 | [type=search]::-webkit-search-decoration {
175 | -webkit-appearance: none;
176 | }
177 | ::-webkit-input-placeholder {
178 | color: inherit;
179 | opacity: .54;
180 | }
181 | ::-webkit-file-upload-button {
182 | -webkit-appearance: button;
183 | font: inherit;
184 | }
185 | html {
186 | font: 112.5%/1.45em georgia, serif;
187 | box-sizing: border-box;
188 | overflow-y: scroll;
189 | }
190 | * {
191 | box-sizing: inherit;
192 | }
193 | *:before {
194 | box-sizing: inherit;
195 | }
196 | *:after {
197 | box-sizing: inherit;
198 | }
199 | body {
200 | color: hsla(0, 0%, 0%, 0.8);
201 | font-family: georgia, serif;
202 | font-weight: normal;
203 | word-wrap: break-word;
204 | font-kerning: normal;
205 | -moz-font-feature-settings: "kern", "liga", "clig", "calt";
206 | -ms-font-feature-settings: "kern", "liga", "clig", "calt";
207 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt";
208 | font-feature-settings: "kern", "liga", "clig", "calt";
209 | }
210 | img {
211 | max-width: 100%;
212 | margin-left: 0;
213 | margin-right: 0;
214 | margin-top: 0;
215 | padding-bottom: 0;
216 | padding-left: 0;
217 | padding-right: 0;
218 | padding-top: 0;
219 | margin-bottom: 1.45rem;
220 | }
221 | h1 {
222 | margin-left: 0;
223 | margin-right: 0;
224 | margin-top: 0;
225 | padding-bottom: 0;
226 | padding-left: 0;
227 | padding-right: 0;
228 | padding-top: 0;
229 | margin-bottom: 1.45rem;
230 | color: inherit;
231 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
232 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
233 | font-weight: bold;
234 | text-rendering: optimizeLegibility;
235 | font-size: 2.25rem;
236 | line-height: 1.1;
237 | }
238 | h2 {
239 | margin-left: 0;
240 | margin-right: 0;
241 | margin-top: 0;
242 | padding-bottom: 0;
243 | padding-left: 0;
244 | padding-right: 0;
245 | padding-top: 0;
246 | margin-bottom: 1.45rem;
247 | color: inherit;
248 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
249 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
250 | font-weight: bold;
251 | text-rendering: optimizeLegibility;
252 | font-size: 1.62671rem;
253 | line-height: 1.1;
254 | }
255 | h3 {
256 | margin-left: 0;
257 | margin-right: 0;
258 | margin-top: 0;
259 | padding-bottom: 0;
260 | padding-left: 0;
261 | padding-right: 0;
262 | padding-top: 0;
263 | margin-bottom: 1.45rem;
264 | color: inherit;
265 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
266 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
267 | font-weight: bold;
268 | text-rendering: optimizeLegibility;
269 | font-size: 1.38316rem;
270 | line-height: 1.1;
271 | }
272 | h4 {
273 | margin-left: 0;
274 | margin-right: 0;
275 | margin-top: 0;
276 | padding-bottom: 0;
277 | padding-left: 0;
278 | padding-right: 0;
279 | padding-top: 0;
280 | margin-bottom: 1.45rem;
281 | color: inherit;
282 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
283 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
284 | font-weight: bold;
285 | text-rendering: optimizeLegibility;
286 | font-size: 1rem;
287 | line-height: 1.1;
288 | }
289 | h5 {
290 | margin-left: 0;
291 | margin-right: 0;
292 | margin-top: 0;
293 | padding-bottom: 0;
294 | padding-left: 0;
295 | padding-right: 0;
296 | padding-top: 0;
297 | margin-bottom: 1.45rem;
298 | color: inherit;
299 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
300 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
301 | font-weight: bold;
302 | text-rendering: optimizeLegibility;
303 | font-size: 0.85028rem;
304 | line-height: 1.1;
305 | }
306 | h6 {
307 | margin-left: 0;
308 | margin-right: 0;
309 | margin-top: 0;
310 | padding-bottom: 0;
311 | padding-left: 0;
312 | padding-right: 0;
313 | padding-top: 0;
314 | margin-bottom: 1.45rem;
315 | color: inherit;
316 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
317 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
318 | font-weight: bold;
319 | text-rendering: optimizeLegibility;
320 | font-size: 0.78405rem;
321 | line-height: 1.1;
322 | }
323 | hgroup {
324 | margin-left: 0;
325 | margin-right: 0;
326 | margin-top: 0;
327 | padding-bottom: 0;
328 | padding-left: 0;
329 | padding-right: 0;
330 | padding-top: 0;
331 | margin-bottom: 1.45rem;
332 | }
333 | ul {
334 | margin-left: 1.45rem;
335 | margin-right: 0;
336 | margin-top: 0;
337 | padding-bottom: 0;
338 | padding-left: 0;
339 | padding-right: 0;
340 | padding-top: 0;
341 | margin-bottom: 1.45rem;
342 | list-style-position: outside;
343 | list-style-image: none;
344 | }
345 | ol {
346 | margin-left: 1.45rem;
347 | margin-right: 0;
348 | margin-top: 0;
349 | padding-bottom: 0;
350 | padding-left: 0;
351 | padding-right: 0;
352 | padding-top: 0;
353 | margin-bottom: 1.45rem;
354 | list-style-position: outside;
355 | list-style-image: none;
356 | }
357 | dl {
358 | margin-left: 0;
359 | margin-right: 0;
360 | margin-top: 0;
361 | padding-bottom: 0;
362 | padding-left: 0;
363 | padding-right: 0;
364 | padding-top: 0;
365 | margin-bottom: 1.45rem;
366 | }
367 | dd {
368 | margin-left: 0;
369 | margin-right: 0;
370 | margin-top: 0;
371 | padding-bottom: 0;
372 | padding-left: 0;
373 | padding-right: 0;
374 | padding-top: 0;
375 | margin-bottom: 1.45rem;
376 | }
377 | p {
378 | margin-left: 0;
379 | margin-right: 0;
380 | margin-top: 0;
381 | padding-bottom: 0;
382 | padding-left: 0;
383 | padding-right: 0;
384 | padding-top: 0;
385 | margin-bottom: 1.45rem;
386 | }
387 | figure {
388 | margin-left: 0;
389 | margin-right: 0;
390 | margin-top: 0;
391 | padding-bottom: 0;
392 | padding-left: 0;
393 | padding-right: 0;
394 | padding-top: 0;
395 | margin-bottom: 1.45rem;
396 | }
397 | pre {
398 | margin-left: 0;
399 | margin-right: 0;
400 | margin-top: 0;
401 | padding-bottom: 0;
402 | padding-left: 0;
403 | padding-right: 0;
404 | padding-top: 0;
405 | margin-bottom: 1.45rem;
406 | font-size: 0.85rem;
407 | line-height: 1.42;
408 | background: hsla(0, 0%, 0%, 0.04);
409 | border-radius: 3px;
410 | overflow: auto;
411 | word-wrap: normal;
412 | padding: 1.45rem;
413 | }
414 | table {
415 | margin-left: 0;
416 | margin-right: 0;
417 | margin-top: 0;
418 | padding-bottom: 0;
419 | padding-left: 0;
420 | padding-right: 0;
421 | padding-top: 0;
422 | margin-bottom: 1.45rem;
423 | font-size: 1rem;
424 | line-height: 1.45rem;
425 | border-collapse: collapse;
426 | width: 100%;
427 | }
428 | fieldset {
429 | margin-left: 0;
430 | margin-right: 0;
431 | margin-top: 0;
432 | padding-bottom: 0;
433 | padding-left: 0;
434 | padding-right: 0;
435 | padding-top: 0;
436 | margin-bottom: 1.45rem;
437 | }
438 | blockquote {
439 | margin-left: 1.45rem;
440 | margin-right: 1.45rem;
441 | margin-top: 0;
442 | padding-bottom: 0;
443 | padding-left: 0;
444 | padding-right: 0;
445 | padding-top: 0;
446 | margin-bottom: 1.45rem;
447 | }
448 | form {
449 | margin-left: 0;
450 | margin-right: 0;
451 | margin-top: 0;
452 | padding-bottom: 0;
453 | padding-left: 0;
454 | padding-right: 0;
455 | padding-top: 0;
456 | margin-bottom: 1.45rem;
457 | }
458 | noscript {
459 | margin-left: 0;
460 | margin-right: 0;
461 | margin-top: 0;
462 | padding-bottom: 0;
463 | padding-left: 0;
464 | padding-right: 0;
465 | padding-top: 0;
466 | margin-bottom: 1.45rem;
467 | }
468 | iframe {
469 | margin-left: 0;
470 | margin-right: 0;
471 | margin-top: 0;
472 | padding-bottom: 0;
473 | padding-left: 0;
474 | padding-right: 0;
475 | padding-top: 0;
476 | margin-bottom: 1.45rem;
477 | }
478 | hr {
479 | margin-left: 0;
480 | margin-right: 0;
481 | margin-top: 0;
482 | padding-bottom: 0;
483 | padding-left: 0;
484 | padding-right: 0;
485 | padding-top: 0;
486 | margin-bottom: calc(1.45rem - 1px);
487 | background: hsla(0, 0%, 0%, 0.2);
488 | border: none;
489 | height: 1px;
490 | }
491 | address {
492 | margin-left: 0;
493 | margin-right: 0;
494 | margin-top: 0;
495 | padding-bottom: 0;
496 | padding-left: 0;
497 | padding-right: 0;
498 | padding-top: 0;
499 | margin-bottom: 1.45rem;
500 | }
501 | b {
502 | font-weight: bold;
503 | }
504 | strong {
505 | font-weight: bold;
506 | }
507 | dt {
508 | font-weight: bold;
509 | }
510 | th {
511 | font-weight: bold;
512 | }
513 | li {
514 | margin-bottom: calc(1.45rem / 2);
515 | }
516 | ol li {
517 | padding-left: 0;
518 | }
519 | ul li {
520 | padding-left: 0;
521 | }
522 | li > ol {
523 | margin-left: 1.45rem;
524 | margin-bottom: calc(1.45rem / 2);
525 | margin-top: calc(1.45rem / 2);
526 | }
527 | li > ul {
528 | margin-left: 1.45rem;
529 | margin-bottom: calc(1.45rem / 2);
530 | margin-top: calc(1.45rem / 2);
531 | }
532 | blockquote *:last-child {
533 | margin-bottom: 0;
534 | }
535 | li *:last-child {
536 | margin-bottom: 0;
537 | }
538 | p *:last-child {
539 | margin-bottom: 0;
540 | }
541 | li > p {
542 | margin-bottom: calc(1.45rem / 2);
543 | }
544 | code {
545 | font-size: 0.85rem;
546 | line-height: 1.45rem;
547 | }
548 | kbd {
549 | font-size: 0.85rem;
550 | line-height: 1.45rem;
551 | }
552 | samp {
553 | font-size: 0.85rem;
554 | line-height: 1.45rem;
555 | }
556 | abbr {
557 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
558 | cursor: help;
559 | }
560 | acronym {
561 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
562 | cursor: help;
563 | }
564 | abbr[title] {
565 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
566 | cursor: help;
567 | text-decoration: none;
568 | }
569 | thead {
570 | text-align: left;
571 | }
572 | td,
573 | th {
574 | text-align: left;
575 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12);
576 | font-feature-settings: "tnum";
577 | -moz-font-feature-settings: "tnum";
578 | -ms-font-feature-settings: "tnum";
579 | -webkit-font-feature-settings: "tnum";
580 | padding-left: 0.96667rem;
581 | padding-right: 0.96667rem;
582 | padding-top: 0.725rem;
583 | padding-bottom: calc(0.725rem - 1px);
584 | }
585 | th:first-child,
586 | td:first-child {
587 | padding-left: 0;
588 | }
589 | th:last-child,
590 | td:last-child {
591 | padding-right: 0;
592 | }
593 | tt,
594 | code {
595 | background-color: hsla(0, 0%, 0%, 0.04);
596 | border-radius: 3px;
597 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono",
598 | "Liberation Mono", Menlo, Courier, monospace;
599 | padding: 0;
600 | padding-top: 0.2em;
601 | padding-bottom: 0.2em;
602 | }
603 | pre code {
604 | background: none;
605 | line-height: 1.42;
606 | }
607 | code:before,
608 | code:after,
609 | tt:before,
610 | tt:after {
611 | letter-spacing: -0.2em;
612 | content: " ";
613 | }
614 | pre code:before,
615 | pre code:after,
616 | pre tt:before,
617 | pre tt:after {
618 | content: "";
619 | }
620 | @media only screen and (max-width: 480px) {
621 | html {
622 | font-size: 100%;
623 | }
624 | }
625 |
--------------------------------------------------------------------------------