Compare commits

...

8 Commits

Author SHA1 Message Date
Jessica Chowdhury
fe028f39ee chore: merge conflicts 2025-08-08 13:49:43 +01:00
Jessica Chowdhury
4a1bb3c405 chore: add locale to URL for relationship drawer links 2025-07-09 15:45:38 +01:00
Jessica Chowdhury
17425e999e chore: ignore locale redirect for logout path 2025-07-09 15:30:35 +01:00
Jessica Chowdhury
2a0d9f1f78 Merge branch 'main' into fix/locale-stuck-on-back 2025-07-09 13:20:48 +01:00
Jessica Chowdhury
c02c0d6fa8 chore: revert changes to localizer and apply redirect at initPage 2025-07-09 11:02:31 +01:00
Jessica Chowdhury
3a61b82f9e Merge branch 'main' into fix/locale-stuck-on-back 2025-07-08 16:27:07 +01:00
Jessica Chowdhury
c4d9c90506 fix(ui): revert locale provider change and set locale search param from localizer 2025-07-08 16:05:30 +01:00
Jessica Chowdhury
8e0b9b4aa2 fix(ui): reset locale to default when none specified in URL 2025-07-08 12:08:04 +01:00
7 changed files with 81 additions and 18 deletions

View File

@@ -4,7 +4,14 @@ import type { groupNavItems } from '@payloadcms/ui/shared'
import type { NavPreferences } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { BrowseByFolderButton, Link, NavGroup, useConfig, useTranslation } from '@payloadcms/ui'
import {
BrowseByFolderButton,
Link,
NavGroup,
useConfig,
useLocale,
useTranslation,
} from '@payloadcms/ui'
import { EntityType } from '@payloadcms/ui/shared'
import { usePathname } from 'next/navigation.js'
import { formatAdminURL } from 'payload/shared'
@@ -24,12 +31,15 @@ export const DefaultNavClient: React.FC<{
routes: { browseByFolder: foldersRoute },
},
folders,
localization,
routes: { admin: adminRoute },
},
} = useConfig()
const { i18n } = useTranslation()
const locale = useLocale()
const folderURL = formatAdminURL({
adminRoute,
path: foldersRoute,
@@ -37,6 +47,8 @@ export const DefaultNavClient: React.FC<{
const viewingRootFolderView = pathname.startsWith(folderURL)
const addLocaleToURL = localization && locale.code ? `?locale=${locale.code}` : ''
return (
<Fragment>
{folders && folders.browseByFolder && <BrowseByFolderButton active={viewingRootFolderView} />}
@@ -48,12 +60,12 @@ export const DefaultNavClient: React.FC<{
let id: string
if (type === EntityType.collection) {
href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })
href = `${formatAdminURL({ adminRoute, path: `/collections/${slug}` })}${addLocaleToURL}`
id = `nav-${slug}`
}
if (type === EntityType.global) {
href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })
href = `${formatAdminURL({ adminRoute, path: `/globals/${slug}` })}${addLocaleToURL}`
id = `nav-global-${slug}`
}

View File

@@ -21,6 +21,10 @@ export const initPage = async ({
useLayoutReq,
}: Args): Promise<InitPageResult> => {
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
const query = qs.parse(queryString, {
depth: 10,
ignoreQueryPrefix: true,
})
const {
cookies,
@@ -35,10 +39,7 @@ export const initPage = async ({
overrides: {
fallbackLocale: false,
req: {
query: qs.parse(queryString, {
depth: 10,
ignoreQueryPrefix: true,
}),
query,
routeParams,
},
urlSuffix: `${route}${searchParams ? queryString : ''}`,
@@ -46,6 +47,7 @@ export const initPage = async ({
})
const {
admin: { routes },
collections,
globals,
routes: { admin: adminRoute },
@@ -80,6 +82,12 @@ export const initPage = async ({
let redirectTo = null
const logoutPath = `${adminRoute || '/admin'}${routes.logout || '/logout'}`
if (req?.user && payload.config?.localization && !query.locale && req.pathname !== logoutPath) {
redirectTo = `${req.pathname}${queryString ? '&' : '?'}locale=${locale.code}`
}
if (
!permissions.canAccessAdmin &&
!isPublicAdminRoute({ adminRoute, config: payload.config, route }) &&

View File

@@ -46,6 +46,7 @@ export function DefaultDashboard(props: DashboardViewServerProps) {
admin: {
components: { afterDashboard, beforeDashboard },
},
localization,
routes: { admin: adminRoute },
},
},
@@ -55,6 +56,8 @@ export function DefaultDashboard(props: DashboardViewServerProps) {
user,
} = props
const addLocaleToURL = localization && locale.code ? `?locale=${locale.code}` : ''
return (
<div className={baseClass}>
<Gutter className={`${baseClass}__wrap`}>
@@ -96,11 +99,14 @@ export function DefaultDashboard(props: DashboardViewServerProps) {
buttonAriaLabel = t('general:showAllLabel', { label: title })
href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })
href = formatAdminURL({
adminRoute,
path: `/collections/${slug}${addLocaleToURL}`,
})
createHREF = formatAdminURL({
adminRoute,
path: `/collections/${slug}/create`,
path: `/collections/${slug}/create${addLocaleToURL}`,
})
hasCreatePermission = permissions?.collections?.[slug]?.create
@@ -115,7 +121,7 @@ export function DefaultDashboard(props: DashboardViewServerProps) {
href = formatAdminURL({
adminRoute,
path: `/globals/${slug}`,
path: `/globals/${slug}${addLocaleToURL}`,
})
// Find the lock status for the global

View File

@@ -5,6 +5,7 @@ import React, { useEffect, useRef, useState } from 'react'
import { Account } from '../../graphics/Account/index.js'
import { useActions } from '../../providers/Actions/index.js'
import { useConfig } from '../../providers/Config/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { Hamburger } from '../Hamburger/index.js'
import { Link } from '../Link/index.js'
@@ -37,6 +38,8 @@ export function AppHeader({ CustomAvatar, CustomIcon }: Props) {
},
} = useConfig()
const locale = useLocale()
const { navOpen } = useNav()
const customControlsRef = useRef<HTMLDivElement>(null)
@@ -61,6 +64,8 @@ export function AppHeader({ CustomAvatar, CustomIcon }: Props) {
const ActionComponents = Actions ? Object.values(Actions) : []
const addLocaleToURL = localization && locale.code ? `?locale=${locale.code}` : ''
return (
<header className={[baseClass, navOpen && `${baseClass}--nav-open`].filter(Boolean).join(' ')}>
<div className={`${baseClass}__bg`} />
@@ -96,7 +101,7 @@ export function AppHeader({ CustomAvatar, CustomIcon }: Props) {
<Link
aria-label={t('authentication:account')}
className={`${baseClass}__account`}
href={formatAdminURL({ adminRoute, path: accountRoute })}
href={formatAdminURL({ adminRoute, path: accountRoute }) + addLocaleToURL}
prefetch={false}
tabIndex={0}
>

View File

@@ -5,6 +5,7 @@ import './index.scss'
import { Link } from '../../elements/Link/index.js'
import { useConfig } from '../../providers/Config/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { formatAdminURL } from '../../utilities/formatAdminURL.js'
import { sanitizeID } from '../../utilities/sanitizeID.js'
import { useDrawerDepth } from '../Drawer/index.js'
@@ -18,17 +19,21 @@ export const IDLabel: React.FC<{ className?: string; id: string; prefix?: string
}) => {
const {
config: {
localization,
routes: { admin: adminRoute },
},
} = useConfig()
const { collectionSlug, globalSlug } = useDocumentInfo()
const drawerDepth = useDrawerDepth()
const locale = useLocale()
const docPath = formatAdminURL({
adminRoute,
path: `/${collectionSlug ? `collections/${collectionSlug}` : `globals/${globalSlug}`}/${id}`,
})
const addLocaleToURL = localization && locale.code ? `?locale=${locale.code}` : ''
const docPath =
formatAdminURL({
adminRoute,
path: `/${collectionSlug ? `collections/${collectionSlug}` : `globals/${globalSlug}`}/${id}`,
}) + addLocaleToURL
return (
<div className={[baseClass, className].filter(Boolean).join(' ')} title={id}>

View File

@@ -6,6 +6,7 @@ import { fieldAffectsData, fieldIsID } from 'payload/shared'
import React from 'react' // TODO: abstract this out to support all routers
import { useConfig } from '../../../providers/Config/index.js'
import { useLocale } from '../../../providers/Locale/index.js'
import { useTranslation } from '../../../providers/Translation/index.js'
import { formatAdminURL } from '../../../utilities/formatAdminURL.js'
import { getDisplayedFieldValue } from '../../../utilities/getDisplayedFieldValue.js'
@@ -31,6 +32,7 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
const {
config: {
localization,
routes: { admin: adminRoute },
},
getEntityConfig,
@@ -38,6 +40,8 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
const collectionConfig = getEntityConfig({ collectionSlug })
const locale = useLocale()
const classNameFromConfigContext = admin && 'className' in admin ? admin.className : undefined
const className =
@@ -59,14 +63,16 @@ export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
className,
}
const addLocaleToURL = localization && locale.code ? `?locale=${locale.code}` : ''
if (link) {
wrapElementProps.prefetch = false
WrapElement = Link
wrapElementProps.href = collectionConfig?.slug
? formatAdminURL({
adminRoute,
path: `/collections/${collectionConfig?.slug}${viewType === 'trash' ? '/trash' : ''}/${encodeURIComponent(rowData.id)}`,
})
adminRoute,
path: `/collections/${collectionConfig?.slug}${viewType === 'trash' ? '/trash' : ''}/${encodeURIComponent(rowData.id)}${addLocaleToURL}`,
})
: ''
}

View File

@@ -657,6 +657,27 @@ describe('Localization', () => {
await expect(searchInput).toHaveAttribute('placeholder', 'Search by Full title')
})
test('should not persist locale after back navigation', async () => {
await changeLocale(page, defaultLocale)
await page.goto(url.create)
await page.locator('#field-title').fill(title)
await saveDocAndAssert(page)
await changeLocale(page, spanishLocale)
await page.goBack()
await page.reload()
const localeLabel = page.locator('.localizer-button__current-label').first()
await expect
.poll(
async () => {
return await localeLabel.textContent()
},
{ timeout: 3000 },
)
.toContain('English')
})
describe('publish specific locale', () => {
test('should create post in correct locale with publishSpecificLocale', async () => {
await page.goto(urlPostsWithDrafts.create)