Skip to content

feat(ui): experimental localize metadata UI#14699

Merged
JessRynkar merged 56 commits into
feat/experimental-localize-metadatafrom
feat/experimental-localize-metadata-ui
Jan 14, 2026
Merged

feat(ui): experimental localize metadata UI#14699
JessRynkar merged 56 commits into
feat/experimental-localize-metadatafrom
feat/experimental-localize-metadata-ui

Conversation

@JarrodMFlesch

@JarrodMFlesch JarrodMFlesch commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

Adds ui changes necessary for the localized status to display correctly in the edit view and versions view.

Related PRs in this feature

@JarrodMFlesch JarrodMFlesch changed the title Feat/experimental localize metadata UI feat(ui): experimental localize metadata UI Nov 20, 2025
@github-actions

github-actions Bot commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 935.55 KB 🆕 Added
packages/payload/meta_index.json esbuild/index.js 1.26 MB 🆕 Added
packages/payload/meta_shared.json esbuild/exports/shared.js 164.49 KB 🆕 Added
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 281.24 KB 🆕 Added
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.17 MB 🆕 Added
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 16.08 KB 🆕 Added
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████▌ }}}$ 82.4%, 766.76 KB
dist/views/Version ${{\color{Goldenrod}{ █▎ }}}$ 5.4%, 50.51 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 16.48 KB
dist/views/Document ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 15.72 KB
dist/views/List ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 11.27 KB
dist/views/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 9.03 KB
dist/views/Versions ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 6.16 KB
dist/views/API ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.03 KB
dist/elements/Nav ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 5.53 KB
dist/views/Account ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 5.46 KB
dist/elements/DocumentHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 4.81 KB
dist/views/Login ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 4.41 KB
dist/views/ForgotPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 3.09 KB
dist/layouts/Root ${{\color{Goldenrod}{ }}}$ 0.3%, 2.91 KB
dist/views/CreateFirstUser ${{\color{Goldenrod}{ }}}$ 0.3%, 2.81 KB
dist/templates/Default ${{\color{Goldenrod}{ }}}$ 0.3%, 2.63 KB
dist/views/BrowseByFolder ${{\color{Goldenrod}{ }}}$ 0.3%, 2.56 KB
dist/views/CollectionFolders ${{\color{Goldenrod}{ }}}$ 0.3%, 2.44 KB
dist/views/ResetPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 2.40 KB
dist/views/Logout ${{\color{Goldenrod}{ }}}$ 0.2%, 1.94 KB
(other) ${{\color{Goldenrod}{ ████▍ }}}$ 17.6%, 164.12 KB

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████▊ }}}$ 67.1%, 842.06 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▉ }}}$ 3.5%, 43.58 KB
dist/collections/operations ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 37.85 KB
dist/versions/migrations ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 18.50 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.22 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.80 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.33 KB
dist/queues/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.05 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.04 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.21 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.53 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.35 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.74 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.65 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.50 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.27 KB
dist/collections/endpoints ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.21 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 5.57 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.50 KB
dist/auth/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 5.42 KB
(other) ${{\color{Goldenrod}{ ████████▏ }}}$ 32.9%, 413.29 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████▋ }}}$ 78.9%, 126.93 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 10.21 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 2.51 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 1.42 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.28 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 1.04 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 943 B
dist/folders/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 916 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.4%, 713 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.4%, 651 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.4%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
dist/auth/sessions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 525 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.3%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.3%, 413 B
dist/utilities/formatLabels.js ${{\color{Goldenrod}{ }}}$ 0.2%, 380 B
dist/utilities/appendUploadSelectFields.js ${{\color{Goldenrod}{ }}}$ 0.2%, 360 B
(other) ${{\color{Goldenrod}{ █████▎ }}}$ 21.1%, 34.02 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▏ }}}$ 12.6%, 35.02 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.5%, 32.00 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▏ }}}$ 8.8%, 24.36 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▏ }}}$ 8.5%, 23.70 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▋ }}}$ 6.8%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.5%, 18.11 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▌ }}}$ 6.4%, 17.75 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 13.77 KB
dist/features/textState ${{\color{Goldenrod}{ █ }}}$ 4.0%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 9.03 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 8.22 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.39 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.12 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.05 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.08 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.00 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.17 KB
dist/lexical/theme ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.62 KB
dist/features/indent ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.50 KB
(other) ${{\color{Goldenrod}{ █████████████████████▊ }}}$ 87.4%, 242.96 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████▍ }}}$ 49.5%, 572.94 KB
dist/elements/FolderView ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 29.29 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 27.20 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 16.91 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 16.39 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 15.78 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.48 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.26 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 14.15 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 13.69 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 10.33 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.03 KB
dist/providers/Folders ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.47 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.38 KB
dist/elements/ListHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.90 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.81 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.73 KB
dist/views/CollectionFolder ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.38 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.31 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.99 KB
(other) ${{\color{Goldenrod}{ ████████████▋ }}}$ 50.5%, 583.79 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ █████ }}}$ 20.3%, 3.12 KB
../../node_modules ${{\color{Goldenrod}{ ████▎ }}}$ 17.2%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▍ }}}$ 9.9%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ ██▏ }}}$ 8.6%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 862 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █▎ }}}$ 5.3%, 812 B
dist/utilities/api.js ${{\color{Goldenrod}{ █▏ }}}$ 4.9%, 756 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 528 B
dist/elements/Translation ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 399 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 339 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 329 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 301 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 159 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 146 B
(other) ${{\color{Goldenrod}{ ███████████████████▉ }}}$ 79.7%, 12.27 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@JessRynkar JessRynkar force-pushed the feat/experimental-localize-metadata branch from 4c0e67c to ea6f9a4 Compare December 18, 2025 15:20

@DanRibbens DanRibbens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One TODO to clean up it seems, looks good otherwise.

Comment thread packages/ui/src/elements/Status/index.tsx Outdated
Adds migration logic.

### 1. Create blank migration file

```bash
payload migrate:create localize_status
```

### 2. Add the migration code

**PostgreSQL / SQLite:**

```typescript
import type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-postgres'
import { sql } from '@payloadcms/db-postgres'
import { localizeStatus } from 'payload/migrations'

export async function up({ db, payload }: MigrateUpArgs): Promise<void> {
  await localizeStatus.up({
    collectionSlug: 'posts', // 👈 Change to your collection
    db,
    payload,
    sql,
  })
}

export async function down({ db, payload }: MigrateDownArgs): Promise<void> {
  await localizeStatus.down({
    collectionSlug: 'posts',
    db,
    payload,
    sql,
  })
}
```

**MongoDB:**

```typescript
import type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-mongodb'
import { localizeStatus } from 'payload/migrations'

export async function up({ payload }: MigrateUpArgs): Promise<void> {
  await localizeStatus.up({
    collectionSlug: 'posts', // 👈 Change to your collection
    payload,
  })
}

export async function down({ payload }: MigrateDownArgs): Promise<void> {
  await localizeStatus.down({
    collectionSlug: 'posts',
    payload,
  })
}
```

##### Related PRs in this feature

- [feat: adds versions.drafts.localizeStatus and allows unpublish
per‑locale #14667](#14667)
- [feat(ui): experimental localize metadata UI
#14699](#14699)
- [chore: localize status migration work
#14862](#14862)

---------

Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
@JessRynkar JessRynkar requested a review from denolfe as a code owner January 14, 2026 10:16
@JessRynkar JessRynkar merged commit 95743f6 into feat/experimental-localize-metadata Jan 14, 2026
3 checks passed
@JessRynkar JessRynkar deleted the feat/experimental-localize-metadata-ui branch January 14, 2026 10:42
JessRynkar added a commit that referenced this pull request Jan 16, 2026
…r-locale functionality (#14667)

## Localized Status (Experimental) 

This PR introduces a new **experimental** option that allows each locale
to track and manage its own publication status independently, for
collection docs and globals.

### Configuration
To enable this feature, you need **two** configurations:

1. Enable the experimental flag in your `payload.config`:

```ts
experimental: {
  localizeStatus: true, // default: `false`
}
```

2. Enable it on specific collections/globals:
```ts
export const Posts: CollectionConfig = {
  slug: 'posts',
  versions: {
    drafts: {
      localizeStatus: true, // default: false
    },
  },
  // ...
}
```

## Key Changes

When enabled:

- **Per-locale status tracking** - Each locale maintains its own
published/draft status
- **Independent publishing** - Publish or unpublish individual locales
without affecting others
- **Locale-aware UI** - Admin panel shows status for the currently
active locale
- **Localized version history** - Versions list reflects the current
locale's status

## Improved Behavior
- Creating a document in one locale sets that locale to the specified
status and other locales default to draft
- You can publish/unpublish specific locales independently
- Collection list views show status for the currently active locale
- Document edit view displays the current locale's status

## Migration Required (If you already have version data)

> If this is a new project then you only need to enable the flags. If
you have existing data you will want to continue with the migration
guide below.

⚠️ Breaking Change: When `localizeStatus` is enabled, existing `_status`
fields will need to be migrated from strings to locale objects.

### ➡️ Step 1
Before doing anything, **take a backup of your current database**.

### ➡️ Step 2
Stop your dev server if it is running

### ➡️  Step 3
**Create migration file**
```ts
// run the following to create a blank
// migration file named `localize_status`
payload migrate:create localize_status
```

**Add migration code**

🔵 **PostgreSQL / SQLite**:
```ts
import type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-postgres'
import { sql } from '@payloadcms/db-postgres'
import { localizeStatus } from 'payload/migrations'

export async function up({ db, payload }: MigrateUpArgs): Promise<void> {
  await localizeStatus.up({
    collectionSlug: 'posts', // 👈 Change to your collection
    db,
    payload,
    sql,
  })
}

export async function down({ db, payload }: MigrateDownArgs): Promise<void> {
  await localizeStatus.down({
    collectionSlug: 'posts', // 👈 Change to your collection  
    db,
    payload,
    sql,
  })
}
```

🟢  **MongoDB**:
```ts
import type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-mongodb'
import { localizeStatus } from 'payload/migrations'

export async function up({ payload }: MigrateUpArgs): Promise<void> {
  await localizeStatus.up({
    collectionSlug: 'posts', // 👈 Change to your collection
    payload,
  })
}

export async function down({ payload }: MigrateDownArgs): Promise<void> {
  await localizeStatus.down({
    collectionSlug: 'posts', // 👈 Change to your collection
    payload,
  })
}
```

### ➡️ Step 4
Run the migration
```ts
payload migrate
```

### ➡️ Step 5
Set `localizeStatus: true` 

**Payload Config**
```ts
// payload config file
experimental: {
  localizeStatus: true, // <-- add this
}
```

**Collection Config**
```ts
// collection you want to migrate
export const Posts: CollectionConfig = {
  slug: 'posts',
  versions: {
    drafts: {
      localizeStatus: true, // <-- add this
    },
  },
  // ...
}
```

### ➡️ Step 6
Run `pnpm dev` and test out the feature.
  
## Related PRs in this feature

- [feat: adds versions.drafts.localizeStatus and allows unpublish
per‑locale #14667](#14667)
- [feat(ui): experimental localize metadata UI
#14699](#14699)
- [chore: localize status migration work
#14862](#14862)

---------

Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
Co-authored-by: Jessica Rynkar <67977755+jessrynkar@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants