├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── gatsby-config.js ├── gatsby-node.js ├── netlify.toml ├── package.json ├── renovate.json ├── src ├── cms │ └── cms.js ├── components │ ├── layouts │ │ └── main │ │ │ ├── Layout.tsx │ │ │ └── layout.scss │ └── util │ │ ├── image │ │ └── Image.tsx │ │ └── seo │ │ └── Seo.tsx ├── constants │ ├── i18n.ts │ └── locales.js ├── img │ ├── gatsby.png │ └── instagram.svg ├── pages │ ├── 404.tsx │ ├── blog │ │ ├── de │ │ │ └── 2018-12-07-hello-world.md │ │ └── en │ │ │ ├── 2019-12-07-hello-world.md │ │ │ └── 2020-11-03-sad.md │ ├── home │ │ ├── index.de.md │ │ └── index.md │ └── index.tsx └── templates │ └── blog-post.tsx ├── static ├── admin │ └── config.yml └── img │ └── instagram.svg ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint','eslint-plugin-prettier'], 8 | env: { 9 | browser: true, 10 | node: true, 11 | }, 12 | 'rules': { 13 | 'react/react-in-jsx-scope': 0, 14 | 'ordered-imports': 0, 15 | 'quotemark': 0, 16 | 'no-console': 0, 17 | 'semicolon': 0, 18 | 'jsx-no-lambda': 0, 19 | }, 20 | extends: [ 21 | 'plugin:react/recommended', 22 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 23 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 24 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 25 | ], 26 | }; -------------------------------------------------------------------------------- /.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 (http://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 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 2, 7 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gatsby + Netlify CMS Starter 2 | 3 | **Note:** This starter uses the [Gatsby v2 Beta](https://www.gatsbyjs.org/blog/2018-06-16-announcing-gatsby-v2-beta-launch/). 4 | 5 | **Note:** This starter is from https://github.com/netlify-templates/gatsby-starter-netlify-cms with some small changes 6 | 7 | This repo is a lightweight, minimal and clean gatsby netlify cms starter with just one component for multi language sites. 8 | It just has a Layout component with React-Helmet in it. 9 | Netlify CMS is configured for a blog and the index page. 10 | 11 | This repo contains an example business website that is built with [Gatsby](https://www.gatsbyjs.org/), and [Netlify CMS](https://www.netlifycms.org): **[Demo Link](https://minimal-multi-language-gatsby-starter-netlify-cms.netlify.com/)**. 12 | 13 | It follows the [JAMstack architecture](https://jamstack.org) by using Git as a single source of truth, and [Netlify](https://www.netlify.com) for continuous deployment, and CDN distribution. 14 | 15 | ## Prerequisites 16 | 17 | - Node (I recommend using v8.2.0 or higher) 18 | - [Gatsby CLI](https://www.gatsbyjs.org/docs/) 19 | 20 | ## Getting Started (Recommended) 21 | 22 | Netlify CMS can run in any frontend web environment, but the quickest way to try it out is by running it on a pre-configured starter site with Netlify. The example here is the Kaldi coffee company template (adapted from [One Click Hugo CMS](https://github.com/netlify-templates/one-click-hugo-cms)). Use the button below to build and deploy your own copy of the repository: 23 | 24 | Deploy to Netlify 25 | 26 | After clicking that button, you’ll authenticate with GitHub and choose a repository name. Netlify will then automatically create a repository in your GitHub account with a copy of the files from the template. Next, it will build and deploy the new site on Netlify, bringing you to the site dashboard when the build is complete. Next, you’ll need to set up Netlify’s Identity service to authorize users to log in to the CMS. 27 | 28 | ### Access Locally 29 | ``` 30 | $ git clone https://github.com/karimould/minimal-multi-language-gatsby-starter-netlify-cms 31 | $ cd [REPO_NAME] 32 | $ yarn 33 | $ npm run develop 34 | ``` 35 | To test the CMS locally, you'll need run a production build of the site: 36 | ``` 37 | $ npm run build 38 | $ npm run serve 39 | ``` 40 | 41 | ## Getting Started (Without Netlify) 42 | ``` 43 | $ gatsby new [SITE_DIRECTORY_NAME] https://github.com/karimould/minimal-multi-language-gatsby-starter-netlify-cms 44 | $ cd [SITE_DIRECTORY_NAME] 45 | $ npm run build 46 | $ npm run serve 47 | ``` 48 | 49 | ### Setting up the CMS 50 | Follow the [Netlify CMS Quick Start Guide](https://www.netlifycms.org/docs/quick-start/#authentication) to set up authentication, and hosting. 51 | 52 | ## Debugging 53 | Windows users might encounter ```node-gyp``` errors when trying to npm install. 54 | To resolve, make sure that you have both Python 2.7 and the Visual C++ build environment installed. 55 | ``` 56 | npm config set python python2.7 57 | npm install --global --production windows-build-tools 58 | ``` 59 | 60 | [Full details here](https://www.npmjs.com/package/node-gyp 'NPM node-gyp page') 61 | 62 | ## Purgecss 63 | This plugin uses [gatsby-plugin-purgecss](https://www.gatsbyjs.org/packages/gatsby-plugin-purgecss/) and [bulma](https://bulma.io/). The bulma builds are usually ~170K but reduced 90% by purgecss. 64 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: 'Gatsby + Netlify CMS Starter Clean', 4 | description: 'This repo contains an example website that is built with Gatsby, and Netlify CMS.It follows the JAMstack architecture by using Git as a single source of truth, and Netlify for continuous deployment, and CDN distribution.', 5 | }, 6 | plugins: [ 7 | 'gatsby-plugin-react-helmet', 8 | 'gatsby-plugin-sass', 9 | `gatsby-plugin-typescript`, 10 | { 11 | // keep as first gatsby-source-filesystem plugin for gatsby image support 12 | resolve: 'gatsby-source-filesystem', 13 | options: { 14 | path: `${__dirname}/static/img`, 15 | name: 'uploads', 16 | }, 17 | }, 18 | { 19 | resolve: 'gatsby-source-filesystem', 20 | options: { 21 | path: `${__dirname}/src/pages`, 22 | name: 'pages', 23 | }, 24 | }, 25 | { 26 | resolve: 'gatsby-source-filesystem', 27 | options: { 28 | path: `${__dirname}/src/img`, 29 | name: 'images', 30 | }, 31 | }, 32 | 'gatsby-plugin-sharp', 33 | 'gatsby-transformer-sharp', 34 | { 35 | resolve: 'gatsby-transformer-remark', 36 | options: { 37 | plugins: [ 38 | { 39 | resolve: 'gatsby-remark-relative-images', 40 | options: { 41 | name: 'uploads', 42 | }, 43 | }, 44 | { 45 | resolve: 'gatsby-remark-images', 46 | options: { 47 | // It's important to specify the maxWidth (in pixels) of 48 | // the content container as this plugin uses this as the 49 | // base for generating different widths of each image. 50 | maxWidth: 2048, 51 | }, 52 | }, 53 | { 54 | resolve: 'gatsby-remark-copy-linked-files', 55 | options: { 56 | destinationDir: 'static', 57 | } 58 | } 59 | ], 60 | }, 61 | }, 62 | { 63 | resolve: 'gatsby-plugin-netlify-cms', 64 | options: { 65 | modulePath: `${__dirname}/src/cms/cms.js`, 66 | }, 67 | }, 68 | 'gatsby-plugin-purgecss', // must be after other CSS plugins 69 | 'gatsby-plugin-netlify', // make sure to keep it last in the array 70 | ], 71 | } 72 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const path = require('path') 3 | const { createFilePath } = require('gatsby-source-filesystem') 4 | const { fmImagesToRelative } = require('gatsby-remark-relative-images') 5 | const locales = require('./src/constants/locales') 6 | 7 | exports.createPages = ({ actions, graphql }) => { 8 | const { createPage } = actions 9 | 10 | return graphql(` 11 | { 12 | allMarkdownRemark(limit: 1000) { 13 | edges { 14 | node { 15 | id 16 | fields { 17 | slug 18 | } 19 | frontmatter { 20 | templateKey 21 | locale 22 | } 23 | } 24 | } 25 | } 26 | } 27 | `).then(result => { 28 | if (result.errors) { 29 | result.errors.forEach(e => console.error(e.toString())) 30 | return Promise.reject(result.errors) 31 | } 32 | const posts = result.data.allMarkdownRemark.edges 33 | 34 | posts.forEach(edge => { 35 | //Check if pages has a template key 36 | const locale = edge.node.frontmatter.locale 37 | if (edge.node.frontmatter.templateKey != null) { 38 | const id = edge.node.id 39 | createPage({ 40 | path: edge.node.fields.slug, 41 | component: path.resolve(`src/templates/${String(edge.node.frontmatter.templateKey)}.tsx`), 42 | // additional data can be passed via context 43 | context: { 44 | id, 45 | locale, 46 | }, 47 | }) 48 | } 49 | }) 50 | }) 51 | } 52 | 53 | exports.onCreatePage = ({ page, actions }) => { 54 | const { createPage, deletePage } = actions 55 | 56 | return new Promise(resolve => { 57 | deletePage(page) 58 | 59 | Object.keys(locales).map(lang => { 60 | const localizedPath = locales[lang].default ? page.path : locales[lang].path + page.path 61 | 62 | return createPage({ 63 | ...page, 64 | path: localizedPath, 65 | context: { 66 | locale: lang, 67 | }, 68 | }) 69 | }) 70 | resolve() 71 | }) 72 | } 73 | 74 | exports.onCreateNode = ({ node, actions, getNode }) => { 75 | const { createNodeField } = actions 76 | fmImagesToRelative(node) // convert image paths for gatsby images 77 | 78 | if (node.internal.type === `MarkdownRemark`) { 79 | const value = createFilePath({ node, getNode }) 80 | createNodeField({ 81 | name: `slug`, 82 | node, 83 | value, 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | [build.environment] 5 | YARN_VERSION = "1.9.4" 6 | YARN_FLAGS = "--no-ignore-optional" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimal-multi-language-gatsby-starter-netlify-cms", 3 | "description": "Example Gatsby, and Netlify CMS project for multi languages websites", 4 | "version": "1.2.0", 5 | "author": "Karim Ould Mahieddine", 6 | "dependencies": { 7 | "@types/react-helmet": "^6.0.0", 8 | "gatsby": "^2.15.29", 9 | "gatsby-image": "^2.2.24", 10 | "gatsby-plugin-netlify": "^2.1.17", 11 | "gatsby-plugin-netlify-cms": "^4.1.22", 12 | "gatsby-plugin-purgecss": "^5.0.0", 13 | "gatsby-plugin-react-helmet": "^3.1.10", 14 | "gatsby-plugin-sass": "^2.1.17", 15 | "gatsby-plugin-sharp": "^2.2.28", 16 | "gatsby-plugin-typescript": "^2.1.11", 17 | "gatsby-remark-copy-linked-files": "^2.1.24", 18 | "gatsby-remark-images": "^3.1.25", 19 | "gatsby-remark-relative-images": "^0.3.0", 20 | "gatsby-source-filesystem": "^2.1.29", 21 | "gatsby-transformer-remark": "^2.6.27", 22 | "gatsby-transformer-sharp": "^2.2.20", 23 | "netlify-cms-app": "^2.9.7", 24 | "node-sass": "^4.12.0", 25 | "react": "^16.10.2", 26 | "react-dom": "^16.10.2", 27 | "react-helmet": "^6.1.0", 28 | "typescript": "^3.6.3" 29 | }, 30 | "keywords": [ 31 | "gatsby" 32 | ], 33 | "license": "MIT", 34 | "main": "n/a", 35 | "scripts": { 36 | "start": "npm run develop", 37 | "clean": "rimraf .cache public", 38 | "build": "npm run clean && gatsby build", 39 | "develop": "npm run clean && gatsby develop", 40 | "serve": "gatsby serve", 41 | "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"", 42 | "test": "echo \"Error: no test specified\" && exit 1" 43 | }, 44 | "devDependencies": { 45 | "@typescript-eslint/eslint-plugin": "^3.2.0", 46 | "@typescript-eslint/parser": "^3.2.0", 47 | "eslint": "^7.2.0", 48 | "eslint-config-prettier": "^6.4.0", 49 | "eslint-plugin-prettier": "^3.1.1", 50 | "eslint-plugin-react": "^7.16.0", 51 | "prettier": "^2.0.5", 52 | "rimraf": "^3.0.0", 53 | "typescript": "^3.6.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "rangeStrategy": "replace", 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "extends": "schedule:weekly" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/cms/cms.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karimould/minimal-multi-language-gatsby-starter-netlify-cms/1d27097149ebb0f62193e40dd98586b05839d742/src/cms/cms.js -------------------------------------------------------------------------------- /src/components/layouts/main/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import './layout.scss' 3 | 4 | interface Props { 5 | children: ReactNode 6 | locale: string 7 | } 8 | 9 | const Layout = ({ children, locale }: Props): JSX.Element => { 10 | return ( 11 | <> 12 |
{children}
13 | 14 | ) 15 | } 16 | 17 | export default Layout 18 | -------------------------------------------------------------------------------- /src/components/layouts/main/layout.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0 0 0 0; 3 | padding: 0 0 0 0; 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 5 | } -------------------------------------------------------------------------------- /src/components/util/image/Image.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 2 | import React from 'react' 3 | import { StaticQuery, graphql } from 'gatsby' 4 | import Img from 'gatsby-image' 5 | 6 | /* 7 | * - `gatsby-image`: https://gatsby.dev/gatsby-image 8 | * - `StaticQuery`: https://gatsby.dev/staticquery 9 | */ 10 | 11 | interface Props { 12 | imageName: string 13 | alt?: string 14 | title?: string 15 | maxWidth?: number 16 | className?: string 17 | } 18 | 19 | const Image = ({ imageName, maxWidth = 500, className = '', alt, title }: Props) => ( 20 | { 36 | const image = data.allImageSharp.edges.find( 37 | (edge: { node: { fluid: { originalName: string } } }) => edge.node.fluid.originalName === imageName, 38 | ) 39 | if (!image) { 40 | return null 41 | } 42 | return ( 43 |
44 | {alt} 45 |
46 | ) 47 | }} 48 | /> 49 | ) 50 | export default Image 51 | -------------------------------------------------------------------------------- /src/components/util/seo/Seo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Helmet from 'react-helmet' 3 | 4 | interface Props { 5 | metaDescription: string 6 | lang?: string 7 | title: string 8 | author?: string 9 | } 10 | 11 | const SEO = ({ metaDescription, lang = 'de', title, author = 'immajung' }: Props): JSX.Element => { 12 | return ( 13 | 53 | ) 54 | } 55 | 56 | export default SEO 57 | -------------------------------------------------------------------------------- /src/constants/i18n.ts: -------------------------------------------------------------------------------- 1 | export const i18n = { 2 | en: { 3 | text: 'This is an static english text from the file ./src/constants/i18n.ts', 4 | }, 5 | de: { 6 | text: 'Das ist ein statischer deutscher text von der Datei ./src/constants/i18n.ts', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/locales.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | en: { 3 | path: 'en', 4 | locale: 'English', 5 | default: true, 6 | }, 7 | de: { 8 | path: 'de', 9 | locale: 'German', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /src/img/gatsby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karimould/minimal-multi-language-gatsby-starter-netlify-cms/1d27097149ebb0f62193e40dd98586b05839d742/src/img/gatsby.png -------------------------------------------------------------------------------- /src/img/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react' 2 | import Layout from '../components/layouts/main/Layout' 3 | import SEO from '../components/util/seo/Seo' 4 | 5 | const NotFoundPage = (): ReactElement => ( 6 | 7 | 8 |

NOT FOUND

9 |

You just hit a route that doesn't exist... the sadness.

10 |
11 | ) 12 | 13 | export default NotFoundPage 14 | -------------------------------------------------------------------------------- /src/pages/blog/de/2018-12-07-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: blog-post 3 | locale: de 4 | pageKey: page_blogpost 5 | title: Hello World DE 6 | date: 2018-12-07T15:04:10.000Z 7 | description: Hello World Blog Post DE 8 | --- 9 | 10 | Hello World 11 | -------------------------------------------------------------------------------- /src/pages/blog/en/2019-12-07-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: blog-post 3 | locale: en 4 | pageKey: page_blogpost 5 | title: Hello World EN1111 6 | date: 2018-12-07T15:04:10.000Z 7 | description: teeeeeest 8 | --- 9 | 10 | Hello World 11 | -------------------------------------------------------------------------------- /src/pages/blog/en/2020-11-03-sad.md: -------------------------------------------------------------------------------- 1 | --- 2 | templateKey: blog-post 3 | locale: en 4 | pageKey: page_blogpost 5 | title: sad 6 | date: 2020-11-03T13:15:47.244Z 7 | description: qwe 8 | --- 9 | qwe 10 | -------------------------------------------------------------------------------- /src/pages/home/index.de.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageKey: page_home 3 | locale: de 4 | seo_title: Homepage Title DE 5 | seo_desc: description for the homepage DE 6 | title: Home DE 7 | text: Some content DE 8 | --- -------------------------------------------------------------------------------- /src/pages/home/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageKey: page_home 3 | locale: en 4 | seo_title: Homepage Title EN 5 | seo_desc: description for the homepage en 6 | title: Home en 7 | text: Some content en 8 | --- -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react' 2 | import { Link, graphql } from 'gatsby' 3 | import Layout from '../components/layouts/main/Layout' 4 | import Image from '../components/util/image/Image' 5 | import { i18n } from '../constants/i18n' 6 | 7 | interface HomepageData { 8 | fields: { 9 | slug: string 10 | } 11 | frontmatter: { 12 | pageKey: string 13 | seo_title: string 14 | seo_desc: string 15 | title: string 16 | text: string 17 | } 18 | } 19 | 20 | interface BlogPosts { 21 | node: { 22 | fields: { 23 | slug: string 24 | } 25 | frontmatter: { 26 | title: string 27 | description: string 28 | date: string 29 | } 30 | } 31 | } 32 | 33 | interface IndexProps { 34 | pageContext: { 35 | [locale: string]: string 36 | } 37 | data: { 38 | homePageData: HomepageData 39 | blogPosts: { 40 | edges: BlogPosts[] 41 | } 42 | } 43 | } 44 | 45 | const IndexPage = ({ pageContext: { locale }, ...props }: IndexProps): ReactElement => { 46 | const { homePageData: data } = props.data 47 | const { edges: posts } = props.data.blogPosts 48 | return ( 49 | 50 | gatsby logo 51 |

title: {data.frontmatter.title}

52 |

Content: {data.frontmatter.text}

53 |

Locale: {locale}

54 |

{i18n[locale].text}

55 | 56 |

Change language

57 | 58 |

BlogPosts:

59 | {posts.map( 60 | ({ node: post }, i): JSX.Element => ( 61 |
62 |

Blog Post Title: {post.frontmatter.title}

63 |

Blog Post Description: {post.frontmatter.description}

64 |

Blog Post Date: {post.frontmatter.date}

65 | 66 | Link to blog post 67 | 68 |
69 | ), 70 | )} 71 |
72 | ) 73 | } 74 | 75 | export default IndexPage 76 | 77 | export const pageQuery = graphql` 78 | query HomeContent($locale: String) { 79 | homePageData: markdownRemark(frontmatter: { pageKey: { eq: "page_home" }, locale: { eq: $locale } }) { 80 | fields { 81 | slug 82 | } 83 | frontmatter { 84 | pageKey 85 | seo_title 86 | seo_desc 87 | title 88 | text 89 | } 90 | } 91 | blogPosts: allMarkdownRemark( 92 | filter: { frontmatter: { pageKey: { eq: "page_blogpost" }, locale: { eq: $locale } } } 93 | ) { 94 | edges { 95 | node { 96 | fields { 97 | slug 98 | } 99 | frontmatter { 100 | title 101 | description 102 | date 103 | } 104 | } 105 | } 106 | } 107 | } 108 | ` 109 | -------------------------------------------------------------------------------- /src/templates/blog-post.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { graphql } from 'gatsby' 3 | import Layout from '../components/layouts/main/Layout' 4 | import SEO from '../components/util/seo/Seo' 5 | 6 | interface BlogpostProps { 7 | pageContext: { 8 | locale: string 9 | } 10 | data: { 11 | markdownRemark: { 12 | id: string 13 | html: string 14 | frontmatter: { 15 | date: Date 16 | title: string 17 | description: string 18 | } 19 | } 20 | } 21 | } 22 | 23 | const BlogPost = ({ pageContext: { locale }, data }: BlogpostProps): JSX.Element => { 24 | const { markdownRemark: post } = data 25 | return ( 26 | 27 | 28 |

title: {post.frontmatter.title}

29 |

description: {post.frontmatter.description}

30 |

date: {post.frontmatter.date}

31 |
32 | 33 | ) 34 | } 35 | 36 | export default BlogPost 37 | 38 | export const pageQuery = graphql` 39 | query BlogPostByID($id: String!) { 40 | markdownRemark(id: { eq: $id }) { 41 | id 42 | html 43 | frontmatter { 44 | date(formatString: "MMMM DD, YYYY") 45 | title 46 | description 47 | } 48 | } 49 | } 50 | ` 51 | -------------------------------------------------------------------------------- /static/admin/config.yml: -------------------------------------------------------------------------------- 1 | backend: 2 | name: git-gateway 3 | branch: master 4 | 5 | media_folder: static/img 6 | public_folder: /img 7 | 8 | collections: 9 | - name: "blog-en" 10 | label: "Blog - EN" 11 | folder: "src/pages/blog/en" 12 | create: true 13 | slug: "{{year}}-{{month}}-{{day}}-{{slug}}" 14 | fields: 15 | - {label: "Template Key", name: "templateKey", widget: "hidden", default: "blog-post"} 16 | - {label: "Locale", name: "locale", widget: "hidden", default: "en"} 17 | - {label: "Page Key", name: "pageKey", widget: "hidden", default: "page_blogpost"} 18 | - {label: "Title", name: "title", widget: "string"} 19 | - {label: "Publish Date", name: "date", widget: "datetime"} 20 | - {label: "Description", name: "description", widget: "text"} 21 | - {label: "Body", name: "body", widget: "markdown"} 22 | - name: "blog-de" 23 | label: "Blog - DE" 24 | folder: "src/pages/blog/de" 25 | create: true 26 | slug: "{{year}}-{{month}}-{{day}}-{{slug}}" 27 | fields: 28 | - {label: "Template Key", name: "templateKey", widget: "hidden", default: "blog-post"} 29 | - {label: "Locale", name: "locale", widget: "hidden", default: "de"} 30 | - {label: "Page Key", name: "pageKey", widget: "hidden", default: "page_blogpost"} 31 | - {label: "Title", name: "title", widget: "string"} 32 | - {label: "Publish Date", name: "date", widget: "datetime"} 33 | - {label: "Description", name: "description", widget: "text"} 34 | - {label: "Body", name: "body", widget: "markdown"} 35 | - label: "Pages" 36 | name: "pages" 37 | files: 38 | - label: "Home - EN" 39 | name: "home" 40 | file: "src/pages/home/index.md" 41 | fields: 42 | - {label: "Page Key", name: "pageKey", widget: "hidden", default: "page_home"} 43 | - {label: "Locale", name: "locale", widget: "hidden", default: "en"} 44 | - {label: "SEO Title", name: "seo_title", widget: "string"} 45 | - {label: "SEO Description", name: "seo_desc", widget: "string"} 46 | - {label: "Title", name: "title", widget: "string"} 47 | - {label: "Text", name: "text", widget: "text"} 48 | - label: "Home - DE" 49 | name: "home" 50 | file: "src/pages/home/index.de.md" 51 | fields: 52 | - {label: "Page Key", name: "pageKey", widget: "hidden", default: "page_home"} 53 | - {label: "Locale", name: "locale", widget: "hidden", default: "de"} 54 | - {label: "SEO Title", name: "seo_title", widget: "string"} 55 | - {label: "SEO Description", name: "seo_desc", widget: "string"} 56 | - {label: "Title", name: "title", widget: "string"} 57 | - {label: "Text", name: "text", widget: "text"} 58 | -------------------------------------------------------------------------------- /static/img/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "*": [ 6 | "types/*" 7 | ] 8 | }, 9 | "outDir": "./dist/", 10 | "suppressImplicitAnyIndexErrors": true, 11 | "sourceMap": true, 12 | "noImplicitAny": true, 13 | "allowSyntheticDefaultImports": true, 14 | "moduleResolution": "node", 15 | "module": "es6", 16 | "target": "es5", 17 | "jsx": "react", 18 | "allowJs": true, 19 | "strict": true, 20 | "resolveJsonModule": true 21 | } 22 | } --------------------------------------------------------------------------------