From 4330586e48b883979b7c8dbc0bbbaec6a8e0850f Mon Sep 17 00:00:00 2001 From: viphan007 Date: Tue, 18 Jun 2024 18:06:07 +0700 Subject: [PATCH] Improve localization for blog, blog listing page --- gatsby-node.js | 125 +++++++++++++++--- .../ContentfulLayoutModuleContainer.js | 1 + src/components/Header.js | 12 +- src/components/Tab/TabHeader.js | 1 + src/components/Tab/TabHeaderItem.js | 6 +- src/components/Tab/TabWrapper.js | 5 +- src/components/layout.js | 2 +- src/fragments/ContentfulContent.js | 7 +- src/fragments/previewFragment.js | 10 +- src/lib/config.mjs | 57 ++------ src/lib/utils/news.js | 2 +- src/templates/ContentfulLayout.js | 4 +- src/templates/ContentfulNewsCategoryLayout.js | 6 +- 13 files changed, 143 insertions(+), 95 deletions(-) diff --git a/gatsby-node.js b/gatsby-node.js index e1426d919fe..ce0a4d735d2 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -90,6 +90,7 @@ exports.createPages = async ({ graphql, actions }) => { slug categories { name + slug } } } @@ -134,6 +135,13 @@ exports.createPages = async ({ graphql, actions }) => { }) }) } + + if (localizedPages.includes('/news/')) { + newsCategories.forEach( + page => page.name && localizedPages.push(`/news/${page.name}/`) + ) + } + const legalsQuery = await graphql(` { allMdx { @@ -270,7 +278,7 @@ exports.createPages = async ({ graphql, actions }) => { h2FontSize, node_locale, localizedPages, - sharedCopy: sharedCopy[node_locale] + sharedCopy: sharedCopy[node_locale], }, }) return @@ -286,6 +294,40 @@ exports.createPages = async ({ graphql, actions }) => { const categoryPath = index ? `${baseCategoryPath}page/${index + 1}/` : baseCategoryPath + + if (showLanguageSelector && translation) { + LOCALES_TRANSLATE.forEach(locale => { + const localeSlug = `/${locale.code}${categoryPath}` + createPage({ + path: localeSlug, + component: path.resolve(mapTemplateLayout(pageType)), + context: { + headerId, + footerId, + seoId, + modules: moduleIds, + themeColor, + pathBuild: localeSlug, + isFaqLayout, + h2FontSize, + localizedPages, + limit: itemsPerPage, + skip: index * itemsPerPage, + categoryId: cat.categoryId, + category: cat.name, + totalItems: cat.total, + currentPage: index + 1, + totalPages, + sharedCopy: sharedCopy[node_locale], + slug: categoryPath, + translation, + locale: locale.code, + node_locale: locale.code, + newsCategories, + }, + }) + }) + } createPage({ path: categoryPath, component: path.resolve(mapTemplateLayout(pageType)), @@ -298,7 +340,6 @@ exports.createPages = async ({ graphql, actions }) => { pathBuild: categoryPath, isFaqLayout, h2FontSize, - node_locale, localizedPages, limit: itemsPerPage, skip: index * itemsPerPage, @@ -307,7 +348,10 @@ exports.createPages = async ({ graphql, actions }) => { totalItems: cat.total, currentPage: index + 1, totalPages, - sharedCopy: sharedCopy[node_locale] + sharedCopy: sharedCopy[node_locale], + translation, + node_locale, + newsCategories, }, }) }) @@ -342,7 +386,7 @@ exports.createPages = async ({ graphql, actions }) => { h2FontSize, node_locale, localizedPages, - sharedCopy: sharedCopy[node_locale] + sharedCopy: sharedCopy[node_locale], }, }) return @@ -370,7 +414,7 @@ exports.createPages = async ({ graphql, actions }) => { locale: locale.code, node_locale: locale.code, localizedPages, - sharedCopy: sharedCopy[locale.code] + sharedCopy: sharedCopy[locale.code], }, }) }) @@ -393,7 +437,7 @@ exports.createPages = async ({ graphql, actions }) => { translation, node_locale, localizedPages, - sharedCopy: sharedCopy[node_locale] + sharedCopy: sharedCopy[node_locale], }, }) }) @@ -418,6 +462,7 @@ exports.createPages = async ({ graphql, actions }) => { slug categories { name + slug } translation node_locale @@ -498,7 +543,7 @@ exports.createPages = async ({ graphql, actions }) => { pathBuild: slug, node_locale, localizedPages, - sharedCopy: sharedCopy[node_locale] + sharedCopy: sharedCopy[node_locale], }, }) }) @@ -518,7 +563,9 @@ exports.createPages = async ({ graphql, actions }) => { } exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { - const { DEFAULT_LOCALE_CODE } = await import('./src/lib/config.mjs') + const { DEFAULT_LOCALE_CODE, LOCALES_TRANSLATE } = await import( + './src/lib/config.mjs' + ) const { redirects, program, config } = store.getState() buildSitemap({ query: ` @@ -548,20 +595,31 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { } allContentfulNews(filter: {isPrivate: {eq: false}, node_locale: {eq: "${DEFAULT_LOCALE_CODE}"}}) { nodes { + contentful_id title slug categories { name + slug } + translation publishDate(formatString: "YYYY-MM-DD") } } + allContentfulNewsLocalized: allContentfulNews(filter: {isPrivate: {eq: false}, translation: {eq: true}, node_locale: {ne: "${DEFAULT_LOCALE_CODE}"}}) { + nodes { + contentful_id + title + node_locale + } + } allPrivateContentfulNews: allContentfulNews(filter: {isPrivate: {eq: true}, node_locale: {eq: "${DEFAULT_LOCALE_CODE}"}}) { nodes { title slug categories { name + slug } } } @@ -572,6 +630,7 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { title categories { name + slug } } } @@ -621,16 +680,26 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { `/pyusd`, `/dev-404-page*`, `/404*`, - `/es/`, - `/ar/`, `/zh-CN/`, - `/de/`, + `/hi-IN/`, + `/it/`, + `/ja/`, + `/ko/`, + `/ru/`, + `/es/`, + `/tr/`, + `/pcm-NG/`, `/news/page/+([0-9])`, `/news/*/page/+([0-9])`, - `/es/news/**`, - `/de/news/**`, `/zh-CN/news/**`, - `/ar/news/**`, + `/hi-IN/news/**`, + `/it/news/**`, + `/ja/news/**`, + `/ko/news/**`, + `/ru/news/**`, + `/es/news/**`, + `/tr/news/**`, + `/pcm-NG/news/**`, ] return !excludePages.some(exclude => minimatch(path, exclude)) }, @@ -645,8 +714,10 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { resolvePages: ({ site, allContentfulNews, + allContentfulNewsLocalized, allContentfulNewsNonCanonical, }) => { + const localizedNews = allContentfulNewsLocalized.nodes const siteTitle = site.siteMetadata.title const privatePages = [] allContentfulNewsNonCanonical.nodes.forEach(page => { @@ -658,6 +729,27 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { const newsUrl = getNewsUrl(page) if (privatePages.indexOf(newsUrl) === -1) { allNews.push({ ...page, path: newsUrl }) + + if (page.translation) { + LOCALES_TRANSLATE.forEach(locale => { + const localeSlug = `/${locale.code}${newsUrl}` + let localeTitle = page.title + localizedNews.forEach(news => { + if ( + news.contentful_id === page.contentful_id && + news.node_locale === locale.code + ) { + localeTitle = news.title + } + }) + allNews.push({ + ...page, + path: localeSlug, + title: localeTitle, + locale: locale.code, + }) + }) + } } }) return allNews.map(page => { @@ -666,6 +758,7 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { siteTitle, publishDate: page.publishDate, title: page.title, + locale: page.locale, } }) }, @@ -673,14 +766,14 @@ exports.onPostBuild = async ({ graphql, store, pathPrefix, reporter }) => { 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' + ' xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"' + ' xmlns:xhtml="http://www.w3.org/1999/xhtml"', - serializer: ({ path, siteTitle, publishDate, title }) => ({ + serializer: ({ path, siteTitle, publishDate, title, locale }) => ({ loc: path, changefreq: 'daily', priority: path === '' ? '1' : '0.8', 'news:news': { 'news:publication': { 'news:name': siteTitle, - 'news:language': 'en', + 'news:language': locale || 'en-US', }, 'news:publication_date': publishDate, 'news:title': title, diff --git a/src/components/Contentful/ContentfulLayoutModuleContainer.js b/src/components/Contentful/ContentfulLayoutModuleContainer.js index 9e0676fe5eb..3c536b21e8a 100644 --- a/src/components/Contentful/ContentfulLayoutModuleContainer.js +++ b/src/components/Contentful/ContentfulLayoutModuleContainer.js @@ -59,6 +59,7 @@ const ContentfulModuleContainer = props => { ? modules.map(item => ({ label: item.title, id: previewMode ? item.title : item.contentful_id, + slug: item.modules?.[0]?.slug, content: ( {contentfulModuleToComponent({ diff --git a/src/components/Header.js b/src/components/Header.js index 1b7819cbf34..b2cd11f0dec 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -3,7 +3,6 @@ import React, { useContext, useEffect, useRef, useState } from 'react' import { DEFAULT_LOCALE_CODE, LOCALES, - PREVIEW_LOCALES, GB_BLOCKED_PATHS, } from '../lib/config.mjs' import ContextClientSide from '../Context/ContextClientSide' @@ -137,12 +136,15 @@ const StyledHeader = props => { let localizedPath if (locale.code === DEFAULT_LOCALE_CODE) { - localizedPath = pathname.replace(/^\/(ar|zh-CN|de|es)/, '') + localizedPath = pathname.replace( + /^\/(zh-CN|hi-IN|it|ja|ko|ru|es|tr|pcm-NG)/, + '' + ) } else { const newLocale = locale.code === DEFAULT_LOCALE_CODE ? '' : locale.code localizedPath = `/${newLocale}${pathname.replace( - /^\/(ar|zh-CN|de|es)\//, + /^\/(zh-CN|hi-IN|it|ja|ko|ru|es|tr|pcm-NG)\//, '/' )}` } @@ -154,8 +156,6 @@ const StyledHeader = props => { ldClient?.flush() } - const LANGUAGES = previewMode ? PREVIEW_LOCALES : LOCALES - // Apply UK(GB) specific temporary geo-blocking rules useEffect(() => { if (country !== 'GB') { @@ -333,7 +333,7 @@ const StyledHeader = props => { - {LANGUAGES.map(locale => ( + {LOCALES.map(locale => ( onChangeLocale(locale)} diff --git a/src/components/Tab/TabHeader.js b/src/components/Tab/TabHeader.js index 7cb1900324d..1e929941220 100644 --- a/src/components/Tab/TabHeader.js +++ b/src/components/Tab/TabHeader.js @@ -15,6 +15,7 @@ const TabHeader = props => { setActiveStateId, isTabParam, } = props + const { header: headerREF } = React.useContext(Context) const { headerRef } = headerREF || {} const { heroContainer: heroContainerREF } = React.useContext(Context) diff --git a/src/components/Tab/TabHeaderItem.js b/src/components/Tab/TabHeaderItem.js index f3d92c7a168..8324fd71ccc 100644 --- a/src/components/Tab/TabHeaderItem.js +++ b/src/components/Tab/TabHeaderItem.js @@ -1,23 +1,25 @@ import React from 'react' import styled, { withTheme } from 'styled-components' -import { Link } from 'gatsby' +import Link from '../Link' import lowerCase from 'lodash/lowerCase' const TabHeaderItem = props => { const { activeId, label, + slug, id, typeLayout, setActiveStateId, isTabParam, } = props + const changeTab = () => { setActiveStateId(id) } if (isTabParam) { - const newsCategory = encodeURIComponent(lowerCase(label)) + const newsCategory = encodeURIComponent(lowerCase(slug)) let newsCategoryUrl = `/news/${newsCategory}/` if (newsCategory === 'latest') { newsCategoryUrl = '/news/' diff --git a/src/components/Tab/TabWrapper.js b/src/components/Tab/TabWrapper.js index bdec476a910..9dc40673379 100644 --- a/src/components/Tab/TabWrapper.js +++ b/src/components/Tab/TabWrapper.js @@ -28,7 +28,7 @@ const TabWrapper = props => { if (newsCategory && isTabParam) { const tabActive = tabs.find( - ({ label }) => encodeURIComponent(lowerCase(label)) === newsCategory + ({ slug }) => encodeURIComponent(lowerCase(slug)) === newsCategory ) setActiveStateId(tabActive?.id) return @@ -38,8 +38,7 @@ const TabWrapper = props => { const newsCategory = pathname.match('/news/([^/]*)/?') if (newsCategory) { const tabActive = tabs.find( - ({ label }) => - encodeURIComponent(lowerCase(label)) === newsCategory[1] + ({ slug }) => encodeURIComponent(lowerCase(slug)) === newsCategory[1] ) setActiveStateId(tabActive?.id || activeTabDefault) return diff --git a/src/components/layout.js b/src/components/layout.js index a5d6d1a8c8f..01381c8cf6b 100644 --- a/src/components/layout.js +++ b/src/components/layout.js @@ -58,7 +58,7 @@ const Layout = props => { }, ]} > - + {children} diff --git a/src/fragments/ContentfulContent.js b/src/fragments/ContentfulContent.js index b7a7a2f336c..23b4929c38c 100644 --- a/src/fragments/ContentfulContent.js +++ b/src/fragments/ContentfulContent.js @@ -981,12 +981,7 @@ export const ContentfulNewsCategoryFields = graphql` type } name - parent { - ... on ContentfulNewsCategory { - contentful_id - name - } - } + slug } ` diff --git a/src/fragments/previewFragment.js b/src/fragments/previewFragment.js index 6e8464c158d..8dad4c2a54c 100644 --- a/src/fragments/previewFragment.js +++ b/src/fragments/previewFragment.js @@ -434,15 +434,7 @@ export const ContentfulNewsCategoryFields = gql` id } name - parent(preview: true) { - ... on NewsCategory { - __typename - sys { - id - } - name - } - } + slug } ` diff --git a/src/lib/config.mjs b/src/lib/config.mjs index 48253f75ea1..1056a4338f7 100644 --- a/src/lib/config.mjs +++ b/src/lib/config.mjs @@ -69,54 +69,6 @@ export const mapTemplateLayout = name => { } export const LOCALES = [ - { - name: 'English', - localizedName: 'English', - code: 'en-US', - shortName: 'EN', - htmlLang: 'en', - }, - { - name: 'Arabic', - localizedName: 'العربية', - shortName: 'AR', - code: 'ar', - htmlLang: 'ar', - }, - { - name: 'Chinese', - localizedName: '中文', - shortName: 'CN', - code: 'zh-CN', - htmlLang: 'zh', - }, - { - name: 'German', - localizedName: 'Deutsch', - shortName: 'DE', - code: 'de', - htmlLang: 'de', - }, - { - name: 'Spanish', - localizedName: 'Español', - shortName: 'ES', - code: 'es', - htmlLang: 'es', - }, -] - -export const LOCALES_TRANSLATE = LOCALES.slice(1) -export const DEFAULT_LOCALE = LOCALES[0] -export const DEFAULT_LOCALE_CODE = DEFAULT_LOCALE.code - -export const mapCodeToHtmlLang = (code, previewMode) => { - const listLocales = previewMode ? PREVIEW_LOCALES : LOCALES - const locale = listLocales.find(l => l.code === code) - return locale ? locale.htmlLang : DEFAULT_LOCALE.htmlLang -} - -export const PREVIEW_LOCALES = [ { name: 'English', localizedName: 'English', @@ -189,4 +141,13 @@ export const PREVIEW_LOCALES = [ }, ] +export const LOCALES_TRANSLATE = LOCALES.slice(1) +export const DEFAULT_LOCALE = LOCALES[0] +export const DEFAULT_LOCALE_CODE = DEFAULT_LOCALE.code + +export const mapCodeToHtmlLang = (code) => { + const locale = LOCALES.find(l => l.code === code) + return locale ? locale.htmlLang : DEFAULT_LOCALE.htmlLang +} + export const GB_BLOCKED_PATHS = ['/buy-crypto/', '/sell-crypto/', '/swaps/'] diff --git a/src/lib/utils/news.js b/src/lib/utils/news.js index 76622a3ab19..c80f9b42f73 100644 --- a/src/lib/utils/news.js +++ b/src/lib/utils/news.js @@ -15,7 +15,7 @@ let getNewsUrl = news => { ? kebabCase(news.slug) : kebabCase(news.title.toLowerCase()) if (news.categories && news.categories.length) - category = news.categories[0].name + category = news.categories[0].slug return `/news/${kebabCase(category)}/${slug}/` } diff --git a/src/templates/ContentfulLayout.js b/src/templates/ContentfulLayout.js index cb9f5604246..1a9deff5e63 100644 --- a/src/templates/ContentfulLayout.js +++ b/src/templates/ContentfulLayout.js @@ -157,14 +157,14 @@ export const ContentfulQuery = graphql` ) { header: contentfulLayoutHeader( contentful_id: { eq: $headerId } - node_locale: { eq: "en-US" } + node_locale: { eq: $node_locale } ) { ...ContentfulLayoutHeaderFields } footer: contentfulLayoutFooter( contentful_id: { eq: $footerId } - node_locale: { eq: "en-US" } + node_locale: { eq: $node_locale } ) { ...ContentfulLayoutFooterFields } diff --git a/src/templates/ContentfulNewsCategoryLayout.js b/src/templates/ContentfulNewsCategoryLayout.js index e22d22b5132..81c5cfe8d34 100644 --- a/src/templates/ContentfulNewsCategoryLayout.js +++ b/src/templates/ContentfulNewsCategoryLayout.js @@ -19,6 +19,8 @@ const ContentfulNewsCategoryLayout = props => { localizedPages, totalPages, sharedCopy, + slug, + translation, } = {}, } = props @@ -62,8 +64,10 @@ const ContentfulNewsCategoryLayout = props => { contentfulModuleToComponent({ ...seoData, pagePath: pathBuild, + originalSlug: slug, + translation, })} - {header && contentfulModuleToComponent(header)} + {header && contentfulModuleToComponent({ ...header, translation })} {hero && contentfulModuleToComponent(hero)} {layoutModuleContainer && contentfulModuleToComponent({