fix: attempt to use user locale preference when not set as query param (#6761)

Fixes https://github.com/payloadcms/payload/issues/6619

Attempt to use user preference if available when loading view data instead of always relying on query param when loading view data.
This commit is contained in:
Jarrod Flesch
2024-06-13 11:22:28 -04:00
committed by GitHub
parent 11de4b037d
commit 082650c0e2
9 changed files with 107 additions and 35 deletions

View File

@@ -1,4 +1,5 @@
import type { I18nClient } from '@payloadcms/translations'
import type { Locale } from 'payload/config'
import type { InitPageResult, PayloadRequestWithData, VisibleEntities } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
@@ -22,7 +23,6 @@ export const initPage = async ({
searchParams,
}: Args): Promise<InitPageResult> => {
const headers = getHeaders()
const localeParam = searchParams?.locale as string
const payload = await getPayloadHMR({ config: configPromise })
const {
@@ -34,10 +34,6 @@ export const initPage = async ({
} = payload.config
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
const defaultLocale =
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
const localeCode = localeParam || defaultLocale
const locale = localization && findLocaleFromCode(localization, localeCode)
const cookies = parseCookies(headers)
const language = getRequestLanguage({ config: payload.config, cookies, headers })
@@ -64,7 +60,6 @@ export const initPage = async ({
const req = await createLocalReq(
{
fallbackLocale: null,
locale: locale.code,
req: {
host: headers.get('host'),
i18n,
@@ -79,9 +74,53 @@ export const initPage = async ({
)
const { permissions, user } = await payload.auth({ headers, req })
req.user = user
const localeParam = searchParams?.locale as string
let locale: Locale
if (localization) {
const defaultLocaleCode = localization.defaultLocale ? localization.defaultLocale : 'en'
let localeCode: string = localeParam
if (!localeCode) {
try {
localeCode = await payload
.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
user,
where: {
and: [
{
'user.relationTo': {
equals: payload.config.admin.user,
},
},
{
'user.value': {
equals: user.id,
},
},
{
key: {
equals: 'locale',
},
},
],
},
})
?.then((res) => res.docs?.[0]?.value as string)
} catch (error) {} // eslint-disable-line no-empty
}
locale = findLocaleFromCode(localization, localeCode)
if (!locale) locale = findLocaleFromCode(localization, defaultLocaleCode)
req.locale = locale.code
}
const visibleEntities: VisibleEntities = {
collections: collections
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))

View File

@@ -91,7 +91,7 @@ export const Account: React.FC<AdminViewProps> = async ({
initialParams={{
depth: 0,
'fallback-locale': 'null',
locale: locale.code,
locale: locale?.code,
uploadEdits: undefined,
}}
>

View File

@@ -26,7 +26,7 @@ export const getDocumentData = async (args: {
id,
collectionSlug: collectionConfig?.slug,
globalSlug: globalConfig?.slug,
locale: locale.code,
locale: locale?.code,
operation: (collectionConfig && id) || globalConfig ? 'update' : 'create',
schemaPath: collectionConfig?.slug || globalConfig?.slug,
},

View File

@@ -10,6 +10,7 @@ import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth'
import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParams'
import { isEditing as getIsEditing } from '@payloadcms/ui/utilities/isEditing'
import { notFound, redirect } from 'next/navigation.js'
import QueryString from 'qs'
import React from 'react'
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
@@ -85,10 +86,14 @@ export const Document: React.FC<AdminViewProps> = async ({
}
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale.code}${
collectionConfig.versions?.drafts ? '&draft=true' : ''
}`
const apiQueryParams = QueryString.stringify(
{
draft: collectionConfig.versions?.drafts ? 'true' : undefined,
locale: locale?.code,
},
{ addQueryPrefix: true },
)
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}${apiQueryParams}`
const editConfig = collectionConfig?.admin?.components?.views?.Edit
ViewOverride = typeof editConfig === 'function' ? editConfig : null
@@ -118,9 +123,14 @@ export const Document: React.FC<AdminViewProps> = async ({
action = `${serverURL}${apiRoute}/globals/${globalSlug}`
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
globalConfig.versions?.drafts ? '&draft=true' : ''
}`
const apiQueryParams = QueryString.stringify(
{
draft: globalConfig.versions?.drafts ? 'true' : undefined,
locale: locale?.code,
},
{ addQueryPrefix: true },
)
apiURL = `${serverURL}${apiRoute}/${globalSlug}${apiQueryParams}`
const editConfig = globalConfig?.admin?.components?.views?.Edit
ViewOverride = typeof editConfig === 'function' ? editConfig : null
@@ -161,7 +171,7 @@ export const Document: React.FC<AdminViewProps> = async ({
depth: 0,
draft: true,
fallbackLocale: null,
locale: locale.code,
locale: locale?.code,
req,
user,
})
@@ -206,12 +216,15 @@ export const Document: React.FC<AdminViewProps> = async ({
/>
)}
<HydrateClientUser permissions={permissions} user={user} />
<EditDepthProvider depth={1} key={`${collectionSlug || globalSlug}-${locale.code}`}>
<EditDepthProvider
depth={1}
key={`${collectionSlug || globalSlug}${locale?.code ? `-${locale?.code}` : ''}`}
>
<FormQueryParamsProvider
initialParams={{
depth: 0,
'fallback-locale': 'null',
locale: locale.code,
locale: locale?.code,
uploadEdits: undefined,
}}
>

View File

@@ -42,7 +42,7 @@ export type InitPageResult = {
docID?: string
globalConfig?: SanitizedGlobalConfig
languageOptions: LanguageOptions
locale: Locale
locale?: Locale
permissions: Permissions
req: PayloadRequestWithData
translations: ClientTranslationsObject

View File

@@ -48,7 +48,7 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
const { Edit } = componentMap[`${collectionSlug ? 'collections' : 'globals'}`][collectionSlug]
const isEditing = Boolean(docID)
const apiURL = docID
? `${serverURL}${apiRoute}/${collectionSlug}/${docID}?locale=${locale.code}`
? `${serverURL}${apiRoute}/${collectionSlug}/${docID}${locale?.code ? `?locale=${locale.code}` : ''}`
: null
const action = `${serverURL}${apiRoute}/${collectionSlug}${
isEditing ? `/${docID}` : ''

View File

@@ -53,14 +53,17 @@ export const DuplicateDocument: React.FC<Props> = ({ id, slug, singularLabel })
return
}
await requests
.post(`${serverURL}${api}/${slug}/${id}/duplicate?locale=${locale.code}`, {
body: JSON.stringify({}),
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
credentials: 'include',
.post(
`${serverURL}${api}/${slug}/${id}/duplicate${locale?.code ? `?locale=${locale.code}` : ''}`,
{
body: JSON.stringify({}),
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
credentials: 'include',
},
},
})
)
.then(async (res) => {
const { doc, errors, message } = await res.json()
if (res.status < 400) {
@@ -69,7 +72,9 @@ export const DuplicateDocument: React.FC<Props> = ({ id, slug, singularLabel })
t('general:successfullyDuplicated', { label: getTranslation(singularLabel, i18n) }),
)
setModified(false)
router.push(`${admin}/collections/${slug}/${doc.id}?locale=${locale.code}`)
router.push(
`${admin}/collections/${slug}/${doc.id}${locale?.code ? `?locale=${locale.code}` : ''}`,
)
} else {
toast.error(
errors?.[0].message ||

View File

@@ -46,12 +46,14 @@ export const FormQueryParamsProvider: React.FC<{
const locale = useLocale()
React.useEffect(() => {
dispatchFormQueryParams({
type: 'SET',
params: {
locale: locale.code,
},
})
if (locale?.code) {
dispatchFormQueryParams({
type: 'SET',
params: {
locale: locale.code,
},
})
}
}, [locale.code])
return (

View File

@@ -221,6 +221,19 @@ describe('Localization', () => {
await expect(page.locator('.id-label')).not.toContainText(originalID)
})
})
describe('locale preference', () => {
test('ensure preference is used when query param is not', async () => {
await page.goto(url.create)
await changeLocale(page, spanishLocale)
await expect(page.locator('#field-title')).toBeEmpty()
await fillValues({ title: spanishTitle })
await saveDocAndAssert(page)
await page.goto(url.admin)
await page.goto(url.list)
await expect(page.locator('.row-1 .cell-title')).toContainText(spanishTitle)
})
})
})
async function fillValues(data: Partial<LocalizedPost>) {