diff --git a/src/components/Redoc/Redoc.tsx b/src/components/Redoc/Redoc.tsx index 1c8f100066..222d712e38 100644 --- a/src/components/Redoc/Redoc.tsx +++ b/src/components/Redoc/Redoc.tsx @@ -25,6 +25,7 @@ import { StoreProvider } from '../StoreBuilder'; import { VersionSelector } from '../VersionSelector'; import { createGlobalStyle } from 'styled-components'; import { globalStyles } from '../../globalStyles'; +import { AVAILABLE_LANGUAGES, getCurrLocale, onSelectLocale } from '../../utils/locale'; export interface RedocProps { store: AppStore; @@ -51,6 +52,8 @@ export class Redoc extends React.Component { } = this.props; const store = this.props.store; + const enabledLocales = AVAILABLE_LANGUAGES.map(language => language.localeCode); + return ( @@ -60,6 +63,14 @@ export class Redoc extends React.Component { { { + try { + if (isBrowser) { + const prevState = JSON.parse(window.localStorage.getItem('mongodb-docs') ?? ''); + localStorage.setItem('mongodb-docs', JSON.stringify({ ...prevState, [key]: value })); + } + } catch { + console.error('Error setting localStorage value'); + } +}; + +const validateLocaleCode = potentialCode => + // Include hidden languages in validation to ensure current locale of hidden sites can still be captured correctly + !!AVAILABLE_LANGUAGES.find(({ localeCode }) => potentialCode === localeCode); + +const removeLeadingSlash = (filePath: string) => filePath.replace(/^\/+/, ''); + +// Remove duplicate slashes in path string +const normalizePath = (path: string) => path.replace(/\/+/g, `/`); + +/** + * Strips the first locale code found in the slug. This function should be used to determine the original un-localized path of a page. + * This assumes that the locale code is the first part of the URL slug. For example: "/zh-cn/docs/foo". + * @param {string} slug + * @returns {string} + */ +const stripLocale = slug => { + // Smartling has extensive replace logic for URLs and slugs that follow the pattern of "https://www.mongodb.com/docs". However, + // there are instances where we can't rely on them for certain components + if (!slug) { + return ''; + } + + // Normalize the slug in case any malformed slugs appear like: "//zh-cn/docs" + const slugWithSlash = slug.startsWith('/') ? slug : `/${slug}`; + const normalizedSlug = normalizePath(slugWithSlash); + const firstPathSlug = normalizedSlug.split('/', 2)[1]; + + // Replace from the original slug to maintain original form + const res = validateLocaleCode(firstPathSlug) + ? normalizePath(slug.replace(firstPathSlug, '')) + : slug; + if (res.startsWith('/') && !slug.startsWith('/')) { + return removeLeadingSlash(res); + } else if (!res.startsWith('/') && slug.startsWith('/')) { + return `/${res}`; + } else { + return res; + } +}; + +/** + * Returns the locale code based on the current location pathname of the page. + * @returns {string} + */ +export const getCurrLocale = () => { + const defaultLang = 'en-us'; + + if (!isBrowser) { + return defaultLang; + } + + // This currently needs to be client-side because the source page doesn't know about locale at + // build time. Smartling's GDN handles localization + // Example on https://www.mongodb.com/zh-cn/docs/manual/introduction: + // expected pathname - /zh-cn/docs/manual/introduction; expected locale - "zh-cn" + const pathname = window.location.pathname; + const expectedDocsPrefixes = ['docs', 'docs-qa']; + const firstPathSlug = pathname.split('/', 2)[1]; + if (expectedDocsPrefixes.includes(firstPathSlug)) { + return defaultLang; + } + + const slugMatchesCode = validateLocaleCode(firstPathSlug); + return slugMatchesCode ? firstPathSlug : defaultLang; +}; + +/** + * Returns the pathname with its locale code prepended. Leading slashes are preserved, if they exist. + * @param {string} pathname - Path name or slug of the page + * @param {string?} localeCode - Optional locale code. By default, the code is determined based on the current location of the page + * @returns {string} + */ +export const localizePath = (pathname: string | undefined, localeCode?: string) => { + if (!pathname) { + return ''; + } + + const unlocalizedPath = stripLocale(pathname); + const code = localeCode && validateLocaleCode(localeCode) ? localeCode : getCurrLocale(); + const languagePrefix = code === 'en-us' ? '' : `${code}/`; + let newPath = languagePrefix + unlocalizedPath; + if (pathname.startsWith('/')) { + newPath = `/${newPath}`; + } + return normalizePath(newPath); +}; + +export const onSelectLocale = locale => { + const location = window.location; + setLocalValue(STORAGE_KEY_PREF_LOCALE, locale); + const localizedPath = localizePath(location.pathname, locale); + window.location.href = localizedPath; +};