fix(ui): should not show publish specific locale button when no localized fields exist (#13459)

### What?
Hides the `Publish in [specific locale]` button on collections and
globals when no localized fields are present.

### Why?
Currently, the `Publish in [specific locale]` shows on every
collection/global as long as `localization` is enabled in the Payload
config, however this is not necessary when the collection/global has no
localized fields.

### How?
Checks that localized fields exist prior to rendering the `Publish in
[specific locale]` button.

Fixes #13447

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
This commit is contained in:
Jessica Rynkar
2025-09-02 10:23:53 +01:00
committed by GitHub
parent fdab2712c0
commit ac691b675b
4 changed files with 71 additions and 3 deletions

View File

@@ -4,7 +4,7 @@ import type { PublishButtonClientProps } from 'payload'
import { useModal } from '@faceless-ui/modal'
import * as qs from 'qs-esm'
import React, { useCallback } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { useForm, useFormModified } from '../../forms/Form/context.js'
import { FormSubmit } from '../../forms/Submit/index.js'
@@ -15,6 +15,7 @@ import { useEditDepth } from '../../providers/EditDepth/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useOperation } from '../../providers/Operation/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { traverseForLocalizedFields } from '../../utilities/traverseForLocalizedFields.js'
import { PopupList } from '../Popup/index.js'
import { ScheduleDrawer } from './ScheduleDrawer/index.js'
@@ -86,6 +87,15 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
(hasAutosave || !modified),
)
const [hasLocalizedFields, setHasLocalizedFields] = useState(false)
useEffect(() => {
const hasLocalizedField = traverseForLocalizedFields(entityConfig?.fields)
setHasLocalizedFields(hasLocalizedField)
}, [entityConfig?.fields])
const canPublishSpecificLocale = localization && hasLocalizedFields && hasPublishPermission
const operation = useOperation()
const disabled = operation === 'update' && !modified
@@ -213,7 +223,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
onClick={defaultPublish}
size="medium"
SubMenuPopupContent={
localization || canSchedulePublish
canPublishSpecificLocale || canSchedulePublish
? ({ close }) => {
return (
<React.Fragment>
@@ -227,7 +237,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
</PopupList.Button>
</PopupList.ButtonGroup>
)}
{localization && canPublish && (
{canPublishSpecificLocale && (
<PopupList.ButtonGroup>
<PopupList.Button id="publish-locale" onClick={secondaryPublish}>
{secondaryLabel}

View File

@@ -0,0 +1,45 @@
import type { ClientField } from 'payload'
export const traverseForLocalizedFields = (fields: ClientField[]): boolean => {
for (const field of fields) {
if ('localized' in field && field.localized) {
return true
}
switch (field.type) {
case 'array':
case 'collapsible':
case 'group':
case 'row':
if (field.fields && traverseForLocalizedFields(field.fields)) {
return true
}
break
case 'blocks':
if (field.blocks) {
for (const block of field.blocks) {
if (block.fields && traverseForLocalizedFields(block.fields)) {
return true
}
}
}
break
case 'tabs':
if (field.tabs) {
for (const tab of field.tabs) {
if ('localized' in tab && tab.localized) {
return true
}
if ('fields' in tab && tab.fields && traverseForLocalizedFields(tab.fields)) {
return true
}
}
}
break
}
}
return false
}

View File

@@ -4,6 +4,9 @@ export const noLocalizedFieldsCollectionSlug = 'no-localized-fields'
export const NoLocalizedFieldsCollection: CollectionConfig = {
slug: noLocalizedFieldsCollectionSlug,
versions: {
drafts: true,
},
fields: [
{
name: 'text',

View File

@@ -29,6 +29,7 @@ import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
import { arrayCollectionSlug } from './collections/Array/index.js'
import { nestedToArrayAndBlockCollectionSlug } from './collections/NestedToArrayAndBlock/index.js'
import { noLocalizedFieldsCollectionSlug } from './collections/NoLocalizedFields/index.js'
import { richTextSlug } from './collections/RichText/index.js'
import {
arrayWithFallbackCollectionSlug,
@@ -61,6 +62,7 @@ let urlCannotCreateDefaultLocale: AdminUrlUtil
let urlPostsWithDrafts: AdminUrlUtil
let urlArray: AdminUrlUtil
let arrayWithFallbackURL: AdminUrlUtil
let noLocalizedFieldsURL: AdminUrlUtil
const title = 'english title'
const spanishTitle = 'spanish title'
@@ -87,6 +89,7 @@ describe('Localization', () => {
urlPostsWithDrafts = new AdminUrlUtil(serverURL, localizedDraftsSlug)
urlArray = new AdminUrlUtil(serverURL, arrayCollectionSlug)
arrayWithFallbackURL = new AdminUrlUtil(serverURL, arrayWithFallbackCollectionSlug)
noLocalizedFieldsURL = new AdminUrlUtil(serverURL, noLocalizedFieldsCollectionSlug)
context = await browser.newContext()
page = await context.newPage()
@@ -671,6 +674,13 @@ describe('Localization', () => {
await expect(page.locator('#field-title')).toBeEmpty()
})
})
test('should not show publish specific locale button when no localized fields exist', async () => {
await page.goto(urlPostsWithDrafts.create)
await expect(page.locator('#publish-locale')).toHaveCount(1)
await page.goto(noLocalizedFieldsURL.create)
await expect(page.locator('#publish-locale')).toHaveCount(0)
})
})
async function createLocalizedArrayItem(page: Page, url: AdminUrlUtil) {