├── .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 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](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 | 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 | --------------------------------------------------------------------------------