fix(next): ensures requested lang header is supported (#5765)

This commit is contained in:
Jarrod Flesch
2024-04-10 13:05:56 -04:00
committed by GitHub
parent 2deeb61f17
commit 364e9832ac
9 changed files with 43 additions and 38 deletions

View File

@@ -1,2 +1,2 @@
export { getNextI18n } from '../utilities/getNextI18n.js'
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
export { getPayloadHMR } from '../utilities/getPayloadHMR.js'

View File

@@ -1,20 +0,0 @@
import type { AcceptedLanguages, I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
export const getNextI18n = async ({
config,
language,
}: {
config: SanitizedConfig
language?: AcceptedLanguages
}): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',
language: language || getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
})

View File

@@ -0,0 +1,19 @@
import type { I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
/**
* In the context of NextJS, this function initializes the i18n object for the current request.
*
* It must be called on the server side, and within the lifecycle of a request since it relies on the request headers and cookies.
*/
export const getNextRequestI18n = async ({ config }: { config: SanitizedConfig }): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',
language: getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
})

View File

@@ -2,7 +2,7 @@ import type { AcceptedLanguages } from '@payloadcms/translations'
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies.js'
import type { SanitizedConfig } from 'payload/config'
import { matchLanguage } from '@payloadcms/translations'
import { extractHeaderLanguage } from '@payloadcms/translations'
type GetRequestLanguageArgs = {
config: SanitizedConfig
@@ -17,14 +17,20 @@ export const getRequestLanguage = ({
defaultLanguage = 'en',
headers,
}: GetRequestLanguageArgs): AcceptedLanguages => {
const acceptLanguage = headers.get('Accept-Language')
const cookieLanguage = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
const langCookie = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
const languageFromCookie = typeof langCookie === 'string' ? langCookie : langCookie?.value
const languageFromHeader = extractHeaderLanguage(headers.get('Accept-Language'))
const fallbackLang = config?.i18n?.fallbackLanguage || defaultLanguage
const reqLanguage =
(typeof cookieLanguage === 'string' ? cookieLanguage : cookieLanguage?.value) ||
acceptLanguage ||
config.i18n.fallbackLanguage ||
defaultLanguage
const supportedLanguageKeys = Object.keys(config?.i18n?.supportedLanguages || {})
return matchLanguage(reqLanguage) || defaultLanguage
if (languageFromCookie && supportedLanguageKeys.includes(languageFromCookie)) {
return languageFromCookie as AcceptedLanguages
}
if (languageFromHeader && supportedLanguageKeys.includes(languageFromHeader)) {
return languageFromHeader
}
return supportedLanguageKeys.includes(fallbackLang) ? (fallbackLang as AcceptedLanguages) : 'en'
}

View File

@@ -3,7 +3,7 @@ import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/t
import type { GenerateViewMetadata } from '../Root/index.js'
import { getNextI18n } from '../../utilities/getNextI18n.js'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateMetadata as apiMeta } from '../API/meta.js'
import { generateMetadata as editMeta } from '../Edit/meta.js'
import { generateMetadata as livePreviewMeta } from '../LivePreview/meta.js'
@@ -89,7 +89,7 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
}
}
const i18n = await getNextI18n({
const i18n = await getNextRequestI18n({
config,
})

View File

@@ -6,8 +6,8 @@ import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
import React, { Fragment } from 'react'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { initPage } from '../../utilities/initPage.js'
import { getNextI18n } from '.././../utilities/getNextI18n.js'
import { NotFoundClient } from './index.client.js'
export const generatePageMetadata = async ({
@@ -19,7 +19,7 @@ export const generatePageMetadata = async ({
}): Promise<Metadata> => {
const config = await configPromise
const i18n = await getNextI18n({
const i18n = await getNextRequestI18n({
config,
})

View File

@@ -1,7 +1,7 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { getNextI18n } from '../../utilities/getNextI18n.js'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateAccountMetadata } from '../Account/index.js'
import { generateCreateFirstUserMetadata } from '../CreateFirstUser/index.js'
import { generateDashboardMetadata } from '../Dashboard/index.js'
@@ -49,7 +49,7 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
const i18n = await getNextI18n({
const i18n = await getNextRequestI18n({
config,
})

View File

@@ -1,5 +1,5 @@
export { importDateFNSLocale } from '../importDateFNSLocale.js'
export { getTranslation } from '../utilities/getTranslation.js'
export { initI18n, t } from '../utilities/init.js'
export { acceptedLanguages, matchLanguage, rtlLanguages } from '../utilities/languages.js'
export { acceptedLanguages, extractHeaderLanguage, rtlLanguages } from '../utilities/languages.js'
export type * from '../types.js'

View File

@@ -149,7 +149,7 @@ function parseAcceptLanguage(acceptLanguageHeader: string): LanguagePreference[]
.sort((a, b) => b.quality - a.quality) // Sort by quality, highest to lowest
}
export function matchLanguage(acceptLanguageHeader: string): AcceptedLanguages | undefined {
export function extractHeaderLanguage(acceptLanguageHeader: string): AcceptedLanguages | undefined {
const parsedHeader = parseAcceptLanguage(acceptLanguageHeader)
let matchedLanguage: AcceptedLanguages