From 56bc2b190cdb19dbcea7bc9557801f5575c053f5 Mon Sep 17 00:00:00 2001 From: Nat Alison Date: Wed, 22 Jan 2020 13:25:15 -0800 Subject: [PATCH] feat(www): Pull in translated gatsby docs (ENABLE_LOCALIZATIONS env var needed) (#20637) * add spanish files to source * disable spanish for now * add spanish again * aksldjasdf * get everything working * display spanish and portuguese pages * get all the links working! * a few more fixes * simplify * whats gatsby-cli doing here * i18n util file * update site metadata * disable translations by default * explain how to enable localizations in readme * add more languages --- www/README.md | 10 ++++ www/gatsby-config.js | 15 ++++++ www/gatsby-node.js | 49 ++++++++++++++------ www/i18n.json | 7 +++ www/package.json | 1 + www/src/components/docs-breadcrumb.js | 2 +- www/src/components/layout.js | 12 +++-- www/src/components/localized-link.js | 13 ++++++ www/src/components/mdx-link.js | 15 ++++++ www/src/components/navigation.js | 2 +- www/src/components/prev-and-next.js | 2 +- www/src/components/site-metadata.js | 6 +-- www/src/gatsby-plugin-theme-ui/components.js | 2 + www/src/templates/template-docs-markdown.js | 12 +++-- www/src/utils/i18n.js | 19 ++++++++ www/src/utils/sidebar/create-link.js | 2 +- 16 files changed, 139 insertions(+), 30 deletions(-) create mode 100644 www/i18n.json create mode 100644 www/src/components/localized-link.js create mode 100644 www/src/components/mdx-link.js create mode 100644 www/src/utils/i18n.js diff --git a/www/README.md b/www/README.md index 1efb4b8d9b5bc..1bf51c15f7dce 100644 --- a/www/README.md +++ b/www/README.md @@ -40,6 +40,16 @@ ANALYTICS_SERVICE_ACCOUNT="service account@email.com" ANALYTICS_SERVICE_ACCOUNT_KEY="PEM KEY VALUE" ``` +### Enabling localizations + +Localizations are currently a work-in-progress and are thus disabled by default. They can be enabled by setting the `ENABLE_LOCALIZATIONS` env variable: + +```shell +ENABLE_LOCALIZATIONS=true +``` + +There is currently no UI to link to the localizations, so you'll have to type in the name of the file you want to go to using the language code (e.g. /es/tutorial/part-one). + ## Running slow build? (Screenshots placeholder) If you are not working on a starter or site showcase, it might be beneficial to use a placeholder image instead of actual screenshots. It will skip downloading screenshots and generating responsive images for all screenshots and replace them with a placeholder image. diff --git a/www/gatsby-config.js b/www/gatsby-config.js index 160d1b52b1e35..0fa269ca1057e 100644 --- a/www/gatsby-config.js +++ b/www/gatsby-config.js @@ -1,4 +1,5 @@ const path = require(`path`) +const langs = require(`./i18n.json`) require(`dotenv`).config({ path: `.env.${process.env.NODE_ENV}`, }) @@ -52,6 +53,20 @@ if (process.env.AIRTABLE_API_KEY) { }) } +if (process.env.ENABLE_LOCALIZATIONS) { + dynamicPlugins.push( + ...langs.map(({ code }) => ({ + resolve: `gatsby-source-git`, + options: { + name: `docs-${code}`, + remote: `https://github.com/gatsbyjs/gatsby-${code}.git`, + branch: `master`, + patterns: `docs/tutorial/**`, + }, + })) + ) +} + module.exports = { siteMetadata: { title: `GatsbyJS`, diff --git a/www/gatsby-node.js b/www/gatsby-node.js index 8620c31d4e967..f35e6d80aedf9 100644 --- a/www/gatsby-node.js +++ b/www/gatsby-node.js @@ -11,9 +11,11 @@ const parseGHUrl = require(`parse-github-url`) const { GraphQLClient } = require(`@jamo/graphql-request`) const moment = require(`moment`) const startersRedirects = require(`./starter-redirects.json`) +const langs = require("./i18n.json") const { generateComparisonPageSet, } = require(`./src/utils/generate-comparison-page-set.js`) +const { localizedPath } = require(`./src/utils/i18n.js`) const yaml = require(`js-yaml`) const docLinksData = yaml.load( fs.readFileSync(`./src/data/sidebars/doc-links.yaml`) @@ -58,6 +60,16 @@ const slugToAnchor = slug => .filter(item => item !== ``) // remove empty values .pop() // take last item +const docSlugFromPath = parsedFilePath => { + if (parsedFilePath.name !== `index` && parsedFilePath.dir !== ``) { + return `/${parsedFilePath.dir}/${parsedFilePath.name}/` + } else if (parsedFilePath.dir === ``) { + return `/${parsedFilePath.name}/` + } else { + return `/${parsedFilePath.dir}/` + } +} + exports.createPages = ({ graphql, actions, reporter }) => { const { createPage, createRedirect } = actions @@ -109,6 +121,7 @@ exports.createPages = ({ graphql, actions, reporter }) => { node { fields { slug + locale package released } @@ -399,6 +412,7 @@ exports.createPages = ({ graphql, actions, reporter }) => { docPages.forEach(({ node }) => { const slug = _.get(node, `fields.slug`) + const locale = _.get(node, `fields.locale`) if (!slug) return if (!_.includes(slug, `/blog/`)) { @@ -444,12 +458,13 @@ exports.createPages = ({ graphql, actions, reporter }) => { } createPage({ - path: `${node.fields.slug}`, // required + path: localizedPath(locale, node.fields.slug), component: slash( node.fields.package ? localPackageTemplate : docsTemplate ), context: { slug: node.fields.slug, + locale, ...nextAndPrev, }, }) @@ -509,16 +524,12 @@ exports.createPages = ({ graphql, actions, reporter }) => { exports.onCreateNode = ({ node, actions, getNode, reporter }) => { const { createNodeField } = actions let slug + let locale if (node.internal.type === `File`) { const parsedFilePath = path.parse(node.relativePath) + // TODO add locale data for non-MDX files if (node.sourceInstanceName === `docs`) { - if (parsedFilePath.name !== `index` && parsedFilePath.dir !== ``) { - slug = `/${parsedFilePath.dir}/${parsedFilePath.name}/` - } else if (parsedFilePath.dir === ``) { - slug = `/${parsedFilePath.name}/` - } else { - slug = `/${parsedFilePath.dir}/` - } + slug = docSlugFromPath(parsedFilePath) } if (slug) { createNodeField({ node, name: `slug`, value: slug }) @@ -531,13 +542,8 @@ exports.onCreateNode = ({ node, actions, getNode, reporter }) => { const parsedFilePath = path.parse(fileNode.relativePath) // Add slugs for docs pages if (fileNode.sourceInstanceName === `docs`) { - if (parsedFilePath.name !== `index` && parsedFilePath.dir !== ``) { - slug = `/${parsedFilePath.dir}/${parsedFilePath.name}/` - } else if (parsedFilePath.dir === ``) { - slug = `/${parsedFilePath.name}/` - } else { - slug = `/${parsedFilePath.dir}/` - } + slug = docSlugFromPath(parsedFilePath) + locale = "en" // Set released status and `published at` for blog posts. if (_.includes(parsedFilePath.dir, `blog`)) { @@ -560,6 +566,16 @@ exports.onCreateNode = ({ node, actions, getNode, reporter }) => { }) } } + + for (let { code } of langs) { + if (fileNode.sourceInstanceName === `docs-${code}`) { + // have to remove the beginning "/docs" path because of the way + // gatsby-source-filesystem and gatsby-source-git differ + slug = docSlugFromPath(path.parse(fileNode.relativePath.substring(5))) + locale = code + } + } + // Add slugs for package READMEs. if ( fileNode.sourceInstanceName === `packages` && @@ -577,6 +593,9 @@ exports.onCreateNode = ({ node, actions, getNode, reporter }) => { createNodeField({ node, name: `anchor`, value: slugToAnchor(slug) }) createNodeField({ node, name: `slug`, value: slug }) } + if (locale) { + createNodeField({ node, name: `locale`, value: locale }) + } } else if (node.internal.type === `AuthorYaml`) { slug = `/contributors/${slugify(node.id, { lower: true, diff --git a/www/i18n.json b/www/i18n.json new file mode 100644 index 0000000000000..f71af6d056eef --- /dev/null +++ b/www/i18n.json @@ -0,0 +1,7 @@ +[ + { "code": "es", "name": "Spanish" }, + { "code": "id", "name": "Indonesian" }, + { "code": "pl", "name": "Polish" }, + { "code": "pt-BR", "name": "Brazilian Portuguese" }, + { "code": "zh-Hans", "name": "Simplified Chinese" } +] diff --git a/www/package.json b/www/package.json index b92eea14545f4..057e7c77d5c1b 100644 --- a/www/package.json +++ b/www/package.json @@ -57,6 +57,7 @@ "gatsby-remark-smartypants": "^2.1.17", "gatsby-source-airtable": "^2.0.12", "gatsby-source-filesystem": "^2.1.40", + "gatsby-source-git": "^1.0.2", "gatsby-source-npm-package-search": "^2.1.19", "gatsby-transformer-csv": "^2.1.19", "gatsby-transformer-documentationjs": "^4.1.20", diff --git a/www/src/components/docs-breadcrumb.js b/www/src/components/docs-breadcrumb.js index 84cf312fb4a38..3ea2f15ccd259 100644 --- a/www/src/components/docs-breadcrumb.js +++ b/www/src/components/docs-breadcrumb.js @@ -1,7 +1,7 @@ /** @jsx jsx */ import { jsx } from "theme-ui" import React from "react" -import { Link } from "gatsby" +import Link from "./localized-link" import ChevronRight from "react-icons/lib/md/chevron-right" import ChevronLeft from "react-icons/lib/md/chevron-left" import getActiveItem from "../utils/sidebar/get-active-item" diff --git a/www/src/components/layout.js b/www/src/components/layout.js index 7683d09c57cf3..22ba2ae61b8f1 100644 --- a/www/src/components/layout.js +++ b/www/src/components/layout.js @@ -24,9 +24,12 @@ import SiteMetadata from "../components/site-metadata" import SkipNavLink from "../components/skip-nav-link" import "../assets/fonts/futura" import LazyModal from "./lazy-modal" +import { defaultLang } from "../utils/i18n" let windowWidth +export const LocaleContext = React.createContext(defaultLang) + class DefaultLayout extends React.Component { constructor() { super() @@ -165,9 +168,12 @@ class DefaultLayout extends React.Component { } return ( - <> + - + @@ -194,7 +200,7 @@ class DefaultLayout extends React.Component { /> - + ) } } diff --git a/www/src/components/localized-link.js b/www/src/components/localized-link.js new file mode 100644 index 0000000000000..43e3a9916cb2a --- /dev/null +++ b/www/src/components/localized-link.js @@ -0,0 +1,13 @@ +import React from "react" +import { Link } from "gatsby" +import { LocaleContext } from "./layout" +import { localizedPath } from "../utils/i18n" + +// Use the globally available context to choose the right path +const LocalizedLink = ({ to, ...props }) => { + const locale = React.useContext(LocaleContext) + + return +} + +export default LocalizedLink diff --git a/www/src/components/mdx-link.js b/www/src/components/mdx-link.js new file mode 100644 index 0000000000000..2f9ed787286b2 --- /dev/null +++ b/www/src/components/mdx-link.js @@ -0,0 +1,15 @@ +import React from "react" +import LocalizedLink from "./localized-link" + +const isHash = str => /^#/.test(str) +const isInternal = to => /^\/(?!\/)/.test(to) + +// Only use for internal links +const MdxLink = ({ href, ...props }) => + isHash(href) || !isInternal(href) ? ( + + ) : ( + + ) + +export default MdxLink diff --git a/www/src/components/navigation.js b/www/src/components/navigation.js index c224558db14d9..d9de82a352fe9 100644 --- a/www/src/components/navigation.js +++ b/www/src/components/navigation.js @@ -1,10 +1,10 @@ /** @jsx jsx */ import { jsx } from "theme-ui" import { useColorMode } from "theme-ui" -import { Link } from "gatsby" import GithubIcon from "react-icons/lib/go/mark-github" import TwitterIcon from "react-icons/lib/fa/twitter" +import Link from "../components/localized-link" import SearchForm from "../components/search-form" import DiscordIcon from "../components/discord" import logo from "../assets/logo.svg" diff --git a/www/src/components/prev-and-next.js b/www/src/components/prev-and-next.js index 4bb1557ac5fcc..861028f22248b 100644 --- a/www/src/components/prev-and-next.js +++ b/www/src/components/prev-and-next.js @@ -1,6 +1,6 @@ /** @jsx jsx */ import { jsx } from "theme-ui" -import { Link } from "gatsby" +import Link from "./localized-link" import ArrowForwardIcon from "react-icons/lib/md/arrow-forward" import ArrowBackIcon from "react-icons/lib/md/arrow-back" diff --git a/www/src/components/site-metadata.js b/www/src/components/site-metadata.js index 16f4ffa4c7f12..5904e320e63b1 100644 --- a/www/src/components/site-metadata.js +++ b/www/src/components/site-metadata.js @@ -4,7 +4,7 @@ import { graphql, useStaticQuery } from "gatsby" import gatsbyIcon from "../assets/gatsby-icon.png" -const SiteMetadata = ({ pathname }) => { +const SiteMetadata = ({ pathname, locale }) => { const { site: { siteMetadata: { siteUrl, title, twitter }, @@ -23,7 +23,7 @@ const SiteMetadata = ({ pathname }) => { return ( - + { - + diff --git a/www/src/gatsby-plugin-theme-ui/components.js b/www/src/gatsby-plugin-theme-ui/components.js index 93739e05f16fb..cf2685bbc455c 100644 --- a/www/src/gatsby-plugin-theme-ui/components.js +++ b/www/src/gatsby-plugin-theme-ui/components.js @@ -9,6 +9,7 @@ import LayerModel from "../components/layer-model" import EmailCaptureForm from "../components/email-capture-form" import HorizontalNavList from "../components/horizontal-nav-list" import CodeBlock from "../components/code-block" +import MdxLink from "../components/mdx-link" export default { GuideList, @@ -19,5 +20,6 @@ export default { LayerModel, EmailCaptureForm, HorizontalNavList, + a: MdxLink, pre: ({ children }) => {children}, } diff --git a/www/src/templates/template-docs-markdown.js b/www/src/templates/template-docs-markdown.js index 4f2e71e11240d..b4ef5385a52e0 100644 --- a/www/src/templates/template-docs-markdown.js +++ b/www/src/templates/template-docs-markdown.js @@ -31,8 +31,8 @@ const containerStyles = { px: 9, } -const getDocsData = location => { - const [urlSegment] = location.pathname.split(`/`).slice(1) +const getDocsData = slug => { + const [urlSegment] = slug.split(`/`).slice(1) const itemListLookup = { docs: itemListDocs, contributing: itemListContributing, @@ -44,7 +44,7 @@ const getDocsData = location => { function DocsTemplate({ data, location, pageContext: { next, prev } }) { const page = data.mdx - const [urlSegment, itemList] = getDocsData(location) + const [urlSegment, itemList] = getDocsData(page.fields.slug) const toc = !page.frontmatter.disableTableOfContents && page.tableOfContents.items @@ -62,6 +62,7 @@ function DocsTemplate({ data, location, pageContext: { next, prev } }) { @@ -149,14 +150,15 @@ function DocsTemplate({ data, location, pageContext: { next, prev } }) { export default DocsTemplate export const pageQuery = graphql` - query($path: String!) { - mdx(fields: { slug: { eq: $path } }) { + query($slug: String!, $locale: String!) { + mdx(fields: { slug: { eq: $slug }, locale: { eq: $locale } }) { body excerpt timeToRead tableOfContents fields { slug + locale anchor } frontmatter { diff --git a/www/src/utils/i18n.js b/www/src/utils/i18n.js new file mode 100644 index 0000000000000..11fe5c1ab5b12 --- /dev/null +++ b/www/src/utils/i18n.js @@ -0,0 +1,19 @@ +const defaultLang = "en" + +function isDefaultLang(locale) { + return locale === defaultLang +} + +function localizedPath(locale, path) { + const isIndex = path === `/` + + // TODO generalize this to other paths + const isLocalized = !isDefaultLang(locale) && path.startsWith("/tutorial/") + // If it's the default language, don't do anything + // If it's another language, add the "path" + // However, if the homepage/index page is linked don't add the "to" + // Because otherwise this would add a trailing slash + return isLocalized ? `${locale}${isIndex ? `` : `${path}`}` : path +} + +module.exports = { defaultLang, localizedPath } diff --git a/www/src/utils/sidebar/create-link.js b/www/src/utils/sidebar/create-link.js index 7ac9e642dea49..1a9ac63e9e447 100644 --- a/www/src/utils/sidebar/create-link.js +++ b/www/src/utils/sidebar/create-link.js @@ -1,6 +1,6 @@ /** @jsx jsx */ import { jsx } from "theme-ui" -import { Link } from "gatsby" +import Link from "../../components/localized-link" import indention from "../../utils/sidebar/indention"