Skip to content

Commit

Permalink
DOP-5134: Language selector (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmeigs authored Nov 19, 2024
1 parent 232dc31 commit db4ee80
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/components/Redoc/Redoc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -51,6 +52,8 @@ export class Redoc extends React.Component<RedocProps> {
} = this.props;
const store = this.props.store;

const enabledLocales = AVAILABLE_LANGUAGES.map(language => language.localeCode);

return (
<ThemeProvider theme={options.theme}>
<StoreProvider value={store}>
Expand All @@ -60,6 +63,14 @@ export class Redoc extends React.Component<RedocProps> {
<UnifiedNav
position="relative"
property={{ name: 'DOCS', searchParams: [] }}
showLanguageSelector={true}
onSelectLocale={onSelectLocale}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
locale={getCurrLocale()}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
enabledLocales={enabledLocales}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
darkMode={false}
Expand All @@ -68,6 +79,14 @@ export class Redoc extends React.Component<RedocProps> {
<UnifiedNav
position="relative"
property={{ name: 'DOCS', searchParams: [] }}
showLanguageSelector={true}
onSelectLocale={onSelectLocale}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
locale={getCurrLocale()}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
enabledLocales={enabledLocales}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
darkMode={true}
Expand Down
125 changes: 125 additions & 0 deletions src/utils/locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Mainly copied from Snooty locale.js and other snooty utils
* Nov 14, 2024
*/

/**
* Key used to access browser storage for user's preferred locale
*/
export const STORAGE_KEY_PREF_LOCALE = 'preferredLocale';

// Update this as more languages are introduced
export const AVAILABLE_LANGUAGES = [
{ language: 'English', localeCode: 'en-us' },
{ language: '简体中文', localeCode: 'zh-cn', fontFamily: 'Noto Sans SC' },
{ language: '한국어', localeCode: 'ko-kr', fontFamily: 'Noto Sans KR' },
{ language: 'Português', localeCode: 'pt-br' },
{ language: '日本語', localeCode: 'ja-jp', fontFamily: 'Noto Sans JP' },
];

const isBrowser = typeof window !== 'undefined';

const setLocalValue = (key: string, value: any) => {
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;
};

0 comments on commit db4ee80

Please sign in to comment.