未验证 提交 245499a0 编写于 作者: J JJ Kasper 提交者: GitHub

Handle preferring default locale over accept-lang preferred locale (#17883)

This updates to set the `NEXT_LOCALE` cookie to the default locale when the user prefers a different locale from the default in their `accept-language` header but visits the default locale path e.g. `/en-US` with a `accept-language` preferred header of `nl` will set the `NEXT_LOCALE=en-US` header and then redirect to `/`

x-ref: https://github.com/vercel/next.js/pull/17370
上级 7cf7c2f1
...@@ -221,12 +221,17 @@ const nextServerlessLoader: loader.Loader = function () { ...@@ -221,12 +221,17 @@ const nextServerlessLoader: loader.Loader = function () {
// get pathname from URL with basePath stripped for locale detection // get pathname from URL with basePath stripped for locale detection
const i18n = ${i18n} const i18n = ${i18n}
const accept = require('@hapi/accept') const accept = require('@hapi/accept')
const cookie = require('next/dist/compiled/cookie')
const { detectLocaleCookie } = require('next/dist/next-server/lib/i18n/detect-locale-cookie') const { detectLocaleCookie } = require('next/dist/next-server/lib/i18n/detect-locale-cookie')
const { detectDomainLocale } = require('next/dist/next-server/lib/i18n/detect-domain-locale') const { detectDomainLocale } = require('next/dist/next-server/lib/i18n/detect-domain-locale')
const { normalizeLocalePath } = require('next/dist/next-server/lib/i18n/normalize-locale-path') const { normalizeLocalePath } = require('next/dist/next-server/lib/i18n/normalize-locale-path')
let locales = i18n.locales let locales = i18n.locales
let defaultLocale = i18n.defaultLocale let defaultLocale = i18n.defaultLocale
let detectedLocale = detectLocaleCookie(req, i18n.locales) let detectedLocale = detectLocaleCookie(req, i18n.locales)
let acceptPreferredLocale = accept.language(
req.headers['accept-language'],
i18n.locales
)
const detectedDomain = detectDomainLocale( const detectedDomain = detectDomainLocale(
i18n.domains, i18n.domains,
...@@ -237,12 +242,8 @@ const nextServerlessLoader: loader.Loader = function () { ...@@ -237,12 +242,8 @@ const nextServerlessLoader: loader.Loader = function () {
detectedLocale = defaultLocale detectedLocale = defaultLocale
} }
if (!detectedLocale) { // if not domain specific locale use accept-language preferred
detectedLocale = accept.language( detectedLocale = detectedLocale || acceptPreferredLocale
req.headers['accept-language'],
i18n.locales
)
}
let localeDomainRedirect let localeDomainRedirect
const localePathResult = normalizeLocalePath(parsedUrl.pathname, i18n.locales) const localePathResult = normalizeLocalePath(parsedUrl.pathname, i18n.locales)
...@@ -279,6 +280,7 @@ const nextServerlessLoader: loader.Loader = function () { ...@@ -279,6 +280,7 @@ const nextServerlessLoader: loader.Loader = function () {
const shouldStripDefaultLocale = const shouldStripDefaultLocale =
detectedDefaultLocale && detectedDefaultLocale &&
denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\` denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\`
const shouldAddLocalePrefix = const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/' !detectedDefaultLocale && denormalizedPagePath === '/'
...@@ -294,6 +296,30 @@ const nextServerlessLoader: loader.Loader = function () { ...@@ -294,6 +296,30 @@ const nextServerlessLoader: loader.Loader = function () {
shouldStripDefaultLocale shouldStripDefaultLocale
) )
) { ) {
// set the NEXT_LOCALE cookie when a user visits the default locale
// with the locale prefix so that they aren't redirected back to
// their accept-language preferred locale
if (
shouldStripDefaultLocale &&
acceptPreferredLocale !== defaultLocale
) {
const previous = res.getHeader('set-cookie')
res.setHeader(
'set-cookie',
[
...(typeof previous === 'string'
? [previous]
: Array.isArray(previous)
? previous
: []),
cookie.serialize('NEXT_LOCALE', defaultLocale, {
httpOnly: true,
path: '/',
})
])
}
res.setHeader( res.setHeader(
'Location', 'Location',
formatUrl({ formatUrl({
......
...@@ -80,6 +80,7 @@ import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path' ...@@ -80,6 +80,7 @@ import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path'
import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie' import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie'
import * as Log from '../../build/output/log' import * as Log from '../../build/output/log'
import { detectDomainLocale } from '../lib/i18n/detect-domain-locale' import { detectDomainLocale } from '../lib/i18n/detect-domain-locale'
import cookie from 'next/dist/compiled/cookie'
const getCustomRouteMatcher = pathMatch(true) const getCustomRouteMatcher = pathMatch(true)
...@@ -310,6 +311,10 @@ export default class Server { ...@@ -310,6 +311,10 @@ export default class Server {
const { pathname, ...parsed } = parseUrl(req.url || '/') const { pathname, ...parsed } = parseUrl(req.url || '/')
let defaultLocale = i18n.defaultLocale let defaultLocale = i18n.defaultLocale
let detectedLocale = detectLocaleCookie(req, i18n.locales) let detectedLocale = detectLocaleCookie(req, i18n.locales)
let acceptPreferredLocale = accept.language(
req.headers['accept-language'],
i18n.locales
)
const detectedDomain = detectDomainLocale(i18n.domains, req) const detectedDomain = detectDomainLocale(i18n.domains, req)
if (detectedDomain) { if (detectedDomain) {
...@@ -317,12 +322,8 @@ export default class Server { ...@@ -317,12 +322,8 @@ export default class Server {
detectedLocale = defaultLocale detectedLocale = defaultLocale
} }
if (!detectedLocale) { // if not domain specific locale use accept-language preferred
detectedLocale = accept.language( detectedLocale = detectedLocale || acceptPreferredLocale
req.headers['accept-language'],
i18n.locales
)
}
let localeDomainRedirect: string | undefined let localeDomainRedirect: string | undefined
const localePathResult = normalizeLocalePath(pathname!, i18n.locales) const localePathResult = normalizeLocalePath(pathname!, i18n.locales)
...@@ -360,6 +361,7 @@ export default class Server { ...@@ -360,6 +361,7 @@ export default class Server {
detectedDefaultLocale && detectedDefaultLocale &&
denormalizedPagePath.toLowerCase() === denormalizedPagePath.toLowerCase() ===
`/${i18n.defaultLocale.toLowerCase()}` `/${i18n.defaultLocale.toLowerCase()}`
const shouldAddLocalePrefix = const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/' !detectedDefaultLocale && denormalizedPagePath === '/'
...@@ -371,6 +373,28 @@ export default class Server { ...@@ -371,6 +373,28 @@ export default class Server {
shouldAddLocalePrefix || shouldAddLocalePrefix ||
shouldStripDefaultLocale) shouldStripDefaultLocale)
) { ) {
// set the NEXT_LOCALE cookie when a user visits the default locale
// with the locale prefix so that they aren't redirected back to
// their accept-language preferred locale
if (
shouldStripDefaultLocale &&
acceptPreferredLocale !== defaultLocale
) {
const previous = res.getHeader('set-cookie')
res.setHeader('set-cookie', [
...(typeof previous === 'string'
? [previous]
: Array.isArray(previous)
? previous
: []),
cookie.serialize('NEXT_LOCALE', defaultLocale, {
httpOnly: true,
path: '/',
}),
])
}
res.setHeader( res.setHeader(
'Location', 'Location',
formatUrl({ formatUrl({
......
...@@ -132,6 +132,43 @@ function runTests(isDev) { ...@@ -132,6 +132,43 @@ function runTests(isDev) {
expect(result2.query).toEqual({}) expect(result2.query).toEqual({})
}) })
it('should set locale cookie when removing default locale and accept-lang doesnt match', async () => {
const res = await fetchViaHTTP(appPort, '/en-US', undefined, {
headers: {
'accept-language': 'nl',
},
redirect: 'manual',
})
expect(res.status).toBe(307)
const parsedUrl = url.parse(res.headers.get('location'), true)
expect(parsedUrl.pathname).toBe('/')
expect(parsedUrl.query).toEqual({})
expect(res.headers.get('set-cookie')).toContain('NEXT_LOCALE=en-US')
})
it('should not redirect to accept-lang preferred locale with locale cookie', async () => {
const res = await fetchViaHTTP(appPort, '/', undefined, {
headers: {
'accept-language': 'nl',
cookie: 'NEXT_LOCALE=en-US',
},
redirect: 'manual',
})
expect(res.status).toBe(200)
const html = await res.text()
const $ = cheerio.load(html)
expect($('#router-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
})
it('should redirect to correct locale domain', async () => { it('should redirect to correct locale domain', async () => {
const checks = [ const checks = [
// test domain, locale prefix, redirect result // test domain, locale prefix, redirect result
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册