Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 174 additions & 80 deletions packages/payload/src/fields/hooks/afterRead/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,17 @@ export const promise = async ({
}
}

const shouldLocalizeField = fieldShouldBeLocalized({
field,
parentIsLocalized: parentIsLocalized!,
})

const shouldHoistLocalizedValue: boolean = Boolean(
flattenLocales &&
fieldAffectsDataResult &&
typeof siblingDoc[field.name!] === 'object' &&
siblingDoc[field.name!] !== null &&
fieldShouldBeLocalized({ field, parentIsLocalized: parentIsLocalized! }) &&
shouldLocalizeField &&
locale !== 'all' &&
req.payload.config.localization,
)
Expand Down Expand Up @@ -258,7 +263,7 @@ export const promise = async ({
// => Object.entries(siblingDoc[field.name]) will be the value of a single locale, not all locales
// => do not run the hook for each locale
!shouldHoistLocalizedValue &&
fieldShouldBeLocalized({ field, parentIsLocalized: parentIsLocalized! }) &&
shouldLocalizeField &&
typeof siblingDoc[field.name] === 'object'

if (fieldAffectsDataResult) {
Expand Down Expand Up @@ -644,44 +649,76 @@ export const promise = async ({

case 'group': {
if (fieldAffectsDataResult) {
let groupDoc = siblingDoc[field.name] as JsonObject
const groupSelect =
typeof select?.[field.name] === 'object'
? (select?.[field.name] as SelectType)
: undefined

if (typeof siblingDoc[field.name] !== 'object') {
groupDoc = {}
if (shouldLocalizeField && !shouldHoistLocalizedValue) {
Object.values(siblingDoc[field.name] || {}).forEach((localizedData) => {
traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: '',
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: path,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select: groupSelect,
selectMode,
showHiddenFields,
siblingDoc: localizedData || {},
triggerAccessControl,
triggerHooks,
})
})
} else {
traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: '',
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: path,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select: groupSelect,
selectMode,
showHiddenFields,
siblingDoc: typeof siblingDoc[field.name] !== 'object' ? {} : siblingDoc[field.name],
triggerAccessControl,
triggerHooks,
})
}

const groupSelect = select?.[field.name]

traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: '',
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: path,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select: typeof groupSelect === 'object' ? groupSelect : undefined,
selectMode,
showHiddenFields,
siblingDoc: groupDoc,
triggerAccessControl,
triggerHooks,
})
} else {
traverseFields({
blockData,
Expand Down Expand Up @@ -814,56 +851,113 @@ export const promise = async ({
}

case 'tab': {
let tabDoc = siblingDoc
let tabSelect: SelectType | undefined
const tabDoc = siblingDoc

const isNamedTab = tabHasName(field)

if (isNamedTab) {
tabDoc = siblingDoc[field.name] as JsonObject

if (typeof siblingDoc[field.name] !== 'object') {
tabDoc = {}
}

if (typeof select?.[field.name] === 'object') {
tabSelect = select?.[field.name] as SelectType
const tabSelect: SelectType | undefined =
typeof select?.[field.name] === 'object'
? (select?.[field.name] as SelectType)
: undefined
if (shouldLocalizeField && !shouldHoistLocalizedValue) {
Object.values(siblingDoc[field.name] || {}).forEach((localizedData) => {
traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: '',
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: path,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select: tabSelect,
selectMode,
showHiddenFields,
siblingDoc: localizedData || {},
triggerAccessControl,
triggerHooks,
})
})
} else {
traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: '',
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: path,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select: tabSelect,
selectMode,
showHiddenFields,
siblingDoc: typeof siblingDoc[field.name] !== 'object' ? {} : siblingDoc[field.name],
triggerAccessControl,
triggerHooks,
})
}
} else {
tabSelect = select
traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: isNamedTab ? '' : indexPath,
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: isNamedTab ? path : parentPath,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select,
selectMode,
showHiddenFields,
siblingDoc: tabDoc,
triggerAccessControl,
triggerHooks,
})
}

traverseFields({
blockData,
collection,
context,
currentDepth,
depth,
doc,
draft,
fallbackLocale,
fieldPromises,
fields: field.fields,
findMany,
flattenLocales,
global,
locale,
overrideAccess,
parentIndexPath: isNamedTab ? '' : indexPath,
parentIsLocalized: parentIsLocalized || field.localized,
parentPath: isNamedTab ? path : parentPath,
parentSchemaPath: schemaPath,
populate,
populationPromises,
req,
select: tabSelect,
selectMode,
showHiddenFields,
siblingDoc: tabDoc,
triggerAccessControl,
triggerHooks,
})

break
}

Expand Down
30 changes: 30 additions & 0 deletions test/localization/collections/AllFields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,36 @@ export const AllFieldsLocalized: CollectionConfig = {
],
},

// Deeply nested: localized tab
{
type: 'tabs',
tabs: [
{
name: 't1',
label: 'Deeply Nested Tab',
localized: true,
fields: [
{
type: 'tabs',
tabs: [
{
name: 't2',
label: 'Nested Tab Level 2',
fields: [
{
name: 'text',
type: 'text',
localized: true,
},
],
},
],
},
],
},
],
},

// Deeply nested: localized group > non-localized group > localized array
{
name: 'g1',
Expand Down
12 changes: 12 additions & 0 deletions test/localization/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3459,6 +3459,11 @@ describe('Localization', () => {
const doc = await payload.create({
collection: allFieldsLocalizedSlug,
data: {
t1: {
t2: {
text: 'EN Deep Text',
},
},
g1: {
g2: {
g2a1: [{ text: 'EN Deep 1' }, { text: 'EN Deep 2' }],
Expand Down Expand Up @@ -3536,9 +3541,16 @@ describe('Localization', () => {

// Verify deeply nested localization has locale keys only at topmost localized field
expect((allLocalesDoc.g1 as any).en).toBeDefined()
expect((allLocalesDoc.g1 as any).g2).toBeUndefined()
expect((allLocalesDoc.g1 as any).en.g2.g2a1).toHaveLength(2)
expect((allLocalesDoc.g1 as any).en.g2.g2a1[0].text).toBe('EN Deep 1')
expect((allLocalesDoc.g1 as any).es).toBeUndefined()

// Verify deeply nested localization in tab has locale keys only at topmost localized field
expect((allLocalesDoc.t1 as any).en).toBeDefined()
expect((allLocalesDoc.t1 as any).t2).toBeUndefined()
expect((allLocalesDoc.t1 as any).en.t2.text).toBe('EN Deep Text')
expect((allLocalesDoc.t1 as any).es).toBeUndefined()
})
})
})
Expand Down
Loading