From 1798fe5a5439ab65728f848bf2685cb5282fe874 Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Fri, 9 Dec 2022 22:11:27 -0300 Subject: [PATCH 01/51] Revert "Merge pull request #8786 from ethereum/revert-i18n-pr" This reverts commit 82dd4c7364a2415de9ce07ebd9d4cabd8172edca, reversing changes made to b24db7d8e423e439ffc264f36a7d3ac9ff282c9c. --- .gitignore | 1 + gatsby-browser.tsx | 51 -- gatsby-config.ts | 68 +- gatsby-node.ts | 129 ++-- gatsby-ssr.tsx | 31 - package.json | 8 +- src/components/BeaconChainActions.tsx | 42 +- src/components/Breadcrumbs.tsx | 12 +- src/components/ButtonDropdown.tsx | 8 +- src/components/EthExchanges.tsx | 33 +- src/components/FeedbackWidget.tsx | 16 +- src/components/FileContributors.tsx | 6 +- src/components/Footer.tsx | 9 +- src/components/Layer2/Layer2Onboard.tsx | 34 +- src/components/Layer2ProductCard.tsx | 17 +- src/components/Layout.tsx | 90 ++- src/components/Leaderboard.tsx | 10 +- src/components/LearningToolsCardGrid.tsx | 8 +- src/components/Link.tsx | 9 +- src/components/Logo.tsx | 10 +- src/components/MergeArticleList.tsx | 83 +-- src/components/MergeInfographic.tsx | 14 +- src/components/Nav/Dropdown.tsx | 11 +- src/components/Nav/Menu.tsx | 6 +- src/components/Nav/Mobile.tsx | 14 +- src/components/Nav/index.tsx | 11 +- src/components/PageMetadata.tsx | 12 +- src/components/Quiz/QuizSummary.tsx | 10 +- src/components/Roadmap.tsx | 15 +- src/components/Search/Input.tsx | 7 +- src/components/Search/index.tsx | 6 +- src/components/ShardChainsList.tsx | 83 +-- src/components/SideNav.tsx | 7 +- src/components/StablecoinAccordion.tsx | 71 +- src/components/StablecoinBoxGrid.tsx | 7 +- .../Staking/StakingCommunityCallout.tsx | 7 +- .../Staking/StakingLaunchpadWidget.tsx | 15 +- src/components/Staking/StakingStatsBox.tsx | 10 +- src/components/StatsBoxGrid.tsx | 54 +- src/components/Translation.tsx | 34 +- src/components/Trilemma.tsx | 13 +- src/components/TutorialMetadata.tsx | 6 +- src/pages-conditional/dapps.tsx | 641 +++++------------- src/pages-conditional/eth.tsx | 38 +- src/pages-conditional/wallets.tsx | 65 +- src/pages-conditional/what-is-ethereum.tsx | 91 +-- src/pages/404.tsx | 16 +- src/pages/assets.tsx | 278 +++----- src/pages/bug-bounty.tsx | 65 +- src/pages/community.tsx | 115 ++-- .../translation-program/acknowledgements.tsx | 41 +- .../translation-program/contributors.tsx | 26 +- src/pages/developers/index.tsx | 29 +- src/pages/developers/learning-tools.tsx | 24 +- src/pages/developers/local-environment.tsx | 31 +- src/pages/developers/tutorials.tsx | 30 +- src/pages/get-eth.tsx | 59 +- src/pages/index.tsx | 147 ++-- src/pages/languages.tsx | 34 +- src/pages/layer-2.tsx | 149 ++-- src/pages/learn/index.tsx | 202 ++---- src/pages/run-a-node.tsx | 55 +- src/pages/stablecoins.tsx | 187 ++--- src/pages/staking/deposit-contract.tsx | 38 +- src/pages/staking/index.tsx | 69 +- src/pages/upgrades/get-involved/index.tsx | 28 +- src/pages/upgrades/index.tsx | 127 ++-- src/pages/upgrades/vision.tsx | 28 +- src/pages/wallets/find-wallet.tsx | 23 +- src/scripts/mergeTranslations.ts | 6 +- src/templates/docs.tsx | 11 +- src/templates/staking.tsx | 11 +- src/templates/static.tsx | 19 +- src/templates/tutorial.tsx | 11 +- src/templates/upgrade.tsx | 17 +- src/templates/use-cases.tsx | 13 +- src/types.ts | 19 +- src/utils/translations.ts | 47 +- yarn.lock | 174 ++--- 79 files changed, 1617 insertions(+), 2435 deletions(-) diff --git a/.gitignore b/.gitignore index 3920d43afc2..779cf0195b2 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ yarn-error.log src/data/contributors.json # These files are generated by `yarn merge-translations` command src/intl/*.json +i18n/locales # Auto generated code when gatsby build the site src/gatsby-types.d.ts diff --git a/gatsby-browser.tsx b/gatsby-browser.tsx index 709de7493a1..bfb9a18f2c4 100644 --- a/gatsby-browser.tsx +++ b/gatsby-browser.tsx @@ -4,10 +4,6 @@ * See: https://www.gatsbyjs.org/docs/browser-apis/ */ -import React from "react" -import browserLang from "browser-lang" -import { withPrefix, GatsbyBrowser } from "gatsby" - import Prism from "prism-react-renderer/prism" ;(typeof global !== "undefined" ? global : window).Prism = Prism @@ -16,53 +12,6 @@ import "@formatjs/intl-locale/polyfill" import "@formatjs/intl-numberformat/polyfill" import "@formatjs/intl-numberformat/locale-data/en" -import Layout from "./src/components/Layout" -import { - supportedLanguages, - defaultLanguage, - isLang, -} from "./src/utils/languages" -import { IS_DEV } from "./src/utils/env" -import { Context } from "./src/types" - // Default languages included: // https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/vendor/prism/includeLangs.js require("prismjs/components/prism-solidity") - -// Prevents from unmounting on page transitions -// https://www.gatsbyjs.com/docs/layout-components/#how-to-prevent-layout-components-from-unmounting -// @ts-ignore: returning `null` is not accepted by the `GatsbyBrowser` type def. -export const wrapPageElement: GatsbyBrowser< - any, - Context ->["wrapPageElement"] = ({ element, props }) => { - const { location, pageContext } = props - const { pathname, search } = location - const { originalPath } = pageContext - - const [, pathLocale] = pathname.split("/") - - // client side redirect on paths that don't have a locale in them. Most useful - // on dev env where we don't have server redirects - if (IS_DEV && !isLang(pathLocale)) { - let detected = - window.localStorage.getItem("eth-org-language") || - browserLang({ - languages: supportedLanguages, - fallback: defaultLanguage, - }) - - if (!isLang(detected)) { - detected = defaultLanguage - } - - const queryParams = search || "" - const newUrl = withPrefix(`/${detected}${originalPath}${queryParams}`) - window.localStorage.setItem("eth-org-language", detected) - window.location.replace(newUrl) - - return null - } - - return {element} -} diff --git a/gatsby-config.ts b/gatsby-config.ts index 5869b08be3f..6f2e358d13a 100644 --- a/gatsby-config.ts +++ b/gatsby-config.ts @@ -35,24 +35,6 @@ const config: GatsbyConfig = { editContentUrl: `https://github.com/ethereum/ethereum-org-website/tree/dev/`, }, plugins: [ - // i18n support - { - resolve: `gatsby-theme-i18n`, - options: { - defaultLang: defaultLanguage, - prefixDefault: true, - locales: supportedLanguages.length - ? supportedLanguages.join(" ") - : null, - configPath: path.resolve(`./i18n/config.json`), - }, - }, - { - resolve: `gatsby-theme-i18n-react-intl`, - options: { - defaultLocale: `./src/intl/en.json`, - }, - }, // Web app manifest { resolve: `gatsby-plugin-manifest`, @@ -265,6 +247,56 @@ const config: GatsbyConfig = { generateMatchPathRewrites: false, }, }, + // i18n support + { + resolve: `gatsby-source-filesystem`, + options: { + path: path.resolve(`./i18n/locales`), + name: `locale`, + }, + }, + // Wraps the entire page with a custom layout component + // Note: keep this before the i18n plugin declaration in order to have the + // i18n provider wrapping the layout component + { + resolve: `gatsby-plugin-layout`, + options: { + component: path.resolve(`./src/components/Layout`), + }, + }, + { + resolve: `gatsby-plugin-react-i18next`, + options: { + localeJsonSourceName: `locale`, // name given to `gatsby-source-filesystem` plugin. + languages: supportedLanguages, + defaultLanguage: defaultLanguage, + generateDefaultLanguagePage: true, + redirect: false, + siteUrl, + trailingSlash: "always", + // i18next options + i18nextOptions: { + fallbackLng: defaultLanguage, + interpolation: { + escapeValue: false, + }, + react: { + transSupportBasicHtmlNodes: true, + transKeepBasicHtmlNodesFor: [ + "br", + "strong", + "i", + "bold", + "b", + "em", + "sup", + ], + }, + keySeparator: false, + nsSeparator: false, + }, + }, + }, ], // https://www.gatsbyjs.com/docs/reference/release-notes/v2.28/#feature-flags-in-gatsby-configjs flags: { diff --git a/gatsby-node.ts b/gatsby-node.ts index ef5f9af66ab..30c624de9c1 100644 --- a/gatsby-node.ts +++ b/gatsby-node.ts @@ -284,17 +284,23 @@ export const createPages: GatsbyNode["createPages"] = async ({ component: path.resolve(`src/templates/${template}.tsx`), context: { language: lang, + languagesToFetch: [lang, defaultLanguage], slug: langSlug, ignoreTranslationBanner: isLegal, isLegal: isLegal, isOutdated: false, isContentEnglish: true, relativePath, // Use English path for template MDX query - // gatsby i18n theme context - locale: lang, - hrefLang: lang, - originalPath: langSlug.slice(3), - dateFormat: "MM/DD/YYYY", + // gatsby i18n plugin + i18n: { + language: lang, + languages: supportedLanguages, + defaultLanguage: defaultLanguage, + generateDefaultLanguagePage: false, + routed: true, + originalPath: langSlug.slice(3), + path: langSlug, + }, }, }) } @@ -306,15 +312,21 @@ export const createPages: GatsbyNode["createPages"] = async ({ component: path.resolve(`src/templates/${template}.tsx`), context: { language, + languagesToFetch: [language, defaultLanguage], slug, isOutdated: !!node.fields.isOutdated, isDefaultLang: language === defaultLanguage, relativePath, - // gatsby i18n theme context - locale: language, - hrefLang: language, - originalPath: slug.slice(3), - dateFormat: "MM/DD/YYYY", + // gatsby i18n plugin + i18n: { + language, + languages: supportedLanguages, + defaultLanguage: defaultLanguage, + generateDefaultLanguagePage: false, + routed: true, + originalPath: slug.slice(3), + path: slug, + }, }, }) }) @@ -344,22 +356,26 @@ export const createPages: GatsbyNode["createPages"] = async ({ page, lang ) - const slug = `/${lang}${originalPath}` - createPage({ path: slug, component: path.resolve(`src/pages-conditional/${page}.tsx`), context: { language: lang, + languagesToFetch: [lang, defaultLanguage], slug, isContentEnglish, isOutdated, - // gatsby i18n theme context - locale: lang, - hrefLang: lang, - originalPath, - dateFormat: "MM/DD/YYYY", + // gatsby i18n plugin + i18n: { + language: lang, + languages: supportedLanguages, + defaultLanguage: defaultLanguage, + generateDefaultLanguagePage: false, + routed: true, + originalPath, + path: slug, + }, }, }) } @@ -376,51 +392,68 @@ export const onCreatePage: GatsbyNode["onCreatePage"] = async ({ }) => { const { createPage, deletePage, createRedirect } = actions - const isDefaultLang = page.path.startsWith(`/${defaultLanguage}`) - - if (isDefaultLang) { - const path = page.path.slice(3) - - if (IS_DEV) { - // create routes without the lang prefix e.g. `/{path}` as our i18n plugin - // only creates `/{lang}/{path}` routes. This is useful on dev env to avoid - // getting a 404 since we don't have server side redirects - createPage({ ...page, path }) - } - - if (!IS_DEV && !path.match(/^\/404(\/|.html)$/)) { - // on prod, indicate our servers to redirect the root paths to the - // `/{defaultLang}/{path}` - createRedirect({ - ...commonRedirectProps, - fromPath: path, - toPath: page.path, - }) - } - } - if (!page.context) { return } - const isTranslated = page.context.locale !== defaultLanguage - const hasNoContext = page.context.isOutdated === undefined + // these are the native Gatsby pages (those living under `/pages`) + // which do not pass through the `createPages` hook thus they don't have our + // custom context in them + const isPageWithoutCustomContext = page.context.isOutdated === undefined + + if (isPageWithoutCustomContext) { + const { language, i18n } = page.context + const isDefaultLang = language === defaultLanguage - if (isTranslated && hasNoContext) { + // as we don't have our custom context for this page, we calculate & add it + // later to them const { isOutdated, isContentEnglish } = await checkIsPageOutdated( - page.context.originalPath, - page.context.locale + i18n.originalPath, + language ) - deletePage(page) - createPage({ + + let newPage = { ...page, context: { ...page.context, + languagesToFetch: [language, defaultLanguage], isOutdated, //display TranslationBanner for translation-component pages that are still in English isContentEnglish, }, - }) + } + + // there seems to be a bug in the i18n plugin where 404 pages get a + // duplicated `/lang` in their `matchPath`s + if (newPage.matchPath?.includes(`/${language}/${language}/*`)) { + newPage = { ...newPage, matchPath: `/${language}/*` } + } + + // on dev, we will have 2 pages for the default lang + // - 1 for the ones with the prefix `/{defaultLang}/learn/` + // - 1 for the ones without the prefix `/learn/` + // we do this to avoid having a 404 on those without the prefix since in + // dev we don't have the redirects from the server + deletePage(page) + + if (IS_DEV) { + createPage(newPage) + } + + // `routed` means that the page have the lang prefix on the url + // e.g. `/en/learn` or `/en` + if (!IS_DEV && i18n.routed) { + createPage(newPage) + + const rootPath = page.path.slice(3) + if (isDefaultLang && !rootPath.match(/^\/404(\/|.html)$/)) { + createRedirect({ + ...commonRedirectProps, + fromPath: rootPath, + toPath: page.path, + }) + } + } } } diff --git a/gatsby-ssr.tsx b/gatsby-ssr.tsx index e0925d5ceb6..22ddf1ee242 100644 --- a/gatsby-ssr.tsx +++ b/gatsby-ssr.tsx @@ -3,34 +3,3 @@ * * See: https://www.gatsbyjs.org/docs/ssr-apis/ */ - -import React from "react" - -import type { GatsbySSR } from "gatsby" - -import Layout from "./src/components/Layout" - -import { Context } from "./src/types" -import { IS_DEV } from "./src/utils/env" -import { isLang } from "./src/utils/languages" - -// Prevents from unmounting on page transitions -// https://www.gatsbyjs.com/docs/layout-components/#how-to-prevent-layout-components-from-unmounting -// @ts-ignore: returning `null` is not accepted by the `GatsbySSR` type def. -export const wrapPageElement: GatsbySSR["wrapPageElement"] = ({ - element, - props, -}) => { - const { location } = props - const { pathname } = location - - const [, pathLocale] = pathname.split("/") - - // this is to avoid having hydration issues on dev mode. Check the logic - // inside gatsby-browser.tsx - if (IS_DEV && !isLang(pathLocale)) { - return null - } - - return {element} -} diff --git a/package.json b/package.json index c12cfa82054..a808a410c65 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,14 @@ "gatsby-plugin-emotion": "^7.19.0", "gatsby-plugin-gatsby-cloud": "^4.20.0", "gatsby-plugin-image": "^2.21.0", + "gatsby-plugin-layout": "^3.24.0", "gatsby-plugin-manifest": "^4.21.0", "gatsby-plugin-matomo": "^0.9.0", "gatsby-plugin-mdx": "^3.0.0", "gatsby-plugin-netlify": "^5.0.1", "gatsby-plugin-react-helmet": "^5.0.0", "gatsby-plugin-react-helmet-canonical-urls": "^1.4.0", + "gatsby-plugin-react-i18next": "^2.0.4", "gatsby-plugin-react-svg": "^3.1.0", "gatsby-plugin-robots-txt": "^1.7.1", "gatsby-plugin-sharp": "^4.21.0", @@ -47,13 +49,13 @@ "gatsby-remark-images": "^6.21.0", "gatsby-remark-reading-time": "^1.1.0", "gatsby-source-filesystem": "^4.21.1", - "gatsby-theme-i18n": "^3.0.0", - "gatsby-theme-i18n-react-intl": "^3.0.0", "gatsby-transformer-csv": "^4.0.0", "gatsby-transformer-gitinfo": "^1.1.0", "gatsby-transformer-json": "^4.11.0", "gatsby-transformer-remark": "^3.0.0", "gatsby-transformer-sharp": "^4.21.0", + "htmr": "^1.0.2", + "i18next": "^21.9.2", "is-relative-url": "^3.0.0", "lodash": "^4.17.21", "luxon": "^1.24.1", @@ -66,9 +68,9 @@ "react-dom": "^18.0.0", "react-emoji-render": "^1.2.4", "react-helmet": "^6.1.0", + "react-i18next": "^11.18.6", "react-icons": "^4.3.1", "react-instantsearch-dom": "^6.32.0", - "react-intl": "^3.12.1", "react-select": "^4.3.0", "recharts": "^2.1.9", "styled-system": "^5.1.5", diff --git a/src/components/BeaconChainActions.tsx b/src/components/BeaconChainActions.tsx index d38e797235f..3ca5727ef72 100644 --- a/src/components/BeaconChainActions.tsx +++ b/src/components/BeaconChainActions.tsx @@ -1,11 +1,8 @@ import React from "react" import { Box, Flex, Heading } from "@chakra-ui/react" - import { useStaticQuery, graphql } from "gatsby" +import { useTranslation } from "gatsby-plugin-react-i18next" -import { useIntl } from "react-intl" - -import { translateMessageId } from "../utils/translations" import { getImage, ImageDataLike } from "../utils/image" import CardList from "./CardList" @@ -57,7 +54,7 @@ type BeaconQueryTypes = { } const BeaconChainActions: React.FC = () => { - const intl = useIntl() + const { t } = useTranslation() const data = useStaticQuery(BeaconStaticQuery) const datapoints: Array = [ @@ -66,44 +63,32 @@ const BeaconChainActions: React.FC = () => { image: getImage(data.beaconscan)!, alt: "", link: "https://beaconscan.com", - description: translateMessageId("consensus-beaconscan-desc", intl), + description: t("consensus-beaconscan-desc"), }, { title: "beaconcha.in", image: getImage(data.beaconchain)!, alt: "", link: "https://beaconcha.in", - description: translateMessageId("consensus-beaconcha-in-desc", intl), + description: t("consensus-beaconcha-in-desc"), }, ] //TODO: we should refactor the naming here instead of using authors into the description field const reads: Array = [ { - title: translateMessageId( - "page-upgrade-article-title-two-point-oh", - intl - ), + title: t("page-upgrade-article-title-two-point-oh"), description: "Status", link: "https://our.status.im/two-point-oh-the-beacon-chain/", }, { - title: translateMessageId( - "page-upgrade-article-title-beacon-chain-explainer", - intl - ), + title: t("page-upgrade-article-title-beacon-chain-explainer"), description: "Ethos.dev", link: "https://ethos.dev/beacon-chain/", }, { - title: translateMessageId( - "page-upgrade-article-title-sharding-consensus", - intl - ), - description: translateMessageId( - "page-upgrade-article-author-ethereum-foundation", - intl - ), + title: t("page-upgrade-article-title-sharding-consensus"), + description: t("page-upgrade-article-author-ethereum-foundation"), link: "https://blog.ethereum.org/2020/03/27/sharding-consensus/", }, ] @@ -117,8 +102,8 @@ const BeaconChainActions: React.FC = () => { mr={{ base: 0, md: 4 }} mb={{ base: 8, md: 0 }} emoji=":money_with_wings:" - title={translateMessageId("consensus-become-staker", intl)} - description={translateMessageId("consensus-become-staker-desc", intl)} + title={t("consensus-become-staker")} + description={t("consensus-become-staker-desc")} > @@ -132,11 +117,8 @@ const BeaconChainActions: React.FC = () => { mr={0} ml={{ base: 0, md: 4 }} emoji=":computer:" - title={translateMessageId("consensus-run-beacon-chain", intl)} - description={translateMessageId( - "consensus-run-beacon-chain-desc", - intl - )} + title={t("consensus-run-beacon-chain")} + description={t("consensus-run-beacon-chain-desc")} > diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 405e85b6c9e..64bb41fc850 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -1,10 +1,10 @@ import React from "react" +import { useTranslation } from "gatsby-plugin-react-i18next" import { Box, UnorderedList, ListItem } from "@chakra-ui/react" -import { useIntl } from "react-intl" import Link from "./Link" -import { isLang, supportedLanguages } from "../utils/languages" -import { isTranslationKey, translateMessageId } from "../utils/translations" +import { isLang } from "../utils/languages" +import { isTranslationKey } from "../utils/translations" export interface IProps { slug: string @@ -29,7 +29,7 @@ const Breadcrumbs: React.FC = ({ startDepth = 0, ...restProps }) => { - const intl = useIntl() + const { t } = useTranslation() const slugChunk = slug.split("/") const sliced = slugChunk.filter((item) => !!item).slice(startDepth) @@ -37,9 +37,9 @@ const Breadcrumbs: React.FC = ({ const crumbs = sliced.map((path, idx) => { // If homepage (e.g. "en"), set text to "home" translation const text = isLang(path) - ? translateMessageId("page-index-meta-title", intl) + ? t("page-index-meta-title") : isTranslationKey(path) - ? translateMessageId(path, intl) + ? t(path) : "" return { diff --git a/src/components/ButtonDropdown.tsx b/src/components/ButtonDropdown.tsx index 501bcc139cb..cdbb6f74fe3 100644 --- a/src/components/ButtonDropdown.tsx +++ b/src/components/ButtonDropdown.tsx @@ -1,7 +1,7 @@ // Libraries import React, { useState, createRef } from "react" import styled from "@emotion/styled" -import { useIntl } from "react-intl" +import { useTranslation } from "gatsby-plugin-react-i18next" import { motion } from "framer-motion" import { MdMenu } from "react-icons/md" @@ -12,7 +12,7 @@ import Translation from "./Translation" // Utils import { useOnClickOutside } from "../hooks/useOnClickOutside" -import { translateMessageId, TranslationKey } from "../utils/translations" +import { TranslationKey } from "../utils/translations" import { trackCustomEvent } from "../utils/matomo" const Container = styled.div` @@ -120,7 +120,7 @@ export interface IProps { const ButtonDropdown: React.FC = ({ list, className }) => { const [isOpen, setIsOpen] = useState(false) - const intl = useIntl() + const { t } = useTranslation() const ref = createRef() useOnClickOutside(ref, () => setIsOpen(false)) @@ -136,7 +136,7 @@ const ButtonDropdown: React.FC = ({ list, className }) => {