diff --git a/components/Search.tsx b/components/Search.tsx index 443c33099c6f..f26c8883bee6 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -6,13 +6,13 @@ import { Flash, Label, ActionList, ActionMenu } from '@primer/react' import { ItemInput } from '@primer/react/lib/deprecated/ActionList/List' import { InfoIcon } from '@primer/octicons-react' +import { useLanguages } from 'components/context/LanguagesContext' import { useTranslation } from 'components/hooks/useTranslation' import { sendEvent, EventType } from 'components/lib/events' import { useMainContext } from './context/MainContext' import { DEFAULT_VERSION, useVersion } from 'components/hooks/useVersion' import { useQuery } from 'components/hooks/useQuery' import { Link } from 'components/Link' -import { useSession } from 'components/hooks/useSession' import styles from './Search.module.scss' @@ -56,8 +56,7 @@ export function Search({ const inputRef = useRef(null) const { t } = useTranslation('search') const { currentVersion } = useVersion() - const { session } = useSession() - const languages = session?.languages + const { languages } = useLanguages() // Figure out language and version for index const { searchVersions, nonEnterpriseDefaultVersion } = useMainContext() diff --git a/components/context/LanguagesContext.tsx b/components/context/LanguagesContext.tsx new file mode 100644 index 000000000000..836b9cf25e31 --- /dev/null +++ b/components/context/LanguagesContext.tsx @@ -0,0 +1,24 @@ +import { createContext, useContext } from 'react' + +type LanguageItem = { + name: string + nativeName?: string + code: string + hreflang: string +} + +export type LanguagesContextT = { + languages: Record +} + +export const LanguagesContext = createContext(null) + +export const useLanguages = (): LanguagesContextT => { + const context = useContext(LanguagesContext) + + if (!context) { + throw new Error('"useLanguagesContext" may only be used inside "LanguagesContext.Provider"') + } + + return context +} diff --git a/components/hooks/useSession.ts b/components/hooks/useSession.ts index 6094e1bf2b9c..581facda0c17 100644 --- a/components/hooks/useSession.ts +++ b/components/hooks/useSession.ts @@ -9,19 +9,10 @@ export default async function fetcher( return res.json() } -type LanguageItem = { - name: string - nativeName?: string - code: string - hreflang: string - wip?: boolean -} - export type Session = { isSignedIn: boolean csrfToken: string userLanguage: string // en, es, ja, cn - languages: Record theme: { colorMode: Pick nightTheme: string diff --git a/components/page-header/HeaderNotifications.tsx b/components/page-header/HeaderNotifications.tsx index f21baeacc3ba..e2062ee9babd 100644 --- a/components/page-header/HeaderNotifications.tsx +++ b/components/page-header/HeaderNotifications.tsx @@ -1,6 +1,7 @@ import { useRouter } from 'next/router' import cx from 'classnames' +import { useLanguages } from 'components/context/LanguagesContext' import { useMainContext } from 'components/context/MainContext' import { useTranslation } from 'components/hooks/useTranslation' import { ExcludesNull } from 'components/lib/ExcludesNull' @@ -24,13 +25,13 @@ export const HeaderNotifications = () => { const { relativePath, allVersions, data, currentPathWithoutLanguage, page } = useMainContext() const { session } = useSession() const userLanguage = session?.userLanguage - const languages = session?.languages || {} + const { languages } = useLanguages() const { t } = useTranslation('header') const translationNotices: Array = [] if (router.locale === 'en') { - if (userLanguage && userLanguage !== 'en' && languages[userLanguage]?.wip === false) { + if (userLanguage && userLanguage !== 'en') { translationNotices.push({ type: NotificationType.TRANSLATION, content: `This article is also available in ${languages[userLanguage]?.name}.`, @@ -42,16 +43,11 @@ export const HeaderNotifications = () => { type: NotificationType.TRANSLATION, content: data.reusables.policies.translation, }) - } else if (router.locale && languages[router.locale]?.wip !== true) { + } else if (router.locale) { translationNotices.push({ type: NotificationType.TRANSLATION, content: t('notices.localization_complete'), }) - } else if (router.locale && languages[router.locale]?.wip) { - translationNotices.push({ - type: NotificationType.TRANSLATION, - content: t('notices.localization_in_progress'), - }) } } const releaseNotices: Array = [] diff --git a/components/page-header/LanguagePicker.tsx b/components/page-header/LanguagePicker.tsx index 7bb7b05ebb4f..fefcab5a9172 100644 --- a/components/page-header/LanguagePicker.tsx +++ b/components/page-header/LanguagePicker.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router' import Cookies from 'js-cookie' -import { useSession } from 'components/hooks/useSession' +import { useLanguages } from 'components/context/LanguagesContext' import { Picker } from 'components/ui/Picker' import { useTranslation } from 'components/hooks/useTranslation' @@ -14,14 +14,12 @@ type Props = { export const LanguagePicker = ({ variant }: Props) => { const router = useRouter() - const { session } = useSession() - const languages = session?.languages + const { languages } = useLanguages() + const locale = router.locale || 'en' const { t } = useTranslation('picker') - if (!languages) return null - const langs = Object.values(languages) const selectedLang = languages[locale] @@ -52,15 +50,13 @@ export const LanguagePicker = ({ variant }: Props) => { !lang.wip) - .map((lang) => ({ - text: lang.nativeName || lang.name, - selected: lang === selectedLang, - locale: lang.code, - href: `${routerPath}`, - onselect: rememberPreferredLanguage, - }))} + options={langs.map((lang) => ({ + text: lang.nativeName || lang.name, + selected: lang === selectedLang, + locale: lang.code, + href: `${routerPath}`, + onselect: rememberPreferredLanguage, + }))} /> ) diff --git a/lib/languages.js b/lib/languages.js index 2460cbfea77b..9b9fbeed3179 100644 --- a/lib/languages.js +++ b/lib/languages.js @@ -6,7 +6,6 @@ const languages = { code: 'en', hreflang: 'en', dir: '', - wip: false, }, cn: { name: 'Simplified Chinese', @@ -15,7 +14,6 @@ const languages = { hreflang: 'zh-Hans', redirectPatterns: [/^\/zh-\w{2}/, /^\/zh/], dir: 'translations/zh-CN', - wip: false, }, ja: { name: 'Japanese', @@ -24,7 +22,6 @@ const languages = { hreflang: 'ja', redirectPatterns: [/^\/jp/], dir: 'translations/ja-JP', - wip: false, }, es: { name: 'Spanish', @@ -32,7 +29,6 @@ const languages = { code: 'es', hreflang: 'es', dir: 'translations/es-ES', - wip: false, }, pt: { name: 'Portuguese', @@ -40,7 +36,6 @@ const languages = { code: 'pt', hreflang: 'pt', dir: 'translations/pt-BR', - wip: false, }, } diff --git a/middleware/api/session.js b/middleware/api/session.js index f8994ec3c743..c6d5e65ea82e 100644 --- a/middleware/api/session.js +++ b/middleware/api/session.js @@ -11,7 +11,6 @@ router.get('/', (req, res) => { isSignedIn: Boolean(req.cookies?.dotcom_user), csrfToken: req.csrfToken?.() || '', userLanguage: req.userLanguage, - languages: req.context.languages, theme: getTheme(req), themeCss: getTheme(req, true), }) diff --git a/middleware/block-robots.js b/middleware/block-robots.js index d308514e80e7..5df95e064d20 100644 --- a/middleware/block-robots.js +++ b/middleware/block-robots.js @@ -1,13 +1,7 @@ -import languages from '../lib/languages.js' import { productMap } from '../lib/all-products.js' import { deprecated } from '../lib/enterprise-server-releases.js' const pathRegExps = [ - // Disallow indexing of WIP localized content - ...Object.values(languages) - .filter((language) => language.wip) - .map((language) => new RegExp(`^/${language.code}(/.*)?$`, 'i')), - // Disallow indexing of WIP products ...Object.values(productMap) .filter((product) => product.wip || product.hidden) diff --git a/middleware/detect-language.js b/middleware/detect-language.js index 822e317f451c..faf718f4740f 100644 --- a/middleware/detect-language.js +++ b/middleware/detect-language.js @@ -36,8 +36,7 @@ function getUserLanguage(browserLanguages) { function getUserLanguageFromCookie(req) { const value = req.cookies[PREFERRED_LOCALE_COOKIE_NAME] - // But if it's a WIP language, reject it. - if (value && languages[value] && !languages[value].wip) { + if (value && languages[value]) { return value } } diff --git a/pages/_app.tsx b/pages/_app.tsx index 9de26a6c1cc0..5923ae5d04f0 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -8,15 +8,17 @@ import '../stylesheets/index.scss' import events from 'components/lib/events' import experiment from 'components/lib/experiment' +import { LanguagesContext, LanguagesContextT } from 'components/context/LanguagesContext' import { useSession } from 'components/hooks/useSession' type MyAppProps = AppProps & { isDotComAuthenticated: boolean + languagesContext: LanguagesContextT } type colorModeAuto = Pick -const MyApp = ({ Component, pageProps }: MyAppProps) => { +const MyApp = ({ Component, pageProps, languagesContext }: MyAppProps) => { const { session, isLoadingSession } = useSession() useEffect(() => { events(session?.csrfToken) @@ -67,7 +69,9 @@ const MyApp = ({ Component, pageProps }: MyAppProps) => { { opacity: isLoadingSession ? 0.1 : 1 } } > - + + + @@ -80,11 +84,18 @@ const MyApp = ({ Component, pageProps }: MyAppProps) => { // executed every time **in the client** if it was the first time // ever (since restart) or from a cached HTML. MyApp.getInitialProps = async (appContext: AppContext) => { + const { ctx } = appContext // calls page's `getInitialProps` and fills `appProps.pageProps` const appProps = await App.getInitialProps(appContext) + const req: any = ctx.req + + // Have to define the type manually here because `req.context.languages` + // comes from Node JS and is not type-aware. + const languages: LanguagesContextT = req.context.languages return { ...appProps, + languagesContext: { languages }, } } diff --git a/script/i18n/test-html-pages.js b/script/i18n/test-html-pages.js index 8cf6cafaf725..d514e0a54ec1 100755 --- a/script/i18n/test-html-pages.js +++ b/script/i18n/test-html-pages.js @@ -40,7 +40,7 @@ async function main() { const languageCodes = [languageCode] || Object.keys(languages) - .filter((language) => !language.wip && language !== 'en') + .filter((language) => language !== 'en') .map((language) => languages[language].code) const versions = singleVersion ? [singleVersion] : Object.keys(allVersions) diff --git a/tests/browser/browser.js b/tests/browser/browser.js index 83ea77f320b2..3c7c5560294f 100644 --- a/tests/browser/browser.js +++ b/tests/browser/browser.js @@ -1,6 +1,5 @@ import { jest } from '@jest/globals' import { latest, oldestSupported } from '../../lib/enterprise-server-releases.js' -import languages from '../../lib/languages.js' jest.useFakeTimers({ legacyFakeTimers: true }) @@ -421,22 +420,6 @@ describe('filter cards', () => { }) }) -describe('language banner', () => { - it('directs user to the English version of the article', async () => { - const wipLanguageKey = Object.keys(languages).find((key) => languages[key].wip) - - // This kinda sucks, but if we don't have a WIP language, we currently can't - // run a reliable test. But hey, on the bright side, if we don't have a WIP - // language then this code will never run anyway! - if (wipLanguageKey) { - const res = await page.goto(`http://localhost:4000/${wipLanguageKey}/actions`) - expect(res.ok()).toBe(true) - const href = await page.$eval('a#to-english-doc', (el) => el.href) - expect(href.endsWith('/en/actions')).toBe(true) - } - }) -}) - // Skipping because next/links are disabled by default for now // Docs Engineering issue: 962 describe.skip('next/link client-side navigation', () => { diff --git a/tests/rendering/block-robots.js b/tests/rendering/block-robots.js index d7bc1e16c5c7..8bc704139192 100644 --- a/tests/rendering/block-robots.js +++ b/tests/rendering/block-robots.js @@ -15,21 +15,10 @@ describe('block robots', () => { }) it('allows crawling of generally available localized content', async () => { - Object.values(languages) - .filter((language) => !language.wip) - .forEach((language) => { - expect(allowIndex(`/${language.code}`)).toBe(true) - expect(allowIndex(`/${language.code}/articles/verifying-your-email-address`)).toBe(true) - }) - }) - - it('disallows crawling of WIP localized content', async () => { - Object.values(languages) - .filter((language) => language.wip) - .forEach((language) => { - expect(allowIndex(`/${language.code}`)).toBe(false) - expect(allowIndex(`/${language.code}/articles/verifying-your-email-address`)).toBe(false) - }) + Object.values(languages).forEach((language) => { + expect(allowIndex(`/${language.code}`)).toBe(true) + expect(allowIndex(`/${language.code}/articles/verifying-your-email-address`)).toBe(true) + }) }) it('disallows crawling of WIP products', async () => { diff --git a/tests/rendering/robots-txt.js b/tests/rendering/robots-txt.js index b74a1df82e0a..5c05bad05480 100644 --- a/tests/rendering/robots-txt.js +++ b/tests/rendering/robots-txt.js @@ -25,16 +25,14 @@ describe('robots.txt', () => { }) it('allows indexing of generally available localized content', async () => { - Object.values(languages) - .filter((language) => !language.wip) - .forEach((language) => { - expect(robots.isAllowed(`https://docs.github.com/${language.code}`)).toBe(true) - expect( - robots.isAllowed( - `https://docs.github.com/${language.code}/articles/verifying-your-email-address` - ) - ).toBe(true) - }) + Object.values(languages).forEach((language) => { + expect(robots.isAllowed(`https://docs.github.com/${language.code}`)).toBe(true) + expect( + robots.isAllowed( + `https://docs.github.com/${language.code}/articles/verifying-your-email-address` + ) + ).toBe(true) + }) }) it('disallows indexing of azurecontainer.io domains', async () => {