Skip to content

Commit

Permalink
DOP-4941: Allow fonts for hidden languages to work without feature fl…
Browse files Browse the repository at this point in the history
…ag (#1239)
  • Loading branch information
rayangler authored Sep 17, 2024
1 parent 790f07c commit 5ca5e23
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 55 deletions.
7 changes: 6 additions & 1 deletion gatsby-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { renderToString } from 'react-dom/server';
import { theme } from './src/theme/docsTheme';
import EuclidCircularASemiBold from './src/styles/fonts/EuclidCircularA-Semibold-WebXL.woff';
import redirectBasedOnLang from './src/utils/head-scripts/redirect-based-on-lang';
import { getHtmlLangFormat } from './src/utils/locale';

export const onRenderBody = ({ setHeadComponents }) => {
export const onRenderBody = ({ setHeadComponents, setHtmlAttributes }) => {
const headComponents = [
// GTM Pathway
<script
Expand Down Expand Up @@ -84,6 +85,10 @@ export const onRenderBody = ({ setHeadComponents }) => {
/>
);
}
setHtmlAttributes({
// Help work with translated content locally; Smartling should handle rewriting the lang
lang: process.env.GATSBY_LOCALE ? getHtmlLangFormat(process.env.GATSBY_LOCALE) : 'en',
});
setHeadComponents(headComponents);
};

Expand Down
14 changes: 2 additions & 12 deletions src/components/DocumentBody.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, lazy } from 'react';
import PropTypes from 'prop-types';
import { graphql } from 'gatsby';
import { Global, css } from '@emotion/react';
import { ImageContextProvider } from '../context/image-context';
import { usePresentationMode } from '../hooks/use-presentation-mode';
import { useCanonicalUrl } from '../hooks/use-canonical-url';
Expand All @@ -11,11 +10,11 @@ import { getMetaFromDirective } from '../utils/get-meta-from-directive';
import { getPlaintext } from '../utils/get-plaintext';
import { getTemplate } from '../utils/get-template';
import useSnootyMetadata from '../utils/use-snooty-metadata';
import { getCurrentLocaleFontFamilyValue } from '../utils/locale';
import { getSiteTitle } from '../utils/get-site-title';
import { PageContext } from '../context/page-context';
import { useBreadcrumbs } from '../hooks/use-breadcrumbs';
import { isBrowser } from '../utils/is-browser';
import { TEMPLATE_CONTAINER_ID } from '../constants';
import Widgets from './Widgets';
import SEO from './SEO';
import FootnoteContext from './Footnote/footnote-context';
Expand Down Expand Up @@ -80,8 +79,6 @@ const getAnonymousFootnoteReferences = (index, numAnonRefs) => {
return index > numAnonRefs ? [] : [`id${index + 1}`];
};

const fontFamily = getCurrentLocaleFontFamilyValue();

const DocumentBody = (props) => {
const { location, data, pageContext } = props;
const page = data?.page?.ast;
Expand Down Expand Up @@ -152,7 +149,7 @@ const DocumentBody = (props) => {
<ImageContextProvider images={props.data?.pageImage?.images ?? []}>
<FootnoteContext.Provider value={{ footnotes }}>
<PageContext.Provider value={{ page, template, slug, options: page?.options, tabsMainColumn }}>
<div id="template-container">
<div id={TEMPLATE_CONTAINER_ID}>
<Template {...props} useChatbot={useChatbot}>
{pageNodes.map((child, index) => (
<ComponentFactory
Expand All @@ -179,13 +176,6 @@ const DocumentBody = (props) => {
</SuspenseHelper>
</div>
)}
<Global
styles={css`
#template-container *:not(:is(code, code *)) {
font-family: ${fontFamily};
}
`}
/>
</>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/Footer.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { UnifiedFooter } from '@mdb/consistent-nav';
import { AVAILABLE_LANGUAGES, getCurrLocale, onSelectLocale } from '../utils/locale';
import { getAvailableLanguages, getCurrLocale, onSelectLocale } from '../utils/locale';

const Footer = () => {
const enabledLocales = AVAILABLE_LANGUAGES.map((language) => language.localeCode);
const enabledLocales = getAvailableLanguages().map((language) => language.localeCode);
return <UnifiedFooter onSelectLocale={onSelectLocale} locale={getCurrLocale()} enabledLocales={enabledLocales} />;
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/Header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UnifiedNav } from '@mdb/consistent-nav';
import { SidenavMobileMenuDropdown } from '../Sidenav';
import SiteBanner from '../Banner/SiteBanner';
import { theme } from '../../theme/docsTheme';
import { AVAILABLE_LANGUAGES, getCurrLocale, onSelectLocale } from '../../utils/locale';
import { getAvailableLanguages, getCurrLocale, onSelectLocale } from '../../utils/locale';
import { HeaderContext } from './header-context';

const StyledHeaderContainer = styled.header(
Expand All @@ -21,7 +21,7 @@ const StyledHeaderContainer = styled.header(
const Header = ({ sidenav, eol, template }) => {
const unifiedNavProperty = 'DOCS';

const enabledLocales = AVAILABLE_LANGUAGES.map((language) => language.localeCode);
const enabledLocales = getAvailableLanguages().map((language) => language.localeCode);
const { bannerContent } = useContext(HeaderContext);

return (
Expand Down
14 changes: 3 additions & 11 deletions src/components/Sidenav/Sidenav.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { formatText } from '../../utils/format-text';
import { TocContext } from '../../context/toc-context';
import { VersionContext } from '../../context/version-context';
import useSnootyMetadata from '../../utils/use-snooty-metadata';
import { getCurrentLocaleFontFamilyValue } from '../../utils/locale';
import { SIDE_NAV_CONTAINER_ID } from '../../constants';
import GuidesLandingTree from './GuidesLandingTree';
import GuidesTOCTree from './GuidesTOCTree';
import IA from './IA';
Expand All @@ -30,8 +30,6 @@ import DocsHomeButton from './DocsHomeButton';

const SIDENAV_WIDTH = 268;

const fontFamily = getCurrentLocaleFontFamilyValue();

// Use LG's css here to style the component without passing props
const sideNavStyling = ({ hideMobile, isCollapsed }) => LeafyCSS`
height: 100%;
Expand Down Expand Up @@ -76,12 +74,6 @@ const disableScroll = (shouldDisableScroll) => css`
}
`;

const translatedFontFamilyStyles = css`
#side-nav-1 * {
font-family: ${fontFamily};
}
`;

// use eol status to determine side nav styling
const getTopAndHeight = (topValue, template) => css`
${template === 'landing'
Expand Down Expand Up @@ -248,10 +240,10 @@ const Sidenav = ({ chapters, guides, page, pageTitle, repoBranches, slug, eol })
<>
<Global
styles={css`
${disableScroll(!hideMobile)} ${translatedFontFamilyStyles}
${disableScroll(!hideMobile)}
`}
/>
<SidenavContainer {...topValues} template={template}>
<SidenavContainer {...topValues} template={template} id={SIDE_NAV_CONTAINER_ID}>
<SidenavMobileTransition hideMobile={hideMobile} isMobile={isMobile}>
<LeafygreenSideNav
aria-label="Side navigation"
Expand Down
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Append "docs" to the beginning of SIDE_NAV_CONTAINER_ID due to differentiate from LG's internal side-nav-container testid
export const SIDE_NAV_CONTAINER_ID = 'docs-side-nav-container';
export const TEMPLATE_CONTAINER_ID = 'template-container';

export const REF_TARGETS = {
'compass-index': 'https://www.mongodb.com/docs/compass/current/#compass-index',
'document-dot-notation': 'https://www.mongodb.com/docs/manual/core/document/#document-dot-notation',
Expand Down
3 changes: 3 additions & 0 deletions src/layouts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { usePresentationMode } from '../hooks/use-presentation-mode';
import { theme } from '../theme/docsTheme';
import useSnootyMetadata from '../utils/use-snooty-metadata';
import { useRemoteMetadata } from '../hooks/use-remote-metadata';
import { getAllLocaleCssStrings } from '../utils/locale';

// TODO: Delete this as a part of the css cleanup
// Currently used to preserve behavior and stop legacy css
Expand All @@ -24,6 +25,8 @@ const footerOverrides = css`
`;

const globalCSS = css`
${getAllLocaleCssStrings()}
body {
font-size: 16px;
line-height: 24px;
Expand Down
1 change: 1 addition & 0 deletions src/utils/head-scripts/redirect-based-on-lang.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function redirectBasedOnLang() {
const storedPrefLocale = JSON.parse(localStorage.getItem('mongodb-docs'))?.['preferredLocale'];

// Based on locale utils; couldn't figure out how to use imports due to how webpack attempts to resolve them
// PLEASE make sure this matches the AVAILABLE_LANGUAGES object, excluding English and other hidden languages
const supportedLocaleCodes = ['zh-cn', 'ko-kr', 'pt-br'];
const isEnglishSite = !supportedLocaleCodes.find((localeCode) =>
window.location.pathname.startsWith('/' + localeCode)
Expand Down
85 changes: 62 additions & 23 deletions src/utils/locale.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { withPrefix } from 'gatsby';
import { SIDE_NAV_CONTAINER_ID, TEMPLATE_CONTAINER_ID } from '../constants';
import { assertTrailingSlash } from './assert-trailing-slash';
import { isBrowser } from './is-browser';
import { normalizePath } from './normalize-path';
Expand All @@ -17,19 +18,32 @@ export const STORAGE_KEY_PREF_LOCALE = 'preferredLocale';
// Update this as more languages are introduced
// Because the client-side redirect script cannot use an import, PLEASE remember to update the list of supported languages
// in redirect-based-on-lang.js
export const AVAILABLE_LANGUAGES = [
const AVAILABLE_LANGUAGES = [
{ language: 'English', localeCode: 'en-us' },
{ language: '简体中文', localeCode: 'zh-cn' },
{ language: '한국어', localeCode: 'ko-kr' },
{ language: '简体中文', localeCode: 'zh-cn', fontFamily: 'Noto Sans SC' },
{ language: '한국어', localeCode: 'ko-kr', fontFamily: 'Noto Sans KR' },
{ language: 'Português', localeCode: 'pt-br' },
];

if (process.env.GATSBY_FEATURE_SHOW_HIDDEN_LOCALES === 'true') {
AVAILABLE_LANGUAGES.push({ language: '日本語', localeCode: 'ja-jp' });
}
// Languages in current development that we do not want displayed publicly yet
const HIDDEN_LANGUAGES = [{ language: '日本語', localeCode: 'ja-jp', fontFamily: 'Noto Sans JP' }];

/**
* @param {boolean} forceAll - Bypasses feature flag requirements if necessary
* @returns An array of languages supported for translation
*/
export const getAvailableLanguages = (forceAll = false) => {
const langs = [...AVAILABLE_LANGUAGES];

if (forceAll || process.env.GATSBY_FEATURE_SHOW_HIDDEN_LOCALES === 'true') {
langs.push(...HIDDEN_LANGUAGES);
}

return langs;
};

const validateLocaleCode = (potentialCode) =>
!!AVAILABLE_LANGUAGES.find(({ localeCode }) => potentialCode === localeCode);
!!getAvailableLanguages().find(({ localeCode }) => potentialCode === localeCode);

/**
* Strips the first locale code found in the slug. This function should be used to determine the original un-localized path of a page.
Expand Down Expand Up @@ -60,21 +74,30 @@ const stripLocale = (slug) => {
}
};

/**
* Returns the font-family name or undefined as a value based on the locale code
* returned from getCurrentLocale, undefined to tell CSS to ignore this and work as
* normal and use LG's styles.
*
* This is currently for overriding font-family from LG.
*/
export const getCurrentLocaleFontFamilyValue = () => {
const fontFamilyMap = {
'zh-cn': 'Noto Sans SC',
'ko-kr': 'Noto Sans KR',
'ja-jp': 'Noto Sans JP',
};
const locale = getCurrLocale();
return fontFamilyMap[locale] ? `${fontFamilyMap[locale]}` : undefined;
export const getAllLocaleCssStrings = () => {
const strings = [];
// We want to bypass feature flag requirements to ensure fonts for hidden languages are always included
const allLangs = getAvailableLanguages(true);

allLangs.forEach(({ localeCode, fontFamily }) => {
if (!fontFamily) {
return;
}
const [languageCode] = localeCode.split('-');
// Only check that languageCode is in the beginning to be flexible when region code is capitalized
// For example: zh-cn and zh-CN will be treated the same.
// We want to target everything except for inline code, code blocks, and the consistent-nav components
strings.push(`
html[lang^=${languageCode}] {
#${TEMPLATE_CONTAINER_ID} *:not(:is(code, code *)),
#${SIDE_NAV_CONTAINER_ID} * {
font-family: ${fontFamily};
}
}
`);
});

return strings;
};

/**
Expand Down Expand Up @@ -136,7 +159,7 @@ export const getLocaleMapping = (siteUrl, slug) => {
const normalizedSiteUrl = siteUrl?.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl;
const localeHrefMap = {};

AVAILABLE_LANGUAGES.forEach(({ localeCode }) => {
getAvailableLanguages().forEach(({ localeCode }) => {
const localizedPath = localizePath(slugForUrl, localeCode);
const targetUrl = normalizedSiteUrl + localizedPath;
localeHrefMap[localeCode] = assertTrailingSlash(targetUrl);
Expand All @@ -151,3 +174,19 @@ export const onSelectLocale = (locale) => {
const localizedPath = localizePath(location.pathname, locale);
window.location.href = localizedPath;
};

/**
* Ensures that a locale code has an all lowercase language code with an all uppercase region code,
* as described in https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang#region_subtag.
* @param {string} localeCode - A valid locale code with either 1 or 2 parts. Example: `zh-cn` or `zh`
* @returns {string | undefined}
*/
export const getHtmlLangFormat = (localeCode) => {
const parts = localeCode.split('-');
if (parts.length < 2) {
return localeCode;
}

const [langCode, regionCode] = parts;
return `${langCode.toLowerCase()}-${regionCode.toUpperCase()}`;
};
4 changes: 2 additions & 2 deletions tests/unit/Head.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Head } from '../../src/components/DocumentBody';
import mockStaticQuery from '../utils/mockStaticQuery';
import * as siteMetadata from '../../src/hooks/use-site-metadata';
import useSnootyMetadata from '../../src/utils/use-snooty-metadata';
import { AVAILABLE_LANGUAGES } from '../../src/utils/locale';
import { getAvailableLanguages } from '../../src/utils/locale';
import { DOTCOM_BASE_URL } from '../../src/utils/base-url';
import mockCompleteEOLPageContext from './data/CompleteEOLPageContext.json';
import mockEOLSnootyMetadata from './data/EOLSnootyMetadata.json';
Expand Down Expand Up @@ -223,7 +223,7 @@ describe('Head', () => {
const mockData = { ...mockHeadPageContext.data };
const { container } = render(<Head pageContext={mockPageContext} data={mockData} />);
const hrefLangLinks = container.querySelectorAll('link.sl_opaque');
expect(hrefLangLinks).toHaveLength(AVAILABLE_LANGUAGES.length);
expect(hrefLangLinks).toHaveLength(getAvailableLanguages().length);
expect(hrefLangLinks).toMatchSnapshot();
});
});
Expand Down
15 changes: 13 additions & 2 deletions tests/unit/utils/locale.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AVAILABLE_LANGUAGES, getLocaleMapping, localizePath } from '../../../src/utils/locale';
import { getAvailableLanguages, getHtmlLangFormat, getLocaleMapping, localizePath } from '../../../src/utils/locale';

describe('getLocaleMapping', () => {
it.each([
Expand All @@ -7,7 +7,7 @@ describe('getLocaleMapping', () => {
['https://www.mongodb.com', 'introduction'],
])('returns a valid mapping of URLs', (siteUrl, slug) => {
const mapping = getLocaleMapping(siteUrl, slug);
expect(Object.keys(mapping)).toHaveLength(AVAILABLE_LANGUAGES.length);
expect(Object.keys(mapping)).toHaveLength(getAvailableLanguages().length);
expect(mapping).toMatchSnapshot();
});
});
Expand Down Expand Up @@ -77,3 +77,14 @@ describe('localizePath', () => {
expect(res).toEqual(expectedRes);
});
});

describe('getHtmlLangFormat', () => {
it.each([
['zh-cn', 'zh-CN'],
['zh-CN', 'zh-CN'],
['zh', 'zh'],
])('transforms %s into %s', (input, expectedRes) => {
const res = getHtmlLangFormat(input);
expect(res).toEqual(expectedRes);
});
});

0 comments on commit 5ca5e23

Please sign in to comment.