revert: "feat: adds new experimental.localizeStatus option (#13207)" (#13928)

This reverts commit 0f6d748365.

# Conflicts:
#	docs/configuration/overview.mdx
#	docs/experimental/overview.mdx
#	packages/ui/src/elements/Status/index.tsx
This commit is contained in:
Dan Ribbens
2025-09-25 11:14:19 -04:00
committed by GitHub
parent c2300059a6
commit 456e3d6459
27 changed files with 54 additions and 341 deletions

View File

@@ -131,29 +131,6 @@ localization: {
Since the filtering happens at the root level of the application and its result is not calculated every time you navigate to a new page, you may want to call `router.refresh` in a custom component that watches when values that affect the result change. In the example above, you would want to do this when `supportedLocales` changes on the tenant document.
## Experimental Options
Experimental options are features that may not be fully stable and may change or be removed in future releases.
These options can be enabled in your Payload Config under the `experimental` key. You can set them like this:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
experimental: {
localizeStatus: true,
},
})
```
The following experimental options are available related to localization:
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`localizeStatus`** | **Boolean.** When `true`, shows document status per locale in the admin panel instead of always showing the latest overall status. Opt-in for backwards compatibility. Defaults to `false`. |
## Field Localization
Payload Localization works on a **field** level—not a document level. In addition to configuring the base Payload Config to support Localization, you need to specify each field that you would like to localize.

View File

@@ -70,7 +70,6 @@ The following options are available:
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
| **`experimental`** | Configure experimental features for Payload. These may be unstable and may change or be removed in future releases. [More details](../experimental/overview). |
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |

View File

@@ -1,66 +0,0 @@
---
title: Experimental Features
label: Overview
order: 10
desc: Enable and configure experimental functionality within Payload. These features may be unstable and may change or be removed without notice.
keywords: experimental, unstable, beta, preview, features, configuration, Payload, cms, headless, javascript, node, react, nextjs
---
Experimental features allow you to try out new functionality before it becomes a stable part of Payload. These features may still be in active development, may have incomplete functionality, and can change or be removed in future releases without warning.
## How It Works
Experimental features are configured via the root-level `experimental` property in your [Payload Config](../configuration/overview). This property contains individual feature flags, each flag can be configured independently, allowing you to selectively opt into specific functionality.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
experimental: {
localizeStatus: true, // highlight-line
},
})
```
## Experimental Options
The following options are available:
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`localizeStatus`** | **Boolean.** When `true`, shows document status per locale in the admin panel instead of always showing the latest overall status. Opt-in for backwards compatibility. Defaults to `false`. |
This list may change without notice.
## When to Use Experimental Features
You might enable an experimental feature when:
- You want early access to new capabilities before their stable release.
- You can accept the risks of using potentially unstable functionality.
- You are testing new features in a development or staging environment.
- You wish to provide feedback to the Payload team on new functionality.
If you are working on a production application, carefully evaluate whether the benefits outweigh the risks. For most stable applications, it is recommended to wait until the feature is officially released.
<Banner type="success">
<strong>Tip:</strong> To stay up to date on experimental features or share
your feedback, visit the{' '}
<a
href="https://github.com/payloadcms/payload/discussions"
target="_blank"
rel="noopener noreferrer"
>
Payload GitHub Discussions
</a>{' '}
or{' '}
<a
href="https://github.com/payloadcms/payload/issues"
target="_blank"
rel="noopener noreferrer"
>
open an issue
</a>
.
</Banner>

View File

@@ -12,7 +12,6 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
autosave,
createdAt,
globalSlug,
localeStatus,
parent,
publishedLocale,
req,
@@ -34,7 +33,6 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
autosave,
createdAt,
latest: true,
localeStatus,
parent,
publishedLocale,
snapshot,

View File

@@ -12,7 +12,6 @@ export const createVersion: CreateVersion = async function createVersion(
autosave,
collectionSlug,
createdAt,
localeStatus,
parent,
publishedLocale,
req,
@@ -38,7 +37,6 @@ export const createVersion: CreateVersion = async function createVersion(
autosave,
createdAt,
latest: true,
localeStatus,
parent,
publishedLocale,
snapshot,

View File

@@ -179,13 +179,6 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
for (let i = 0; i < result.docs.length; i++) {
const id = result.docs[i].parent
const localeStatus = result.docs[i].localeStatus || {}
if (locale && localeStatus[locale]) {
result.docs[i].status = localeStatus[locale]
result.docs[i].version._status = localeStatus[locale]
}
result.docs[i] = result.docs[i].version ?? {}
result.docs[i].id = id
}

View File

@@ -15,7 +15,6 @@ export async function createGlobalVersion<T extends TypeWithID>(
autosave,
createdAt,
globalSlug,
localeStatus,
publishedLocale,
req,
returning,
@@ -36,7 +35,6 @@ export async function createGlobalVersion<T extends TypeWithID>(
autosave,
createdAt,
latest: true,
localeStatus,
publishedLocale,
snapshot,
updatedAt,

View File

@@ -15,7 +15,6 @@ export async function createVersion<T extends TypeWithID>(
autosave,
collectionSlug,
createdAt,
localeStatus,
parent,
publishedLocale,
req,
@@ -41,7 +40,6 @@ export async function createVersion<T extends TypeWithID>(
autosave,
createdAt,
latest: true,
localeStatus,
parent,
publishedLocale,
snapshot,

View File

@@ -36,17 +36,15 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
where: combinedWhere,
})
for (let i = 0; i < result.docs.length; i++) {
const id = result.docs[i].parent
const localeStatus = result.docs[i].localeStatus || {}
if (locale && localeStatus[locale]) {
result.docs[i].status = localeStatus[locale]
result.docs[i].version._status = localeStatus[locale]
}
return {
...result,
docs: result.docs.map((doc) => {
doc = {
id: doc.parent,
...doc.version,
}
result.docs[i] = result.docs[i].version ?? {}
result.docs[i].id = id
return doc
}),
}
return result
}

View File

@@ -19,7 +19,6 @@ type AutosaveCellProps = {
rowData: {
autosave?: boolean
id: number | string
localeStatus?: Record<string, 'draft' | 'published'>
publishedLocale?: string
version: {
_status: string

View File

@@ -289,7 +289,6 @@ export const createOperation = async <
autosave,
collection: collectionConfig,
docWithLocales: result,
locale,
operation: 'create',
payload,
publishSpecificLocale,

View File

@@ -317,7 +317,6 @@ export const updateDocument = async <
collection: collectionConfig,
docWithLocales: result,
draft: shouldSaveDraft,
locale,
operation: 'update',
payload,
publishSpecificLocale,

View File

@@ -223,16 +223,6 @@ export const createClientConfig = ({
break
case 'experimental':
if (config.experimental) {
clientConfig.experimental = {}
if (config.experimental?.localizeStatus) {
clientConfig.experimental.localizeStatus = config.experimental.localizeStatus
}
}
break
case 'folders':
if (config.folders) {
clientConfig.folders = {

View File

@@ -47,7 +47,6 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
defaultDepth: 2,
defaultMaxTextLength: 40000,
endpoints: [],
experimental: {},
globals: [],
graphQL: {
disablePlaygroundInProduction: true,
@@ -122,7 +121,6 @@ export const addDefaultsToConfig = (config: Config): Config => {
config.defaultDepth = config.defaultDepth ?? 2
config.defaultMaxTextLength = config.defaultMaxTextLength ?? 40000
config.endpoints = config.endpoints ?? []
config.experimental = config.experimental ?? {}
config.globals = config.globals ?? []
config.graphQL = {
disableIntrospectionInProduction: true,

View File

@@ -726,14 +726,6 @@ export type ImportMapGenerators = Array<
}) => void
>
/**
* Experimental features.
* These may be unstable and may change or be removed in future releases.
*/
export type ExperimentalConfig = {
localizeStatus?: boolean
}
export type AfterErrorHook = (
args: AfterErrorHookArgs,
) => AfterErrorResult | Promise<AfterErrorResult>
@@ -1069,12 +1061,6 @@ export type Config = {
email?: EmailAdapter | Promise<EmailAdapter>
/** Custom REST endpoints */
endpoints?: Endpoint[]
/**
* Configure experimental features for Payload.
*
* These features may be unstable and may change or be removed in future releases.
*/
experimental?: ExperimentalConfig
/**
* Options for folder view within the admin panel
*
@@ -1344,7 +1330,6 @@ export type SanitizedConfig = {
/** Default richtext editor to use for richText fields */
editor?: RichTextAdapter<any, any, any>
endpoints: Endpoint[]
experimental?: ExperimentalConfig
globals: SanitizedGlobalConfig[]
i18n: Required<I18nOptions>
jobs: SanitizedJobsConfig

View File

@@ -390,7 +390,6 @@ export type CreateVersionArgs<T = TypeWithID> = {
autosave: boolean
collectionSlug: CollectionSlug
createdAt: string
localeStatus?: Record<string, 'draft' | 'published'>
/** ID of the parent document for which the version should be created for */
parent: number | string
publishedLocale?: string
@@ -415,7 +414,6 @@ export type CreateGlobalVersionArgs<T = TypeWithID> = {
autosave: boolean
createdAt: string
globalSlug: GlobalSlug
localeStatus?: Record<string, 'draft' | 'published'>
/** ID of the parent document for which the version should be created for */
parent: number | string
publishedLocale?: string

View File

@@ -302,7 +302,6 @@ export const updateOperation = async <
docWithLocales: result,
draft: shouldSaveDraft,
global: globalConfig,
locale,
operation: 'update',
payload,
publishSpecificLocale,

View File

@@ -1,5 +1,4 @@
// @ts-strict-ignore
import type { SanitizedConfig } from '../config/types.js'
import type { CheckboxField, Field, Option } from '../fields/config/types.js'
export const statuses: Option[] = [
@@ -44,23 +43,3 @@ export const versionSnapshotField: CheckboxField = {
},
index: true,
}
export function buildLocaleStatusField(config: SanitizedConfig): Field[] {
if (!config.localization || !config.localization.locales) {
return []
}
return config.localization.locales.map((locale) => {
const code = typeof locale === 'string' ? locale : locale.code
return {
name: code,
type: 'select',
index: true,
options: [
{ label: ({ t }) => t('version:draft'), value: 'draft' },
{ label: ({ t }) => t('version:published'), value: 'published' },
],
}
})
}

View File

@@ -2,7 +2,7 @@ import type { SanitizedCollectionConfig } from '../collections/config/types.js'
import type { SanitizedConfig } from '../config/types.js'
import type { Field, FlattenedField } from '../fields/config/types.js'
import { buildLocaleStatusField, versionSnapshotField } from './baseFields.js'
import { versionSnapshotField } from './baseFields.js'
export const buildVersionCollectionFields = <T extends boolean = false>(
config: SanitizedConfig,
@@ -62,23 +62,6 @@ export const buildVersionCollectionFields = <T extends boolean = false>(
return locale.code
}),
})
if (config.experimental?.localizeStatus) {
const localeStatusFields = buildLocaleStatusField(config)
fields.push({
name: 'localeStatus',
type: 'group',
admin: {
disableBulkEdit: true,
disabled: true,
},
fields: localeStatusFields,
...(flatten && {
flattenedFields: localeStatusFields as FlattenedField[],
})!,
})
}
}
fields.push({

View File

@@ -2,7 +2,7 @@ import type { SanitizedConfig } from '../config/types.js'
import type { Field, FlattenedField } from '../fields/config/types.js'
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import { buildLocaleStatusField, versionSnapshotField } from './baseFields.js'
import { versionSnapshotField } from './baseFields.js'
export const buildVersionGlobalFields = <T extends boolean = false>(
config: SanitizedConfig,
@@ -56,23 +56,6 @@ export const buildVersionGlobalFields = <T extends boolean = false>(
return locale.code
}),
})
if (config.experimental.localizeStatus) {
const localeStatusFields = buildLocaleStatusField(config)
fields.push({
name: 'localeStatus',
type: 'group',
admin: {
disableBulkEdit: true,
disabled: true,
},
fields: localeStatusFields,
...(flatten && {
flattenedFields: localeStatusFields as FlattenedField[],
})!,
})
}
}
fields.push({

View File

@@ -111,12 +111,6 @@ export const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
draft.version = {} as T
}
// Lift locale status from version data if available
const localeStatus = draft.localeStatus || {}
if (locale && localeStatus[locale]) {
;(draft.version as { _status?: string })['_status'] = localeStatus[locale]
}
// Disregard all other draft content at this point,
// Only interested in the version itself.
// Operations will handle firing hooks, etc.

View File

@@ -1,5 +1,3 @@
import { version } from 'os'
// @ts-strict-ignore
import type { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types.js'
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
@@ -18,7 +16,6 @@ type Args = {
draft?: boolean
global?: SanitizedGlobalConfig
id?: number | string
locale?: null | string
operation?: 'create' | 'restoreVersion' | 'update'
payload: Payload
publishSpecificLocale?: string
@@ -34,7 +31,6 @@ export const saveVersion = async ({
docWithLocales: doc,
draft,
global,
locale,
operation,
payload,
publishSpecificLocale,
@@ -46,7 +42,6 @@ export const saveVersion = async ({
let createNewVersion = true
const now = new Date().toISOString()
const versionData = deepCopyObjectSimple(doc)
if (draft) {
versionData._status = 'draft'
}
@@ -60,39 +55,39 @@ export const saveVersion = async ({
}
try {
let docs
const findVersionArgs = {
limit: 1,
pagination: false,
req,
sort: '-updatedAt',
}
if (collection) {
;({ docs } = await payload.db.findVersions({
...findVersionArgs,
collection: collection.slug,
limit: 1,
pagination: false,
req,
where: {
parent: {
equals: id,
},
},
}))
} else {
;({ docs } = await payload.db.findGlobalVersions({
...findVersionArgs,
global: global!.slug,
limit: 1,
pagination: false,
req,
}))
}
const [latestVersion] = docs
if (autosave) {
let docs
const findVersionArgs = {
limit: 1,
pagination: false,
req,
sort: '-updatedAt',
}
if (collection) {
;({ docs } = await payload.db.findVersions({
...findVersionArgs,
collection: collection.slug,
limit: 1,
pagination: false,
req,
where: {
parent: {
equals: id,
},
},
}))
} else {
;({ docs } = await payload.db.findGlobalVersions({
...findVersionArgs,
global: global!.slug,
limit: 1,
pagination: false,
req,
}))
}
const [latestVersion] = docs
// overwrite the latest version if it's set to autosave
if (latestVersion && 'autosave' in latestVersion && latestVersion.autosave === true) {
createNewVersion = false
@@ -130,53 +125,11 @@ export const saveVersion = async ({
}
if (createNewVersion) {
let localeStatus = {}
const localizationEnabled =
payload.config.localization && payload.config.localization.locales.length > 0
if (
localizationEnabled &&
payload.config.localization !== false &&
payload.config.experimental?.localizeStatus
) {
const allLocales = (
(payload.config.localization && payload.config.localization?.locales) ||
[]
).map((locale) => (typeof locale === 'string' ? locale : locale.code))
// If `publish all`, set all locales to published
if (versionData._status === 'published' && !publishSpecificLocale) {
localeStatus = Object.fromEntries(allLocales.map((code) => [code, 'published']))
} else if (publishSpecificLocale || (locale && versionData._status === 'draft')) {
const status: 'draft' | 'published' = publishSpecificLocale ? 'published' : 'draft'
const incomingLocale = String(publishSpecificLocale || locale)
const existing = latestVersion?.localeStatus
// If no locale statuses are set, set it and set all others to draft
if (!existing) {
localeStatus = {
...Object.fromEntries(
allLocales.filter((code) => code !== incomingLocale).map((code) => [code, 'draft']),
),
[incomingLocale]: status,
}
} else {
// If locales already exist, update the status for the incoming locale
const { [incomingLocale]: _, ...rest } = existing
localeStatus = {
...rest,
[incomingLocale]: status,
}
}
}
}
const createVersionArgs = {
autosave: Boolean(autosave),
collectionSlug: undefined as string | undefined,
createdAt: operation === 'restoreVersion' ? versionData.createdAt : now,
globalSlug: undefined as string | undefined,
localeStatus,
parent: collection ? id : undefined,
publishedLocale: publishSpecificLocale || undefined,
req,

View File

@@ -122,7 +122,6 @@ export type SanitizedGlobalVersions = {
export type TypeWithVersion<T> = {
createdAt: string
id: string
localeStatus: Record<string, 'draft' | 'published'>
parent: number | string
publishedLocale?: string
snapshot?: boolean

View File

@@ -24,7 +24,6 @@ export const Status: React.FC = () => {
hasPublishedDoc,
incrementVersionCount,
isTrashed,
savedDocumentData: doc,
setHasPublishedDoc,
setMostRecentVersionIsAutosaved,
setUnpublishedVersionCount,
@@ -38,7 +37,6 @@ export const Status: React.FC = () => {
routes: { api },
serverURL,
},
getEntityConfig,
} = useConfig()
const { reset: resetForm } = useForm()
@@ -48,22 +46,16 @@ export const Status: React.FC = () => {
const unPublishModalSlug = `confirm-un-publish-${id}`
const revertModalSlug = `confirm-revert-${id}`
let statusToRender: 'changed' | 'draft' | 'published' = 'draft'
let statusToRender: 'changed' | 'draft' | 'published'
const collectionConfig = getEntityConfig({ collectionSlug })
const globalConfig = getEntityConfig({ globalSlug })
const docConfig = collectionConfig || globalConfig
const autosaveEnabled =
typeof docConfig?.versions?.drafts === 'object' ? docConfig.versions.drafts.autosave : false
if (autosaveEnabled) {
if (hasPublishedDoc) {
statusToRender = unpublishedVersionCount > 0 ? 'changed' : 'published'
}
} else {
statusToRender = doc._status || 'draft'
if (unpublishedVersionCount > 0 && hasPublishedDoc) {
statusToRender = 'changed'
} else if (!hasPublishedDoc) {
statusToRender = 'draft'
} else if (hasPublishedDoc && unpublishedVersionCount <= 0) {
statusToRender = 'published'
}
const displayStatusKey = isTrashed
? hasPublishedDoc
? 'previouslyPublished'
@@ -201,7 +193,7 @@ export const Status: React.FC = () => {
{!isTrashed &&
canUpdate &&
hasPublishedDoc &&
(statusToRender === 'changed' || statusToRender === 'draft') && (
statusToRender === 'changed' && (
<React.Fragment>
&nbsp;&mdash;&nbsp;
<Button

View File

@@ -428,9 +428,6 @@ export default buildConfigWithDefaults({
slug: 'global-text',
},
],
experimental: {
localizeStatus: true,
},
localization: {
filterAvailableLocales: ({ locales }) => {
return locales.filter((locale) => locale.code !== 'xx')

View File

@@ -673,33 +673,6 @@ describe('Localization', () => {
await changeLocale(page, defaultLocale)
await expect(page.locator('#field-title')).toBeEmpty()
})
test('should show localized status in collection list', async () => {
await page.goto(urlPostsWithDrafts.create)
const engTitle = 'Eng published'
const spanTitle = 'Spanish draft'
await changeLocale(page, defaultLocale)
await fillValues({ title: engTitle })
await saveDocAndAssert(page)
await changeLocale(page, spanishLocale)
await fillValues({ title: spanTitle })
await saveDocAndAssert(page, '#action-save-draft')
await page.goto(urlPostsWithDrafts.list)
const columns = page.getByRole('button', { name: 'Columns' })
await columns.click()
await page.locator('#_status').click()
await expect(page.locator('.row-1 .cell-title')).toContainText(spanTitle)
await expect(page.locator('.row-1 .cell-_status')).toContainText('Draft')
await changeLocale(page, defaultLocale)
await expect(page.locator('.row-1 .cell-title')).toContainText(engTitle)
await expect(page.locator('.row-1 .cell-_status')).toContainText('Published')
})
})
test('should not show publish specific locale button when no localized fields exist', async () => {

View File

@@ -1097,7 +1097,7 @@ describe('Versions', () => {
await textField.fill('spanish draft')
await saveDocAndAssert(page, '#action-save-draft')
await expect(status).toContainText('Draft')
await expect(status).toContainText('Changed')
await changeLocale(page, 'en')
await textField.fill('english published')
@@ -1130,7 +1130,7 @@ describe('Versions', () => {
const publishedDoc = data.docs[0]
expect(publishedDoc?.text).toStrictEqual({
expect(publishedDoc.text).toStrictEqual({
en: 'english published',
es: 'spanish published',
})