diff --git a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts index b9d08a09e25..50895a0392c 100644 --- a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts +++ b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts @@ -151,11 +151,19 @@ export const buildJoinAggregation = async ({ join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : '' const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${localeSuffix}` + let foreignField: string + + if (join.getForeignPath) { + foreignField = `${join.getForeignPath({ locale })}${polymorphicSuffix}` + } else { + foreignField = `${join.field.on}${polymorphicSuffix}` + } + aggregate.push( { $lookup: { as: `${as}.docs`, - foreignField: `${join.field.on}${localeSuffix}${polymorphicSuffix}`, + foreignField, from: adapter.collections[slug].collection.name, localField: versions ? 'parent' : '_id', pipeline, diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 862969d9044..eafa2be926e 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -517,6 +517,7 @@ export type SanitizedJoin = { * The field configuration defining the join */ field: JoinField + getForeignPath?(args: { locale?: TypedLocale }): string /** * The path of the join field in dot notation */ diff --git a/packages/payload/src/fields/config/sanitizeJoinField.ts b/packages/payload/src/fields/config/sanitizeJoinField.ts index faffaa0d92b..806490dc11e 100644 --- a/packages/payload/src/fields/config/sanitizeJoinField.ts +++ b/packages/payload/src/fields/config/sanitizeJoinField.ts @@ -39,6 +39,7 @@ export const sanitizeJoinField = ({ const pathSegments = field.on.split('.') // Split the schema path into segments let currentSegmentIndex = 0 + let localized = false // Traverse fields and match based on the schema path traverseFields({ callback: ({ field, next }) => { @@ -48,6 +49,27 @@ export const sanitizeJoinField = ({ const currentSegment = pathSegments[currentSegmentIndex] // match field on path segments if ('name' in field && field.name === currentSegment) { + if ('localized' in field && field.localized) { + localized = true + const fieldIndex = currentSegmentIndex + + join.getForeignPath = ({ locale }) => { + return pathSegments.reduce((acc, segment, index) => { + let result = `${acc}${segment}` + + if (index === fieldIndex) { + result = `${result}.${locale}` + } + + if (index !== pathSegments.length - 1) { + result = `${result}.` + } + + return result + }, '') + } + } + // Check if this is the last segment in the path if ( (currentSegmentIndex === pathSegments.length - 1 && @@ -78,7 +100,8 @@ export const sanitizeJoinField = ({ join.targetField = joinRelationship // override the join field localized property to use whatever the relationship field has - field.localized = joinRelationship.localized + // or if it's nested to a localized array / blocks / tabs / group + field.localized = localized // override the join field hasMany property to use whatever the relationship field has field.hasMany = joinRelationship.hasMany diff --git a/packages/payload/src/utilities/traverseFields.ts b/packages/payload/src/utilities/traverseFields.ts index 1bda56c2745..4061c04d6c3 100644 --- a/packages/payload/src/utilities/traverseFields.ts +++ b/packages/payload/src/utilities/traverseFields.ts @@ -109,15 +109,36 @@ export const traverseFields = ({ if (field.type === 'tabs' && 'tabs' in field) { for (const tab of field.tabs) { let tabRef = ref + + if (skip) { + return false + } + if ('name' in tab && tab.name) { if (!ref[tab.name] || typeof ref[tab.name] !== 'object') { if (fillEmpty) { - ref[tab.name] = {} + if (tab.localized) { + ref[tab.name] = { en: {} } + } else { + ref[tab.name] = {} + } } else { continue } } + if ( + callback && + callback({ + field: { ...tab, type: 'tab' }, + next, + parentRef: currentParentRef, + ref: tabRef, + }) + ) { + return true + } + tabRef = tabRef[tab.name] if (tab.localized) { @@ -132,29 +153,34 @@ export const traverseFields = ({ }) } } - continue + } + } else { + if ( + callback && + callback({ + field: { ...tab, type: 'tab' }, + next, + parentRef: currentParentRef, + ref: tabRef, + }) + ) { + return true } } - if ( - callback && - callback({ - field: { ...tab, type: 'tab' }, - next, + if (!tab.localized) { + traverseFields({ + callback, + fields: tab.fields, + fillEmpty, parentRef: currentParentRef, ref: tabRef, }) - ) { - return true } - traverseFields({ - callback, - fields: tab.fields, - fillEmpty, - parentRef: currentParentRef, - ref: tabRef, - }) + if (skip) { + return false + } } return @@ -166,10 +192,18 @@ export const traverseFields = ({ if (!ref[field.name]) { if (fillEmpty) { if (field.type === 'group') { - ref[field.name] = {} - } else if (field.type === 'array' || field.type === 'blocks') { if (field.localized) { + ref[field.name] = { + en: {}, + } + } else { ref[field.name] = {} + } + } else if (field.type === 'array' || field.type === 'blocks') { + if (field.localized) { + ref[field.name] = { + en: [], + } } else { ref[field.name] = [] } diff --git a/test/joins/collections/Categories.ts b/test/joins/collections/Categories.ts index c38af7a3300..ea509ecd568 100644 --- a/test/joins/collections/Categories.ts +++ b/test/joins/collections/Categories.ts @@ -109,6 +109,12 @@ export const Categories: CollectionConfig = { collection: 'posts', on: 'array.category', }, + { + name: 'localizedArrayPosts', + type: 'join', + collection: 'posts', + on: 'localizedArray.category', + }, { name: 'blocksPosts', type: 'join', diff --git a/test/joins/collections/Posts.ts b/test/joins/collections/Posts.ts index fbac4d4b17d..d7fd5c17b68 100644 --- a/test/joins/collections/Posts.ts +++ b/test/joins/collections/Posts.ts @@ -104,6 +104,18 @@ export const Posts: CollectionConfig = { }, ], }, + { + name: 'localizedArray', + type: 'array', + localized: true, + fields: [ + { + name: 'category', + type: 'relationship', + relationTo: categoriesSlug, + }, + ], + }, { name: 'blocks', type: 'blocks', diff --git a/test/joins/int.spec.ts b/test/joins/int.spec.ts index 34a71f37445..b2ca5a7c357 100644 --- a/test/joins/int.spec.ts +++ b/test/joins/int.spec.ts @@ -115,6 +115,7 @@ describe('Joins Field', () => { camelCaseCategory: category.id, }, array: [{ category: category.id }], + localizedArray: [{ category: category.id }], blocks: [{ blockType: 'block', category: category.id }], }) } @@ -214,6 +215,16 @@ describe('Joins Field', () => { expect(categoryWithPosts.arrayPosts.docs).toBeDefined() }) + it('should populate joins with localized array relationships', async () => { + const categoryWithPosts = await payload.findByID({ + id: category.id, + collection: categoriesSlug, + }) + + expect(categoryWithPosts.localizedArrayPosts.docs).toBeDefined() + expect(categoryWithPosts.localizedArrayPosts.docs).toHaveLength(10) + }) + it('should populate joins with blocks relationships', async () => { const categoryWithPosts = await payload.findByID({ id: category.id, diff --git a/test/joins/payload-types.ts b/test/joins/payload-types.ts index df65271f272..5281144b988 100644 --- a/test/joins/payload-types.ts +++ b/test/joins/payload-types.ts @@ -41,6 +41,7 @@ export interface Config { 'group.relatedPosts': 'posts'; 'group.camelCasePosts': 'posts'; arrayPosts: 'posts'; + localizedArrayPosts: 'posts'; blocksPosts: 'posts'; polymorphic: 'posts'; polymorphics: 'posts'; @@ -199,6 +200,12 @@ export interface Post { id?: string | null; }[] | null; + localizedArray?: + | { + category?: (string | null) | Category; + id?: string | null; + }[] + | null; blocks?: | { category?: (string | null) | Category; @@ -272,6 +279,10 @@ export interface Category { docs?: (string | Post)[] | null; hasNextPage?: boolean | null; } | null; + localizedArrayPosts?: { + docs?: (string | Post)[] | null; + hasNextPage?: boolean | null; + } | null; blocksPosts?: { docs?: (string | Post)[] | null; hasNextPage?: boolean | null; @@ -649,6 +660,12 @@ export interface PostsSelect { category?: T; id?: T; }; + localizedArray?: + | T + | { + category?: T; + id?: T; + }; blocks?: | T | { @@ -680,6 +697,7 @@ export interface CategoriesSelect { camelCasePosts?: T; }; arrayPosts?: T; + localizedArrayPosts?: T; blocksPosts?: T; polymorphic?: T; polymorphics?: T;