@@ -1,9 +1,14 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
import { Link } from 'components/Link'
|
||||
import { useLanguages } from 'components/context/LanguagesContext'
|
||||
import { Picker } from 'components/ui/Picker'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
// This value is replicated in two places! See middleware/detect-language.js
|
||||
const PREFERRED_LOCALE_COOKIE_NAME = 'preferredlang'
|
||||
|
||||
type Props = {
|
||||
variant?: 'inline'
|
||||
}
|
||||
@@ -22,6 +27,22 @@ export const LanguagePicker = ({ variant }: Props) => {
|
||||
// in a "denormalized" way.
|
||||
const routerPath = router.asPath.split('#')[0]
|
||||
|
||||
function rememberPreferredLanguage(code: string) {
|
||||
try {
|
||||
Cookies.set(PREFERRED_LOCALE_COOKIE_NAME, code, {
|
||||
expires: 365,
|
||||
secure: document.location.protocol !== 'http:',
|
||||
})
|
||||
} catch (err) {
|
||||
// You can never be too careful because setting a cookie
|
||||
// can fail. For example, some browser
|
||||
// extensions disallow all setting of cookies and attempts
|
||||
// at the `document.cookie` setter could throw. Just swallow
|
||||
// and move on.
|
||||
console.warn('Unable to set preferred language cookie', err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Picker
|
||||
variant={variant}
|
||||
@@ -33,7 +54,13 @@ export const LanguagePicker = ({ variant }: Props) => {
|
||||
text: lang.nativeName || lang.name,
|
||||
selected: lang === selectedLang,
|
||||
item: (
|
||||
<Link href={routerPath} locale={lang.code}>
|
||||
<Link
|
||||
href={routerPath}
|
||||
locale={lang.code}
|
||||
onClick={() => {
|
||||
rememberPreferredLanguage(lang.code)
|
||||
}}
|
||||
>
|
||||
{lang.nativeName ? (
|
||||
<>
|
||||
<span lang={lang.code}>{lang.nativeName}</span> (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Enabling automatic user license sync for your enterprise
|
||||
intro: 'You can manage license usage across your {% data variables.product.prodname_enterprise %} deployments by automatically syncing user licenses from {% data variables.product.product_location %} to {% data variables.product.prodname_ghe_cloud %}.'
|
||||
intro: 'You can manage license usage across your {% data variables.product.prodname_enterprise %} environments by automatically syncing user licenses from {% data variables.product.product_location %} to {% data variables.product.prodname_ghe_cloud %}.'
|
||||
redirect_from:
|
||||
- /enterprise/admin/installation/enabling-automatic-user-license-sync-between-github-enterprise-server-and-github-enterprise-cloud
|
||||
- /enterprise/admin/configuration/enabling-automatic-user-license-sync-between-github-enterprise-server-and-github-enterprise-cloud
|
||||
@@ -19,10 +19,18 @@ shortTitle: Automatic user license sync
|
||||
---
|
||||
## About license synchronization
|
||||
|
||||
After you enable license synchronization, you'll be able to view license usage for your entire enterprise across {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %}. {% data variables.product.prodname_github_connect %} syncs license between {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %} weekly. For more information, see "[Managing your license for {% data variables.product.prodname_enterprise %}](/billing/managing-your-license-for-github-enterprise)."
|
||||
{% data reusables.enterprise-licensing.about-license-sync %} For more information, see "[About {% data variables.product.prodname_github_connect %}](/admin/configuration/configuring-github-connect/about-github-connect#data-transmission-for-github-connect)."
|
||||
|
||||
If you enable automatic user license sync for your enterprise, {% data variables.product.prodname_github_connect %} will automatically synchronize license usage between {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %} weekly.
|
||||
|
||||
If you use multiple {% data variables.product.prodname_ghe_server %} instances, you can enable automatic license sync between each of your instances and the same organization or enterprise account on {% data variables.product.prodname_ghe_cloud %}.
|
||||
|
||||
{% data reusables.enterprise-licensing.view-consumed-licenses %}
|
||||
|
||||
You can also manually upload {% data variables.product.prodname_ghe_server %} user license information to {% data variables.product.prodname_ghe_cloud %}. For more information, see "[Syncing license usage between {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %}](/billing/managing-your-license-for-github-enterprise/syncing-license-usage-between-github-enterprise-server-and-github-enterprise-cloud)."
|
||||
|
||||
{% data reusables.enterprise-licensing.verified-domains-license-sync %}
|
||||
|
||||
## Enabling license synchronization
|
||||
|
||||
Before enabling license synchronization on {% data variables.product.product_location %}, you must enable {% data variables.product.prodname_github_connect %}. For more information, see "[Managing {% data variables.product.prodname_github_connect %}](/admin/configuration/configuring-github-connect/managing-github-connect)."
|
||||
|
||||
@@ -16,13 +16,17 @@ shortTitle: Sync license usage
|
||||
|
||||
{% data reusables.enterprise-licensing.about-license-sync %}
|
||||
|
||||
If you allow {% data variables.product.product_location_enterprise %} to connect to your enterprise account on {% data variables.product.prodname_dotcom_the_website %}, you can sync license usage between the environments automatically. Automatic synchronization ensures that you see up-to-date license details on {% data variables.product.prodname_dotcom_the_website %}. If you don't want to allow {% data variables.product.product_location %} to connect to {% data variables.product.prodname_dotcom_the_website %}, you can manually sync license usage by uploading a file from {% data variables.product.product_location %} to {% data variables.product.prodname_dotcom_the_website %}.
|
||||
To ensure that you see up-to-date license details on {% data variables.product.prodname_dotcom_the_website %}, you can sync license usage between the environments automatically, using {% data variables.product.prodname_github_connect %}. For more information about {% data variables.product.prodname_github_connect %}, see "[About {% data variables.product.prodname_github_connect %}]({% ifversion ghec %}/enterprise-server@latest{% endif %}/admin/configuration/configuring-github-connect/about-github-connect){% ifversion ghec %}" in the {% data variables.product.prodname_ghe_server %} documentation.{% elsif ghes %}."{% endif %}
|
||||
|
||||
For more information about licenses and usage for {% data variables.product.prodname_ghe_server %}, see "[About licenses for {% data variables.product.prodname_enterprise %}](/billing/managing-your-license-for-github-enterprise/about-licenses-for-github-enterprise)."
|
||||
If you don't want to enable {% data variables.product.prodname_github_connect %}, you can manually sync license usage by uploading a file from {% data variables.product.prodname_ghe_server %} to {% data variables.product.prodname_dotcom_the_website %}.
|
||||
|
||||
{% data reusables.enterprise-licensing.view-consumed-licenses %}
|
||||
|
||||
{% data reusables.enterprise-licensing.verified-domains-license-sync %}
|
||||
|
||||
## Automatically syncing license usage
|
||||
|
||||
You can use {% data variables.product.prodname_github_connect %} to automatically sync user license count and usage between {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %}. For more information, see "[Enabling automatic user license sync for your enterprise]({% ifversion ghec %}/enterprise-server@latest{% endif %}/admin/configuration/configuring-github-connect/enabling-automatic-user-license-sync-for-your-enterprise){% ifversion ghec %}" in the {% data variables.product.prodname_ghe_server %} documentation.{% elsif ghes %}."{% endif %}
|
||||
You can use {% data variables.product.prodname_github_connect %} to automatically synchronize user license count and usage between {% data variables.product.prodname_ghe_server %} and {% data variables.product.prodname_ghe_cloud %}. For more information, see "[Enabling automatic user license sync for your enterprise]({% ifversion ghec %}/enterprise-server@latest{% endif %}/admin/configuration/configuring-github-connect/enabling-automatic-user-license-sync-for-your-enterprise){% ifversion ghec %}" in the {% data variables.product.prodname_ghe_server %} documentation.{% elsif ghes %}."{% endif %}
|
||||
|
||||
## Manually syncing license usage
|
||||
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
You can allocate the user count for your {% data variables.product.prodname_enterprise %} license to members of both {% data variables.product.product_location_enterprise %} and an enterprise account on {% data variables.product.prodname_ghe_cloud %}. When you add a user to either environment, the user will consume one license. If a user has accounts in both environments, to consume only one license, the user's primary email address on {% data variables.product.product_location_enterprise %} must be the same as the user's verified email address on {% data variables.product.prodname_dotcom_the_website %}. You can sync license count and usage between the environments.
|
||||
{% data variables.product.prodname_enterprise %} uses a unique-user licensing model, where each person only consumes one license, no matter how many {% data variables.product.prodname_ghe_server %} instances the person uses, or how many organizations the person is a member of on {% data variables.product.prodname_ghe_cloud %}. This model allows each person to use multiple {% data variables.product.prodname_enterprise %} environments without incurring extra costs.
|
||||
|
||||
For a person using multiple {% data variables.product.prodname_enterprise %} environments to only consume a single license, you must synchronize license usage between environments. Then, {% data variables.product.company_short %} will deduplicate users based on the email addresses associated with their personal accounts. Multiple personal accounts will consume a single license when there is a match between an account's primary email address on {% data variables.product.prodname_ghe_server %} and/or an account's verified email address on {% data variables.product.prodname_dotcom_the_website %}. For more information about verification of email addresses on {% data variables.product.prodname_dotcom_the_website %}, see "[Verifying your email address](/enterprise-cloud@latest/get-started/signing-up-for-github/verifying-your-email-address){% ifversion not ghec %}" in the {% data variables.product.prodname_ghe_cloud %} documentation.{% else %}."{% endif %}
|
||||
|
||||
When you synchronize license usage, only the user ID and email addresses for each personal account on {% data variables.product.prodname_ghe_server %} are transmitted to {% data variables.product.prodname_ghe_cloud %}.
|
||||
@@ -0,0 +1,5 @@
|
||||
{% note %}
|
||||
|
||||
**Note:** If you synchronize license usage and your enterprise account on {% data variables.product.prodname_dotcom_the_website %} does not use {% data variables.product.prodname_emus %}, we highly recommend enabling verified domains for your enterprise account on {% data variables.product.prodname_dotcom_the_website %}. For privacy reasons, your consumed license report only includes the email address associated with a personal account on {% data variables.product.prodname_dotcom_the_website %} if the address is hosted by a verified domain. If one person is erroneously consuming multiple licenses, having access to the email address that is being used for deduplication makes troubleshooting much easier. For more information. see "[Verifying or approving a domain for your enterprise](/enterprise-cloud@latest/admin/configuration/configuring-your-enterprise/verifying-or-approving-a-domain-for-your-enterprise)" and "[About {% data variables.product.prodname_emus %}](/enterprise-cloud@latest/admin/identity-and-access-management/managing-iam-with-enterprise-managed-users/about-enterprise-managed-users){% ifversion not ghec %}" in the {% data variables.product.prodname_ghe_cloud %} documentation.{% else %}."{% endif %}
|
||||
|
||||
{% endnote %}
|
||||
@@ -0,0 +1 @@
|
||||
After you synchronize license usage, you can see a report of consumed licenses across all your environments in the enterprise settings on {% data variables.product.prodname_dotcom_the_website %}. For more information, see "[Viewing license usage for {% data variables.product.prodname_enterprise %}](/enterprise-cloud@latest/billing/managing-your-license-for-github-enterprise/viewing-license-usage-for-github-enterprise)."
|
||||
@@ -9,9 +9,9 @@ const nonEnterpriseDefaultVersionPrefix = `/${nonEnterpriseDefaultVersion}`
|
||||
|
||||
// Return the new URI if there is one, otherwise return undefined.
|
||||
export default function getRedirect(uri, context) {
|
||||
const { redirects, pages } = context
|
||||
const { redirects, userLanguage } = context
|
||||
|
||||
let language = 'en'
|
||||
let language = userLanguage || 'en'
|
||||
let withoutLanguage = uri
|
||||
if (languagePrefixRegex.test(uri)) {
|
||||
language = uri.match(languagePrefixRegex)[1]
|
||||
@@ -109,12 +109,7 @@ export default function getRedirect(uri, context) {
|
||||
}
|
||||
|
||||
if (basicCorrection) {
|
||||
return (
|
||||
getRedirect(basicCorrection, {
|
||||
redirects,
|
||||
pages,
|
||||
}) || basicCorrection
|
||||
)
|
||||
return getRedirect(basicCorrection, context) || basicCorrection
|
||||
}
|
||||
|
||||
if (withoutLanguage.startsWith('/admin/')) {
|
||||
|
||||
@@ -30,11 +30,21 @@ const TRANSLATION_DRIFT_EXCEPTIONS = [
|
||||
* first since it's the most expensive work. This gets us a nested object with pages attached that we can use
|
||||
* as the basis for the siteTree after we do some versioning. We can also use it to derive the pageList.
|
||||
*/
|
||||
export async function loadUnversionedTree() {
|
||||
export async function loadUnversionedTree(languagesOnly = null) {
|
||||
if (languagesOnly && !Array.isArray(languagesOnly)) {
|
||||
throw new Error("'languagesOnly' has to be an array")
|
||||
}
|
||||
const unversionedTree = {}
|
||||
|
||||
const languagesValues = Object.entries(languages)
|
||||
.filter(([language]) => {
|
||||
return !languagesOnly || languagesOnly.includes(language)
|
||||
})
|
||||
.map(([, data]) => {
|
||||
return data
|
||||
})
|
||||
await Promise.all(
|
||||
Object.values(languages).map(async (langObj) => {
|
||||
languagesValues.map(async (langObj) => {
|
||||
const localizedContentPath = path.posix.join(__dirname, '..', langObj.dir, 'content')
|
||||
unversionedTree[langObj.code] = await createTree(localizedContentPath, langObj)
|
||||
})
|
||||
@@ -129,7 +139,10 @@ export async function versionPages(obj, version, langCode, site) {
|
||||
|
||||
// Derive a flat array of Page objects in all languages.
|
||||
export async function loadPageList(unversionedTree, languagesOnly = null) {
|
||||
const rawTree = unversionedTree || (await loadUnversionedTree())
|
||||
if (languagesOnly && !Array.isArray(languagesOnly)) {
|
||||
throw new Error("'languagesOnly' has to be an array")
|
||||
}
|
||||
const rawTree = unversionedTree || (await loadUnversionedTree(languagesOnly))
|
||||
const pageList = []
|
||||
|
||||
await Promise.all(
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import libLanguages from '../lib/languages.js'
|
||||
import languages, { languageKeys } from '../lib/languages.js'
|
||||
import parser from 'accept-language-parser'
|
||||
const languageCodes = Object.keys(libLanguages)
|
||||
|
||||
const chineseRegions = ['CN', 'HK']
|
||||
|
||||
// This value is replicated in two places! See <LanguagePicker/> component.
|
||||
// Note, the only reason this is exported is to benefit the tests.
|
||||
export const PREFERRED_LOCALE_COOKIE_NAME = 'preferredlang'
|
||||
|
||||
function translationExists(language) {
|
||||
if (language.code === 'zh') {
|
||||
return chineseRegions.includes(language.region)
|
||||
}
|
||||
return languageCodes.includes(language.code)
|
||||
return languageKeys.includes(language.code)
|
||||
}
|
||||
|
||||
function getLanguageCode(language) {
|
||||
@@ -17,33 +20,41 @@ function getLanguageCode(language) {
|
||||
|
||||
function getUserLanguage(browserLanguages) {
|
||||
try {
|
||||
let userLanguage = getLanguageCode(browserLanguages[0])
|
||||
let numTopPreferences = 1
|
||||
for (let lang = 0; lang < browserLanguages.length; lang++) {
|
||||
// If language has multiple regions, Chrome adds the non-region language to list
|
||||
if (lang > 0 && browserLanguages[lang].code !== browserLanguages[lang - 1].code)
|
||||
numTopPreferences++
|
||||
if (translationExists(browserLanguages[lang]) && numTopPreferences < 3) {
|
||||
userLanguage = getLanguageCode(browserLanguages[lang])
|
||||
break
|
||||
return getLanguageCode(browserLanguages[lang])
|
||||
}
|
||||
}
|
||||
return userLanguage
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// determine language code from a path. Default to en if no valid match
|
||||
export function getLanguageCodeFromPath(path) {
|
||||
const maybeLanguage = (path.split('/')[path.startsWith('/_next/data/') ? 4 : 1] || '').slice(0, 2)
|
||||
return languageCodes.includes(maybeLanguage) ? maybeLanguage : 'en'
|
||||
return languageKeys.includes(maybeLanguage) ? maybeLanguage : 'en'
|
||||
}
|
||||
|
||||
export default function detectLanguage(req, res, next) {
|
||||
req.language = getLanguageCodeFromPath(req.path)
|
||||
// Detecting browser language by user preference
|
||||
const browserLanguages = parser.parse(req.headers['accept-language'])
|
||||
req.userLanguage = getUserLanguage(browserLanguages)
|
||||
req.userLanguage = getUserLanguageFromCookie(req)
|
||||
if (!req.userLanguage) {
|
||||
const browserLanguages = parser.parse(req.headers['accept-language'])
|
||||
req.userLanguage = getUserLanguage(browserLanguages)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import patterns from '../../lib/patterns.js'
|
||||
import { URL } from 'url'
|
||||
import languages, { pathLanguagePrefixed } from '../../lib/languages.js'
|
||||
import { pathLanguagePrefixed } from '../../lib/languages.js'
|
||||
import getRedirect from '../../lib/get-redirect.js'
|
||||
import { cacheControlFactory } from '../cache-control.js'
|
||||
|
||||
@@ -13,16 +13,7 @@ export default function handleRedirects(req, res, next) {
|
||||
|
||||
// blanket redirects for languageless homepage
|
||||
if (req.path === '/') {
|
||||
let language = 'en'
|
||||
|
||||
// if set, redirect to user's preferred language translation or else English
|
||||
if (
|
||||
req.context.userLanguage &&
|
||||
languages[req.context.userLanguage] &&
|
||||
!languages[req.context.userLanguage].wip
|
||||
) {
|
||||
language = req.context.userLanguage
|
||||
}
|
||||
const language = getLanguage(req)
|
||||
|
||||
// Undo the cookie setting that CSRF sets.
|
||||
res.removeHeader('set-cookie')
|
||||
@@ -70,17 +61,12 @@ export default function handleRedirects(req, res, next) {
|
||||
// needs to become `/en/authentication/connecting-to-github-with-ssh`
|
||||
const possibleRedirectTo = `/en${req.path}`
|
||||
if (possibleRedirectTo in req.context.pages) {
|
||||
// As of Jan 2022 we always redirect to `/en` if the URL doesn't
|
||||
// specify a language. ...except for the root home page (`/`).
|
||||
// It's unfortunate but that's how it currently works.
|
||||
// It's tracked in #1145
|
||||
// Perhaps a more ideal solution would be to do something similar to
|
||||
// the code above for `req.path === '/'` where we look at the user
|
||||
// agent for a header and/or cookie.
|
||||
const language = getLanguage(req)
|
||||
|
||||
// Note, it's important to use `req.url` here and not `req.path`
|
||||
// because the full URL can contain query strings.
|
||||
// E.g. `/foo?json=breadcrumbs`
|
||||
redirect = `/en${req.url}`
|
||||
redirect = `/${language}${req.url}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +98,13 @@ export default function handleRedirects(req, res, next) {
|
||||
return res.redirect(permanent ? 301 : 302, redirect)
|
||||
}
|
||||
|
||||
function getLanguage(req, default_ = 'en') {
|
||||
// req.context.userLanguage, if it truthy, is always a valid supported
|
||||
// language. It's whatever was in the user's request but filtered
|
||||
// based on non-WIP languages in lib/languages.js
|
||||
return req.context.userLanguage || default_
|
||||
}
|
||||
|
||||
function usePermanentRedirect(req) {
|
||||
// If the redirect was to essentially swap `enterprise-server@latest`
|
||||
// for `enterprise-server@3.x` then, we definitely don't want to
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import { jest, beforeAll, expect } from '@jest/globals'
|
||||
import { jest, expect } from '@jest/globals'
|
||||
|
||||
import { getDOM } from '../helpers/supertest.js'
|
||||
import { loadPages } from '../../lib/page-data.js'
|
||||
|
||||
describe('process learning tracks', () => {
|
||||
let pageList
|
||||
|
||||
// Because calling `loadPages` will trigger a warmup, this can potentially
|
||||
// be very slow in CI. So we need a timeout.
|
||||
jest.setTimeout(60 * 1000)
|
||||
|
||||
beforeAll(async () => {
|
||||
// Only doing English because they're the only files we do PRs for.
|
||||
pageList = (await loadPages()).filter((page) => page.languageCode === 'en')
|
||||
})
|
||||
|
||||
test('pages with learningTracks ', async () => {
|
||||
const pageList = await loadPages(undefined, ['en'])
|
||||
|
||||
for (const page of pageList) {
|
||||
if (page.learningTracks && page.learningTracks.length > 0) {
|
||||
for (const permalink of page.permalinks) {
|
||||
|
||||
@@ -2,12 +2,14 @@ import { fileURLToPath } from 'url'
|
||||
import path from 'path'
|
||||
import { isPlainObject } from 'lodash-es'
|
||||
import supertest from 'supertest'
|
||||
import { jest } from '@jest/globals'
|
||||
|
||||
import createApp from '../../lib/app.js'
|
||||
import enterpriseServerReleases from '../../lib/enterprise-server-releases.js'
|
||||
import Page from '../../lib/page.js'
|
||||
import { get } from '../helpers/supertest.js'
|
||||
import versionSatisfiesRange from '../../lib/version-satisfies-range.js'
|
||||
import { jest } from '@jest/globals'
|
||||
import { PREFERRED_LOCALE_COOKIE_NAME } from '../../middleware/detect-language.js'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
@@ -132,6 +134,28 @@ describe('redirects', () => {
|
||||
expect(res.headers.location).toBe('/ja')
|
||||
expect(res.headers['cache-control']).toBe('private, no-store')
|
||||
})
|
||||
test('homepage redirects to preferred language by cookie', async () => {
|
||||
const res = await get('/', {
|
||||
headers: {
|
||||
Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`,
|
||||
'Accept-Language': 'es', // note how this is going to be ignored
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(302)
|
||||
expect(res.headers.location).toBe('/ja')
|
||||
expect(res.headers['cache-control']).toBe('private, no-store')
|
||||
})
|
||||
test('homepage redirects to preferred language by cookie if valid', async () => {
|
||||
const res = await get('/', {
|
||||
headers: {
|
||||
Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=xy`,
|
||||
'Accept-Language': 'ja', // note how this is going to be ignored
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(302)
|
||||
expect(res.headers.location).toBe('/ja')
|
||||
expect(res.headers['cache-control']).toBe('private, no-store')
|
||||
})
|
||||
})
|
||||
|
||||
describe('external redirects', () => {
|
||||
@@ -149,13 +173,63 @@ describe('redirects', () => {
|
||||
})
|
||||
|
||||
describe('localized redirects', () => {
|
||||
const redirectFrom =
|
||||
'/desktop/contributing-to-projects/changing-a-remote-s-url-from-github-desktop'
|
||||
const redirectTo =
|
||||
'/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/changing-a-remotes-url-from-github-desktop'
|
||||
|
||||
test('redirect_from for renamed pages', async () => {
|
||||
const { res } = await get(
|
||||
'/ja/desktop/contributing-to-projects/changing-a-remote-s-url-from-github-desktop'
|
||||
)
|
||||
const { res } = await get(`/ja${redirectFrom}`)
|
||||
expect(res.statusCode).toBe(301)
|
||||
const expected =
|
||||
'/ja/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/changing-a-remotes-url-from-github-desktop'
|
||||
const expected = `/ja${redirectTo}`
|
||||
expect(res.headers.location).toBe(expected)
|
||||
})
|
||||
|
||||
test('redirect_from for renamed pages by Accept-Language header', async () => {
|
||||
const { res } = await get(redirectFrom, {
|
||||
headers: {
|
||||
'Accept-Language': 'ja',
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(302)
|
||||
const expected = `/ja${redirectTo}`
|
||||
expect(res.headers.location).toBe(expected)
|
||||
})
|
||||
|
||||
test('redirect_from for renamed pages but ignore Accept-Language header if not recognized', async () => {
|
||||
const { res } = await get(redirectFrom, {
|
||||
headers: {
|
||||
// None of these are recognized
|
||||
'Accept-Language': 'sv,fr,gr',
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(302)
|
||||
const expected = `/en${redirectTo}`
|
||||
expect(res.headers.location).toBe(expected)
|
||||
})
|
||||
|
||||
test('redirect_from for renamed pages but ignore unrecognized Accept-Language header values', async () => {
|
||||
const { res } = await get(redirectFrom, {
|
||||
headers: {
|
||||
// Only the last one is recognized
|
||||
'Accept-Language': 'sv,ja',
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(302)
|
||||
const expected = `/ja${redirectTo}`
|
||||
expect(res.headers.location).toBe(expected)
|
||||
})
|
||||
|
||||
test('will inject the preferred language from cookie', async () => {
|
||||
const { res } = await get(redirectFrom, {
|
||||
headers: {
|
||||
Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`,
|
||||
'Accept-Language': 'es', // note how this is going to be ignored
|
||||
},
|
||||
})
|
||||
// 302 because the redirect depended on cookie
|
||||
expect(res.statusCode).toBe(302)
|
||||
const expected = `/ja${redirectTo}`
|
||||
expect(res.headers.location).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -148,4 +148,18 @@ describe('getRedirect basics', () => {
|
||||
// it already has the enterprise-server prefix.
|
||||
expect(getRedirect('/enterprise-server/foo', ctx)).toBe(`/en/enterprise-server@${latest}/bar`)
|
||||
})
|
||||
|
||||
it('should redirect according to the req.context.userLanguage', () => {
|
||||
const ctx = {
|
||||
pages: {},
|
||||
redirects: {
|
||||
'/foo': '/bar',
|
||||
},
|
||||
userLanguage: 'ja',
|
||||
}
|
||||
expect(getRedirect('/foo', ctx)).toBe(`/ja/bar`)
|
||||
// falls back to 'en' if it's falsy
|
||||
ctx.userLanguage = null
|
||||
expect(getRedirect('/foo', ctx)).toBe(`/en/bar`)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user