feat!: auto-removes localized property from localized fields within other localized fields (#7933)
## Description Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized. Up until this point, Payload would _allow_ you to nest a localized field within another localized field, and this might have worked in MongoDB but it will throw errors in Postgres. Now, Payload will automatically remove the `localized: true` property from sub-fields within `sanitizeFields` if a parent field is localized. This could potentially be a breaking change if you have a configuration with MongoDB that nests localized fields within localized fields. ## Migrating You probably only need to migrate if you are using MongoDB, as there, you may not have noticed any problems. But in Postgres or SQLite, this would have caused issues so it's unlikely that you've made it too far without experiencing issues due to a nested localized fields config. In the event you would like to keep existing data in this fashion, we have added a `compatibility.allowLocalizedWithinLocalized` flag to the Payload config, which you can set to `true`, and Payload will then disable this new sanitization step. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
This commit is contained in:
@@ -71,6 +71,7 @@ The following options are available:
|
|||||||
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||||
|
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
|
||||||
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
||||||
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
||||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||||
@@ -253,3 +254,13 @@ import type { Config, SanitizedConfig } from 'payload'
|
|||||||
The Payload Config only lives on the server and is not allowed to contain any client-side code. That way, you can load up the Payload Config in any server environment or standalone script, without having to use Bundlers or Node.js loaders to handle importing client-only modules (e.g. scss files or React Components) without any errors.
|
The Payload Config only lives on the server and is not allowed to contain any client-side code. That way, you can load up the Payload Config in any server environment or standalone script, without having to use Bundlers or Node.js loaders to handle importing client-only modules (e.g. scss files or React Components) without any errors.
|
||||||
|
|
||||||
Behind the curtains, the Next.js-based Admin Panel generates a ClientConfig, which strips away any server-only code and enriches the config with React Components.
|
Behind the curtains, the Next.js-based Admin Panel generates a ClientConfig, which strips away any server-only code and enriches the config with React Components.
|
||||||
|
|
||||||
|
## Compatibility flags
|
||||||
|
|
||||||
|
The Payload Config can accept compatibility flags for running the newest versions but with older databases. You should only use these flags if you need to, and should confirm that you need to prior to enabling these flags.
|
||||||
|
|
||||||
|
`allowLocalizedWithinLocalized`
|
||||||
|
|
||||||
|
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
|
||||||
|
|
||||||
|
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ export type RichTextAdapterProvider<
|
|||||||
> = ({
|
> = ({
|
||||||
config,
|
config,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
}: {
|
}: {
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
/**
|
/**
|
||||||
@@ -279,6 +280,7 @@ export type RichTextAdapterProvider<
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
isRoot?: boolean
|
isRoot?: boolean
|
||||||
|
parentIsLocalized: boolean
|
||||||
}) =>
|
}) =>
|
||||||
| Promise<RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>>
|
| Promise<RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>>
|
||||||
| RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>
|
| RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const sanitizeCollection = async (
|
|||||||
collectionConfig: sanitized,
|
collectionConfig: sanitized,
|
||||||
config,
|
config,
|
||||||
fields: sanitized.fields,
|
fields: sanitized.fields,
|
||||||
|
parentIsLocalized: false,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
|
|||||||
config.editor = await incomingConfig.editor({
|
config.editor = await incomingConfig.editor({
|
||||||
config: config as SanitizedConfig,
|
config: config as SanitizedConfig,
|
||||||
isRoot: true,
|
isRoot: true,
|
||||||
|
parentIsLocalized: false,
|
||||||
})
|
})
|
||||||
if (config.editor.i18n && Object.keys(config.editor.i18n).length >= 0) {
|
if (config.editor.i18n && Object.keys(config.editor.i18n).length >= 0) {
|
||||||
config.i18n.translations = deepMergeSimple(config.i18n.translations, config.editor.i18n)
|
config.i18n.translations = deepMergeSimple(config.i18n.translations, config.editor.i18n)
|
||||||
|
|||||||
@@ -811,6 +811,19 @@ export type Config = {
|
|||||||
* @see https://payloadcms.com/docs/configuration/collections#collection-configs
|
* @see https://payloadcms.com/docs/configuration/collections#collection-configs
|
||||||
*/
|
*/
|
||||||
collections?: CollectionConfig[]
|
collections?: CollectionConfig[]
|
||||||
|
/**
|
||||||
|
* Compatibility flags for prior Payload versions
|
||||||
|
*/
|
||||||
|
compatibility?: {
|
||||||
|
/**
|
||||||
|
* By default, Payload will remove the `localized: true` property
|
||||||
|
* from fields if a parent field is localized. Set this property
|
||||||
|
* to `true` only if you have an existing Payload database from pre-3.0
|
||||||
|
* that you would like to maintain without migrating. This is only
|
||||||
|
* relevant for MongoDB databases.
|
||||||
|
*/
|
||||||
|
allowLocalizedWithinLocalized: true
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Prefix a string to all cookies that Payload sets.
|
* Prefix a string to all cookies that Payload sets.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -23,18 +23,19 @@ type Args = {
|
|||||||
config: Config
|
config: Config
|
||||||
existingFieldNames?: Set<string>
|
existingFieldNames?: Set<string>
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
|
parentIsLocalized: boolean
|
||||||
/**
|
/**
|
||||||
* If true, a richText field will require an editor property to be set, as the sanitizeFields function will not add it from the payload config if not present.
|
* If true, a richText field will require an editor property to be set, as the sanitizeFields function will not add it from the payload config if not present.
|
||||||
*
|
*
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
requireFieldLevelRichTextEditor?: boolean
|
requireFieldLevelRichTextEditor?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this property is set, RichText fields won't be sanitized immediately. Instead, they will be added to this array as promises
|
* If this property is set, RichText fields won't be sanitized immediately. Instead, they will be added to this array as promises
|
||||||
* so that you can sanitize them together, after the config has been sanitized.
|
* so that you can sanitize them together, after the config has been sanitized.
|
||||||
*/
|
*/
|
||||||
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>
|
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
|
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
|
||||||
* This validation will be skipped if validRelationships is null.
|
* This validation will be skipped if validRelationships is null.
|
||||||
@@ -47,6 +48,7 @@ export const sanitizeFields = async ({
|
|||||||
config,
|
config,
|
||||||
existingFieldNames = new Set(),
|
existingFieldNames = new Set(),
|
||||||
fields,
|
fields,
|
||||||
|
parentIsLocalized,
|
||||||
requireFieldLevelRichTextEditor = false,
|
requireFieldLevelRichTextEditor = false,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
@@ -137,7 +139,17 @@ export const sanitizeFields = async ({
|
|||||||
existingFieldNames.add(field.name)
|
existingFieldNames.add(field.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.localized && !config.localization) delete field.localized
|
if (typeof field.localized !== 'undefined') {
|
||||||
|
let shouldDisableLocalized = !config.localization
|
||||||
|
|
||||||
|
if (!config.compatibility?.allowLocalizedWithinLocalized && parentIsLocalized) {
|
||||||
|
shouldDisableLocalized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDisableLocalized) {
|
||||||
|
delete field.localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof field.validate === 'undefined') {
|
if (typeof field.validate === 'undefined') {
|
||||||
const defaultValidate = validations[field.type]
|
const defaultValidate = validations[field.type]
|
||||||
@@ -174,6 +186,7 @@ export const sanitizeFields = async ({
|
|||||||
field.editor = await field.editor({
|
field.editor = await field.editor({
|
||||||
config: _config,
|
config: _config,
|
||||||
isRoot: requireFieldLevelRichTextEditor,
|
isRoot: requireFieldLevelRichTextEditor,
|
||||||
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +214,7 @@ export const sanitizeFields = async ({
|
|||||||
config,
|
config,
|
||||||
existingFieldNames: new Set(),
|
existingFieldNames: new Set(),
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
requireFieldLevelRichTextEditor,
|
requireFieldLevelRichTextEditor,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
@@ -213,6 +227,7 @@ export const sanitizeFields = async ({
|
|||||||
config,
|
config,
|
||||||
existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames,
|
existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
requireFieldLevelRichTextEditor,
|
requireFieldLevelRichTextEditor,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
@@ -230,6 +245,7 @@ export const sanitizeFields = async ({
|
|||||||
config,
|
config,
|
||||||
existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames,
|
existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
|
parentIsLocalized: parentIsLocalized || (tabHasName(tab) && tab.localized),
|
||||||
requireFieldLevelRichTextEditor,
|
requireFieldLevelRichTextEditor,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const sanitizeGlobals = async (
|
|||||||
global.fields = await sanitizeFields({
|
global.fields = await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
fields: global.fields,
|
fields: global.fields,
|
||||||
|
parentIsLocalized: false,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const BlocksFeature = createServerFeature<
|
|||||||
BlocksFeatureProps,
|
BlocksFeatureProps,
|
||||||
BlocksFeatureClientProps
|
BlocksFeatureClientProps
|
||||||
>({
|
>({
|
||||||
feature: async ({ config: _config, isRoot, props }) => {
|
feature: async ({ config: _config, isRoot, parentIsLocalized, props }) => {
|
||||||
// Build clientProps
|
// Build clientProps
|
||||||
const clientProps: BlocksFeatureClientProps = {
|
const clientProps: BlocksFeatureClientProps = {
|
||||||
clientBlockSlugs: [],
|
clientBlockSlugs: [],
|
||||||
@@ -44,6 +44,7 @@ export const BlocksFeature = createServerFeature<
|
|||||||
blocks: props.inlineBlocks ?? [],
|
blocks: props.inlineBlocks ?? [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
parentIsLocalized,
|
||||||
requireFieldLevelRichTextEditor: isRoot,
|
requireFieldLevelRichTextEditor: isRoot,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -49,12 +49,13 @@ export type SerializedTableRowNode = Spread<
|
|||||||
_SerializedTableRowNode
|
_SerializedTableRowNode
|
||||||
>
|
>
|
||||||
export const EXPERIMENTAL_TableFeature = createServerFeature({
|
export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||||
feature: async ({ config, isRoot }) => {
|
feature: async ({ config, isRoot, parentIsLocalized }) => {
|
||||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||||
|
|
||||||
const sanitizedFields = await sanitizeFields({
|
const sanitizedFields = await sanitizeFields({
|
||||||
config: config as unknown as Config,
|
config: config as unknown as Config,
|
||||||
fields,
|
fields,
|
||||||
|
parentIsLocalized,
|
||||||
requireFieldLevelRichTextEditor: isRoot,
|
requireFieldLevelRichTextEditor: isRoot,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const LinkFeature = createServerFeature<
|
|||||||
LinkFeatureServerProps,
|
LinkFeatureServerProps,
|
||||||
ClientProps
|
ClientProps
|
||||||
>({
|
>({
|
||||||
feature: async ({ config: _config, isRoot, props }) => {
|
feature: async ({ config: _config, isRoot, parentIsLocalized, props }) => {
|
||||||
if (!props) {
|
if (!props) {
|
||||||
props = {}
|
props = {}
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,7 @@ export const LinkFeature = createServerFeature<
|
|||||||
const sanitizedFields = await sanitizeFields({
|
const sanitizedFields = await sanitizeFields({
|
||||||
config: _config as unknown as Config,
|
config: _config as unknown as Config,
|
||||||
fields: _transformedFields,
|
fields: _transformedFields,
|
||||||
|
parentIsLocalized,
|
||||||
requireFieldLevelRichTextEditor: isRoot,
|
requireFieldLevelRichTextEditor: isRoot,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export type FeatureProviderServer<
|
|||||||
/** unSanitizedEditorConfig.features, but mapped */
|
/** unSanitizedEditorConfig.features, but mapped */
|
||||||
featureProviderMap: ServerFeatureProviderMap
|
featureProviderMap: ServerFeatureProviderMap
|
||||||
isRoot?: boolean
|
isRoot?: boolean
|
||||||
|
parentIsLocalized: boolean
|
||||||
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
||||||
resolvedFeatures: ResolvedServerFeatureMap
|
resolvedFeatures: ResolvedServerFeatureMap
|
||||||
// unSanitized EditorConfig,
|
// unSanitized EditorConfig,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const UploadFeature = createServerFeature<
|
|||||||
UploadFeatureProps,
|
UploadFeatureProps,
|
||||||
UploadFeaturePropsClient
|
UploadFeaturePropsClient
|
||||||
>({
|
>({
|
||||||
feature: async ({ config: _config, isRoot, props }) => {
|
feature: async ({ config: _config, isRoot, parentIsLocalized, props }) => {
|
||||||
if (!props) {
|
if (!props) {
|
||||||
props = { collections: {} }
|
props = { collections: {} }
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,7 @@ export const UploadFeature = createServerFeature<
|
|||||||
props.collections[collection].fields = await sanitizeFields({
|
props.collections[collection].fields = await sanitizeFields({
|
||||||
config: _config as unknown as Config,
|
config: _config as unknown as Config,
|
||||||
fields: props.collections[collection].fields,
|
fields: props.collections[collection].fields,
|
||||||
|
parentIsLocalized,
|
||||||
requireFieldLevelRichTextEditor: isRoot,
|
requireFieldLevelRichTextEditor: isRoot,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import { richTextValidateHOC } from './validate/index.js'
|
|||||||
let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
|
let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
|
||||||
|
|
||||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||||
return async ({ config, isRoot }) => {
|
return async ({ config, isRoot, parentIsLocalized }) => {
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV !== 'production' &&
|
process.env.NODE_ENV !== 'production' &&
|
||||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
|
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
|
||||||
@@ -77,6 +77,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
defaultSanitizedServerEditorConfig = await sanitizeServerEditorConfig(
|
defaultSanitizedServerEditorConfig = await sanitizeServerEditorConfig(
|
||||||
defaultEditorConfig,
|
defaultEditorConfig,
|
||||||
config,
|
config,
|
||||||
|
parentIsLocalized,
|
||||||
)
|
)
|
||||||
features = deepCopyObject(defaultEditorFeatures)
|
features = deepCopyObject(defaultEditorFeatures)
|
||||||
}
|
}
|
||||||
@@ -108,6 +109,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
resolvedFeatureMap = await loadFeatures({
|
resolvedFeatureMap = await loadFeatures({
|
||||||
config,
|
config,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
unSanitizedEditorConfig: {
|
unSanitizedEditorConfig: {
|
||||||
features,
|
features,
|
||||||
lexical,
|
lexical,
|
||||||
|
|||||||
@@ -108,10 +108,12 @@ export function sortFeaturesForOptimalLoading(
|
|||||||
export async function loadFeatures({
|
export async function loadFeatures({
|
||||||
config,
|
config,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
}: {
|
}: {
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
isRoot?: boolean
|
isRoot?: boolean
|
||||||
|
parentIsLocalized: boolean
|
||||||
unSanitizedEditorConfig: ServerEditorConfig
|
unSanitizedEditorConfig: ServerEditorConfig
|
||||||
}): Promise<ResolvedServerFeatureMap> {
|
}): Promise<ResolvedServerFeatureMap> {
|
||||||
// First remove all duplicate features. The LAST feature with a given key wins.
|
// First remove all duplicate features. The LAST feature with a given key wins.
|
||||||
@@ -179,6 +181,7 @@ export async function loadFeatures({
|
|||||||
config,
|
config,
|
||||||
featureProviderMap,
|
featureProviderMap,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
resolvedFeatures,
|
resolvedFeatures,
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -125,9 +125,11 @@ export const sanitizeServerFeatures = (
|
|||||||
export async function sanitizeServerEditorConfig(
|
export async function sanitizeServerEditorConfig(
|
||||||
editorConfig: ServerEditorConfig,
|
editorConfig: ServerEditorConfig,
|
||||||
config: SanitizedConfig,
|
config: SanitizedConfig,
|
||||||
|
parentIsLocalized?: boolean,
|
||||||
): Promise<SanitizedServerEditorConfig> {
|
): Promise<SanitizedServerEditorConfig> {
|
||||||
const resolvedFeatureMap = await loadFeatures({
|
const resolvedFeatureMap = await loadFeatures({
|
||||||
config,
|
config,
|
||||||
|
parentIsLocalized,
|
||||||
unSanitizedEditorConfig: editorConfig,
|
unSanitizedEditorConfig: editorConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ export type LexicalRichTextAdapterProvider =
|
|||||||
({
|
({
|
||||||
config,
|
config,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
}: {
|
}: {
|
||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
isRoot?: boolean
|
isRoot?: boolean
|
||||||
|
parentIsLocalized: boolean
|
||||||
}) => Promise<LexicalRichTextAdapter>
|
}) => Promise<LexicalRichTextAdapter>
|
||||||
|
|
||||||
export type LexicalRichTextFieldProps = {
|
export type LexicalRichTextFieldProps = {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type CreateServerFeatureArgs<UnSanitizedProps, SanitizedProps, ClientProp
|
|||||||
/** unSanitizedEditorConfig.features, but mapped */
|
/** unSanitizedEditorConfig.features, but mapped */
|
||||||
featureProviderMap: ServerFeatureProviderMap
|
featureProviderMap: ServerFeatureProviderMap
|
||||||
isRoot?: boolean
|
isRoot?: boolean
|
||||||
|
parentIsLocalized: boolean
|
||||||
props: UnSanitizedProps
|
props: UnSanitizedProps
|
||||||
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
||||||
resolvedFeatures: ResolvedServerFeatureMap
|
resolvedFeatures: ResolvedServerFeatureMap
|
||||||
@@ -57,6 +58,7 @@ export const createServerFeature: <
|
|||||||
config,
|
config,
|
||||||
featureProviderMap,
|
featureProviderMap,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
resolvedFeatures,
|
resolvedFeatures,
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -64,6 +66,7 @@ export const createServerFeature: <
|
|||||||
config,
|
config,
|
||||||
featureProviderMap,
|
featureProviderMap,
|
||||||
isRoot,
|
isRoot,
|
||||||
|
parentIsLocalized,
|
||||||
props,
|
props,
|
||||||
resolvedFeatures,
|
resolvedFeatures,
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function slateEditor(
|
|||||||
args.admin.link.fields = await sanitizeFields({
|
args.admin.link.fields = await sanitizeFields({
|
||||||
config: config as unknown as Config,
|
config: config as unknown as Config,
|
||||||
fields: transformExtraFields(args.admin?.link?.fields, config),
|
fields: transformExtraFields(args.admin?.link?.fields, config),
|
||||||
|
parentIsLocalized: false,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export function slateEditor(
|
|||||||
args.admin.upload.collections[collection].fields = await sanitizeFields({
|
args.admin.upload.collections[collection].fields = await sanitizeFields({
|
||||||
config: config as unknown as Config,
|
config: config as unknown as Config,
|
||||||
fields: args.admin?.upload?.collections[collection]?.fields,
|
fields: args.admin?.upload?.collections[collection]?.fields,
|
||||||
|
parentIsLocalized: false,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
export const LocalizedWithinLocalized: CollectionConfig = {
|
||||||
|
slug: 'localized-within-localized',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'tabs',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'myTab',
|
||||||
|
label: 'My Tab',
|
||||||
|
localized: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'shouldNotBeLocalized',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'myArray',
|
||||||
|
type: 'array',
|
||||||
|
localized: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'shouldNotBeLocalized',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'myBlocks',
|
||||||
|
type: 'blocks',
|
||||||
|
localized: true,
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
slug: 'myBlock',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'shouldNotBeLocalized',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'myGroup',
|
||||||
|
type: 'group',
|
||||||
|
localized: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'shouldNotBeLocalized',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { devUser } from '../credentials.js'
|
|||||||
import { ArrayCollection } from './collections/Array/index.js'
|
import { ArrayCollection } from './collections/Array/index.js'
|
||||||
import { BlocksCollection } from './collections/Blocks/index.js'
|
import { BlocksCollection } from './collections/Blocks/index.js'
|
||||||
import { Group } from './collections/Group/index.js'
|
import { Group } from './collections/Group/index.js'
|
||||||
|
import { LocalizedWithinLocalized } from './collections/LocalizedWithinLocalized/index.js'
|
||||||
import { NestedArray } from './collections/NestedArray/index.js'
|
import { NestedArray } from './collections/NestedArray/index.js'
|
||||||
import { NestedFields } from './collections/NestedFields/index.js'
|
import { NestedFields } from './collections/NestedFields/index.js'
|
||||||
import { NestedToArrayAndBlock } from './collections/NestedToArrayAndBlock/index.js'
|
import { NestedToArrayAndBlock } from './collections/NestedToArrayAndBlock/index.js'
|
||||||
@@ -288,6 +289,7 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
LocalizedWithinLocalized,
|
||||||
],
|
],
|
||||||
globals: [
|
globals: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { fileURLToPath } from 'url'
|
|||||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||||
import type { LocalizedPost, WithLocalizedRelationship } from './payload-types.js'
|
import type { LocalizedPost, WithLocalizedRelationship } from './payload-types.js'
|
||||||
|
|
||||||
import { englishLocale } from '../globals/config.js'
|
|
||||||
import { idToString } from '../helpers/idToString.js'
|
import { idToString } from '../helpers/idToString.js'
|
||||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||||
import { arrayCollectionSlug } from './collections/Array/index.js'
|
import { arrayCollectionSlug } from './collections/Array/index.js'
|
||||||
@@ -15,6 +14,7 @@ import { nestedToArrayAndBlockCollectionSlug } from './collections/NestedToArray
|
|||||||
import { tabSlug } from './collections/Tab/index.js'
|
import { tabSlug } from './collections/Tab/index.js'
|
||||||
import {
|
import {
|
||||||
defaultLocale,
|
defaultLocale,
|
||||||
|
defaultLocale as englishLocale,
|
||||||
englishTitle,
|
englishTitle,
|
||||||
hungarianLocale,
|
hungarianLocale,
|
||||||
localizedPostsSlug,
|
localizedPostsSlug,
|
||||||
@@ -1396,6 +1396,17 @@ describe('Localization', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('nested localized field sanitization', () => {
|
||||||
|
it('should sanitize nested localized fields', () => {
|
||||||
|
const collection = payload.collections['localized-within-localized'].config
|
||||||
|
|
||||||
|
expect(collection.fields[0].tabs[0].fields[0].localized).toBeUndefined()
|
||||||
|
expect(collection.fields[1].fields[0].localized).toBeUndefined()
|
||||||
|
expect(collection.fields[2].blocks[0].fields[0].localized).toBeUndefined()
|
||||||
|
expect(collection.fields[3].fields[0].localized).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('nested blocks', () => {
|
describe('nested blocks', () => {
|
||||||
let id
|
let id
|
||||||
it('should allow creating nested blocks per locale', async () => {
|
it('should allow creating nested blocks per locale', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user