diff --git a/packages/db-mongodb/src/createGlobalVersion.ts b/packages/db-mongodb/src/createGlobalVersion.ts index 3d6a004a8da..a4c849b6ad8 100644 --- a/packages/db-mongodb/src/createGlobalVersion.ts +++ b/packages/db-mongodb/src/createGlobalVersion.ts @@ -6,7 +6,17 @@ import { withSession } from './withSession.js' export const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion( this: MongooseAdapter, - { autosave, createdAt, globalSlug, parent, req = {} as PayloadRequest, updatedAt, versionData }, + { + autosave, + createdAt, + globalSlug, + parent, + publishedLocale, + req = {} as PayloadRequest, + snapshot, + updatedAt, + versionData, + }, ) { const VersionModel = this.versions[globalSlug] const options = await withSession(this, req) @@ -18,6 +28,8 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo createdAt, latest: true, parent, + publishedLocale, + snapshot, updatedAt, version: versionData, }, diff --git a/packages/db-mongodb/src/createVersion.ts b/packages/db-mongodb/src/createVersion.ts index 2093052ccb4..2839ea41e58 100644 --- a/packages/db-mongodb/src/createVersion.ts +++ b/packages/db-mongodb/src/createVersion.ts @@ -11,7 +11,9 @@ export const createVersion: CreateVersion = async function createVersion( collectionSlug, createdAt, parent, + publishedLocale, req = {} as PayloadRequest, + snapshot, updatedAt, versionData, }, @@ -26,6 +28,8 @@ export const createVersion: CreateVersion = async function createVersion( createdAt, latest: true, parent, + publishedLocale, + snapshot, updatedAt, version: versionData, }, diff --git a/packages/db-mongodb/src/findGlobalVersions.ts b/packages/db-mongodb/src/findGlobalVersions.ts index 2063eb91b9c..61c6cbcf2f5 100644 --- a/packages/db-mongodb/src/findGlobalVersions.ts +++ b/packages/db-mongodb/src/findGlobalVersions.ts @@ -25,6 +25,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV ) { const Model = this.versions[global] const versionFields = buildVersionGlobalFields( + this.payload.config, this.payload.globals.config.find(({ slug }) => slug === global), ) const options = { diff --git a/packages/db-mongodb/src/init.ts b/packages/db-mongodb/src/init.ts index 0f694deb8f5..e679e98408c 100644 --- a/packages/db-mongodb/src/init.ts +++ b/packages/db-mongodb/src/init.ts @@ -21,7 +21,7 @@ export const init: Init = function init(this: MongooseAdapter) { if (collection.versions) { const versionModelName = getDBName({ config: collection, versions: true }) - const versionCollectionFields = buildVersionCollectionFields(collection) + const versionCollectionFields = buildVersionCollectionFields(this.payload.config, collection) const versionSchema = buildSchema(this.payload.config, versionCollectionFields, { disableUnique: true, @@ -64,7 +64,7 @@ export const init: Init = function init(this: MongooseAdapter) { if (global.versions) { const versionModelName = getDBName({ config: global, versions: true }) - const versionGlobalFields = buildVersionGlobalFields(global) + const versionGlobalFields = buildVersionGlobalFields(this.payload.config, global) const versionSchema = buildSchema(this.payload.config, versionGlobalFields, { disableUnique: true, diff --git a/packages/db-postgres/src/predefinedMigrations/v2-v3/index.ts b/packages/db-postgres/src/predefinedMigrations/v2-v3/index.ts index a591c85938d..ea9ed42fc5e 100644 --- a/packages/db-postgres/src/predefinedMigrations/v2-v3/index.ts +++ b/packages/db-postgres/src/predefinedMigrations/v2-v3/index.ts @@ -118,7 +118,7 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => { const versionsTableName = adapter.tableNameMap.get( `_${toSnakeCase(collection.slug)}${adapter.versionsSuffix}`, ) - const versionFields = buildVersionCollectionFields(collection) + const versionFields = buildVersionCollectionFields(payload.config, collection) const versionPathsToQuery: PathsToQuery = new Set() traverseFields({ @@ -191,7 +191,7 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => { `_${toSnakeCase(global.slug)}${adapter.versionsSuffix}`, ) - const versionFields = buildVersionGlobalFields(global) + const versionFields = buildVersionGlobalFields(payload.config, global) const versionPathsToQuery: PathsToQuery = new Set() diff --git a/packages/db-sqlite/src/init.ts b/packages/db-sqlite/src/init.ts index 2a8cc7d40ea..69aa08f9594 100644 --- a/packages/db-sqlite/src/init.ts +++ b/packages/db-sqlite/src/init.ts @@ -37,6 +37,7 @@ export const init: Init = function init(this: SQLiteAdapter) { }) this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { const tableName = this.tableNameMap.get(toSnakeCase(collection.slug)) + const config = this.payload.config const baseExtraConfig: BaseExtraConfig = {} @@ -51,6 +52,17 @@ export const init: Init = function init(this: SQLiteAdapter) { } } + if (collection.upload.filenameCompoundIndex) { + const indexName = `${tableName}_filename_compound_idx` + + baseExtraConfig.filename_compound_index = (cols) => { + const colsConstraint = collection.upload.filenameCompoundIndex.map((f) => { + return cols[f] + }) + return uniqueIndex(indexName).on(colsConstraint[0], ...colsConstraint.slice(1)) + } + } + buildTable({ adapter: this, disableNotNull: !!collection?.versions?.drafts, @@ -66,7 +78,7 @@ export const init: Init = function init(this: SQLiteAdapter) { const versionsTableName = this.tableNameMap.get( `_${toSnakeCase(collection.slug)}${this.versionsSuffix}`, ) - const versionFields = buildVersionCollectionFields(collection) + const versionFields = buildVersionCollectionFields(config, collection) buildTable({ adapter: this, @@ -105,7 +117,8 @@ export const init: Init = function init(this: SQLiteAdapter) { versions: true, versionsCustomName: true, }) - const versionFields = buildVersionGlobalFields(global) + const config = this.payload.config + const versionFields = buildVersionGlobalFields(config, global) buildTable({ adapter: this, diff --git a/packages/db-vercel-postgres/src/predefinedMigrations/v2-v3/index.ts b/packages/db-vercel-postgres/src/predefinedMigrations/v2-v3/index.ts index acca6834695..38043a2a9a2 100644 --- a/packages/db-vercel-postgres/src/predefinedMigrations/v2-v3/index.ts +++ b/packages/db-vercel-postgres/src/predefinedMigrations/v2-v3/index.ts @@ -118,7 +118,8 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => { const versionsTableName = adapter.tableNameMap.get( `_${toSnakeCase(collection.slug)}${adapter.versionsSuffix}`, ) - const versionFields = buildVersionCollectionFields(collection) + + const versionFields = buildVersionCollectionFields(payload.config, collection) const versionPathsToQuery: PathsToQuery = new Set() traverseFields({ @@ -191,7 +192,7 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => { `_${toSnakeCase(global.slug)}${adapter.versionsSuffix}`, ) - const versionFields = buildVersionGlobalFields(global) + const versionFields = buildVersionGlobalFields(payload.config, global) const versionPathsToQuery: PathsToQuery = new Set() diff --git a/packages/drizzle/src/createGlobalVersion.ts b/packages/drizzle/src/createGlobalVersion.ts index 810be5af685..3af9547b998 100644 --- a/packages/drizzle/src/createGlobalVersion.ts +++ b/packages/drizzle/src/createGlobalVersion.ts @@ -10,7 +10,14 @@ import { upsertRow } from './upsertRow/index.js' export async function createGlobalVersion( this: DrizzleAdapter, - { autosave, globalSlug, req = {} as PayloadRequest, versionData }: CreateGlobalVersionArgs, + { + autosave, + globalSlug, + publishedLocale, + req = {} as PayloadRequest, + snapshot, + versionData, + }: CreateGlobalVersionArgs, ) { const db = this.sessions[await req?.transactionID]?.db || this.drizzle const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug) @@ -22,10 +29,12 @@ export async function createGlobalVersion( data: { autosave, latest: true, + publishedLocale, + snapshot, version: versionData, }, db, - fields: buildVersionGlobalFields(global), + fields: buildVersionGlobalFields(this.payload.config, global), operation: 'create', req, tableName, diff --git a/packages/drizzle/src/createVersion.ts b/packages/drizzle/src/createVersion.ts index 4d608808f33..7ad12cf0e49 100644 --- a/packages/drizzle/src/createVersion.ts +++ b/packages/drizzle/src/createVersion.ts @@ -14,7 +14,9 @@ export async function createVersion( autosave, collectionSlug, parent, + publishedLocale, req = {} as PayloadRequest, + snapshot, versionData, }: CreateVersionArgs, ) { @@ -33,6 +35,8 @@ export async function createVersion( autosave, latest: true, parent, + publishedLocale, + snapshot, version, } @@ -44,7 +48,7 @@ export async function createVersion( adapter: this, data, db, - fields: buildVersionCollectionFields(collection), + fields: buildVersionCollectionFields(this.payload.config, collection), operation: 'create', req, tableName, diff --git a/packages/drizzle/src/deleteVersions.ts b/packages/drizzle/src/deleteVersions.ts index 69504bc2357..4f1df9f2d86 100644 --- a/packages/drizzle/src/deleteVersions.ts +++ b/packages/drizzle/src/deleteVersions.ts @@ -19,7 +19,7 @@ export const deleteVersions: DeleteVersions = async function deleteVersion( `_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`, ) - const fields = buildVersionCollectionFields(collectionConfig) + const fields = buildVersionCollectionFields(this.payload.config, collectionConfig) const { docs } = await findMany({ adapter: this, diff --git a/packages/drizzle/src/findGlobalVersions.ts b/packages/drizzle/src/findGlobalVersions.ts index 1d1d22f8aae..f94095c40b3 100644 --- a/packages/drizzle/src/findGlobalVersions.ts +++ b/packages/drizzle/src/findGlobalVersions.ts @@ -30,7 +30,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV `_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`, ) - const fields = buildVersionGlobalFields(globalConfig) + const fields = buildVersionGlobalFields(this.payload.config, globalConfig) return findMany({ adapter: this, diff --git a/packages/drizzle/src/findVersions.ts b/packages/drizzle/src/findVersions.ts index 8d8dcbafeec..eac2df63b72 100644 --- a/packages/drizzle/src/findVersions.ts +++ b/packages/drizzle/src/findVersions.ts @@ -28,7 +28,7 @@ export const findVersions: FindVersions = async function findVersions( `_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`, ) - const fields = buildVersionCollectionFields(collectionConfig) + const fields = buildVersionCollectionFields(this.payload.config, collectionConfig) return findMany({ adapter: this, diff --git a/packages/drizzle/src/postgres/init.ts b/packages/drizzle/src/postgres/init.ts index 57c5617499e..3ff5e16cf0d 100644 --- a/packages/drizzle/src/postgres/init.ts +++ b/packages/drizzle/src/postgres/init.ts @@ -63,7 +63,7 @@ export const init: Init = function init(this: BasePostgresAdapter) { const versionsTableName = this.tableNameMap.get( `_${toSnakeCase(collection.slug)}${this.versionsSuffix}`, ) - const versionFields = buildVersionCollectionFields(collection) + const versionFields = buildVersionCollectionFields(this.payload.config, collection) buildTable({ adapter: this, @@ -97,7 +97,7 @@ export const init: Init = function init(this: BasePostgresAdapter) { versions: true, versionsCustomName: true, }) - const versionFields = buildVersionGlobalFields(global) + const versionFields = buildVersionGlobalFields(this.payload.config, global) buildTable({ adapter: this, diff --git a/packages/drizzle/src/queryDrafts.ts b/packages/drizzle/src/queryDrafts.ts index e09df59f103..636d486feb7 100644 --- a/packages/drizzle/src/queryDrafts.ts +++ b/packages/drizzle/src/queryDrafts.ts @@ -15,7 +15,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts( const tableName = this.tableNameMap.get( `_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`, ) - const fields = buildVersionCollectionFields(collectionConfig) + const fields = buildVersionCollectionFields(this.payload.config, collectionConfig) const combinedWhere = combineQueries({ latest: { equals: true } }, where) diff --git a/packages/drizzle/src/updateGlobalVersion.ts b/packages/drizzle/src/updateGlobalVersion.ts index ff6352f211b..5c557881411 100644 --- a/packages/drizzle/src/updateGlobalVersion.ts +++ b/packages/drizzle/src/updateGlobalVersion.ts @@ -35,7 +35,7 @@ export async function updateGlobalVersion( `_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`, ) - const fields = buildVersionGlobalFields(globalConfig) + const fields = buildVersionGlobalFields(this.payload.config, globalConfig) const { where } = await buildQuery({ adapter: this, diff --git a/packages/drizzle/src/updateVersion.ts b/packages/drizzle/src/updateVersion.ts index 5f922ec0a4a..bd0f0a7449d 100644 --- a/packages/drizzle/src/updateVersion.ts +++ b/packages/drizzle/src/updateVersion.ts @@ -32,7 +32,7 @@ export async function updateVersion( `_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`, ) - const fields = buildVersionCollectionFields(collectionConfig) + const fields = buildVersionCollectionFields(this.payload.config, collectionConfig) const { where } = await buildQuery({ adapter: this, diff --git a/packages/graphql/src/schema/initCollections.ts b/packages/graphql/src/schema/initCollections.ts index 22b6db259f6..a1a643ebbe0 100644 --- a/packages/graphql/src/schema/initCollections.ts +++ b/packages/graphql/src/schema/initCollections.ts @@ -276,7 +276,7 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ if (collectionConfig.versions) { const versionIDType = config.db.defaultIDType === 'text' ? GraphQLString : GraphQLInt const versionCollectionFields: Field[] = [ - ...buildVersionCollectionFields(collectionConfig), + ...buildVersionCollectionFields(config, collectionConfig), { name: 'id', type: config.db.defaultIDType as 'text', diff --git a/packages/graphql/src/schema/initGlobals.ts b/packages/graphql/src/schema/initGlobals.ts index dd8452c3f48..d11bc7a3fb4 100644 --- a/packages/graphql/src/schema/initGlobals.ts +++ b/packages/graphql/src/schema/initGlobals.ts @@ -105,7 +105,7 @@ export function initGlobals({ config, graphqlResult }: InitGlobalsGraphQLArgs): const idType = config.db.defaultIDType === 'number' ? GraphQLInt : GraphQLString const versionGlobalFields: Field[] = [ - ...buildVersionGlobalFields(global), + ...buildVersionGlobalFields(config, global), { name: 'id', type: config.db.defaultIDType as 'text', diff --git a/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx b/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx index 26a3f7555f4..72d61c26825 100644 --- a/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx +++ b/packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx @@ -7,20 +7,12 @@ import { baseClass } from '../../Tab/index.js' export const VersionsPill: React.FC = () => { const { versions } = useDocumentInfo() - // To prevent CLS (versions are currently loaded client-side), render non-breaking space if there are no versions - // The pill is already conditionally rendered to begin with based on whether the document is version-enabled - // documents that are version enabled _always_ have at least one version - const hasVersions = versions?.totalDocs > 0 + // don't count snapshots + const totalVersions = versions?.docs.filter((version) => !version.snapshot).length || 0 - if (hasVersions) { - return ( - - {versions.totalDocs.toString()} - - ) + if (!versions?.totalDocs) { + return null } + + return {totalVersions} } diff --git a/packages/next/src/routes/rest/collections/updateByID.ts b/packages/next/src/routes/rest/collections/updateByID.ts index 207c51cb9ba..ca64dfb4798 100644 --- a/packages/next/src/routes/rest/collections/updateByID.ts +++ b/packages/next/src/routes/rest/collections/updateByID.ts @@ -16,6 +16,7 @@ export const updateByID: CollectionRouteHandlerWithID = async ({ const depth = searchParams.get('depth') const autosave = searchParams.get('autosave') === 'true' const draft = searchParams.get('draft') === 'true' + const publishSpecificLocale = req.query.publishSpecificLocale as string | undefined const id = sanitizeCollectionID({ id: incomingID, @@ -30,6 +31,7 @@ export const updateByID: CollectionRouteHandlerWithID = async ({ data: req.data, depth: isNumber(depth) ? Number(depth) : undefined, draft, + publishSpecificLocale, req, }) diff --git a/packages/next/src/routes/rest/globals/update.ts b/packages/next/src/routes/rest/globals/update.ts index b9625b7320c..afc8e70add7 100644 --- a/packages/next/src/routes/rest/globals/update.ts +++ b/packages/next/src/routes/rest/globals/update.ts @@ -11,6 +11,7 @@ export const update: GlobalRouteHandler = async ({ globalConfig, req }) => { const depth = searchParams.get('depth') const draft = searchParams.get('draft') === 'true' const autosave = searchParams.get('autosave') === 'true' + const publishSpecificLocale = req.query.publishSpecificLocale as string | undefined const result = await updateOperationGlobal({ slug: globalConfig.slug, @@ -19,6 +20,7 @@ export const update: GlobalRouteHandler = async ({ globalConfig, req }) => { depth: isNumber(depth) ? Number(depth) : undefined, draft, globalConfig, + publishSpecificLocale, req, }) diff --git a/packages/next/src/views/Version/SelectComparison/index.tsx b/packages/next/src/views/Version/SelectComparison/index.tsx index 834237b95c7..69da0770d7e 100644 --- a/packages/next/src/views/Version/SelectComparison/index.tsx +++ b/packages/next/src/views/Version/SelectComparison/index.tsx @@ -2,7 +2,7 @@ import type { PaginatedDocs, Where } from 'payload' -import { fieldBaseClass, ReactSelect, useConfig, useTranslation } from '@payloadcms/ui' +import { fieldBaseClass, Pill, ReactSelect, useConfig, useTranslation } from '@payloadcms/ui' import { formatDate } from '@payloadcms/ui/shared' import * as qs from 'qs-esm' import React, { useCallback, useEffect, useState } from 'react' @@ -45,6 +45,9 @@ export const SelectComparison: React.FC = (props) => { const [errorLoading, setErrorLoading] = useState('') const { i18n, t } = useTranslation() const loadedAllOptionsRef = React.useRef(false) + const { + config: { localization }, + } = useConfig() const getResults = useCallback( async ({ lastLoadedPage: lastLoadedPageArg }) => { @@ -65,6 +68,11 @@ export const SelectComparison: React.FC = (props) => { not_equals: versionID, }, }, + { + snapshot: { + not_equals: true, + }, + }, ], }, } @@ -107,15 +115,31 @@ export const SelectComparison: React.FC = (props) => { const additionalOptions = data.docs.map((doc) => { const status = doc.version._status + let publishedLocalePill = null + const publishedLocale = doc.publishedLocale || undefined const { currentLabel, latestVersion, pillStyle, previousLabel } = versionInfo[status] || {} + if (localization && localization?.locales && publishedLocale) { + const localeCode = Array.isArray(publishedLocale) + ? publishedLocale[0] + : publishedLocale + + const locale = localization.locales.find((loc) => loc.code === localeCode) + const formattedLabel = locale?.label?.[i18n?.language] || locale?.label + + if (formattedLabel) { + publishedLocalePill = {formattedLabel} + } + } + return { label: (
{formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat })}    {renderPill(doc, latestVersion, currentLabel, previousLabel, pillStyle)} + {publishedLocalePill}
), value: doc.id, diff --git a/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx b/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx index 0d9da0fb040..777ea7c8a90 100644 --- a/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx +++ b/packages/next/src/views/Versions/cells/AutosaveCell/index.tsx @@ -1,5 +1,5 @@ 'use client' -import { Pill, useTableCell, useTranslation } from '@payloadcms/ui' +import { Pill, useConfig, useTableCell, useTranslation } from '@payloadcms/ui' import React, { Fragment } from 'react' type AutosaveCellProps = { @@ -24,10 +24,15 @@ export const AutosaveCell: React.FC = ({ latestDraftVersion, latestPublishedVersion, }) => { - const { t } = useTranslation() + const { i18n, t } = useTranslation() const { rowData } = useTableCell() + const { + config: { localization }, + } = useConfig() + const publishedLocale = rowData?.publishedLocale || undefined const status = rowData?.version._status + let publishedLocalePill = null const versionInfo = { draft: { @@ -46,15 +51,22 @@ export const AutosaveCell: React.FC = ({ const { currentLabel, latestVersion, pillStyle, previousLabel } = versionInfo[status] || {} + if (localization && localization?.locales && publishedLocale) { + const localeCode = Array.isArray(publishedLocale) ? publishedLocale[0] : publishedLocale + + const locale = localization.locales.find((loc) => loc.code === localeCode) + const formattedLabel = locale?.label?.[i18n?.language] || locale?.label + + if (formattedLabel) { + publishedLocalePill = {formattedLabel} + } + } + return ( - {rowData?.autosave && ( - - {t('version:autosave')} -    - - )} + {rowData?.autosave && {t('version:autosave')}} {status && renderPill(rowData, latestVersion, currentLabel, previousLabel, pillStyle)} + {publishedLocalePill} ) } diff --git a/packages/next/src/views/Versions/index.tsx b/packages/next/src/views/Versions/index.tsx index 0698dad6b4c..54038380841 100644 --- a/packages/next/src/views/Versions/index.tsx +++ b/packages/next/src/views/Versions/index.tsx @@ -56,9 +56,18 @@ export const VersionsView: PayloadServerReactComponent = asyn sort: sort as string, user, where: { - parent: { - equals: id, - }, + and: [ + { + parent: { + equals: id, + }, + }, + { + snapshot: { + not_equals: true, + }, + }, + ], }, }) if (collectionConfig?.versions?.drafts) { @@ -92,6 +101,11 @@ export const VersionsView: PayloadServerReactComponent = asyn req, sort: sort as string, user, + where: { + snapshot: { + not_equals: true, + }, + }, }) if (globalConfig?.versions?.drafts) { diff --git a/packages/payload/src/collections/operations/find.ts b/packages/payload/src/collections/operations/find.ts index d6592e2c71f..fc812f58662 100644 --- a/packages/payload/src/collections/operations/find.ts +++ b/packages/payload/src/collections/operations/find.ts @@ -118,7 +118,7 @@ export const findOperation = async ( collectionConfig: collection.config, overrideAccess, req, - versionFields: buildVersionCollectionFields(collection.config), + versionFields: buildVersionCollectionFields(payload.config, collection.config), where: fullWhere, }) diff --git a/packages/payload/src/collections/operations/findVersions.ts b/packages/payload/src/collections/operations/findVersions.ts index b92cf815b61..3426a32f1f4 100644 --- a/packages/payload/src/collections/operations/findVersions.ts +++ b/packages/payload/src/collections/operations/findVersions.ts @@ -52,7 +52,7 @@ export const findVersionsOperation = async accessResults = await executeAccess({ req }, collectionConfig.access.readVersions) } - const versionFields = buildVersionCollectionFields(collectionConfig) + const versionFields = buildVersionCollectionFields(payload.config, collectionConfig) await validateQueryPaths({ collectionConfig, diff --git a/packages/payload/src/collections/operations/local/update.ts b/packages/payload/src/collections/operations/local/update.ts index 31ea589d7f4..b15eafe05f6 100644 --- a/packages/payload/src/collections/operations/local/update.ts +++ b/packages/payload/src/collections/operations/local/update.ts @@ -31,6 +31,7 @@ export type BaseOptions = { locale?: TypedLocale overrideAccess?: boolean overwriteExistingFiles?: boolean + publishSpecificLocale?: string req?: PayloadRequest showHiddenFields?: boolean user?: Document @@ -75,6 +76,7 @@ async function updateLocal( filePath, overrideAccess = true, overwriteExistingFiles = false, + publishSpecificLocale, showHiddenFields, where, } = options @@ -100,6 +102,7 @@ async function updateLocal( overrideAccess, overwriteExistingFiles, payload, + publishSpecificLocale, req, showHiddenFields, where, diff --git a/packages/payload/src/collections/operations/update.ts b/packages/payload/src/collections/operations/update.ts index efcf16ec5e3..b7f506d8b8d 100644 --- a/packages/payload/src/collections/operations/update.ts +++ b/packages/payload/src/collections/operations/update.ts @@ -128,7 +128,7 @@ export const updateOperation = async ( collectionConfig: collection.config, overrideAccess, req, - versionFields: buildVersionCollectionFields(collection.config), + versionFields: buildVersionCollectionFields(payload.config, collection.config), where: versionsWhere, }) diff --git a/packages/payload/src/collections/operations/updateByID.ts b/packages/payload/src/collections/operations/updateByID.ts index b628778e15a..0da7b768d24 100644 --- a/packages/payload/src/collections/operations/updateByID.ts +++ b/packages/payload/src/collections/operations/updateByID.ts @@ -3,6 +3,7 @@ import type { DeepPartial } from 'ts-essentials' import httpStatus from 'http-status' import type { FindOneArgs } from '../../database/types.js' +import type { Args } from '../../fields/hooks/beforeChange/index.js' import type { CollectionSlug } from '../../index.js' import type { PayloadRequest } from '../../types/index.js' import type { @@ -43,6 +44,7 @@ export type Arguments = { id: number | string overrideAccess?: boolean overwriteExistingFiles?: boolean + publishSpecificLocale?: string req: PayloadRequest showHiddenFields?: boolean } @@ -72,6 +74,10 @@ export const updateByIDOperation = async ( })) || args }, Promise.resolve()) + if (args.publishSpecificLocale) { + args.req.locale = args.publishSpecificLocale + } + const { id, autosave = false, @@ -81,6 +87,7 @@ export const updateByIDOperation = async ( draft: draftArg = false, overrideAccess, overwriteExistingFiles = false, + publishSpecificLocale, req: { fallbackLocale, locale, @@ -264,13 +271,16 @@ export const updateByIDOperation = async ( // beforeChange - Fields // ///////////////////////////////////// - let result = await beforeChange({ + let publishedDocWithLocales = docWithLocales + let versionSnapshotResult + + const beforeChangeArgs: Args> = { id, collection: collectionConfig, context: req.context, - data, + data: { ...data, id }, doc: originalDoc, - docWithLocales, + docWithLocales: undefined, global: null, operation: 'update', req, @@ -279,6 +289,27 @@ export const updateByIDOperation = async ( collectionConfig.versions.drafts && !collectionConfig.versions.drafts.validate && data._status !== 'published', + } + + if (publishSpecificLocale) { + publishedDocWithLocales = await getLatestCollectionVersion({ + id, + config: collectionConfig, + payload, + published: true, + query: findOneArgs, + req, + }) + + versionSnapshotResult = await beforeChange({ + ...beforeChangeArgs, + docWithLocales, + }) + } + + let result = await beforeChange({ + ...beforeChangeArgs, + docWithLocales: publishedDocWithLocales, }) // ///////////////////////////////////// @@ -328,7 +359,9 @@ export const updateByIDOperation = async ( }, draft: shouldSaveDraft, payload, + publishSpecificLocale, req, + snapshot: versionSnapshotResult, }) } diff --git a/packages/payload/src/database/types.ts b/packages/payload/src/database/types.ts index 88b4d1a384a..f3857abeb72 100644 --- a/packages/payload/src/database/types.ts +++ b/packages/payload/src/database/types.ts @@ -311,7 +311,9 @@ export type CreateVersionArgs = { createdAt: string /** ID of the parent document for which the version should be created for */ parent: number | string + publishedLocale?: string req: PayloadRequest + snapshot?: true updatedAt: string versionData: T } @@ -326,7 +328,9 @@ export type CreateGlobalVersionArgs = { globalSlug: string /** ID of the parent document for which the version should be created for */ parent: number | string + publishedLocale?: string req: PayloadRequest + snapshot?: true updatedAt: string versionData: T } diff --git a/packages/payload/src/fields/hooks/beforeChange/index.ts b/packages/payload/src/fields/hooks/beforeChange/index.ts index 9d369c8416d..11a4235d3e1 100644 --- a/packages/payload/src/fields/hooks/beforeChange/index.ts +++ b/packages/payload/src/fields/hooks/beforeChange/index.ts @@ -5,8 +5,7 @@ import type { JsonObject, Operation, PayloadRequest, RequestContext } from '../. import { ValidationError } from '../../../errors/index.js' import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js' import { traverseFields } from './traverseFields.js' - -type Args = { +export type Args = { collection: null | SanitizedCollectionConfig context: RequestContext data: T diff --git a/packages/payload/src/fields/hooks/beforeChange/promise.ts b/packages/payload/src/fields/hooks/beforeChange/promise.ts index 7e30d2488c0..4140d9b4398 100644 --- a/packages/payload/src/fields/hooks/beforeChange/promise.ts +++ b/packages/payload/src/fields/hooks/beforeChange/promise.ts @@ -310,14 +310,13 @@ export const promise = async ({ case 'blocks': { const rows = siblingData[field.name] - if (Array.isArray(rows)) { const promises = [] rows.forEach((row, i) => { const rowSiblingDoc = getExistingRowDoc(row as JsonObject, siblingDoc[field.name]) const rowSiblingDocWithLocales = getExistingRowDoc( row as JsonObject, - siblingDocWithLocales[field.name], + siblingDocWithLocales ? siblingDocWithLocales[field.name] : {}, ) const blockTypeToMatch = (row as JsonObject).blockType || rowSiblingDoc.blockType diff --git a/packages/payload/src/globals/operations/findVersions.ts b/packages/payload/src/globals/operations/findVersions.ts index 575c0ee757b..19d5bd6f469 100644 --- a/packages/payload/src/globals/operations/findVersions.ts +++ b/packages/payload/src/globals/operations/findVersions.ts @@ -41,7 +41,7 @@ export const findVersionsOperation = async >( where, } = args - const versionFields = buildVersionGlobalFields(globalConfig) + const versionFields = buildVersionGlobalFields(payload.config, globalConfig) try { // ///////////////////////////////////// diff --git a/packages/payload/src/globals/operations/local/update.ts b/packages/payload/src/globals/operations/local/update.ts index 01fa21e2fc4..2c8881edc6d 100644 --- a/packages/payload/src/globals/operations/local/update.ts +++ b/packages/payload/src/globals/operations/local/update.ts @@ -16,6 +16,7 @@ export type Options = { fallbackLocale?: TypedLocale locale?: TypedLocale overrideAccess?: boolean + publishSpecificLocale?: TypedLocale req?: PayloadRequest showHiddenFields?: boolean slug: TSlug @@ -26,7 +27,15 @@ export default async function updateLocal( payload: Payload, options: Options, ): Promise> { - const { slug: globalSlug, data, depth, draft, overrideAccess = true, showHiddenFields } = options + const { + slug: globalSlug, + data, + depth, + draft, + overrideAccess = true, + publishSpecificLocale, + showHiddenFields, + } = options const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug) @@ -41,6 +50,7 @@ export default async function updateLocal( draft, globalConfig, overrideAccess, + publishSpecificLocale, req: await createLocalReq(options, payload), showHiddenFields, }) diff --git a/packages/payload/src/globals/operations/update.ts b/packages/payload/src/globals/operations/update.ts index 94ecf99999a..cbcadc129b7 100644 --- a/packages/payload/src/globals/operations/update.ts +++ b/packages/payload/src/globals/operations/update.ts @@ -1,7 +1,7 @@ import type { DeepPartial } from 'ts-essentials' import type { GlobalSlug, JsonObject } from '../../index.js' -import type { PayloadRequest, Where } from '../../types/index.js' +import type { Operation, PayloadRequest, Where } from '../../types/index.js' import type { DataFromGlobalSlug, SanitizedGlobalConfig } from '../config/types.js' import executeAccess from '../../auth/executeAccess.js' @@ -25,6 +25,7 @@ type Args = { draft?: boolean globalConfig: SanitizedGlobalConfig overrideAccess?: boolean + publishSpecificLocale?: string req: PayloadRequest showHiddenFields?: boolean slug: string @@ -33,6 +34,10 @@ type Args = { export const updateOperation = async ( args: Args, ): Promise> => { + if (args.publishSpecificLocale) { + args.req.locale = args.publishSpecificLocale + } + const { slug, autosave, @@ -40,6 +45,7 @@ export const updateOperation = async ( draft: draftArg, globalConfig, overrideAccess, + publishSpecificLocale, req: { fallbackLocale, locale, payload }, req, showHiddenFields, @@ -75,7 +81,7 @@ export const updateOperation = async ( // ///////////////////////////////////// // 2. Retrieve document // ///////////////////////////////////// - const { global, globalExists } = await getLatestGlobalVersion({ + const globalVersion = await getLatestGlobalVersion({ slug, config: globalConfig, locale, @@ -83,10 +89,11 @@ export const updateOperation = async ( req, where: query, }) + const { global, globalExists } = globalVersion || {} let globalJSON: JsonObject = {} - if (global) { + if (globalVersion && globalVersion.global) { globalJSON = deepCopyObjectSimple(global) if (globalJSON._id) { @@ -170,18 +177,43 @@ export const updateOperation = async ( // ///////////////////////////////////// // beforeChange - Fields // ///////////////////////////////////// + let publishedDocWithLocales = globalJSON + let versionSnapshotResult - let result = await beforeChange({ + const beforeChangeArgs = { collection: null, context: req.context, data, doc: originalDoc, - docWithLocales: globalJSON, + docWithLocales: undefined, global: globalConfig, - operation: 'update', + operation: 'update' as Operation, req, skipValidation: shouldSaveDraft && globalConfig.versions.drafts && !globalConfig.versions.drafts.validate, + } + + if (publishSpecificLocale) { + const latestVersion = await getLatestGlobalVersion({ + slug, + config: globalConfig, + payload, + published: true, + req, + where: query, + }) + + publishedDocWithLocales = latestVersion?.global || {} + + versionSnapshotResult = await beforeChange({ + ...beforeChangeArgs, + docWithLocales: globalJSON, + }) + } + + let result = await beforeChange({ + ...beforeChangeArgs, + docWithLocales: publishedDocWithLocales, }) // ///////////////////////////////////// @@ -207,7 +239,6 @@ export const updateOperation = async ( // ///////////////////////////////////// // Create version // ///////////////////////////////////// - if (globalConfig.versions) { const { globalType } = result result = await saveVersion({ @@ -220,9 +251,15 @@ export const updateOperation = async ( draft: shouldSaveDraft, global: globalConfig, payload, + publishSpecificLocale, req, + snapshot: versionSnapshotResult, }) - result.globalType = globalType + + result = { + ...result, + globalType, + } } // ///////////////////////////////////// diff --git a/packages/payload/src/versions/baseFields.ts b/packages/payload/src/versions/baseFields.ts index 0fb743d637d..458bf3f32aa 100644 --- a/packages/payload/src/versions/baseFields.ts +++ b/packages/payload/src/versions/baseFields.ts @@ -28,4 +28,19 @@ const baseVersionFields: Field[] = [ }, ] +// When publishing a specific locale, +// we need to create a new draft which acts as a +// "snapshot" to retain all existing draft data. +// This field will be used to exclude any snapshot versions +// from the admin Versions list +export const versionSnapshotField: Field = { + name: 'snapshot', + type: 'checkbox', + admin: { + disableBulkEdit: true, + disabled: true, + }, + index: true, +} + export default baseVersionFields diff --git a/packages/payload/src/versions/buildCollectionFields.ts b/packages/payload/src/versions/buildCollectionFields.ts index 65c81d683b1..89eb41aa854 100644 --- a/packages/payload/src/versions/buildCollectionFields.ts +++ b/packages/payload/src/versions/buildCollectionFields.ts @@ -1,7 +1,13 @@ import type { SanitizedCollectionConfig } from '../collections/config/types.js' +import type { SanitizedConfig } from '../config/types.js' import type { Field } from '../fields/config/types.js' -export const buildVersionCollectionFields = (collection: SanitizedCollectionConfig): Field[] => { +import { versionSnapshotField } from './baseFields.js' + +export const buildVersionCollectionFields = ( + config: SanitizedConfig, + collection: SanitizedCollectionConfig, +): Field[] => { const fields: Field[] = [ { name: 'parent', @@ -33,6 +39,27 @@ export const buildVersionCollectionFields = (collection: SanitizedCollectionConf ] if (collection?.versions?.drafts) { + if (config.localization) { + fields.push(versionSnapshotField) + + fields.push({ + name: 'publishedLocale', + type: 'select', + admin: { + disableBulkEdit: true, + disabled: true, + }, + index: true, + options: config.localization.locales.map((locale) => { + if (typeof locale === 'string') { + return locale + } + + return locale.code + }), + }) + } + fields.push({ name: 'latest', type: 'checkbox', @@ -41,14 +68,14 @@ export const buildVersionCollectionFields = (collection: SanitizedCollectionConf }, index: true, }) - } - if (collection?.versions?.drafts && collection?.versions?.drafts?.autosave) { - fields.push({ - name: 'autosave', - type: 'checkbox', - index: true, - }) + if (collection?.versions?.drafts?.autosave) { + fields.push({ + name: 'autosave', + type: 'checkbox', + index: true, + }) + } } return fields diff --git a/packages/payload/src/versions/buildGlobalFields.ts b/packages/payload/src/versions/buildGlobalFields.ts index af18b65e57e..5954967edd7 100644 --- a/packages/payload/src/versions/buildGlobalFields.ts +++ b/packages/payload/src/versions/buildGlobalFields.ts @@ -1,7 +1,13 @@ +import type { SanitizedConfig } from '../config/types.js' import type { Field } from '../fields/config/types.js' import type { SanitizedGlobalConfig } from '../globals/config/types.js' -export const buildVersionGlobalFields = (global: SanitizedGlobalConfig): Field[] => { +import { versionSnapshotField } from './baseFields.js' + +export const buildVersionGlobalFields = ( + config: SanitizedConfig, + global: SanitizedGlobalConfig, +): Field[] => { const fields: Field[] = [ { name: 'version', @@ -25,6 +31,27 @@ export const buildVersionGlobalFields = (global: SanitizedGlobalConfig): Field[] ] if (global?.versions?.drafts) { + if (config.localization) { + fields.push(versionSnapshotField) + + fields.push({ + name: 'publishedLocale', + type: 'select', + admin: { + disableBulkEdit: true, + disabled: true, + }, + index: true, + options: config.localization.locales.map((locale) => { + if (typeof locale === 'string') { + return locale + } + + return locale.code + }), + }) + } + fields.push({ name: 'latest', type: 'checkbox', @@ -33,14 +60,14 @@ export const buildVersionGlobalFields = (global: SanitizedGlobalConfig): Field[] }, index: true, }) - } - if (global?.versions?.drafts && global?.versions?.drafts?.autosave) { - fields.push({ - name: 'autosave', - type: 'checkbox', - index: true, - }) + if (global?.versions?.drafts?.autosave) { + fields.push({ + name: 'autosave', + type: 'checkbox', + index: true, + }) + } } return fields diff --git a/packages/payload/src/versions/drafts/replaceWithDraftIfAvailable.ts b/packages/payload/src/versions/drafts/replaceWithDraftIfAvailable.ts index a2db0588b13..4c61999955e 100644 --- a/packages/payload/src/versions/drafts/replaceWithDraftIfAvailable.ts +++ b/packages/payload/src/versions/drafts/replaceWithDraftIfAvailable.ts @@ -49,9 +49,18 @@ const replaceWithDraftIfAvailable = async ({ if (docHasTimestamps(doc)) { queryToBuild.and.push({ - updatedAt: { - greater_than: doc.updatedAt, - }, + or: [ + { + updatedAt: { + greater_than: doc.updatedAt, + }, + }, + { + latest: { + equals: true, + }, + }, + ], }) } diff --git a/packages/payload/src/versions/getLatestCollectionVersion.ts b/packages/payload/src/versions/getLatestCollectionVersion.ts index c68017a481c..8366d3bbe29 100644 --- a/packages/payload/src/versions/getLatestCollectionVersion.ts +++ b/packages/payload/src/versions/getLatestCollectionVersion.ts @@ -3,12 +3,11 @@ import type { FindOneArgs } from '../database/types.js' import type { Payload, PayloadRequest } from '../types/index.js' import type { TypeWithVersion } from './types.js' -import { docHasTimestamps } from '../types/index.js' - type Args = { config: SanitizedCollectionConfig id: number | string payload: Payload + published?: boolean query: FindOneArgs req?: PayloadRequest } @@ -17,11 +16,16 @@ export const getLatestCollectionVersion = async ({ id, config, payload, + published, query, req, }: Args): Promise => { let latestVersion: TypeWithVersion + const whereQuery = published + ? { and: [{ parent: { equals: id } }, { 'version._status': { equals: 'published' } }] } + : { and: [{ parent: { equals: id } }, { latest: { equals: true } }] } + if (config.versions?.drafts) { const { docs } = await payload.db.findVersions({ collection: config.slug, @@ -29,15 +33,19 @@ export const getLatestCollectionVersion = async ({ pagination: false, req, sort: '-updatedAt', - where: { parent: { equals: id } }, + where: whereQuery, }) ;[latestVersion] = docs } - const doc = await payload.db.findOne({ ...query, req }) + if (!latestVersion) { + if (!published) { + const doc = await payload.db.findOne({ ...query, req }) + + return doc + } - if (!latestVersion || (docHasTimestamps(doc) && latestVersion.updatedAt < doc.updatedAt)) { - return doc + return undefined } return { diff --git a/packages/payload/src/versions/getLatestGlobalVersion.ts b/packages/payload/src/versions/getLatestGlobalVersion.ts index 42303c0595e..5d0413bf634 100644 --- a/packages/payload/src/versions/getLatestGlobalVersion.ts +++ b/packages/payload/src/versions/getLatestGlobalVersion.ts @@ -7,6 +7,7 @@ type Args = { config: SanitizedGlobalConfig locale?: string payload: Payload + published?: boolean req?: PayloadRequest slug: string where: Where @@ -17,11 +18,16 @@ export const getLatestGlobalVersion = async ({ config, locale, payload, + published, req, where, }: Args): Promise<{ global: Document; globalExists: boolean }> => { let latestVersion + const whereQuery = published + ? { 'version._status': { equals: 'published' } } + : { latest: { equals: true } } + if (config.versions?.drafts) { latestVersion = ( await payload.db.findGlobalVersions({ @@ -30,7 +36,7 @@ export const getLatestGlobalVersion = async ({ locale, pagination: false, req, - sort: '-updatedAt', + where: whereQuery, }) ).docs[0] } @@ -43,7 +49,7 @@ export const getLatestGlobalVersion = async ({ }) const globalExists = Boolean(global) - if (!latestVersion || (docHasTimestamps(global) && latestVersion.updatedAt < global.updatedAt)) { + if (!latestVersion) { return { global, globalExists, diff --git a/packages/payload/src/versions/saveVersion.ts b/packages/payload/src/versions/saveVersion.ts index f18fc9df247..ade83fa26e7 100644 --- a/packages/payload/src/versions/saveVersion.ts +++ b/packages/payload/src/versions/saveVersion.ts @@ -15,7 +15,9 @@ type Args = { global?: SanitizedGlobalConfig id?: number | string payload: Payload + publishSpecificLocale?: string req?: PayloadRequest + snapshot?: any } export const saveVersion = async ({ @@ -26,7 +28,9 @@ export const saveVersion = async ({ draft, global, payload, + publishSpecificLocale, req, + snapshot, }: Args): Promise => { let result let createNewVersion = true @@ -75,7 +79,9 @@ export const saveVersion = async ({ const data: Record = { createdAt: new Date(latestVersion.createdAt).toISOString(), updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(), - version: versionData, + version: { + ...versionData, + }, } const updateVersionArgs = { @@ -101,28 +107,50 @@ export const saveVersion = async ({ } if (createNewVersion) { + const createVersionArgs = { + autosave: Boolean(autosave), + collectionSlug: undefined, + createdAt: doc?.createdAt ? new Date(doc.createdAt).toISOString() : now, + globalSlug: undefined, + parent: collection ? id : undefined, + publishedLocale: publishSpecificLocale || undefined, + req, + updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(), + versionData, + } + if (collection) { - result = await payload.db.createVersion({ - autosave: Boolean(autosave), - collectionSlug: collection.slug, - createdAt: doc?.createdAt ? new Date(doc.createdAt).toISOString() : now, - parent: collection ? id : undefined, - req, - updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(), - versionData, - }) + createVersionArgs.collectionSlug = collection.slug + result = await payload.db.createVersion(createVersionArgs) } if (global) { - result = await payload.db.createGlobalVersion({ - autosave: Boolean(autosave), - createdAt: doc?.createdAt ? new Date(doc.createdAt).toISOString() : now, - globalSlug: global.slug, - parent: collection ? id : undefined, - req, - updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(), - versionData, - }) + createVersionArgs.globalSlug = global.slug + result = await payload.db.createGlobalVersion(createVersionArgs) + } + + if (publishSpecificLocale && snapshot) { + const snapshotData = deepCopyObjectSimple(snapshot) + if (snapshotData._id) {delete snapshotData._id} + + snapshotData._status = 'draft' + + const snapshotDate = new Date().toISOString() + + const updatedArgs = { + ...createVersionArgs, + createdAt: snapshotDate, + snapshot: true, + updatedAt: snapshotDate, + versionData: snapshotData, + } as any + + if (collection) { + await payload.db.createVersion(updatedArgs) + } + if (global) { + await payload.db.createGlobalVersion(updatedArgs) + } } } } catch (err) { diff --git a/packages/payload/src/versions/types.ts b/packages/payload/src/versions/types.ts index 904c8205450..bf0b7bf2056 100644 --- a/packages/payload/src/versions/types.ts +++ b/packages/payload/src/versions/types.ts @@ -36,6 +36,7 @@ export type TypeWithVersion = { createdAt: string id: string parent: number | string + snapshot?: boolean updatedAt: string version: T } diff --git a/packages/translations/src/clientKeys.ts b/packages/translations/src/clientKeys.ts index 93202e8befa..6901ba11cb1 100644 --- a/packages/translations/src/clientKeys.ts +++ b/packages/translations/src/clientKeys.ts @@ -207,6 +207,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'general:noValue', 'general:of', 'general:open', + 'general:only', 'general:or', 'general:order', 'general:pageNotFound', @@ -331,6 +332,7 @@ export const clientTranslationKeys = createClientTranslationKeys([ 'version:publish', 'version:publishChanges', 'version:published', + 'version:publishIn', 'version:publishing', 'version:restoreAsDraft', 'version:restoredSuccessfully', diff --git a/packages/translations/src/languages/ar.ts b/packages/translations/src/languages/ar.ts index d414770f818..9fc249f6c5c 100644 --- a/packages/translations/src/languages/ar.ts +++ b/packages/translations/src/languages/ar.ts @@ -264,6 +264,7 @@ export const arTranslations: DefaultTranslationsObject = { nothingFound: 'لم يتم العثور على شيء', noValue: 'لا يوجد قيمة', of: 'من', + only: 'فقط', open: 'فتح', or: 'أو', order: 'التّرتيب', @@ -413,6 +414,7 @@ export const arTranslations: DefaultTranslationsObject = { publish: 'نشر', publishChanges: 'نشر التّغييرات', published: 'تمّ النّشر', + publishIn: 'نشر في {{locale}}', publishing: 'نشر', restoreAsDraft: 'استعادة كمسودة', restoredSuccessfully: 'تمّت الاستعادة بنحاح.', diff --git a/packages/translations/src/languages/az.ts b/packages/translations/src/languages/az.ts index 309ed28cc4f..14dd9272171 100644 --- a/packages/translations/src/languages/az.ts +++ b/packages/translations/src/languages/az.ts @@ -266,6 +266,7 @@ export const azTranslations: DefaultTranslationsObject = { nothingFound: 'Heç nə tapılmadı', noValue: 'Dəyər yoxdur', of: 'dən', + only: 'Yalnız', open: 'Aç', or: 'Və ya', order: 'Sıra', @@ -420,6 +421,7 @@ export const azTranslations: DefaultTranslationsObject = { publish: 'Dərc et', publishChanges: 'Dəyişiklikləri dərc et', published: 'Dərc edilmiş', + publishIn: '{{locale}} dili ilə nəşr edin', publishing: 'Nəşr', restoreAsDraft: 'Qaralamalar kimi bərpa et', restoredSuccessfully: 'Uğurla bərpa edildi.', diff --git a/packages/translations/src/languages/bg.ts b/packages/translations/src/languages/bg.ts index a0161396292..83e684e3592 100644 --- a/packages/translations/src/languages/bg.ts +++ b/packages/translations/src/languages/bg.ts @@ -265,6 +265,7 @@ export const bgTranslations: DefaultTranslationsObject = { nothingFound: 'Нищо не беше открито', noValue: 'Няма стойност', of: 'от', + only: 'Само', open: 'Отвори', or: 'Или', order: 'Ред', @@ -419,6 +420,7 @@ export const bgTranslations: DefaultTranslationsObject = { publish: 'Публикувай', publishChanges: 'Публикувай промените', published: 'Публикувано', + publishIn: 'Публикувайте в {{locale}}', publishing: 'Публикуване', restoreAsDraft: 'Възстанови като чернова', restoredSuccessfully: 'Успешно възстановяване.', diff --git a/packages/translations/src/languages/cs.ts b/packages/translations/src/languages/cs.ts index d5ddd0e8d31..3da8072bd95 100644 --- a/packages/translations/src/languages/cs.ts +++ b/packages/translations/src/languages/cs.ts @@ -265,6 +265,7 @@ export const csTranslations: DefaultTranslationsObject = { nothingFound: 'Nic nenalezeno', noValue: 'Žádná hodnota', of: 'z', + only: 'Pouze', open: 'Otevřít', or: 'Nebo', order: 'Pořadí', @@ -418,6 +419,7 @@ export const csTranslations: DefaultTranslationsObject = { publish: 'Publikovat', publishChanges: 'Publikovat změny', published: 'Publikováno', + publishIn: 'Publikovat v {{locale}}', publishing: 'Publikování', restoreAsDraft: 'Obnovit jako koncept', restoredSuccessfully: 'Úspěšně obnoveno.', diff --git a/packages/translations/src/languages/de.ts b/packages/translations/src/languages/de.ts index 8cc19ea6e02..3f9353b8c5a 100644 --- a/packages/translations/src/languages/de.ts +++ b/packages/translations/src/languages/de.ts @@ -270,6 +270,7 @@ export const deTranslations: DefaultTranslationsObject = { nothingFound: 'Keine Ergebnisse', noValue: 'Kein Wert', of: 'von', + only: 'Nur', open: 'Öffnen', or: 'oder', order: 'Reihenfolge', @@ -424,6 +425,7 @@ export const deTranslations: DefaultTranslationsObject = { publish: 'Veröffentlichen', publishChanges: 'Änderungen veröffentlichen', published: 'Veröffentlicht', + publishIn: 'Veröffentlichen in {{locale}}', publishing: 'Veröffentlichung', restoreAsDraft: 'Als Entwurf wiederherstellen', restoredSuccessfully: 'Erfolgreich wiederhergestellt.', diff --git a/packages/translations/src/languages/en.ts b/packages/translations/src/languages/en.ts index 8b674f2f068..9b91245aea9 100644 --- a/packages/translations/src/languages/en.ts +++ b/packages/translations/src/languages/en.ts @@ -268,6 +268,7 @@ export const enTranslations = { nothingFound: 'Nothing found', noValue: 'No value', of: 'of', + only: 'Only', open: 'Open', or: 'Or', order: 'Order', @@ -422,6 +423,7 @@ export const enTranslations = { publish: 'Publish', publishChanges: 'Publish changes', published: 'Published', + publishIn: 'Publish in {{locale}}', publishing: 'Publishing', restoreAsDraft: 'Restore as draft', restoredSuccessfully: 'Restored Successfully.', diff --git a/packages/translations/src/languages/es.ts b/packages/translations/src/languages/es.ts index 4e1cd203cc7..41e5d351f4e 100644 --- a/packages/translations/src/languages/es.ts +++ b/packages/translations/src/languages/es.ts @@ -270,6 +270,7 @@ export const esTranslations: DefaultTranslationsObject = { nothingFound: 'No se encontró nada', noValue: 'Sin valor', of: 'de', + only: 'Solo', open: 'Abrir', or: 'O', order: 'Orden', @@ -424,6 +425,7 @@ export const esTranslations: DefaultTranslationsObject = { publish: 'Publicar', publishChanges: 'Publicar cambios', published: 'Publicado', + publishIn: 'Publicar en {{locale}}', publishing: 'Publicación', restoreAsDraft: 'Restaurar como borrador', restoredSuccessfully: 'Restaurado éxito.', diff --git a/packages/translations/src/languages/fa.ts b/packages/translations/src/languages/fa.ts index a8d736307b1..e8360788dc1 100644 --- a/packages/translations/src/languages/fa.ts +++ b/packages/translations/src/languages/fa.ts @@ -265,6 +265,7 @@ export const faTranslations: DefaultTranslationsObject = { nothingFound: 'چیزی یافت نشد', noValue: 'بدون مقدار', of: 'از', + only: 'فقط', open: 'باز کردن', or: 'یا', order: 'چیدمان', @@ -417,6 +418,7 @@ export const faTranslations: DefaultTranslationsObject = { publish: 'انتشار', publishChanges: 'انتشار تغییرات', published: 'انتشار یافته', + publishIn: 'منتشر کنید در {{locale}}', publishing: 'انتشار', restoreAsDraft: 'بازیابی به عنوان پیش‌نویس', restoredSuccessfully: 'با موفقیت بازیابی شد.', diff --git a/packages/translations/src/languages/fr.ts b/packages/translations/src/languages/fr.ts index bcae56384ea..bdd460a9037 100644 --- a/packages/translations/src/languages/fr.ts +++ b/packages/translations/src/languages/fr.ts @@ -273,6 +273,7 @@ export const frTranslations: DefaultTranslationsObject = { nothingFound: 'Rien n’a été trouvé', noValue: 'Aucune valeur', of: 'de', + only: 'Seulement', open: 'Ouvrir', or: 'ou', order: 'Ordre', @@ -431,6 +432,7 @@ export const frTranslations: DefaultTranslationsObject = { publish: 'Publier', publishChanges: 'Publier les modifications', published: 'Publié', + publishIn: 'Publier en {{locale}}', publishing: 'Publication', restoreAsDraft: 'Restaurer comme brouillon', restoredSuccessfully: 'Restauré(e) avec succès.', diff --git a/packages/translations/src/languages/he.ts b/packages/translations/src/languages/he.ts index b32144e6b6c..8a8c46ec8e0 100644 --- a/packages/translations/src/languages/he.ts +++ b/packages/translations/src/languages/he.ts @@ -260,6 +260,7 @@ export const heTranslations: DefaultTranslationsObject = { nothingFound: 'לא נמצא כלום', noValue: 'אין ערך', of: 'מתוך', + only: undefined, open: 'פתח', or: 'או', order: 'סדר', @@ -407,6 +408,7 @@ export const heTranslations: DefaultTranslationsObject = { publish: 'פרסם', publishChanges: 'פרסם שינויים', published: 'פורסם', + publishIn: 'פרסם ב-{{locale}}', publishing: 'מפרסם', restoreAsDraft: 'שחזר כטיוטה', restoredSuccessfully: 'שוחזר בהצלחה.', diff --git a/packages/translations/src/languages/hr.ts b/packages/translations/src/languages/hr.ts index 2f25855af45..3c5156edf04 100644 --- a/packages/translations/src/languages/hr.ts +++ b/packages/translations/src/languages/hr.ts @@ -266,6 +266,7 @@ export const hrTranslations: DefaultTranslationsObject = { nothingFound: 'Ništa nije pronađeno', noValue: 'Bez vrijednosti', of: 'Od', + only: 'Samo', open: 'Otvori', or: 'Ili', order: 'Poredak', @@ -417,6 +418,7 @@ export const hrTranslations: DefaultTranslationsObject = { publish: 'Objaviti', publishChanges: 'Objavi promjene', published: 'Objavljeno', + publishIn: undefined, publishing: 'Objavljivanje', restoreAsDraft: 'Vrati kao skicu', restoredSuccessfully: 'Uspješno vraćeno.', diff --git a/packages/translations/src/languages/hu.ts b/packages/translations/src/languages/hu.ts index 6014af762e2..a779520ebb7 100644 --- a/packages/translations/src/languages/hu.ts +++ b/packages/translations/src/languages/hu.ts @@ -268,6 +268,7 @@ export const huTranslations: DefaultTranslationsObject = { nothingFound: 'Nincs találat', noValue: 'Nincs érték', of: 'a', + only: 'Csak', open: 'Megnyitás', or: 'Vagy', order: 'Sorrend', @@ -424,6 +425,7 @@ export const huTranslations: DefaultTranslationsObject = { publish: 'Közzététel', publishChanges: 'Módosítások közzététele', published: 'Közzétett', + publishIn: 'Közzététel ebben a {{locale}} területkódban', publishing: 'Közzététel', restoreAsDraft: 'Visszaállítás piszkozatként', restoredSuccessfully: 'Sikeresen visszaállítva.', diff --git a/packages/translations/src/languages/it.ts b/packages/translations/src/languages/it.ts index 22bef7f771c..49288a27212 100644 --- a/packages/translations/src/languages/it.ts +++ b/packages/translations/src/languages/it.ts @@ -269,6 +269,7 @@ export const itTranslations: DefaultTranslationsObject = { nothingFound: 'Non è stato trovato nulla', noValue: 'Nessun valore', of: 'di', + only: 'Solo', open: 'Apri', or: 'Oppure', order: 'Ordine', @@ -424,6 +425,7 @@ export const itTranslations: DefaultTranslationsObject = { publish: 'Pubblicare', publishChanges: 'Pubblica modifiche', published: 'Pubblicato', + publishIn: 'Pubblica in {{locale}}', publishing: 'Pubblicazione', restoreAsDraft: 'Ripristina come bozza', restoredSuccessfully: 'Ripristinato con successo.', diff --git a/packages/translations/src/languages/ja.ts b/packages/translations/src/languages/ja.ts index c62d4b05d8c..3be5e90fe80 100644 --- a/packages/translations/src/languages/ja.ts +++ b/packages/translations/src/languages/ja.ts @@ -266,6 +266,7 @@ export const jaTranslations: DefaultTranslationsObject = { nothingFound: 'Nothing found', noValue: '未設定', of: '/', + only: 'のみ', open: '開く', or: 'または', order: '表示順', @@ -418,6 +419,7 @@ export const jaTranslations: DefaultTranslationsObject = { publish: '公開する', publishChanges: '変更内容を公開', published: '公開済み', + publishIn: '{{locale}}で公開する', publishing: '公開', restoreAsDraft: '下書きとして復元', restoredSuccessfully: '正常に復元されました。', diff --git a/packages/translations/src/languages/ko.ts b/packages/translations/src/languages/ko.ts index 4aec5ded184..6d1c7a2eb36 100644 --- a/packages/translations/src/languages/ko.ts +++ b/packages/translations/src/languages/ko.ts @@ -265,6 +265,7 @@ export const koTranslations: DefaultTranslationsObject = { nothingFound: '찾을 수 없습니다', noValue: '값 없음', of: '의', + only: '오직', open: '열기', or: '또는', order: '순서', @@ -414,6 +415,7 @@ export const koTranslations: DefaultTranslationsObject = { publish: '게시', publishChanges: '변경 사항 게시', published: '게시됨', + publishIn: '{{locale}}에서 게시하십시오.', publishing: '게시', restoreAsDraft: '임시 저장으로 복원', restoredSuccessfully: '복원이 완료되었습니다.', diff --git a/packages/translations/src/languages/my.ts b/packages/translations/src/languages/my.ts index 0a0dad5f060..c957f98de66 100644 --- a/packages/translations/src/languages/my.ts +++ b/packages/translations/src/languages/my.ts @@ -268,6 +268,7 @@ export const myTranslations: DefaultTranslationsObject = { nothingFound: 'ဘာမှလည်း မတွေ့ဘူး။', noValue: 'တန်ဖိုး မရှိပါ။', of: '၏', + only: 'Hanya', open: 'ဖွင့်မည်။', or: 'သို့မဟုတ်', order: 'အစဉ်လိုက်', @@ -426,6 +427,7 @@ export const myTranslations: DefaultTranslationsObject = { publish: 'ထုတ်ဝေသည်။', publishChanges: 'အပြောင်းအလဲများကို တင်ခဲ့သည်။', published: 'တင်ပြီးပြီ။', + publishIn: 'Terbitkan di {{locale}}', publishing: 'ထုတ်ဝေခြင်း', restoreAsDraft: 'Pulihkan sebagai draf', restoredSuccessfully: 'အောင်မြင်စွာ ပြန်လည်ရယူခဲ့သည်။', diff --git a/packages/translations/src/languages/nb.ts b/packages/translations/src/languages/nb.ts index b5a3ce0218b..4e2cf2dc2e2 100644 --- a/packages/translations/src/languages/nb.ts +++ b/packages/translations/src/languages/nb.ts @@ -266,6 +266,7 @@ export const nbTranslations: DefaultTranslationsObject = { nothingFound: 'Ingenting funnet', noValue: 'Ingen verdi', of: 'av', + only: undefined, open: 'Åpne', or: 'Eller', order: 'Rekkefølge', @@ -420,6 +421,7 @@ export const nbTranslations: DefaultTranslationsObject = { publish: 'Publisere', publishChanges: 'Publiser endringer', published: 'Publisert', + publishIn: 'Publiser i {{locale}}', publishing: 'Publisering', restoreAsDraft: 'Gjenopprett som utkast', restoredSuccessfully: 'Gjenopprettet.', diff --git a/packages/translations/src/languages/nl.ts b/packages/translations/src/languages/nl.ts index 56b03e3c027..371bc61e4c0 100644 --- a/packages/translations/src/languages/nl.ts +++ b/packages/translations/src/languages/nl.ts @@ -268,6 +268,7 @@ export const nlTranslations: DefaultTranslationsObject = { nothingFound: 'Niets gevonden', noValue: 'Geen waarde', of: 'van', + only: 'Alleen', open: 'Open', or: 'Of', order: 'Volgorde', @@ -423,6 +424,7 @@ export const nlTranslations: DefaultTranslationsObject = { publish: 'Publiceren', publishChanges: 'Publiceer wijzigingen', published: 'Gepubliceerd', + publishIn: 'Publiceer in {{locale}}', publishing: 'Publicatie', restoreAsDraft: 'Herstellen als concept', restoredSuccessfully: 'Herstelling succesvol.', diff --git a/packages/translations/src/languages/pl.ts b/packages/translations/src/languages/pl.ts index 2a1a869fec3..2a9491b0fb3 100644 --- a/packages/translations/src/languages/pl.ts +++ b/packages/translations/src/languages/pl.ts @@ -266,6 +266,7 @@ export const plTranslations: DefaultTranslationsObject = { nothingFound: 'Nic nie znaleziono', noValue: 'Brak wartości', of: 'z', + only: 'Tylko', open: 'Otwórz', or: 'lub', order: 'Kolejność', @@ -420,6 +421,7 @@ export const plTranslations: DefaultTranslationsObject = { publish: 'Publikuj', publishChanges: 'Opublikuj zmiany', published: 'Opublikowano', + publishIn: undefined, publishing: 'Publikacja', restoreAsDraft: 'Przywróć jako szkic', restoredSuccessfully: 'Przywrócono pomyślnie.', diff --git a/packages/translations/src/languages/pt.ts b/packages/translations/src/languages/pt.ts index 5a762f3f80e..f8ae147c03f 100644 --- a/packages/translations/src/languages/pt.ts +++ b/packages/translations/src/languages/pt.ts @@ -267,6 +267,7 @@ export const ptTranslations: DefaultTranslationsObject = { nothingFound: 'Nada encontrado', noValue: 'Nenhum valor', of: 'de', + only: undefined, open: 'Abrir', or: 'Ou', order: 'Ordem', @@ -421,6 +422,7 @@ export const ptTranslations: DefaultTranslationsObject = { publish: 'Publicar', publishChanges: 'Publicar alterações', published: 'Publicado', + publishIn: undefined, publishing: 'Publicação', restoreAsDraft: 'Restaurar como rascunho', restoredSuccessfully: 'Restaurado com sucesso.', diff --git a/packages/translations/src/languages/ro.ts b/packages/translations/src/languages/ro.ts index f7dec25f4e0..e2b73f02aa0 100644 --- a/packages/translations/src/languages/ro.ts +++ b/packages/translations/src/languages/ro.ts @@ -270,6 +270,7 @@ export const roTranslations: DefaultTranslationsObject = { nothingFound: 'Nimic găsit', noValue: 'Nici o valoare', of: 'de', + only: 'Doar', open: 'Deschide', or: 'Sau', order: 'ORdine', @@ -428,6 +429,7 @@ export const roTranslations: DefaultTranslationsObject = { publish: 'Publicați', publishChanges: 'Publicați modificările', published: 'Publicat', + publishIn: 'Publicați în {{locale}}', publishing: 'Editare', restoreAsDraft: 'Restaurează ca proiect', restoredSuccessfully: 'Restaurat cu succes.', diff --git a/packages/translations/src/languages/rs.ts b/packages/translations/src/languages/rs.ts index 2490206a6e3..32e6b1ff61b 100644 --- a/packages/translations/src/languages/rs.ts +++ b/packages/translations/src/languages/rs.ts @@ -265,6 +265,7 @@ export const rsTranslations: DefaultTranslationsObject = { nothingFound: 'Ништа није пронађено', noValue: 'Без вредности', of: 'Од', + only: undefined, open: 'Отвори', or: 'Или', order: 'Редослед', @@ -415,6 +416,7 @@ export const rsTranslations: DefaultTranslationsObject = { publish: 'Објавити', publishChanges: 'Објави промене', published: 'Објављено', + publishIn: undefined, publishing: 'Objavljivanje', restoreAsDraft: 'Vrati kao nacrt', restoredSuccessfully: 'Успешно враћено.', diff --git a/packages/translations/src/languages/rsLatin.ts b/packages/translations/src/languages/rsLatin.ts index 5724a5156db..a569053a8d3 100644 --- a/packages/translations/src/languages/rsLatin.ts +++ b/packages/translations/src/languages/rsLatin.ts @@ -265,6 +265,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = { nothingFound: 'Ništa nije pronađeno', noValue: 'Bez vrednosti', of: 'Od', + only: undefined, open: 'Otvori', or: 'Ili', order: 'Redosled', @@ -416,6 +417,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = { publish: 'Objaviti', publishChanges: 'Objavljivanje', published: 'Objavljeno', + publishIn: 'Objavite na {{locale}}', publishing: 'Objavljivanje', restoreAsDraft: 'Vrati kao nacrt', restoredSuccessfully: 'Uspešno vraćeno.', diff --git a/packages/translations/src/languages/ru.ts b/packages/translations/src/languages/ru.ts index c060312bf37..176c1514cbd 100644 --- a/packages/translations/src/languages/ru.ts +++ b/packages/translations/src/languages/ru.ts @@ -268,6 +268,7 @@ export const ruTranslations: DefaultTranslationsObject = { nothingFound: 'Ничего не найдено', noValue: 'Нет значения', of: 'из', + only: 'Только', open: 'Открыть', or: 'Или же', order: 'Порядок', @@ -422,6 +423,7 @@ export const ruTranslations: DefaultTranslationsObject = { publish: 'Публиковать', publishChanges: 'Опубликовать изменения', published: 'Опубликовано', + publishIn: 'Опубликовать на {{locale}}', publishing: 'Публикация', restoreAsDraft: 'Восстановить как черновик', restoredSuccessfully: 'Восстановлен успешно.', diff --git a/packages/translations/src/languages/sk.ts b/packages/translations/src/languages/sk.ts index ad8b8cc5cf3..2a6468b9cf8 100644 --- a/packages/translations/src/languages/sk.ts +++ b/packages/translations/src/languages/sk.ts @@ -267,6 +267,7 @@ export const skTranslations: DefaultTranslationsObject = { nothingFound: 'Nič nenájdené', noValue: 'Žiadna hodnota', of: 'z', + only: undefined, open: 'Otvoriť', or: 'Alebo', order: 'Poradie', @@ -420,6 +421,7 @@ export const skTranslations: DefaultTranslationsObject = { publish: 'Publikovať', publishChanges: 'Publikovať zmeny', published: 'Publikované', + publishIn: 'Publikujte v {{locale}}', publishing: 'Publikovanie', restoreAsDraft: 'Obnoviť ako koncept', restoredSuccessfully: 'Úspešne obnovené.', diff --git a/packages/translations/src/languages/sv.ts b/packages/translations/src/languages/sv.ts index f8b9b3f7a84..038484eeff3 100644 --- a/packages/translations/src/languages/sv.ts +++ b/packages/translations/src/languages/sv.ts @@ -266,6 +266,7 @@ export const svTranslations: DefaultTranslationsObject = { nothingFound: 'Inget hittades', noValue: 'Inget värde', of: 'av', + only: 'Endast', open: 'Öppna', or: 'Eller', order: 'Ordning', @@ -419,6 +420,7 @@ export const svTranslations: DefaultTranslationsObject = { publish: 'Publicera', publishChanges: 'Publicera ändringar', published: 'Publicerad', + publishIn: 'Publicera i {{locale}}', publishing: 'Publicering', restoreAsDraft: 'Återställ som utkast', restoredSuccessfully: 'Återställd framgångsrikt.', diff --git a/packages/translations/src/languages/th.ts b/packages/translations/src/languages/th.ts index 97091b070db..b0ac549e4c8 100644 --- a/packages/translations/src/languages/th.ts +++ b/packages/translations/src/languages/th.ts @@ -262,6 +262,7 @@ export const thTranslations: DefaultTranslationsObject = { nothingFound: 'ไม่พบสิ่งใด', noValue: 'ไม่มีค่า', of: 'จาก', + only: 'เท่านั้น', open: 'เปิด', or: 'หรือ', order: 'เรียงตาม', @@ -411,6 +412,7 @@ export const thTranslations: DefaultTranslationsObject = { publish: 'เผยแพร่', publishChanges: 'เผยแพร่การแก้ไข', published: 'เผยแพร่แล้ว', + publishIn: 'เผยแพร่ใน {{locale}}', publishing: 'การเผยแพร่', restoreAsDraft: 'เรียกคืนเป็นร่าง', restoredSuccessfully: 'กู้คืนเวอร์ชันสำเร็จ', diff --git a/packages/translations/src/languages/tr.ts b/packages/translations/src/languages/tr.ts index f66d63721d2..35a08fdc9c0 100644 --- a/packages/translations/src/languages/tr.ts +++ b/packages/translations/src/languages/tr.ts @@ -269,6 +269,7 @@ export const trTranslations: DefaultTranslationsObject = { nothingFound: 'Hiçbir şey bulunamadı', noValue: 'Değer yok', of: 'of', + only: 'Sadece', open: 'Aç', or: 'Or', order: 'Order', @@ -421,6 +422,7 @@ export const trTranslations: DefaultTranslationsObject = { publish: 'Yayınla', publishChanges: 'Değişiklikleri yayınla', published: 'Yayınlandı', + publishIn: '{{locale}} dilinde yayınlayın.', publishing: 'Yayınlama', restoreAsDraft: 'Taslak olarak geri yükle', restoredSuccessfully: 'Geri getirme başarılı.', diff --git a/packages/translations/src/languages/uk.ts b/packages/translations/src/languages/uk.ts index ae46b9d5a94..5ef3ec4432d 100644 --- a/packages/translations/src/languages/uk.ts +++ b/packages/translations/src/languages/uk.ts @@ -266,6 +266,7 @@ export const ukTranslations: DefaultTranslationsObject = { nothingFound: 'Нічого не знайдено', noValue: 'Немає значення', of: 'з', + only: 'Лише', open: 'Відкрити', or: 'або', order: 'Порядок', @@ -419,6 +420,7 @@ export const ukTranslations: DefaultTranslationsObject = { publish: 'Опублікувати', publishChanges: 'Опублікувати зміни', published: 'Опубліковано', + publishIn: 'Опублікувати в {{locale}}', publishing: 'Публікація', restoreAsDraft: 'Відновити як чернетку', restoredSuccessfully: 'Відновлено успішно.', diff --git a/packages/translations/src/languages/vi.ts b/packages/translations/src/languages/vi.ts index fb809fc8da6..4b0f5b4ba31 100644 --- a/packages/translations/src/languages/vi.ts +++ b/packages/translations/src/languages/vi.ts @@ -264,6 +264,7 @@ export const viTranslations: DefaultTranslationsObject = { nothingFound: 'Không tìm thấy', noValue: 'Không có giá trị', of: 'trong số', + only: undefined, open: 'Mở', or: 'hoặc', order: 'Thứ tự', @@ -414,6 +415,7 @@ export const viTranslations: DefaultTranslationsObject = { publish: 'Công bố', publishChanges: 'Xuất bản tài liệu', published: 'Đã xuất bản', + publishIn: 'Xuất bản trong {{locale}}', publishing: 'Xuất bản', restoreAsDraft: 'Khôi phục như bản nháp', restoredSuccessfully: 'Đã khôi phục thành công.', diff --git a/packages/translations/src/languages/zh.ts b/packages/translations/src/languages/zh.ts index f9e04e20e0a..00082a3cfd8 100644 --- a/packages/translations/src/languages/zh.ts +++ b/packages/translations/src/languages/zh.ts @@ -258,6 +258,7 @@ export const zhTranslations: DefaultTranslationsObject = { nothingFound: '没有找到任何东西', noValue: '没有值', of: '的', + only: undefined, open: '打开', or: '或', order: '排序', @@ -404,6 +405,7 @@ export const zhTranslations: DefaultTranslationsObject = { publish: '发布', publishChanges: '发布修改', published: '已发布', + publishIn: undefined, publishing: '发布', restoreAsDraft: '恢复为草稿', restoredSuccessfully: '恢复成功。', diff --git a/packages/translations/src/languages/zhTw.ts b/packages/translations/src/languages/zhTw.ts index cc251edf79d..0bc3985b4c4 100644 --- a/packages/translations/src/languages/zhTw.ts +++ b/packages/translations/src/languages/zhTw.ts @@ -258,6 +258,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { nothingFound: '沒有找到任何東西', noValue: '沒有數值', of: '的', + only: '僅限', open: '打開', or: '或', order: '排序', @@ -404,6 +405,7 @@ export const zhTwTranslations: DefaultTranslationsObject = { publish: '發佈', publishChanges: '發佈修改', published: '已發佈', + publishIn: undefined, publishing: '發布', restoreAsDraft: '恢復為草稿', restoredSuccessfully: '回復成功。', diff --git a/packages/ui/src/elements/Button/types.ts b/packages/ui/src/elements/Button/types.ts index fc9a066244b..414f00a5417 100644 --- a/packages/ui/src/elements/Button/types.ts +++ b/packages/ui/src/elements/Button/types.ts @@ -1,6 +1,10 @@ import type { ElementType, MouseEvent } from 'react' import type React from 'react' +type secondaryAction = { + label: string + onClick: (event: MouseEvent) => void +} export type Props = { 'aria-label'?: string buttonId?: string @@ -17,6 +21,7 @@ export type Props = { newTab?: boolean onClick?: (event: MouseEvent) => void round?: boolean + secondaryActions?: secondaryAction | secondaryAction[] size?: 'large' | 'medium' | 'small' SubMenuPopupContent?: React.ReactNode to?: string diff --git a/packages/ui/src/elements/PublishButton/index.tsx b/packages/ui/src/elements/PublishButton/index.tsx index 38fe3529ba1..5cba012f05f 100644 --- a/packages/ui/src/elements/PublishButton/index.tsx +++ b/packages/ui/src/elements/PublishButton/index.tsx @@ -2,6 +2,7 @@ import type { MappedComponent } from 'payload' +import * as qs from 'qs-esm' import React, { useCallback } from 'react' import { useForm, useFormModified } from '../../forms/Form/context.js' @@ -14,15 +15,8 @@ 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 { PopupList } from '../Popup/index.js' export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labelProp }) => { - const { - config: { - routes: { api }, - serverURL, - }, - } = useConfig() - const { id, collectionSlug, @@ -33,12 +27,20 @@ export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labe unpublishedVersions, } = useDocumentInfo() + const { config } = useConfig() const { submit } = useForm() const modified = useFormModified() const editDepth = useEditDepth() const { code: locale } = useLocale() - const { t } = useTranslation() + const { + localization, + routes: { api }, + serverURL, + } = config + + const { i18n, t } = useTranslation() + const { code } = useLocale() const label = labelProp || t('version:publishChanges') const hasNewerVersions = unpublishedVersions?.totalDocs > 0 @@ -94,6 +96,26 @@ export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labe }) }, [submit]) + const publishSpecificLocale = useCallback( + (locale) => { + const params = qs.stringify({ + publishSpecificLocale: locale, + }) + + const action = `${serverURL}${api}${ + globalSlug ? `/globals/${globalSlug}` : `/${collectionSlug}/${id ? `${'/' + id}` : ''}` + }${params ? '?' + params : ''}` + + void submit({ + action, + overrides: { + _status: 'published', + }, + }) + }, + [api, collectionSlug, globalSlug, id, serverURL, submit], + ) + if (!hasPublishPermission) { return null } @@ -104,6 +126,28 @@ export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labe disabled={!canPublish} onClick={publish} size="medium" + SubMenuPopupContent={ + localization + ? localization.locales.map((locale) => { + const formattedLabel = + typeof locale.label === 'string' + ? locale.label + : locale.label && locale.label[i18n?.language] + + const isActive = typeof locale === 'string' ? locale === code : locale.code === code + + if (isActive) { + return ( + + publishSpecificLocale(locale.code)}> + {t('version:publishIn', { locale: formattedLabel || locale.code })} + + + ) + } + }) + : null + } type="button" > {label} diff --git a/packages/ui/src/fields/Upload/UploadCard/index.scss b/packages/ui/src/fields/Upload/UploadCard/index.scss index a9a46df7e48..d4a7f57cc1d 100644 --- a/packages/ui/src/fields/Upload/UploadCard/index.scss +++ b/packages/ui/src/fields/Upload/UploadCard/index.scss @@ -18,7 +18,6 @@ &--size-small { padding: calc(var(--base) / 3) calc(var(--base) / 2); - .thumbnail { width: 25px; height: 25px; diff --git a/packages/ui/src/providers/DocumentInfo/index.tsx b/packages/ui/src/providers/DocumentInfo/index.tsx index f6a837a159f..716dca54abe 100644 --- a/packages/ui/src/providers/DocumentInfo/index.tsx +++ b/packages/ui/src/providers/DocumentInfo/index.tsx @@ -256,6 +256,7 @@ const DocumentInfo: React.FC< const versionParams = { depth: 0, + limit: 0, where: { and: [], }, @@ -343,9 +344,18 @@ const DocumentInfo: React.FC< and: [ ...versionParams.where.and, { - updatedAt: { - greater_than: publishedJSON?.updatedAt, - }, + or: [ + { + updatedAt: { + greater_than: publishedJSON.updatedAt, + }, + }, + { + latest: { + equals: true, + }, + }, + ], }, ], }, diff --git a/test/versions/collections/Localized.ts b/test/versions/collections/Localized.ts new file mode 100644 index 00000000000..5653957a5df --- /dev/null +++ b/test/versions/collections/Localized.ts @@ -0,0 +1,24 @@ +import type { CollectionConfig } from 'payload' + +import { localizedCollectionSlug } from '../slugs.js' + +const LocalizedPosts: CollectionConfig = { + slug: localizedCollectionSlug, + versions: { + drafts: true, + }, + fields: [ + { + name: 'text', + type: 'text', + localized: true, + }, + { + name: 'description', + type: 'text', + localized: true, + }, + ], +} + +export default LocalizedPosts diff --git a/test/versions/config.ts b/test/versions/config.ts index 3500ed07e06..1bf443f9a1c 100644 --- a/test/versions/config.ts +++ b/test/versions/config.ts @@ -8,12 +8,14 @@ import CustomIDs from './collections/CustomIDs.js' import DisablePublish from './collections/DisablePublish.js' import DraftPosts from './collections/Drafts.js' import DraftWithMax from './collections/DraftsWithMax.js' +import LocalizedPosts from './collections/Localized.js' import Posts from './collections/Posts.js' import VersionPosts from './collections/Versions.js' import AutosaveGlobal from './globals/Autosave.js' import DisablePublishGlobal from './globals/DisablePublish.js' import DraftGlobal from './globals/Draft.js' import DraftWithMaxGlobal from './globals/DraftWithMax.js' +import LocalizedGlobal from './globals/LocalizedGlobal.js' import { seed } from './seed.js' export default buildConfigWithDefaults({ @@ -28,14 +30,36 @@ export default buildConfigWithDefaults({ AutosavePosts, DraftPosts, DraftWithMax, + LocalizedPosts, VersionPosts, CustomIDs, ], - globals: [AutosaveGlobal, DraftGlobal, DraftWithMaxGlobal, DisablePublishGlobal], + globals: [AutosaveGlobal, DraftGlobal, DraftWithMaxGlobal, DisablePublishGlobal, LocalizedGlobal], indexSortableFields: true, localization: { defaultLocale: 'en', - locales: ['en', 'es'], + locales: [ + { + code: 'en', + label: 'English', + }, + { + code: 'es', + label: { + en: 'Spanish', + es: 'Español', + de: 'Spanisch', + }, + }, + { + code: 'de', + label: { + en: 'German', + es: 'Alemán', + de: 'Deutsch', + }, + }, + ], }, onInit: async (payload) => { if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') { diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 5e84aa11248..c92c1a0b8e1 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -48,8 +48,8 @@ import { reInitializeDB } from '../helpers/reInitializeDB.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js' import { titleToDelete } from './shared.js' import { - autoSaveGlobalSlug, autosaveCollectionSlug, + autoSaveGlobalSlug, customIDSlug, disablePublishGlobalSlug, disablePublishSlug, @@ -57,6 +57,8 @@ import { draftGlobalSlug, draftWithMaxCollectionSlug, draftWithMaxGlobalSlug, + localizedCollectionSlug, + localizedGlobalSlug, postCollectionSlug, } from './slugs.js' @@ -66,6 +68,8 @@ const dirname = path.dirname(filename) const { beforeAll, beforeEach, describe } = test let payload: PayloadTestSDK +let global: AdminUrlUtil +let id: string const waitForAutoSaveToComplete = async (page: Page) => { await expect(async () => { @@ -97,6 +101,8 @@ describe('versions', () => { let disablePublishURL: AdminUrlUtil let customIDURL: AdminUrlUtil let postURL: AdminUrlUtil + let global: AdminUrlUtil + let id: string beforeAll(async ({ browser }, testInfo) => { testInfo.setTimeout(TEST_TIMEOUT_LONG) @@ -282,7 +288,7 @@ describe('versions', () => { hasText: 'Versions', }) await versionsTab.waitFor({ state: 'visible' }) - const versionsPill = versionsTab.locator('.doc-tab__count--has-count') + const versionsPill = versionsTab.locator('.doc-tab__count') await versionsPill.waitFor({ state: 'visible' }) const versionCount = versionsTab.locator('.doc-tab__count').first() await expect(versionCount).toHaveText('11') @@ -320,7 +326,6 @@ describe('versions', () => { await saveDocAndAssert(page, '#action-save-draft') const savedDocURL = page.url() await page.goto(`${savedDocURL}/versions`) - await page.waitForURL(`${savedDocURL}/versions`) const row2 = page.locator('tbody .row-2') const versionID = await row2.locator('.cell-id').textContent() await page.goto(`${savedDocURL}/versions/${versionID}`) @@ -612,4 +617,90 @@ describe('versions', () => { expect(versionsTabUpdated).toBeTruthy() }) }) + describe('Collections - publish specific locale', () => { + beforeAll(() => { + url = new AdminUrlUtil(serverURL, localizedCollectionSlug) + global = new AdminUrlUtil(serverURL, localizedGlobalSlug) + }) + test('should show publish individual locale dropdown', async () => { + await page.goto(url.create) + const publishOptions = page.locator('.doc-controls__controls .popup') + + await expect(publishOptions).toBeVisible() + }) + + test('should show option to publish current locale', async () => { + await page.goto(url.create) + const publishOptions = page.locator('.doc-controls__controls .popup') + await publishOptions.click() + + const publishSpecificLocale = page.locator('.doc-controls__controls .popup__content') + + await expect(publishSpecificLocale).toContainText('English') + }) + + test('should publish specific locale', async () => { + await page.goto(url.create) + await changeLocale(page, 'es') + const textField = page.locator('#field-text') + const status = page.locator('.status__value') + + await textField.fill('spanish published') + await saveDocAndAssert(page) + await expect(status).toContainText('Changed') + + await textField.fill('spanish draft') + await saveDocAndAssert(page, '#action-save-draft') + await expect(status).toContainText('Changed') + + await changeLocale(page, 'en') + await textField.fill('english published') + const publishOptions = page.locator('.doc-controls__controls .popup') + await publishOptions.click() + + const publishSpecificLocale = page.locator('.popup-button-list button').first() + await expect(publishSpecificLocale).toContainText('English') + await publishSpecificLocale.click() + + id = await page.locator('.id-label').getAttribute('title') + + const data = await payload.find({ + collection: localizedCollectionSlug, + locale: '*', + where: { + id: { equals: id }, + }, + }) + + const publishedDoc = data.docs[0] + + expect(publishedDoc.text).toStrictEqual({ + en: 'english published', + es: 'spanish published', + }) + }) + }) + + describe('Globals - publish individual locale', () => { + beforeAll(() => { + url = new AdminUrlUtil(serverURL, localizedGlobalSlug) + }) + + test('should show publish individual locale dropdown', async () => { + await page.goto(url.global(localizedGlobalSlug)) + const publishOptions = page.locator('.doc-controls__controls .popup') + + await expect(publishOptions).toBeVisible() + }) + + test('should show option to publish current locale', async () => { + await page.goto(url.global(localizedGlobalSlug)) + const publishOptions = page.locator('.doc-controls__controls .popup') + await publishOptions.click() + + const publishSpecificLocale = page.locator('.doc-controls__controls .popup__content') + + await expect(publishSpecificLocale).toContainText('English') + }) + }) }) diff --git a/test/versions/globals/LocalizedGlobal.ts b/test/versions/globals/LocalizedGlobal.ts new file mode 100644 index 00000000000..3847a41554c --- /dev/null +++ b/test/versions/globals/LocalizedGlobal.ts @@ -0,0 +1,24 @@ +import type { GlobalConfig } from 'payload' + +import { localizedGlobalSlug } from '../slugs.js' + +const LocalizedGlobal: GlobalConfig = { + slug: localizedGlobalSlug, + fields: [ + { + name: 'title', + type: 'text', + localized: true, + }, + { + name: 'content', + type: 'text', + localized: true, + }, + ], + versions: { + drafts: true, + }, +} + +export default LocalizedGlobal diff --git a/test/versions/localized.int.spec.ts b/test/versions/localized.int.spec.ts new file mode 100644 index 00000000000..4eb730cf1bc --- /dev/null +++ b/test/versions/localized.int.spec.ts @@ -0,0 +1,584 @@ +import type { Payload } from 'payload' + +import path from 'path' +import { fileURLToPath } from 'url' + +import { initPayloadInt } from '../helpers/initPayloadInt.js' +import { localizedCollectionSlug, localizedGlobalSlug } from './slugs.js' + +let payload: Payload + +const collection = localizedCollectionSlug +const global = localizedGlobalSlug + +const filename = fileURLToPath(import.meta.url) +const dirname = path.dirname(filename) + +describe('Versions', () => { + beforeAll(async () => { + process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit + ;({ payload } = await initPayloadInt(dirname)) + }) + + afterAll(async () => { + if (typeof payload.db.destroy === 'function') { + await payload.db.destroy() + } + }) + + describe('Collections', () => { + let postID: string + + beforeEach(async () => { + await payload.delete({ + collection, + where: {}, + }) + }) + + it('should save correct doc data when publishing individual locale', async () => { + // save spanish draft + const draft1 = await payload.create({ + collection, + data: { + text: 'Spanish draft', + }, + draft: true, + locale: 'es', + }) + + postID = draft1.id as any + + // save english draft + const draft2 = await payload.update({ + id: postID, + collection, + data: { + text: 'English draft', + description: 'My English description', + }, + draft: true, + locale: 'en', + }) + + // save german draft + const draft3 = await payload.update({ + id: postID, + collection, + data: { + text: 'German draft', + }, + draft: true, + locale: 'de', + }) + + // publish only english + const publishedEN1 = await payload.update({ + id: postID, + collection, + data: { + text: 'English published 1', + _status: 'published', + }, + draft: false, + locale: 'en', + publishSpecificLocale: 'en', + }) + + const docWithoutSpanishDraft = await payload.findByID({ + collection, + id: postID, + locale: 'all', + }) + + // We're getting the published version, + // which should not leak any unpublished Spanish content + // and should retain the English fields that were not explicitly + // passed in from publishedEN1 + expect(docWithoutSpanishDraft.text.es).toBeUndefined() + expect(docWithoutSpanishDraft.description.en).toStrictEqual('My English description') + + const docWithSpanishDraft1 = await payload.findByID({ + collection, + id: postID, + locale: 'all', + draft: true, + }) + + // After updating English via specific locale, + // We should expect to see that Spanish translations were maintained + expect(docWithSpanishDraft1.text.es).toStrictEqual('Spanish draft') + expect(docWithSpanishDraft1.text.en).toStrictEqual('English published 1') + expect(docWithSpanishDraft1.description.en).toStrictEqual('My English description') + + const publishedEN2 = await payload.update({ + id: postID, + collection, + data: { + text: 'English published 2', + _status: 'published', + }, + draft: false, + locale: 'en', + publishSpecificLocale: 'en', + }) + + const docWithoutSpanishDraft2 = await payload.findByID({ + collection, + id: postID, + locale: 'all', + }) + + // On the second consecutive publish of a specific locale, + // Make sure we maintain draft data that has never been published + // even after two + consecutive publish events + expect(docWithoutSpanishDraft2.text.es).toBeUndefined() + expect(docWithoutSpanishDraft2.text.en).toStrictEqual('English published 2') + expect(docWithoutSpanishDraft2.description.en).toStrictEqual('My English description') + + await payload.update({ + id: postID, + collection, + data: { + text: 'German draft 1', + _status: 'draft', + }, + draft: true, + locale: 'de', + }) + + const docWithGermanDraft = await payload.findByID({ + collection, + id: postID, + locale: 'all', + draft: true, + }) + + // Make sure we retain the Spanish draft, + // which may be lost when we create a new draft with German. + // Update operation should fetch both draft locales as well as published + // and merge them. + expect(docWithGermanDraft.text.de).toStrictEqual('German draft 1') + expect(docWithGermanDraft.text.es).toStrictEqual('Spanish draft') + expect(docWithGermanDraft.text.en).toStrictEqual('English published 2') + + const publishedDE = await payload.update({ + id: postID, + collection, + data: { + _status: 'published', + text: 'German published 1', + }, + draft: false, + locale: 'de', + publishSpecificLocale: 'de', + }) + + const publishedENFinal = await payload.update({ + id: postID, + collection, + data: { + text: 'English published 3', + _status: 'published', + }, + draft: false, + locale: 'en', + publishSpecificLocale: 'en', + }) + + const finalPublishedNoES = await payload.findByID({ + collection, + id: postID, + locale: 'all', + }) + + expect(finalPublishedNoES.text.de).toStrictEqual('German published 1') + expect(finalPublishedNoES.text.en).toStrictEqual('English published 3') + expect(finalPublishedNoES.text.es).toBeUndefined() + + const finalDraft = await payload.findByID({ + collection, + id: postID, + locale: 'all', + draft: true, + }) + + expect(finalDraft.text.de).toStrictEqual('German published 1') + expect(finalDraft.text.en).toStrictEqual('English published 3') + expect(finalDraft.text.es).toStrictEqual('Spanish draft') + + const published = await payload.update({ + collection, + id: postID, + data: { + _status: 'published', + }, + }) + + const finalPublished = await payload.findByID({ + collection, + id: postID, + locale: 'all', + draft: true, + }) + + expect(finalPublished.text.de).toStrictEqual('German published 1') + expect(finalPublished.text.en).toStrictEqual('English published 3') + expect(finalPublished.text.es).toStrictEqual('Spanish draft') + }) + + it('should not leak draft data', async () => { + const draft = await payload.create({ + collection, + data: { + text: 'Spanish draft', + }, + draft: true, + locale: 'es', + }) + + const published = await payload.update({ + id: draft.id, + collection, + data: { + text: 'English publish', + _status: 'published', + }, + draft: false, + publishSpecificLocale: 'en', + }) + + const publishedOnlyEN = await payload.findByID({ + collection, + id: draft.id, + locale: 'all', + }) + + expect(publishedOnlyEN.text.es).toBeUndefined() + expect(publishedOnlyEN.text.en).toStrictEqual('English publish') + }) + + it('should merge draft data from other locales when publishing all', async () => { + const draft = await payload.create({ + collection, + data: { + text: 'Spanish draft', + }, + draft: true, + locale: 'es', + }) + + const published = await payload.update({ + id: draft.id, + collection, + data: { + text: 'English publish', + _status: 'published', + }, + draft: false, + publishSpecificLocale: 'en', + }) + + const publishedOnlyEN = await payload.findByID({ + collection, + id: draft.id, + locale: 'all', + }) + + expect(publishedOnlyEN.text.es).toBeUndefined() + expect(publishedOnlyEN.text.en).toStrictEqual('English publish') + + const published2 = await payload.update({ + id: draft.id, + collection, + data: { + _status: 'published', + }, + draft: false, + }) + + const publishedAll = await payload.findByID({ + collection, + id: published2.id, + locale: 'all', + }) + + expect(publishedAll.text.es).toStrictEqual('Spanish draft') + expect(publishedAll.text.en).toStrictEqual('English publish') + }) + + it('should publish non-default individual locale', async () => { + const draft = await payload.create({ + collection, + data: { + text: 'Spanish draft', + }, + draft: true, + locale: 'es', + }) + + const published = await payload.update({ + id: draft.id, + collection, + data: { + text: 'German publish', + _status: 'published', + }, + draft: false, + publishSpecificLocale: 'de', + }) + + const publishedOnlyDE = await payload.findByID({ + collection, + id: published.id, + locale: 'all', + }) + + expect(publishedOnlyDE.text.es).toBeUndefined() + expect(publishedOnlyDE.text.en).toBeUndefined() + expect(publishedOnlyDE.text.de).toStrictEqual('German publish') + }) + + it('should show correct data in latest version', async () => { + const draft = await payload.create({ + collection, + data: { + text: 'Spanish draft', + }, + draft: true, + locale: 'es', + }) + + const published = await payload.update({ + id: draft.id, + collection, + data: { + text: 'English publish', + _status: 'published', + }, + draft: false, + publishSpecificLocale: 'en', + }) + + const publishedOnlyEN = await payload.findByID({ + collection, + id: published.id, + locale: 'all', + }) + + expect(publishedOnlyEN.text.es).toBeUndefined() + expect(publishedOnlyEN.text.en).toStrictEqual('English publish') + + const allVersions = await payload.findVersions({ + collection, + locale: 'all', + }) + + const versions = allVersions.docs.filter( + (version) => version.parent === published.id && version.snapshot !== true, + ) + const latestVersion = versions[0].version + + expect(latestVersion.text.es).toBeUndefined() + expect(latestVersion.text.en).toStrictEqual('English publish') + }) + }) + + describe('Globals', () => { + it('should save correct global data when publishing individual locale', async () => { + // publish german + await payload.updateGlobal({ + slug: global, + data: { + title: 'German published', + _status: 'published', + }, + locale: 'de', + }) + + // save spanish draft + await payload.updateGlobal({ + slug: global, + data: { + title: 'Spanish draft', + content: 'Spanish draft content', + }, + draft: true, + locale: 'es', + }) + + // publish only english + await payload.updateGlobal({ + slug: global, + data: { + title: 'Eng published', + _status: 'published', + }, + locale: 'en', + publishSpecificLocale: 'en', + }) + + const globalData = await payload.findGlobal({ + slug: global, + locale: 'all', + }) + + // Expect only previously published data to be present + expect(globalData.title.es).toBeUndefined() + expect(globalData.title.en).toStrictEqual('Eng published') + expect(globalData.title.de).toStrictEqual('German published') + }) + + it('should not leak draft data', async () => { + // save spanish draft + await payload.updateGlobal({ + slug: global, + data: { + title: 'Another spanish draft', + }, + draft: true, + locale: 'es', + }) + + // publish only english + await payload.updateGlobal({ + slug: global, + data: { + title: 'Eng published', + _status: 'published', + }, + draft: false, + locale: 'en', + publishSpecificLocale: 'en', + }) + + const globalData = await payload.findGlobal({ + slug: global, + locale: 'all', + }) + + // Expect no draft data to be present + expect(globalData.title.es).toBeUndefined() + expect(globalData.title.en).toStrictEqual('Eng published') + }) + + it('should merge draft data from other locales when publishing all', async () => { + // save spanish draft + await payload.updateGlobal({ + slug: global, + data: { + title: 'Spanish draft', + content: 'Spanish draft content', + }, + draft: true, + locale: 'es', + }) + + // publish only english + await payload.updateGlobal({ + slug: global, + data: { + title: 'Eng published', + _status: 'published', + }, + locale: 'en', + publishSpecificLocale: 'en', + }) + + const publishedOnlyEN = await payload.findGlobal({ + slug: global, + locale: 'all', + }) + + expect(publishedOnlyEN.title.es).toBeUndefined() + expect(publishedOnlyEN.title.en).toStrictEqual('Eng published') + + await payload.updateGlobal({ + slug: global, + data: { + _status: 'published', + }, + }) + + const publishedAll = await payload.findGlobal({ + slug: global, + locale: 'all', + }) + + expect(publishedAll.title.es).toStrictEqual('Spanish draft') + expect(publishedAll.title.en).toStrictEqual('Eng published') + }) + + it('should publish non-default individual locale', async () => { + // save spanish draft + await payload.updateGlobal({ + slug: global, + data: { + title: 'Test span draft', + content: 'Test span draft content', + }, + draft: true, + locale: 'es', + }) + + // publish only german + await payload.updateGlobal({ + slug: global, + data: { + title: 'German published', + _status: 'published', + }, + locale: 'de', + publishSpecificLocale: 'de', + }) + + const globalData = await payload.findGlobal({ + slug: global, + locale: 'all', + }) + + // Expect only previous draft data to be present + expect(globalData.title.es).toStrictEqual('Spanish draft') + expect(globalData.title.de).toStrictEqual('German published') + }) + + it('should show correct data in latest version', async () => { + // save spanish draft + await payload.updateGlobal({ + slug: global, + data: { + title: 'New spanish draft', + content: 'New spanish draft content', + }, + draft: true, + locale: 'es', + }) + + // publish only english + await payload.updateGlobal({ + slug: global, + data: { + title: 'New eng', + _status: 'published', + }, + draft: false, + publishSpecificLocale: 'en', + }) + + const allVersions = await payload.findGlobalVersions({ + slug: global, + locale: 'all', + where: { + 'version._status': { + equals: 'published', + }, + }, + }) + + const versions = allVersions.docs + const latestVersion = versions[0].version + expect(latestVersion.title.es).toStrictEqual('Spanish draft') + expect(latestVersion.title.en).toStrictEqual('New eng') + }) + }) +}) diff --git a/test/versions/payload-types.ts b/test/versions/payload-types.ts index d11a13644d3..00ec9897fa1 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -16,6 +16,7 @@ export interface Config { 'autosave-posts': AutosavePost; 'draft-posts': DraftPost; 'draft-with-max-posts': DraftWithMaxPost; + 'localized-posts': LocalizedPost; 'version-posts': VersionPost; 'custom-ids': CustomId; users: User; @@ -30,8 +31,9 @@ export interface Config { 'draft-global': DraftGlobal; 'draft-with-max-global': DraftWithMaxGlobal; 'disable-publish-global': DisablePublishGlobal; + 'localized-global': LocalizedGlobal; }; - locale: 'en' | 'es'; + locale: 'en' | 'es' | 'de'; user: User & { collection: 'users'; }; @@ -148,6 +150,22 @@ export interface DraftWithMaxPost { createdAt: string; _status?: ('draft' | 'published') | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "localized-posts". + */ +type LocalizedString = { + [k: string]: string; +}; + +export interface LocalizedPost { + id: string; + text?: string | LocalizedString | null; + description?: string | LocalizedString | null; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "custom-ids". @@ -253,6 +271,18 @@ export interface DisablePublishGlobal { updatedAt?: string | null; createdAt?: string | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "localized-global". + */ +export interface LocalizedGlobal { + id: string; + title?: string | null; + content?: string | null; + _status?: ('draft' | 'published') | null; + updatedAt?: string | null; + createdAt?: string | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth". @@ -263,6 +293,6 @@ export interface Auth { declare module 'payload' { - // @ts-ignore + // @ts-ignore export interface GeneratedTypes extends Config {} -} \ No newline at end of file +} diff --git a/test/versions/slugs.ts b/test/versions/slugs.ts index efca348f0e9..30769af8da0 100644 --- a/test/versions/slugs.ts +++ b/test/versions/slugs.ts @@ -25,3 +25,7 @@ export const draftGlobalSlug = 'draft-global' export const draftWithMaxGlobalSlug = 'draft-with-max-global' export const globalSlugs = [autoSaveGlobalSlug, draftGlobalSlug] + +export const localizedCollectionSlug = 'localized-posts' + +export const localizedGlobalSlug = 'localized-global'