@roxxel/payload-multilang 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,21 +18,112 @@ const valuesAreEqual = (left, right)=>{
18
18
  }
19
19
  return JSON.stringify(left) === JSON.stringify(right);
20
20
  };
21
- const fieldAffectsData = (field)=>'name' in field && typeof field.name === 'string' && field.type !== 'ui';
21
+ const fieldAffectsData = (field)=>'name' in field && typeof field.name === 'string' && field.name !== 'id' && field.type !== 'ui';
22
22
  const fieldSynchronizes = (field)=>{
23
23
  const custom = isObject(field.custom) ? field.custom : {};
24
24
  const multilang = isObject(custom.multilang) ? custom.multilang : {};
25
25
  return multilang.synchronize === true;
26
26
  };
27
- const getSynchronizedFields = (fields = [])=>fields.flatMap((field)=>{
28
- if (!fieldAffectsData(field) || !fieldSynchronizes(field)) {
27
+ const fieldSynchronizesPosition = (field)=>{
28
+ const custom = isObject(field.custom) ? field.custom : {};
29
+ const multilang = isObject(custom.multilang) ? custom.multilang : {};
30
+ return multilang.synchronizePosition === true;
31
+ };
32
+ const hasName = (value)=>isObject(value) && typeof value.name === 'string';
33
+ const getSynchronizedBlocks = (field, inheritedSynchronize)=>{
34
+ if (field.type !== 'blocks') {
35
+ return [];
36
+ }
37
+ return field.blocks.flatMap((block)=>{
38
+ const fields = getSynchronizedFields(block.fields, inheritedSynchronize);
39
+ if (fields.length === 0) {
40
+ return [];
41
+ }
42
+ return [
43
+ {
44
+ slug: block.slug,
45
+ fields
46
+ }
47
+ ];
48
+ });
49
+ };
50
+ const getSynchronizedTabFields = (field, inheritedSynchronize)=>field.tabs.flatMap((tab)=>{
51
+ const fields = getSynchronizedFields(tab.fields, inheritedSynchronize);
52
+ if (fields.length === 0) {
53
+ return [];
54
+ }
55
+ if (hasName(tab)) {
56
+ return [
57
+ {
58
+ name: tab.name,
59
+ fields,
60
+ kind: 'group'
61
+ }
62
+ ];
63
+ }
64
+ return fields;
65
+ });
66
+ const getSynchronizedFields = (fields = [], inheritedSynchronize = false)=>fields.flatMap((field)=>{
67
+ if (field.type === 'row' || field.type === 'collapsible') {
68
+ return getSynchronizedFields(field.fields, inheritedSynchronize || fieldSynchronizes(field));
69
+ }
70
+ if (field.type === 'tabs') {
71
+ return getSynchronizedTabFields(field, inheritedSynchronize);
72
+ }
73
+ if (!fieldAffectsData(field)) {
74
+ return [];
75
+ }
76
+ const synchronize = inheritedSynchronize || fieldSynchronizes(field);
77
+ if (field.type === 'array') {
78
+ const fields = getSynchronizedFields(field.fields, synchronize);
79
+ const synchronizePosition = synchronize || fieldSynchronizesPosition(field);
80
+ if (!synchronizePosition && fields.length === 0) {
81
+ return [];
82
+ }
83
+ return [
84
+ {
85
+ name: field.name,
86
+ fields,
87
+ kind: 'array',
88
+ synchronizePosition
89
+ }
90
+ ];
91
+ }
92
+ if (field.type === 'blocks') {
93
+ const blocks = getSynchronizedBlocks(field, synchronize);
94
+ const synchronizePosition = synchronize || fieldSynchronizesPosition(field);
95
+ if (!synchronizePosition && blocks.length === 0) {
96
+ return [];
97
+ }
98
+ return [
99
+ {
100
+ name: field.name,
101
+ blocks,
102
+ kind: 'blocks',
103
+ synchronizePosition
104
+ }
105
+ ];
106
+ }
107
+ if (field.type === 'group') {
108
+ const fields = getSynchronizedFields(field.fields, synchronize);
109
+ if (fields.length === 0) {
110
+ return [];
111
+ }
112
+ return [
113
+ {
114
+ name: field.name,
115
+ fields,
116
+ kind: 'group'
117
+ }
118
+ ];
119
+ }
120
+ if (!synchronize) {
29
121
  return [];
30
122
  }
31
123
  return [
32
124
  {
33
125
  name: field.name,
34
- type: field.type,
35
- strategy: field.type === 'array' || field.type === 'blocks' ? 'shell' : 'value'
126
+ kind: 'value'
36
127
  }
37
128
  ];
38
129
  });
@@ -41,59 +132,167 @@ const getRowKey = (row, index)=>{
41
132
  return id === undefined ? `index:${index}` : `id:${String(id)}`;
42
133
  };
43
134
  const getShellSignature = (value, field)=>JSON.stringify(toArray(value).map((row, index)=>({
44
- blockName: field.type === 'blocks' ? getStringValue(row.blockName) : undefined,
45
- blockType: field.type === 'blocks' ? getStringValue(row.blockType) : undefined,
135
+ blockName: field.kind === 'blocks' ? getStringValue(row.blockName) : undefined,
136
+ blockType: field.kind === 'blocks' ? getStringValue(row.blockType) : undefined,
46
137
  key: getRowKey(row, index)
47
138
  })));
48
- const shellChanged = (field, nextDoc, previous)=>{
49
- if (!(field.name in nextDoc)) {
139
+ const positionChanged = (field, source, previous)=>{
140
+ if (field.kind !== 'array' && field.kind !== 'blocks' || !field.synchronizePosition || !(field.name in source)) {
50
141
  return false;
51
142
  }
52
- return getShellSignature(nextDoc[field.name], field) !== getShellSignature(previous[field.name], field);
143
+ return getShellSignature(source[field.name], field) !== getShellSignature(previous[field.name], field);
144
+ };
145
+ const getBlockFieldsForRow = (field, sourceRow)=>{
146
+ if (field.kind !== 'blocks') {
147
+ return [];
148
+ }
149
+ const blockType = getStringValue(sourceRow.blockType);
150
+ return field.blocks?.find((block)=>block.slug === blockType)?.fields || [];
53
151
  };
54
- const createShellRow = (sourceRow, targetRow, field)=>{
152
+ const createSynchronizedRow = (sourceRow, previousRow, targetRow, field)=>{
55
153
  const row = targetRow ? {
56
154
  ...targetRow
57
155
  } : {};
58
- if (field.type === 'blocks' && 'blockType' in sourceRow) {
156
+ if (field.kind === 'blocks') {
157
+ row.id = randomUUID();
158
+ }
159
+ if (field.synchronizePosition && field.kind === 'blocks' && 'blockType' in sourceRow) {
59
160
  row.blockType = sourceRow.blockType;
60
161
  }
61
- if (field.type === 'blocks') {
162
+ if (field.synchronizePosition && field.kind === 'blocks') {
62
163
  if ('blockName' in sourceRow) {
63
164
  row.blockName = sourceRow.blockName;
64
165
  } else {
65
166
  delete row.blockName;
66
167
  }
67
168
  }
68
- return row;
169
+ const fields = field.kind === 'array' ? field.fields : getBlockFieldsForRow(field, sourceRow);
170
+ return synchronizeData({
171
+ fields,
172
+ previous: previousRow || {},
173
+ source: sourceRow,
174
+ target: row
175
+ });
69
176
  };
70
- const synchronizeShellValue = ({ field, previousSourceValue, sourceValue, targetValue })=>{
177
+ const synchronizeContainerValue = ({ field, previousSourceValue, sourceValue, targetValue })=>{
71
178
  const targetRows = toArray(targetValue);
72
- const previousSourceIndexesByKey = new Map(toArray(previousSourceValue).map((row, index)=>[
179
+ const previousSourceRows = toArray(previousSourceValue);
180
+ const sourceRows = toArray(sourceValue);
181
+ if (!field.synchronizePosition) {
182
+ const sourceRowsByPreviousKey = new Map(sourceRows.map((row, index)=>{
183
+ const previousRow = previousSourceRows[index];
184
+ const key = previousRow ? getRowKey(previousRow, index) : getRowKey(row, index);
185
+ return [
186
+ key,
187
+ row
188
+ ];
189
+ }));
190
+ return targetRows.map((targetRow, index)=>{
191
+ const previousRow = previousSourceRows[index];
192
+ const sourceRow = previousRow && sourceRowsByPreviousKey.get(getRowKey(previousRow, index)) || sourceRows[index];
193
+ return sourceRow ? createSynchronizedRow(sourceRow, previousRow, targetRow, field) : {
194
+ ...targetRow
195
+ };
196
+ });
197
+ }
198
+ const previousSourceIndexesByKey = new Map(previousSourceRows.map((row, index)=>[
73
199
  getRowKey(row, index),
74
200
  index
75
201
  ]));
76
- return toArray(sourceValue).map((sourceRow, index)=>{
202
+ return sourceRows.map((sourceRow, index)=>{
77
203
  const previousIndex = previousSourceIndexesByKey.get(getRowKey(sourceRow, index));
78
- return createShellRow(sourceRow, previousIndex === undefined ? undefined : targetRows[previousIndex], field);
204
+ return createSynchronizedRow(sourceRow, previousIndex === undefined ? undefined : previousSourceRows[previousIndex], previousIndex === undefined ? undefined : targetRows[previousIndex], field);
79
205
  });
80
206
  };
81
- const getSynchronizedDataForTranslation = ({ fields, nextDoc, previous, translation })=>fields.reduce((acc, field)=>{
82
- if (field.strategy === 'value') {
83
- acc[field.name] = nextDoc[field.name];
207
+ const descriptorChanged = ({ field, previous, source })=>{
208
+ if (field.kind === 'value') {
209
+ return !valuesAreEqual(source[field.name], previous[field.name]);
210
+ }
211
+ if (field.kind === 'group') {
212
+ return descriptorsChanged({
213
+ fields: field.fields,
214
+ previous: asDocument(previous[field.name]),
215
+ source: asDocument(source[field.name])
216
+ });
217
+ }
218
+ if (!(field.name in source)) {
219
+ return false;
220
+ }
221
+ if (positionChanged(field, source, previous)) {
222
+ return true;
223
+ }
224
+ const previousRows = toArray(previous[field.name]);
225
+ const previousRowsByKey = new Map(previousRows.map((row, index)=>[
226
+ getRowKey(row, index),
227
+ row
228
+ ]));
229
+ return toArray(source[field.name]).some((sourceRow, index)=>{
230
+ const previousRow = previousRowsByKey.get(getRowKey(sourceRow, index)) || previousRows[index];
231
+ const fields = field.kind === 'array' ? field.fields : getBlockFieldsForRow(field, sourceRow);
232
+ return descriptorsChanged({
233
+ fields,
234
+ previous: previousRow || {},
235
+ source: sourceRow
236
+ });
237
+ });
238
+ };
239
+ const descriptorsChanged = ({ fields, previous, source })=>fields.some((field)=>descriptorChanged({
240
+ field,
241
+ previous,
242
+ source
243
+ }));
244
+ const synchronizeData = ({ fields, previous, source, target })=>fields.reduce((acc, field)=>{
245
+ if (field.kind === 'value') {
246
+ if (field.name in source) {
247
+ acc[field.name] = source[field.name];
248
+ } else {
249
+ delete acc[field.name];
250
+ }
251
+ return acc;
252
+ }
253
+ if (field.kind === 'group') {
254
+ if (field.name in source) {
255
+ const value = synchronizeData({
256
+ fields: field.fields,
257
+ previous: asDocument(previous[field.name]),
258
+ source: asDocument(source[field.name]),
259
+ target: asDocument(acc[field.name])
260
+ });
261
+ if (!valuesAreEqual(value, acc[field.name])) {
262
+ acc[field.name] = value;
263
+ }
264
+ } else {
265
+ delete acc[field.name];
266
+ }
84
267
  return acc;
85
268
  }
86
- const value = synchronizeShellValue({
269
+ const value = synchronizeContainerValue({
87
270
  field,
88
271
  previousSourceValue: previous[field.name],
89
- sourceValue: nextDoc[field.name],
90
- targetValue: translation[field.name]
272
+ sourceValue: source[field.name],
273
+ targetValue: target[field.name]
91
274
  });
92
- if (!valuesAreEqual(value, translation[field.name])) {
275
+ if (!valuesAreEqual(value, target[field.name])) {
93
276
  acc[field.name] = value;
94
277
  }
95
278
  return acc;
279
+ }, {
280
+ ...target
281
+ });
282
+ const getSynchronizedDataForTranslation = ({ fields, nextDoc, previous, translation })=>{
283
+ const synchronized = synchronizeData({
284
+ fields,
285
+ previous,
286
+ source: nextDoc,
287
+ target: translation
288
+ });
289
+ return fields.reduce((acc, field)=>{
290
+ if (!valuesAreEqual(synchronized[field.name], translation[field.name])) {
291
+ acc[field.name] = synchronized[field.name];
292
+ }
293
+ return acc;
96
294
  }, {});
295
+ };
97
296
  const isConfiguredLanguage = (collection, language)=>Boolean(language && collection.languages.some((configuredLanguage)=>configuredLanguage.code === language));
98
297
  const getDuplicateLanguageIDs = async ({ collection, excludeID, group, language, req })=>{
99
298
  const and = [
@@ -183,12 +382,11 @@ export const createSynchronizedFieldsAfterChangeHook = ({ collection, fields })=
183
382
  if (!group || !currentID) {
184
383
  return doc;
185
384
  }
186
- const changedFields = synchronizedFields.filter((field)=>{
187
- if (field.strategy === 'shell') {
188
- return shellChanged(field, nextDoc, previous);
189
- }
190
- return field.name in nextDoc && !valuesAreEqual(nextDoc[field.name], previous[field.name]);
191
- });
385
+ const changedFields = synchronizedFields.filter((field)=>descriptorChanged({
386
+ field,
387
+ previous,
388
+ source: nextDoc
389
+ }));
192
390
  if (changedFields.length === 0) {
193
391
  return doc;
194
392
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/translatedCollection.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionBeforeChangeHook,\n CollectionConfig,\n Field,\n Where,\n} from 'payload'\n\nimport { randomUUID } from 'crypto'\n\nimport type { ResolvedMultilangCollection } from '../types.js'\n\nimport { MULTILANG_SKIP_HOOK } from '../constants.js'\nimport { asDocument, getID, getStringValue } from '../lib/data.js'\n\nconst LANGUAGE_LABEL = {\n en: 'Language',\n uk: 'Мова',\n}\n\nconst TRANSLATIONS_LABEL = {\n en: 'Translations',\n uk: 'Переклади',\n}\n\ntype FieldAffectingData = { name: string } & Field\ntype SynchronizedField = {\n name: string\n strategy: 'shell' | 'value'\n type: 'array' | 'blocks' | Field['type']\n}\n\nconst hasSkipContext = (context?: Record<string, unknown>): boolean =>\n context?.[MULTILANG_SKIP_HOOK] === true\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst toArray = (value: unknown): Record<string, unknown>[] =>\n Array.isArray(value) ? value.filter(isObject) : []\n\nconst valuesAreEqual = (left: unknown, right: unknown): boolean => {\n if (left === right) {\n return true\n }\n\n return JSON.stringify(left) === JSON.stringify(right)\n}\n\nconst fieldAffectsData = (field: Field): field is FieldAffectingData =>\n 'name' in field && typeof field.name === 'string' && field.type !== 'ui'\n\nconst fieldSynchronizes = (field: Field): boolean => {\n const custom = isObject(field.custom) ? field.custom : {}\n const multilang = isObject(custom.multilang) ? custom.multilang : {}\n\n return multilang.synchronize === true\n}\n\nconst getSynchronizedFields = (fields: Field[] = []): SynchronizedField[] =>\n fields.flatMap((field) => {\n if (!fieldAffectsData(field) || !fieldSynchronizes(field)) {\n return []\n }\n\n return [\n {\n name: field.name,\n type: field.type,\n strategy: field.type === 'array' || field.type === 'blocks' ? 'shell' : 'value',\n },\n ]\n })\n\nconst getRowKey = (row: Record<string, unknown>, index: number): string => {\n const id = getID(row.id)\n\n return id === undefined ? `index:${index}` : `id:${String(id)}`\n}\n\nconst getShellSignature = (value: unknown, field: SynchronizedField): string =>\n JSON.stringify(\n toArray(value).map((row, index) => ({\n blockName: field.type === 'blocks' ? getStringValue(row.blockName) : undefined,\n blockType: field.type === 'blocks' ? getStringValue(row.blockType) : undefined,\n key: getRowKey(row, index),\n })),\n )\n\nconst shellChanged = (\n field: SynchronizedField,\n nextDoc: Record<string, unknown>,\n previous: Record<string, unknown>,\n): boolean => {\n if (!(field.name in nextDoc)) {\n return false\n }\n\n return (\n getShellSignature(nextDoc[field.name], field) !==\n getShellSignature(previous[field.name], field)\n )\n}\n\nconst createShellRow = (\n sourceRow: Record<string, unknown>,\n targetRow: Record<string, unknown> | undefined,\n field: SynchronizedField,\n): Record<string, unknown> => {\n const row = targetRow ? { ...targetRow } : {}\n\n if (field.type === 'blocks' && 'blockType' in sourceRow) {\n row.blockType = sourceRow.blockType\n }\n\n if (field.type === 'blocks') {\n if ('blockName' in sourceRow) {\n row.blockName = sourceRow.blockName\n } else {\n delete row.blockName\n }\n }\n\n return row\n}\n\nconst synchronizeShellValue = ({\n field,\n previousSourceValue,\n sourceValue,\n targetValue,\n}: {\n field: SynchronizedField\n previousSourceValue: unknown\n sourceValue: unknown\n targetValue: unknown\n}): unknown[] => {\n const targetRows = toArray(targetValue)\n const previousSourceIndexesByKey = new Map(\n toArray(previousSourceValue).map((row, index) => [getRowKey(row, index), index] as const),\n )\n\n return toArray(sourceValue).map((sourceRow, index) => {\n const previousIndex = previousSourceIndexesByKey.get(getRowKey(sourceRow, index))\n\n return createShellRow(\n sourceRow,\n previousIndex === undefined ? undefined : targetRows[previousIndex],\n field,\n )\n })\n}\n\nconst getSynchronizedDataForTranslation = ({\n fields,\n nextDoc,\n previous,\n translation,\n}: {\n fields: SynchronizedField[]\n nextDoc: Record<string, unknown>\n previous: Record<string, unknown>\n translation: Record<string, unknown>\n}): Record<string, unknown> =>\n fields.reduce<Record<string, unknown>>((acc, field) => {\n if (field.strategy === 'value') {\n acc[field.name] = nextDoc[field.name]\n\n return acc\n }\n\n const value = synchronizeShellValue({\n field,\n previousSourceValue: previous[field.name],\n sourceValue: nextDoc[field.name],\n targetValue: translation[field.name],\n })\n\n if (!valuesAreEqual(value, translation[field.name])) {\n acc[field.name] = value\n }\n\n return acc\n }, {})\n\nconst isConfiguredLanguage = (\n collection: ResolvedMultilangCollection,\n language: string | undefined,\n): boolean =>\n Boolean(\n language &&\n collection.languages.some((configuredLanguage) => configuredLanguage.code === language),\n )\n\ntype CollectionEditViews = NonNullable<\n NonNullable<NonNullable<CollectionConfig['admin']>['components']>['views']\n>['edit']\n\nconst getDuplicateLanguageIDs = async ({\n collection,\n excludeID,\n group,\n language,\n req,\n}: {\n collection: ResolvedMultilangCollection\n excludeID?: number | string\n group: string\n language: string\n req: Parameters<CollectionBeforeChangeHook>[0]['req']\n}): Promise<Array<number | string>> => {\n const and: Where[] = [\n {\n [collection.fieldNames.group]: {\n equals: group,\n },\n },\n {\n [collection.fieldNames.language]: {\n equals: language,\n },\n },\n ]\n\n if (excludeID) {\n and.push({\n id: {\n not_equals: excludeID,\n },\n })\n }\n\n const result = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n limit: 200,\n overrideAccess: true,\n req,\n where: {\n and,\n },\n })\n\n return result.docs\n .map((doc) => getID(asDocument(doc).id))\n .filter((id): id is number | string => id !== undefined)\n}\n\nexport const createTranslatedCollectionBeforeChangeHook =\n ({ collection }: { collection: ResolvedMultilangCollection }): CollectionBeforeChangeHook =>\n async ({ context, data, operation, originalDoc, req }) => {\n if (hasSkipContext(context)) {\n return data\n }\n\n const nextData = asDocument(data)\n const original = asDocument(originalDoc)\n const { group: groupField, language: languageField } = collection.fieldNames\n const originalLanguage = getStringValue(original[languageField])\n const originalLanguageIsConfigured = isConfiguredLanguage(collection, originalLanguage)\n const incomingLanguage = getStringValue(nextData[languageField])\n\n if (operation === 'update' && originalLanguage && !originalLanguageIsConfigured) {\n if (\n !incomingLanguage ||\n incomingLanguage === originalLanguage ||\n !isConfiguredLanguage(collection, incomingLanguage)\n ) {\n nextData[languageField] = ''\n }\n } else if (!incomingLanguage) {\n nextData[languageField] =\n operation === 'create'\n ? collection.defaultLanguage.code\n : originalLanguageIsConfigured\n ? originalLanguage\n : ''\n }\n\n const language = getStringValue(nextData[languageField])\n\n if (\n operation === 'update' &&\n originalLanguage &&\n originalLanguageIsConfigured &&\n language !== originalLanguage\n ) {\n throw new Error('Document language cannot be changed after creation.')\n }\n\n if (language && !isConfiguredLanguage(collection, language)) {\n throw new Error(`Language \"${language}\" is not configured for ${collection.slug}.`)\n }\n\n if (!getStringValue(nextData[groupField])) {\n const fallbackGroup = getStringValue(original[groupField])\n nextData[groupField] = fallbackGroup || randomUUID()\n }\n\n const group = getStringValue(nextData[groupField])\n\n if (language && group) {\n const currentID = operation === 'update' ? getID(original.id) : undefined\n const duplicateIDs = await getDuplicateLanguageIDs({\n collection,\n excludeID: currentID,\n group,\n language,\n req,\n })\n\n if (duplicateIDs.length > 0) {\n throw new Error(\n `A ${collection.slug} translation already exists for language \"${language}\" in this translation group.`,\n )\n }\n }\n\n return nextData\n }\n\nexport const createSynchronizedFieldsAfterChangeHook =\n ({\n collection,\n fields,\n }: {\n collection: ResolvedMultilangCollection\n fields?: Field[]\n }): CollectionAfterChangeHook =>\n async ({ context, doc, operation, previousDoc, req }) => {\n const synchronizedFields = getSynchronizedFields(fields)\n\n if (hasSkipContext(context) || operation !== 'update' || synchronizedFields.length === 0) {\n return doc\n }\n\n const nextDoc = asDocument(doc)\n const previous = asDocument(previousDoc)\n const group = getStringValue(nextDoc[collection.fieldNames.group])\n const currentID = getID(nextDoc.id)\n\n if (!group || !currentID) {\n return doc\n }\n\n const changedFields = synchronizedFields.filter((field) => {\n if (field.strategy === 'shell') {\n return shellChanged(field, nextDoc, previous)\n }\n\n return field.name in nextDoc && !valuesAreEqual(nextDoc[field.name], previous[field.name])\n })\n\n if (changedFields.length === 0) {\n return doc\n }\n\n const translations = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n limit: 200,\n overrideAccess: true,\n req,\n where: {\n and: [\n {\n [collection.fieldNames.group]: {\n equals: group,\n },\n },\n {\n id: {\n not_equals: currentID,\n },\n },\n ],\n },\n })\n\n await Promise.all(\n translations.docs\n .map((translation) => {\n const translationDoc = asDocument(translation)\n const id = getID(translationDoc.id)\n\n if (id === undefined) {\n return undefined\n }\n\n const synchronizedData = getSynchronizedDataForTranslation({\n fields: changedFields,\n nextDoc,\n previous,\n translation: translationDoc,\n })\n\n if (Object.keys(synchronizedData).length === 0) {\n return undefined\n }\n\n return {\n id,\n data: synchronizedData,\n }\n })\n .filter(\n (update): update is { data: Record<string, unknown>; id: number | string } =>\n update !== undefined,\n )\n .map(({ id, data }) =>\n req.payload.update({\n id,\n collection: collection.slug,\n context: {\n [MULTILANG_SKIP_HOOK]: true,\n },\n data,\n overrideAccess: true,\n req,\n }),\n ),\n )\n\n return doc\n }\n\nexport const createHiddenFields = ({\n collection,\n}: {\n collection: ResolvedMultilangCollection\n}): Field[] => [\n {\n name: collection.fieldNames.language,\n type: 'text',\n admin: {\n disableListColumn: true,\n hidden: true,\n },\n index: true,\n label: LANGUAGE_LABEL,\n },\n {\n name: collection.fieldNames.group,\n type: 'text',\n admin: {\n disableListColumn: true,\n hidden: true,\n },\n index: true,\n label: TRANSLATIONS_LABEL,\n },\n {\n name: collection.fieldNames.meta,\n type: 'json',\n admin: {\n disableListColumn: true,\n hidden: true,\n },\n },\n {\n name: 'multilangLanguageMetabox',\n type: 'ui',\n admin: {\n components: {\n Field: '@roxxel/payload-multilang/client#LanguageMetabox',\n },\n position: 'sidebar',\n },\n },\n]\n\nexport const getLanguageColumnName = (): string => 'payloadMultilangTranslations'\n\nexport const createLanguageColumnFields = ({\n collection,\n}: {\n collection: ResolvedMultilangCollection\n}): Field[] => {\n const label = collection.languages\n .map((language) => language.flagLabel || language.code.toUpperCase())\n .join(' ')\n\n return [\n {\n name: getLanguageColumnName(),\n type: 'ui',\n admin: {\n components: {\n Cell: '@roxxel/payload-multilang/rsc#TranslationColumnCell',\n },\n disableListColumn: false,\n width: '132px',\n },\n label: label || TRANSLATIONS_LABEL,\n },\n ]\n}\n\nexport const translatedCollectionMeta = ({\n collection,\n}: {\n collection: ResolvedMultilangCollection\n}) => ({\n defaultLanguage: collection.defaultLanguage,\n fieldNames: collection.fieldNames,\n languages: collection.languages,\n})\n\nconst getDefaultColumns = ({\n collection,\n existingDefaultColumns,\n languageColumnName,\n}: {\n collection: ResolvedMultilangCollection\n existingDefaultColumns: string[]\n languageColumnName: string\n}): string[] => {\n const hiddenMultilangColumns = new Set([\n collection.fieldNames.group,\n collection.fieldNames.language,\n collection.fieldNames.meta,\n languageColumnName,\n ])\n const visibleExistingColumns = existingDefaultColumns.filter(\n (column) => !hiddenMultilangColumns.has(column),\n )\n\n return [...visibleExistingColumns, languageColumnName]\n}\n\nexport const withTranslatedCollection = ({\n collection,\n config,\n}: {\n collection: ResolvedMultilangCollection\n config: CollectionConfig\n}): CollectionConfig => {\n const existingDefaultColumns = config.admin?.defaultColumns || []\n const languageColumnName = getLanguageColumnName()\n const existingEditViews =\n (config.admin?.components?.views?.edit as Record<string, unknown> | undefined) || {}\n const defaultColumns = getDefaultColumns({\n collection,\n existingDefaultColumns,\n languageColumnName,\n })\n\n return {\n ...config,\n admin: {\n ...config.admin,\n components: {\n ...config.admin?.components,\n beforeList: [\n ...(config.admin?.components?.beforeList || []),\n '@roxxel/payload-multilang/client#LanguageListToolbar',\n ],\n beforeListTable: config.admin?.components?.beforeListTable,\n views: {\n ...config.admin?.components?.views,\n edit: {\n ...existingEditViews,\n translations: {\n Component: '@roxxel/payload-multilang/rsc#TranslationsTab',\n path: '/translations',\n tab: {\n label: ({ t }) => t('payloadMultilang:translations'),\n order: 80,\n },\n },\n } as CollectionEditViews,\n },\n },\n custom: {\n ...config.admin?.custom,\n payloadMultilang: translatedCollectionMeta({\n collection,\n }),\n },\n defaultColumns,\n },\n endpoints: [...(config.endpoints || [])],\n fields: [\n ...(config.fields || []),\n ...createHiddenFields({ collection }),\n ...createLanguageColumnFields({ collection }),\n ],\n hooks: {\n ...config.hooks,\n afterChange: [\n createSynchronizedFieldsAfterChangeHook({\n collection,\n fields: config.fields,\n }),\n ...(config.hooks?.afterChange || []),\n ],\n beforeChange: [\n createTranslatedCollectionBeforeChangeHook({\n collection,\n }),\n ...(config.hooks?.beforeChange || []),\n ],\n },\n }\n}\n"],"names":["randomUUID","MULTILANG_SKIP_HOOK","asDocument","getID","getStringValue","LANGUAGE_LABEL","en","uk","TRANSLATIONS_LABEL","hasSkipContext","context","isObject","value","Array","isArray","toArray","filter","valuesAreEqual","left","right","JSON","stringify","fieldAffectsData","field","name","type","fieldSynchronizes","custom","multilang","synchronize","getSynchronizedFields","fields","flatMap","strategy","getRowKey","row","index","id","undefined","String","getShellSignature","map","blockName","blockType","key","shellChanged","nextDoc","previous","createShellRow","sourceRow","targetRow","synchronizeShellValue","previousSourceValue","sourceValue","targetValue","targetRows","previousSourceIndexesByKey","Map","previousIndex","get","getSynchronizedDataForTranslation","translation","reduce","acc","isConfiguredLanguage","collection","language","Boolean","languages","some","configuredLanguage","code","getDuplicateLanguageIDs","excludeID","group","req","and","fieldNames","equals","push","not_equals","result","payload","find","slug","depth","limit","overrideAccess","where","docs","doc","createTranslatedCollectionBeforeChangeHook","data","operation","originalDoc","nextData","original","groupField","languageField","originalLanguage","originalLanguageIsConfigured","incomingLanguage","defaultLanguage","Error","fallbackGroup","currentID","duplicateIDs","length","createSynchronizedFieldsAfterChangeHook","previousDoc","synchronizedFields","changedFields","translations","Promise","all","translationDoc","synchronizedData","Object","keys","update","createHiddenFields","admin","disableListColumn","hidden","label","meta","components","Field","position","getLanguageColumnName","createLanguageColumnFields","flagLabel","toUpperCase","join","Cell","width","translatedCollectionMeta","getDefaultColumns","existingDefaultColumns","languageColumnName","hiddenMultilangColumns","Set","visibleExistingColumns","column","has","withTranslatedCollection","config","defaultColumns","existingEditViews","views","edit","beforeList","beforeListTable","Component","path","tab","t","order","payloadMultilang","endpoints","hooks","afterChange","beforeChange"],"mappings":"AAQA,SAASA,UAAU,QAAQ,SAAQ;AAInC,SAASC,mBAAmB,QAAQ,kBAAiB;AACrD,SAASC,UAAU,EAAEC,KAAK,EAAEC,cAAc,QAAQ,iBAAgB;AAElE,MAAMC,iBAAiB;IACrBC,IAAI;IACJC,IAAI;AACN;AAEA,MAAMC,qBAAqB;IACzBF,IAAI;IACJC,IAAI;AACN;AASA,MAAME,iBAAiB,CAACC,UACtBA,SAAS,CAACT,oBAAoB,KAAK;AAErC,MAAMU,WAAW,CAACC,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF;AAEhE,MAAMG,UAAU,CAACH,QACfC,MAAMC,OAAO,CAACF,SAASA,MAAMI,MAAM,CAACL,YAAY,EAAE;AAEpD,MAAMM,iBAAiB,CAACC,MAAeC;IACrC,IAAID,SAASC,OAAO;QAClB,OAAO;IACT;IAEA,OAAOC,KAAKC,SAAS,CAACH,UAAUE,KAAKC,SAAS,CAACF;AACjD;AAEA,MAAMG,mBAAmB,CAACC,QACxB,UAAUA,SAAS,OAAOA,MAAMC,IAAI,KAAK,YAAYD,MAAME,IAAI,KAAK;AAEtE,MAAMC,oBAAoB,CAACH;IACzB,MAAMI,SAAShB,SAASY,MAAMI,MAAM,IAAIJ,MAAMI,MAAM,GAAG,CAAC;IACxD,MAAMC,YAAYjB,SAASgB,OAAOC,SAAS,IAAID,OAAOC,SAAS,GAAG,CAAC;IAEnE,OAAOA,UAAUC,WAAW,KAAK;AACnC;AAEA,MAAMC,wBAAwB,CAACC,SAAkB,EAAE,GACjDA,OAAOC,OAAO,CAAC,CAACT;QACd,IAAI,CAACD,iBAAiBC,UAAU,CAACG,kBAAkBH,QAAQ;YACzD,OAAO,EAAE;QACX;QAEA,OAAO;YACL;gBACEC,MAAMD,MAAMC,IAAI;gBAChBC,MAAMF,MAAME,IAAI;gBAChBQ,UAAUV,MAAME,IAAI,KAAK,WAAWF,MAAME,IAAI,KAAK,WAAW,UAAU;YAC1E;SACD;IACH;AAEF,MAAMS,YAAY,CAACC,KAA8BC;IAC/C,MAAMC,KAAKlC,MAAMgC,IAAIE,EAAE;IAEvB,OAAOA,OAAOC,YAAY,CAAC,MAAM,EAAEF,OAAO,GAAG,CAAC,GAAG,EAAEG,OAAOF,KAAK;AACjE;AAEA,MAAMG,oBAAoB,CAAC5B,OAAgBW,QACzCH,KAAKC,SAAS,CACZN,QAAQH,OAAO6B,GAAG,CAAC,CAACN,KAAKC,QAAW,CAAA;YAClCM,WAAWnB,MAAME,IAAI,KAAK,WAAWrB,eAAe+B,IAAIO,SAAS,IAAIJ;YACrEK,WAAWpB,MAAME,IAAI,KAAK,WAAWrB,eAAe+B,IAAIQ,SAAS,IAAIL;YACrEM,KAAKV,UAAUC,KAAKC;QACtB,CAAA;AAGJ,MAAMS,eAAe,CACnBtB,OACAuB,SACAC;IAEA,IAAI,CAAExB,CAAAA,MAAMC,IAAI,IAAIsB,OAAM,GAAI;QAC5B,OAAO;IACT;IAEA,OACEN,kBAAkBM,OAAO,CAACvB,MAAMC,IAAI,CAAC,EAAED,WACvCiB,kBAAkBO,QAAQ,CAACxB,MAAMC,IAAI,CAAC,EAAED;AAE5C;AAEA,MAAMyB,iBAAiB,CACrBC,WACAC,WACA3B;IAEA,MAAMY,MAAMe,YAAY;QAAE,GAAGA,SAAS;IAAC,IAAI,CAAC;IAE5C,IAAI3B,MAAME,IAAI,KAAK,YAAY,eAAewB,WAAW;QACvDd,IAAIQ,SAAS,GAAGM,UAAUN,SAAS;IACrC;IAEA,IAAIpB,MAAME,IAAI,KAAK,UAAU;QAC3B,IAAI,eAAewB,WAAW;YAC5Bd,IAAIO,SAAS,GAAGO,UAAUP,SAAS;QACrC,OAAO;YACL,OAAOP,IAAIO,SAAS;QACtB;IACF;IAEA,OAAOP;AACT;AAEA,MAAMgB,wBAAwB,CAAC,EAC7B5B,KAAK,EACL6B,mBAAmB,EACnBC,WAAW,EACXC,WAAW,EAMZ;IACC,MAAMC,aAAaxC,QAAQuC;IAC3B,MAAME,6BAA6B,IAAIC,IACrC1C,QAAQqC,qBAAqBX,GAAG,CAAC,CAACN,KAAKC,QAAU;YAACF,UAAUC,KAAKC;YAAQA;SAAM;IAGjF,OAAOrB,QAAQsC,aAAaZ,GAAG,CAAC,CAACQ,WAAWb;QAC1C,MAAMsB,gBAAgBF,2BAA2BG,GAAG,CAACzB,UAAUe,WAAWb;QAE1E,OAAOY,eACLC,WACAS,kBAAkBpB,YAAYA,YAAYiB,UAAU,CAACG,cAAc,EACnEnC;IAEJ;AACF;AAEA,MAAMqC,oCAAoC,CAAC,EACzC7B,MAAM,EACNe,OAAO,EACPC,QAAQ,EACRc,WAAW,EAMZ,GACC9B,OAAO+B,MAAM,CAA0B,CAACC,KAAKxC;QAC3C,IAAIA,MAAMU,QAAQ,KAAK,SAAS;YAC9B8B,GAAG,CAACxC,MAAMC,IAAI,CAAC,GAAGsB,OAAO,CAACvB,MAAMC,IAAI,CAAC;YAErC,OAAOuC;QACT;QAEA,MAAMnD,QAAQuC,sBAAsB;YAClC5B;YACA6B,qBAAqBL,QAAQ,CAACxB,MAAMC,IAAI,CAAC;YACzC6B,aAAaP,OAAO,CAACvB,MAAMC,IAAI,CAAC;YAChC8B,aAAaO,WAAW,CAACtC,MAAMC,IAAI,CAAC;QACtC;QAEA,IAAI,CAACP,eAAeL,OAAOiD,WAAW,CAACtC,MAAMC,IAAI,CAAC,GAAG;YACnDuC,GAAG,CAACxC,MAAMC,IAAI,CAAC,GAAGZ;QACpB;QAEA,OAAOmD;IACT,GAAG,CAAC;AAEN,MAAMC,uBAAuB,CAC3BC,YACAC,WAEAC,QACED,YACAD,WAAWG,SAAS,CAACC,IAAI,CAAC,CAACC,qBAAuBA,mBAAmBC,IAAI,KAAKL;AAOlF,MAAMM,0BAA0B,OAAO,EACrCP,UAAU,EACVQ,SAAS,EACTC,KAAK,EACLR,QAAQ,EACRS,GAAG,EAOJ;IACC,MAAMC,MAAe;QACnB;YACE,CAACX,WAAWY,UAAU,CAACH,KAAK,CAAC,EAAE;gBAC7BI,QAAQJ;YACV;QACF;QACA;YACE,CAACT,WAAWY,UAAU,CAACX,QAAQ,CAAC,EAAE;gBAChCY,QAAQZ;YACV;QACF;KACD;IAED,IAAIO,WAAW;QACbG,IAAIG,IAAI,CAAC;YACP1C,IAAI;gBACF2C,YAAYP;YACd;QACF;IACF;IAEA,MAAMQ,SAAS,MAAMN,IAAIO,OAAO,CAACC,IAAI,CAAC;QACpClB,YAAYA,WAAWmB,IAAI;QAC3BC,OAAO;QACPC,OAAO;QACPC,gBAAgB;QAChBZ;QACAa,OAAO;YACLZ;QACF;IACF;IAEA,OAAOK,OAAOQ,IAAI,CACfhD,GAAG,CAAC,CAACiD,MAAQvF,MAAMD,WAAWwF,KAAKrD,EAAE,GACrCrB,MAAM,CAAC,CAACqB,KAA8BA,OAAOC;AAClD;AAEA,OAAO,MAAMqD,6CACX,CAAC,EAAE1B,UAAU,EAA+C,GAC5D,OAAO,EAAEvD,OAAO,EAAEkF,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEnB,GAAG,EAAE;QACnD,IAAIlE,eAAeC,UAAU;YAC3B,OAAOkF;QACT;QAEA,MAAMG,WAAW7F,WAAW0F;QAC5B,MAAMI,WAAW9F,WAAW4F;QAC5B,MAAM,EAAEpB,OAAOuB,UAAU,EAAE/B,UAAUgC,aAAa,EAAE,GAAGjC,WAAWY,UAAU;QAC5E,MAAMsB,mBAAmB/F,eAAe4F,QAAQ,CAACE,cAAc;QAC/D,MAAME,+BAA+BpC,qBAAqBC,YAAYkC;QACtE,MAAME,mBAAmBjG,eAAe2F,QAAQ,CAACG,cAAc;QAE/D,IAAIL,cAAc,YAAYM,oBAAoB,CAACC,8BAA8B;YAC/E,IACE,CAACC,oBACDA,qBAAqBF,oBACrB,CAACnC,qBAAqBC,YAAYoC,mBAClC;gBACAN,QAAQ,CAACG,cAAc,GAAG;YAC5B;QACF,OAAO,IAAI,CAACG,kBAAkB;YAC5BN,QAAQ,CAACG,cAAc,GACrBL,cAAc,WACV5B,WAAWqC,eAAe,CAAC/B,IAAI,GAC/B6B,+BACED,mBACA;QACV;QAEA,MAAMjC,WAAW9D,eAAe2F,QAAQ,CAACG,cAAc;QAEvD,IACEL,cAAc,YACdM,oBACAC,gCACAlC,aAAaiC,kBACb;YACA,MAAM,IAAII,MAAM;QAClB;QAEA,IAAIrC,YAAY,CAACF,qBAAqBC,YAAYC,WAAW;YAC3D,MAAM,IAAIqC,MAAM,CAAC,UAAU,EAAErC,SAAS,wBAAwB,EAAED,WAAWmB,IAAI,CAAC,CAAC,CAAC;QACpF;QAEA,IAAI,CAAChF,eAAe2F,QAAQ,CAACE,WAAW,GAAG;YACzC,MAAMO,gBAAgBpG,eAAe4F,QAAQ,CAACC,WAAW;YACzDF,QAAQ,CAACE,WAAW,GAAGO,iBAAiBxG;QAC1C;QAEA,MAAM0E,QAAQtE,eAAe2F,QAAQ,CAACE,WAAW;QAEjD,IAAI/B,YAAYQ,OAAO;YACrB,MAAM+B,YAAYZ,cAAc,WAAW1F,MAAM6F,SAAS3D,EAAE,IAAIC;YAChE,MAAMoE,eAAe,MAAMlC,wBAAwB;gBACjDP;gBACAQ,WAAWgC;gBACX/B;gBACAR;gBACAS;YACF;YAEA,IAAI+B,aAAaC,MAAM,GAAG,GAAG;gBAC3B,MAAM,IAAIJ,MACR,CAAC,EAAE,EAAEtC,WAAWmB,IAAI,CAAC,0CAA0C,EAAElB,SAAS,4BAA4B,CAAC;YAE3G;QACF;QAEA,OAAO6B;IACT,EAAC;AAEH,OAAO,MAAMa,0CACX,CAAC,EACC3C,UAAU,EACVlC,MAAM,EAIP,GACD,OAAO,EAAErB,OAAO,EAAEgF,GAAG,EAAEG,SAAS,EAAEgB,WAAW,EAAElC,GAAG,EAAE;QAClD,MAAMmC,qBAAqBhF,sBAAsBC;QAEjD,IAAItB,eAAeC,YAAYmF,cAAc,YAAYiB,mBAAmBH,MAAM,KAAK,GAAG;YACxF,OAAOjB;QACT;QAEA,MAAM5C,UAAU5C,WAAWwF;QAC3B,MAAM3C,WAAW7C,WAAW2G;QAC5B,MAAMnC,QAAQtE,eAAe0C,OAAO,CAACmB,WAAWY,UAAU,CAACH,KAAK,CAAC;QACjE,MAAM+B,YAAYtG,MAAM2C,QAAQT,EAAE;QAElC,IAAI,CAACqC,SAAS,CAAC+B,WAAW;YACxB,OAAOf;QACT;QAEA,MAAMqB,gBAAgBD,mBAAmB9F,MAAM,CAAC,CAACO;YAC/C,IAAIA,MAAMU,QAAQ,KAAK,SAAS;gBAC9B,OAAOY,aAAatB,OAAOuB,SAASC;YACtC;YAEA,OAAOxB,MAAMC,IAAI,IAAIsB,WAAW,CAAC7B,eAAe6B,OAAO,CAACvB,MAAMC,IAAI,CAAC,EAAEuB,QAAQ,CAACxB,MAAMC,IAAI,CAAC;QAC3F;QAEA,IAAIuF,cAAcJ,MAAM,KAAK,GAAG;YAC9B,OAAOjB;QACT;QAEA,MAAMsB,eAAe,MAAMrC,IAAIO,OAAO,CAACC,IAAI,CAAC;YAC1ClB,YAAYA,WAAWmB,IAAI;YAC3BC,OAAO;YACPC,OAAO;YACPC,gBAAgB;YAChBZ;YACAa,OAAO;gBACLZ,KAAK;oBACH;wBACE,CAACX,WAAWY,UAAU,CAACH,KAAK,CAAC,EAAE;4BAC7BI,QAAQJ;wBACV;oBACF;oBACA;wBACErC,IAAI;4BACF2C,YAAYyB;wBACd;oBACF;iBACD;YACH;QACF;QAEA,MAAMQ,QAAQC,GAAG,CACfF,aAAavB,IAAI,CACdhD,GAAG,CAAC,CAACoB;YACJ,MAAMsD,iBAAiBjH,WAAW2D;YAClC,MAAMxB,KAAKlC,MAAMgH,eAAe9E,EAAE;YAElC,IAAIA,OAAOC,WAAW;gBACpB,OAAOA;YACT;YAEA,MAAM8E,mBAAmBxD,kCAAkC;gBACzD7B,QAAQgF;gBACRjE;gBACAC;gBACAc,aAAasD;YACf;YAEA,IAAIE,OAAOC,IAAI,CAACF,kBAAkBT,MAAM,KAAK,GAAG;gBAC9C,OAAOrE;YACT;YAEA,OAAO;gBACLD;gBACAuD,MAAMwB;YACR;QACF,GACCpG,MAAM,CACL,CAACuG,SACCA,WAAWjF,WAEdG,GAAG,CAAC,CAAC,EAAEJ,EAAE,EAAEuD,IAAI,EAAE,GAChBjB,IAAIO,OAAO,CAACqC,MAAM,CAAC;gBACjBlF;gBACA4B,YAAYA,WAAWmB,IAAI;gBAC3B1E,SAAS;oBACP,CAACT,oBAAoB,EAAE;gBACzB;gBACA2F;gBACAL,gBAAgB;gBAChBZ;YACF;QAIN,OAAOe;IACT,EAAC;AAEH,OAAO,MAAM8B,qBAAqB,CAAC,EACjCvD,UAAU,EAGX,GAAc;QACb;YACEzC,MAAMyC,WAAWY,UAAU,CAACX,QAAQ;YACpCzC,MAAM;YACNgG,OAAO;gBACLC,mBAAmB;gBACnBC,QAAQ;YACV;YACAvF,OAAO;YACPwF,OAAOvH;QACT;QACA;YACEmB,MAAMyC,WAAWY,UAAU,CAACH,KAAK;YACjCjD,MAAM;YACNgG,OAAO;gBACLC,mBAAmB;gBACnBC,QAAQ;YACV;YACAvF,OAAO;YACPwF,OAAOpH;QACT;QACA;YACEgB,MAAMyC,WAAWY,UAAU,CAACgD,IAAI;YAChCpG,MAAM;YACNgG,OAAO;gBACLC,mBAAmB;gBACnBC,QAAQ;YACV;QACF;QACA;YACEnG,MAAM;YACNC,MAAM;YACNgG,OAAO;gBACLK,YAAY;oBACVC,OAAO;gBACT;gBACAC,UAAU;YACZ;QACF;KACD,CAAA;AAED,OAAO,MAAMC,wBAAwB,IAAc,+BAA8B;AAEjF,OAAO,MAAMC,6BAA6B,CAAC,EACzCjE,UAAU,EAGX;IACC,MAAM2D,QAAQ3D,WAAWG,SAAS,CAC/B3B,GAAG,CAAC,CAACyB,WAAaA,SAASiE,SAAS,IAAIjE,SAASK,IAAI,CAAC6D,WAAW,IACjEC,IAAI,CAAC;IAER,OAAO;QACL;YACE7G,MAAMyG;YACNxG,MAAM;YACNgG,OAAO;gBACLK,YAAY;oBACVQ,MAAM;gBACR;gBACAZ,mBAAmB;gBACnBa,OAAO;YACT;YACAX,OAAOA,SAASpH;QAClB;KACD;AACH,EAAC;AAED,OAAO,MAAMgI,2BAA2B,CAAC,EACvCvE,UAAU,EAGX,GAAM,CAAA;QACLqC,iBAAiBrC,WAAWqC,eAAe;QAC3CzB,YAAYZ,WAAWY,UAAU;QACjCT,WAAWH,WAAWG,SAAS;IACjC,CAAA,EAAE;AAEF,MAAMqE,oBAAoB,CAAC,EACzBxE,UAAU,EACVyE,sBAAsB,EACtBC,kBAAkB,EAKnB;IACC,MAAMC,yBAAyB,IAAIC,IAAI;QACrC5E,WAAWY,UAAU,CAACH,KAAK;QAC3BT,WAAWY,UAAU,CAACX,QAAQ;QAC9BD,WAAWY,UAAU,CAACgD,IAAI;QAC1Bc;KACD;IACD,MAAMG,yBAAyBJ,uBAAuB1H,MAAM,CAC1D,CAAC+H,SAAW,CAACH,uBAAuBI,GAAG,CAACD;IAG1C,OAAO;WAAID;QAAwBH;KAAmB;AACxD;AAEA,OAAO,MAAMM,2BAA2B,CAAC,EACvChF,UAAU,EACViF,MAAM,EAIP;IACC,MAAMR,yBAAyBQ,OAAOzB,KAAK,EAAE0B,kBAAkB,EAAE;IACjE,MAAMR,qBAAqBV;IAC3B,MAAMmB,oBACJ,AAACF,OAAOzB,KAAK,EAAEK,YAAYuB,OAAOC,QAAgD,CAAC;IACrF,MAAMH,iBAAiBV,kBAAkB;QACvCxE;QACAyE;QACAC;IACF;IAEA,OAAO;QACL,GAAGO,MAAM;QACTzB,OAAO;YACL,GAAGyB,OAAOzB,KAAK;YACfK,YAAY;gBACV,GAAGoB,OAAOzB,KAAK,EAAEK,UAAU;gBAC3ByB,YAAY;uBACNL,OAAOzB,KAAK,EAAEK,YAAYyB,cAAc,EAAE;oBAC9C;iBACD;gBACDC,iBAAiBN,OAAOzB,KAAK,EAAEK,YAAY0B;gBAC3CH,OAAO;oBACL,GAAGH,OAAOzB,KAAK,EAAEK,YAAYuB,KAAK;oBAClCC,MAAM;wBACJ,GAAGF,iBAAiB;wBACpBpC,cAAc;4BACZyC,WAAW;4BACXC,MAAM;4BACNC,KAAK;gCACH/B,OAAO,CAAC,EAAEgC,CAAC,EAAE,GAAKA,EAAE;gCACpBC,OAAO;4BACT;wBACF;oBACF;gBACF;YACF;YACAlI,QAAQ;gBACN,GAAGuH,OAAOzB,KAAK,EAAE9F,MAAM;gBACvBmI,kBAAkBtB,yBAAyB;oBACzCvE;gBACF;YACF;YACAkF;QACF;QACAY,WAAW;eAAKb,OAAOa,SAAS,IAAI,EAAE;SAAE;QACxChI,QAAQ;eACFmH,OAAOnH,MAAM,IAAI,EAAE;eACpByF,mBAAmB;gBAAEvD;YAAW;eAChCiE,2BAA2B;gBAAEjE;YAAW;SAC5C;QACD+F,OAAO;YACL,GAAGd,OAAOc,KAAK;YACfC,aAAa;gBACXrD,wCAAwC;oBACtC3C;oBACAlC,QAAQmH,OAAOnH,MAAM;gBACvB;mBACImH,OAAOc,KAAK,EAAEC,eAAe,EAAE;aACpC;YACDC,cAAc;gBACZvE,2CAA2C;oBACzC1B;gBACF;mBACIiF,OAAOc,KAAK,EAAEE,gBAAgB,EAAE;aACrC;QACH;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/translatedCollection.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionBeforeChangeHook,\n CollectionConfig,\n Field,\n Where,\n} from 'payload'\n\nimport { randomUUID } from 'crypto'\n\nimport type { ResolvedMultilangCollection } from '../types.js'\n\nimport { MULTILANG_SKIP_HOOK } from '../constants.js'\nimport { asDocument, getID, getStringValue } from '../lib/data.js'\n\nconst LANGUAGE_LABEL = {\n en: 'Language',\n uk: 'Мова',\n}\n\nconst TRANSLATIONS_LABEL = {\n en: 'Translations',\n uk: 'Переклади',\n}\n\ntype FieldAffectingData = { name: string } & Field\ntype SynchronizedBlock = {\n fields: SynchronizedField[]\n slug: string\n}\ntype SynchronizedField =\n | {\n blocks: SynchronizedBlock[]\n kind: 'blocks'\n name: string\n synchronizePosition: boolean\n }\n | {\n fields: SynchronizedField[]\n kind: 'array'\n name: string\n synchronizePosition: boolean\n }\n | {\n fields: SynchronizedField[]\n kind: 'group'\n name: string\n }\n | {\n kind: 'value'\n name: string\n }\ntype SynchronizedContainerField = Extract<SynchronizedField, { kind: 'array' | 'blocks' }>\n\nconst hasSkipContext = (context?: Record<string, unknown>): boolean =>\n context?.[MULTILANG_SKIP_HOOK] === true\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst toArray = (value: unknown): Record<string, unknown>[] =>\n Array.isArray(value) ? value.filter(isObject) : []\n\nconst valuesAreEqual = (left: unknown, right: unknown): boolean => {\n if (left === right) {\n return true\n }\n\n return JSON.stringify(left) === JSON.stringify(right)\n}\n\nconst fieldAffectsData = (field: Field): field is FieldAffectingData =>\n 'name' in field && typeof field.name === 'string' && field.name !== 'id' && field.type !== 'ui'\n\nconst fieldSynchronizes = (field: Field): boolean => {\n const custom = isObject(field.custom) ? field.custom : {}\n const multilang = isObject(custom.multilang) ? custom.multilang : {}\n\n return multilang.synchronize === true\n}\n\nconst fieldSynchronizesPosition = (field: Field): boolean => {\n const custom = isObject(field.custom) ? field.custom : {}\n const multilang = isObject(custom.multilang) ? custom.multilang : {}\n\n return multilang.synchronizePosition === true\n}\n\nconst hasName = (value: unknown): value is { name: string } =>\n isObject(value) && typeof value.name === 'string'\n\nconst getSynchronizedBlocks = (\n field: Field,\n inheritedSynchronize: boolean,\n): SynchronizedBlock[] => {\n if (field.type !== 'blocks') {\n return []\n }\n\n return field.blocks.flatMap((block) => {\n const fields = getSynchronizedFields(block.fields, inheritedSynchronize)\n\n if (fields.length === 0) {\n return []\n }\n\n return [\n {\n slug: block.slug,\n fields,\n },\n ]\n })\n}\n\nconst getSynchronizedTabFields = (\n field: Extract<Field, { type: 'tabs' }>,\n inheritedSynchronize: boolean,\n): SynchronizedField[] =>\n field.tabs.flatMap<SynchronizedField>((tab) => {\n const fields = getSynchronizedFields(tab.fields, inheritedSynchronize)\n\n if (fields.length === 0) {\n return []\n }\n\n if (hasName(tab)) {\n return [\n {\n name: tab.name,\n fields,\n kind: 'group',\n },\n ]\n }\n\n return fields\n })\n\nconst getSynchronizedFields = (\n fields: Field[] = [],\n inheritedSynchronize = false,\n): SynchronizedField[] =>\n fields.flatMap<SynchronizedField>((field) => {\n if (field.type === 'row' || field.type === 'collapsible') {\n return getSynchronizedFields(\n field.fields,\n inheritedSynchronize || fieldSynchronizes(field),\n )\n }\n\n if (field.type === 'tabs') {\n return getSynchronizedTabFields(field, inheritedSynchronize)\n }\n\n if (!fieldAffectsData(field)) {\n return []\n }\n\n const synchronize = inheritedSynchronize || fieldSynchronizes(field)\n\n if (field.type === 'array') {\n const fields = getSynchronizedFields(field.fields, synchronize)\n const synchronizePosition = synchronize || fieldSynchronizesPosition(field)\n\n if (!synchronizePosition && fields.length === 0) {\n return []\n }\n\n return [\n {\n name: field.name,\n fields,\n kind: 'array',\n synchronizePosition,\n },\n ]\n }\n\n if (field.type === 'blocks') {\n const blocks = getSynchronizedBlocks(field, synchronize)\n const synchronizePosition = synchronize || fieldSynchronizesPosition(field)\n\n if (!synchronizePosition && blocks.length === 0) {\n return []\n }\n\n return [\n {\n name: field.name,\n blocks,\n kind: 'blocks',\n synchronizePosition,\n },\n ]\n }\n\n if (field.type === 'group') {\n const fields = getSynchronizedFields(field.fields, synchronize)\n\n if (fields.length === 0) {\n return []\n }\n\n return [\n {\n name: field.name,\n fields,\n kind: 'group',\n },\n ]\n }\n\n if (!synchronize) {\n return []\n }\n\n return [\n {\n name: field.name,\n kind: 'value',\n },\n ]\n })\n\nconst getRowKey = (row: Record<string, unknown>, index: number): string => {\n const id = getID(row.id)\n\n return id === undefined ? `index:${index}` : `id:${String(id)}`\n}\n\nconst getShellSignature = (value: unknown, field: SynchronizedField): string =>\n JSON.stringify(\n toArray(value).map((row, index) => ({\n blockName: field.kind === 'blocks' ? getStringValue(row.blockName) : undefined,\n blockType: field.kind === 'blocks' ? getStringValue(row.blockType) : undefined,\n key: getRowKey(row, index),\n })),\n )\n\nconst positionChanged = (\n field: SynchronizedField,\n source: Record<string, unknown>,\n previous: Record<string, unknown>,\n): boolean => {\n if (\n (field.kind !== 'array' && field.kind !== 'blocks') ||\n !field.synchronizePosition ||\n !(field.name in source)\n ) {\n return false\n }\n\n return (\n getShellSignature(source[field.name], field) !==\n getShellSignature(previous[field.name], field)\n )\n}\n\nconst getBlockFieldsForRow = (\n field: SynchronizedField,\n sourceRow: Record<string, unknown>,\n): SynchronizedField[] => {\n if (field.kind !== 'blocks') {\n return []\n }\n\n const blockType = getStringValue(sourceRow.blockType)\n\n return field.blocks?.find((block) => block.slug === blockType)?.fields || []\n}\n\nconst createSynchronizedRow = (\n sourceRow: Record<string, unknown>,\n previousRow: Record<string, unknown> | undefined,\n targetRow: Record<string, unknown> | undefined,\n field: SynchronizedContainerField,\n): Record<string, unknown> => {\n const row = targetRow ? { ...targetRow } : {}\n\n if (field.kind === 'blocks') {\n row.id = randomUUID()\n }\n\n if (field.synchronizePosition && field.kind === 'blocks' && 'blockType' in sourceRow) {\n row.blockType = sourceRow.blockType\n }\n\n if (field.synchronizePosition && field.kind === 'blocks') {\n if ('blockName' in sourceRow) {\n row.blockName = sourceRow.blockName\n } else {\n delete row.blockName\n }\n }\n\n const fields =\n field.kind === 'array' ? field.fields : getBlockFieldsForRow(field, sourceRow)\n\n return synchronizeData({\n fields,\n previous: previousRow || {},\n source: sourceRow,\n target: row,\n })\n}\n\nconst synchronizeContainerValue = ({\n field,\n previousSourceValue,\n sourceValue,\n targetValue,\n}: {\n field: SynchronizedContainerField\n previousSourceValue: unknown\n sourceValue: unknown\n targetValue: unknown\n}): unknown[] => {\n const targetRows = toArray(targetValue)\n const previousSourceRows = toArray(previousSourceValue)\n const sourceRows = toArray(sourceValue)\n\n if (!field.synchronizePosition) {\n const sourceRowsByPreviousKey = new Map(\n sourceRows.map((row, index) => {\n const previousRow = previousSourceRows[index]\n const key = previousRow ? getRowKey(previousRow, index) : getRowKey(row, index)\n\n return [key, row] as const\n }),\n )\n\n return targetRows.map((targetRow, index) => {\n const previousRow = previousSourceRows[index]\n const sourceRow =\n (previousRow && sourceRowsByPreviousKey.get(getRowKey(previousRow, index))) ||\n sourceRows[index]\n\n return sourceRow\n ? createSynchronizedRow(sourceRow, previousRow, targetRow, field)\n : { ...targetRow }\n })\n }\n\n const previousSourceIndexesByKey = new Map(\n previousSourceRows.map((row, index) => [getRowKey(row, index), index] as const),\n )\n\n return sourceRows.map((sourceRow, index) => {\n const previousIndex = previousSourceIndexesByKey.get(getRowKey(sourceRow, index))\n\n return createSynchronizedRow(\n sourceRow,\n previousIndex === undefined ? undefined : previousSourceRows[previousIndex],\n previousIndex === undefined ? undefined : targetRows[previousIndex],\n field,\n )\n })\n}\n\nconst descriptorChanged = ({\n field,\n previous,\n source,\n}: {\n field: SynchronizedField\n previous: Record<string, unknown>\n source: Record<string, unknown>\n}): boolean => {\n if (field.kind === 'value') {\n return !valuesAreEqual(source[field.name], previous[field.name])\n }\n\n if (field.kind === 'group') {\n return descriptorsChanged({\n fields: field.fields,\n previous: asDocument(previous[field.name]),\n source: asDocument(source[field.name]),\n })\n }\n\n if (!(field.name in source)) {\n return false\n }\n\n if (positionChanged(field, source, previous)) {\n return true\n }\n\n const previousRows = toArray(previous[field.name])\n const previousRowsByKey = new Map(\n previousRows.map((row, index) => [getRowKey(row, index), row] as const),\n )\n\n return toArray(source[field.name]).some((sourceRow, index) => {\n const previousRow = previousRowsByKey.get(getRowKey(sourceRow, index)) || previousRows[index]\n const fields =\n field.kind === 'array' ? field.fields : getBlockFieldsForRow(field, sourceRow)\n\n return descriptorsChanged({\n fields,\n previous: previousRow || {},\n source: sourceRow,\n })\n })\n}\n\nconst descriptorsChanged = ({\n fields,\n previous,\n source,\n}: {\n fields: SynchronizedField[]\n previous: Record<string, unknown>\n source: Record<string, unknown>\n}): boolean =>\n fields.some((field) =>\n descriptorChanged({\n field,\n previous,\n source,\n }),\n )\n\nconst synchronizeData = ({\n fields,\n previous,\n source,\n target,\n}: {\n fields: SynchronizedField[]\n previous: Record<string, unknown>\n source: Record<string, unknown>\n target: Record<string, unknown>\n}): Record<string, unknown> =>\n fields.reduce<Record<string, unknown>>((acc, field) => {\n if (field.kind === 'value') {\n if (field.name in source) {\n acc[field.name] = source[field.name]\n } else {\n delete acc[field.name]\n }\n\n return acc\n }\n\n if (field.kind === 'group') {\n if (field.name in source) {\n const value = synchronizeData({\n fields: field.fields,\n previous: asDocument(previous[field.name]),\n source: asDocument(source[field.name]),\n target: asDocument(acc[field.name]),\n })\n\n if (!valuesAreEqual(value, acc[field.name])) {\n acc[field.name] = value\n }\n } else {\n delete acc[field.name]\n }\n\n return acc\n }\n\n const value = synchronizeContainerValue({\n field,\n previousSourceValue: previous[field.name],\n sourceValue: source[field.name],\n targetValue: target[field.name],\n })\n\n if (!valuesAreEqual(value, target[field.name])) {\n acc[field.name] = value\n }\n\n return acc\n }, { ...target })\n\nconst getSynchronizedDataForTranslation = ({\n fields,\n nextDoc,\n previous,\n translation,\n}: {\n fields: SynchronizedField[]\n nextDoc: Record<string, unknown>\n previous: Record<string, unknown>\n translation: Record<string, unknown>\n}): Record<string, unknown> => {\n const synchronized = synchronizeData({\n fields,\n previous,\n source: nextDoc,\n target: translation,\n })\n\n return fields.reduce<Record<string, unknown>>((acc, field) => {\n if (!valuesAreEqual(synchronized[field.name], translation[field.name])) {\n acc[field.name] = synchronized[field.name]\n }\n\n return acc\n }, {})\n}\n\nconst isConfiguredLanguage = (\n collection: ResolvedMultilangCollection,\n language: string | undefined,\n): boolean =>\n Boolean(\n language &&\n collection.languages.some((configuredLanguage) => configuredLanguage.code === language),\n )\n\ntype CollectionEditViews = NonNullable<\n NonNullable<NonNullable<CollectionConfig['admin']>['components']>['views']\n>['edit']\n\nconst getDuplicateLanguageIDs = async ({\n collection,\n excludeID,\n group,\n language,\n req,\n}: {\n collection: ResolvedMultilangCollection\n excludeID?: number | string\n group: string\n language: string\n req: Parameters<CollectionBeforeChangeHook>[0]['req']\n}): Promise<Array<number | string>> => {\n const and: Where[] = [\n {\n [collection.fieldNames.group]: {\n equals: group,\n },\n },\n {\n [collection.fieldNames.language]: {\n equals: language,\n },\n },\n ]\n\n if (excludeID) {\n and.push({\n id: {\n not_equals: excludeID,\n },\n })\n }\n\n const result = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n limit: 200,\n overrideAccess: true,\n req,\n where: {\n and,\n },\n })\n\n return result.docs\n .map((doc) => getID(asDocument(doc).id))\n .filter((id): id is number | string => id !== undefined)\n}\n\nexport const createTranslatedCollectionBeforeChangeHook =\n ({ collection }: { collection: ResolvedMultilangCollection }): CollectionBeforeChangeHook =>\n async ({ context, data, operation, originalDoc, req }) => {\n if (hasSkipContext(context)) {\n return data\n }\n\n const nextData = asDocument(data)\n const original = asDocument(originalDoc)\n const { group: groupField, language: languageField } = collection.fieldNames\n const originalLanguage = getStringValue(original[languageField])\n const originalLanguageIsConfigured = isConfiguredLanguage(collection, originalLanguage)\n const incomingLanguage = getStringValue(nextData[languageField])\n\n if (operation === 'update' && originalLanguage && !originalLanguageIsConfigured) {\n if (\n !incomingLanguage ||\n incomingLanguage === originalLanguage ||\n !isConfiguredLanguage(collection, incomingLanguage)\n ) {\n nextData[languageField] = ''\n }\n } else if (!incomingLanguage) {\n nextData[languageField] =\n operation === 'create'\n ? collection.defaultLanguage.code\n : originalLanguageIsConfigured\n ? originalLanguage\n : ''\n }\n\n const language = getStringValue(nextData[languageField])\n\n if (\n operation === 'update' &&\n originalLanguage &&\n originalLanguageIsConfigured &&\n language !== originalLanguage\n ) {\n throw new Error('Document language cannot be changed after creation.')\n }\n\n if (language && !isConfiguredLanguage(collection, language)) {\n throw new Error(`Language \"${language}\" is not configured for ${collection.slug}.`)\n }\n\n if (!getStringValue(nextData[groupField])) {\n const fallbackGroup = getStringValue(original[groupField])\n nextData[groupField] = fallbackGroup || randomUUID()\n }\n\n const group = getStringValue(nextData[groupField])\n\n if (language && group) {\n const currentID = operation === 'update' ? getID(original.id) : undefined\n const duplicateIDs = await getDuplicateLanguageIDs({\n collection,\n excludeID: currentID,\n group,\n language,\n req,\n })\n\n if (duplicateIDs.length > 0) {\n throw new Error(\n `A ${collection.slug} translation already exists for language \"${language}\" in this translation group.`,\n )\n }\n }\n\n return nextData\n }\n\nexport const createSynchronizedFieldsAfterChangeHook =\n ({\n collection,\n fields,\n }: {\n collection: ResolvedMultilangCollection\n fields?: Field[]\n }): CollectionAfterChangeHook =>\n async ({ context, doc, operation, previousDoc, req }) => {\n const synchronizedFields = getSynchronizedFields(fields)\n\n if (hasSkipContext(context) || operation !== 'update' || synchronizedFields.length === 0) {\n return doc\n }\n\n const nextDoc = asDocument(doc)\n const previous = asDocument(previousDoc)\n const group = getStringValue(nextDoc[collection.fieldNames.group])\n const currentID = getID(nextDoc.id)\n\n if (!group || !currentID) {\n return doc\n }\n\n const changedFields = synchronizedFields.filter((field) =>\n descriptorChanged({\n field,\n previous,\n source: nextDoc,\n }),\n )\n\n if (changedFields.length === 0) {\n return doc\n }\n\n const translations = await req.payload.find({\n collection: collection.slug,\n depth: 0,\n limit: 200,\n overrideAccess: true,\n req,\n where: {\n and: [\n {\n [collection.fieldNames.group]: {\n equals: group,\n },\n },\n {\n id: {\n not_equals: currentID,\n },\n },\n ],\n },\n })\n\n await Promise.all(\n translations.docs\n .map((translation) => {\n const translationDoc = asDocument(translation)\n const id = getID(translationDoc.id)\n\n if (id === undefined) {\n return undefined\n }\n\n const synchronizedData = getSynchronizedDataForTranslation({\n fields: changedFields,\n nextDoc,\n previous,\n translation: translationDoc,\n })\n\n if (Object.keys(synchronizedData).length === 0) {\n return undefined\n }\n\n return {\n id,\n data: synchronizedData,\n }\n })\n .filter(\n (update): update is { data: Record<string, unknown>; id: number | string } =>\n update !== undefined,\n )\n .map(({ id, data }) =>\n req.payload.update({\n id,\n collection: collection.slug,\n context: {\n [MULTILANG_SKIP_HOOK]: true,\n },\n data,\n overrideAccess: true,\n req,\n }),\n ),\n )\n\n return doc\n }\n\nexport const createHiddenFields = ({\n collection,\n}: {\n collection: ResolvedMultilangCollection\n}): Field[] => [\n {\n name: collection.fieldNames.language,\n type: 'text',\n admin: {\n disableListColumn: true,\n hidden: true,\n },\n index: true,\n label: LANGUAGE_LABEL,\n },\n {\n name: collection.fieldNames.group,\n type: 'text',\n admin: {\n disableListColumn: true,\n hidden: true,\n },\n index: true,\n label: TRANSLATIONS_LABEL,\n },\n {\n name: collection.fieldNames.meta,\n type: 'json',\n admin: {\n disableListColumn: true,\n hidden: true,\n },\n },\n {\n name: 'multilangLanguageMetabox',\n type: 'ui',\n admin: {\n components: {\n Field: '@roxxel/payload-multilang/client#LanguageMetabox',\n },\n position: 'sidebar',\n },\n },\n]\n\nexport const getLanguageColumnName = (): string => 'payloadMultilangTranslations'\n\nexport const createLanguageColumnFields = ({\n collection,\n}: {\n collection: ResolvedMultilangCollection\n}): Field[] => {\n const label = collection.languages\n .map((language) => language.flagLabel || language.code.toUpperCase())\n .join(' ')\n\n return [\n {\n name: getLanguageColumnName(),\n type: 'ui',\n admin: {\n components: {\n Cell: '@roxxel/payload-multilang/rsc#TranslationColumnCell',\n },\n disableListColumn: false,\n width: '132px',\n },\n label: label || TRANSLATIONS_LABEL,\n },\n ]\n}\n\nexport const translatedCollectionMeta = ({\n collection,\n}: {\n collection: ResolvedMultilangCollection\n}) => ({\n defaultLanguage: collection.defaultLanguage,\n fieldNames: collection.fieldNames,\n languages: collection.languages,\n})\n\nconst getDefaultColumns = ({\n collection,\n existingDefaultColumns,\n languageColumnName,\n}: {\n collection: ResolvedMultilangCollection\n existingDefaultColumns: string[]\n languageColumnName: string\n}): string[] => {\n const hiddenMultilangColumns = new Set([\n collection.fieldNames.group,\n collection.fieldNames.language,\n collection.fieldNames.meta,\n languageColumnName,\n ])\n const visibleExistingColumns = existingDefaultColumns.filter(\n (column) => !hiddenMultilangColumns.has(column),\n )\n\n return [...visibleExistingColumns, languageColumnName]\n}\n\nexport const withTranslatedCollection = ({\n collection,\n config,\n}: {\n collection: ResolvedMultilangCollection\n config: CollectionConfig\n}): CollectionConfig => {\n const existingDefaultColumns = config.admin?.defaultColumns || []\n const languageColumnName = getLanguageColumnName()\n const existingEditViews =\n (config.admin?.components?.views?.edit as Record<string, unknown> | undefined) || {}\n const defaultColumns = getDefaultColumns({\n collection,\n existingDefaultColumns,\n languageColumnName,\n })\n\n return {\n ...config,\n admin: {\n ...config.admin,\n components: {\n ...config.admin?.components,\n beforeList: [\n ...(config.admin?.components?.beforeList || []),\n '@roxxel/payload-multilang/client#LanguageListToolbar',\n ],\n beforeListTable: config.admin?.components?.beforeListTable,\n views: {\n ...config.admin?.components?.views,\n edit: {\n ...existingEditViews,\n translations: {\n Component: '@roxxel/payload-multilang/rsc#TranslationsTab',\n path: '/translations',\n tab: {\n label: ({ t }) => t('payloadMultilang:translations'),\n order: 80,\n },\n },\n } as CollectionEditViews,\n },\n },\n custom: {\n ...config.admin?.custom,\n payloadMultilang: translatedCollectionMeta({\n collection,\n }),\n },\n defaultColumns,\n },\n endpoints: [...(config.endpoints || [])],\n fields: [\n ...(config.fields || []),\n ...createHiddenFields({ collection }),\n ...createLanguageColumnFields({ collection }),\n ],\n hooks: {\n ...config.hooks,\n afterChange: [\n createSynchronizedFieldsAfterChangeHook({\n collection,\n fields: config.fields,\n }),\n ...(config.hooks?.afterChange || []),\n ],\n beforeChange: [\n createTranslatedCollectionBeforeChangeHook({\n collection,\n }),\n ...(config.hooks?.beforeChange || []),\n ],\n },\n }\n}\n"],"names":["randomUUID","MULTILANG_SKIP_HOOK","asDocument","getID","getStringValue","LANGUAGE_LABEL","en","uk","TRANSLATIONS_LABEL","hasSkipContext","context","isObject","value","Array","isArray","toArray","filter","valuesAreEqual","left","right","JSON","stringify","fieldAffectsData","field","name","type","fieldSynchronizes","custom","multilang","synchronize","fieldSynchronizesPosition","synchronizePosition","hasName","getSynchronizedBlocks","inheritedSynchronize","blocks","flatMap","block","fields","getSynchronizedFields","length","slug","getSynchronizedTabFields","tabs","tab","kind","getRowKey","row","index","id","undefined","String","getShellSignature","map","blockName","blockType","key","positionChanged","source","previous","getBlockFieldsForRow","sourceRow","find","createSynchronizedRow","previousRow","targetRow","synchronizeData","target","synchronizeContainerValue","previousSourceValue","sourceValue","targetValue","targetRows","previousSourceRows","sourceRows","sourceRowsByPreviousKey","Map","get","previousSourceIndexesByKey","previousIndex","descriptorChanged","descriptorsChanged","previousRows","previousRowsByKey","some","reduce","acc","getSynchronizedDataForTranslation","nextDoc","translation","synchronized","isConfiguredLanguage","collection","language","Boolean","languages","configuredLanguage","code","getDuplicateLanguageIDs","excludeID","group","req","and","fieldNames","equals","push","not_equals","result","payload","depth","limit","overrideAccess","where","docs","doc","createTranslatedCollectionBeforeChangeHook","data","operation","originalDoc","nextData","original","groupField","languageField","originalLanguage","originalLanguageIsConfigured","incomingLanguage","defaultLanguage","Error","fallbackGroup","currentID","duplicateIDs","createSynchronizedFieldsAfterChangeHook","previousDoc","synchronizedFields","changedFields","translations","Promise","all","translationDoc","synchronizedData","Object","keys","update","createHiddenFields","admin","disableListColumn","hidden","label","meta","components","Field","position","getLanguageColumnName","createLanguageColumnFields","flagLabel","toUpperCase","join","Cell","width","translatedCollectionMeta","getDefaultColumns","existingDefaultColumns","languageColumnName","hiddenMultilangColumns","Set","visibleExistingColumns","column","has","withTranslatedCollection","config","defaultColumns","existingEditViews","views","edit","beforeList","beforeListTable","Component","path","t","order","payloadMultilang","endpoints","hooks","afterChange","beforeChange"],"mappings":"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"}
@@ -166,7 +166,15 @@ Mark fields that should stay shared across translations directly in the field co
166
166
  }
167
167
  ```
168
168
 
169
- For repeatable fields with child fields, such as `array` and `blocks`, synchronization only applies to the outer shell. Item IDs, order, block types, and block names follow the source document, while each translation keeps its own nested field values.
169
+ `custom.multilang.synchronize: true` can be set on any data field. Scalar fields copy their value to every translation. Container fields are traversed recursively:
170
+
171
+ - `group` synchronizes every child field recursively.
172
+ - `array` and `blocks` synchronize their row or block shell and every child field recursively.
173
+ - `tabs` are not synchronized as values, but fields inside tabs are traversed and can synchronize normally.
174
+
175
+ Use `custom.multilang.synchronizePosition: true` on `array` or `blocks` when only the outer shell should follow the source document. This synchronizes row or block order, plus block type and block name for blocks, while preserving each translation's nested field values.
176
+
177
+ If a container is not marked but a descendant field is marked with `synchronize: true`, existing rows or blocks are matched without changing translation-local row order or membership.
170
178
 
171
179
  ## Globals
172
180
 
package/docs/usage.md CHANGED
@@ -27,6 +27,12 @@ payloadMultilang({
27
27
 
28
28
  Editors can then choose the document language in the sidebar and create or connect translations from the collection list, document sidebar, or `Translations` edit tab.
29
29
 
30
+ ## Synchronized Fields
31
+
32
+ Mark shared fields with `custom.multilang.synchronize: true`. For `group`, `array`, and `blocks`, this synchronizes nested values recursively. `tabs` are only traversed; fields inside tabs can still opt in.
33
+
34
+ Use `custom.multilang.synchronizePosition: true` on `array` or `blocks` to synchronize only the repeatable shell, preserving localized values inside each row or block.
35
+
30
36
  ## App-Level Convenience Helpers
31
37
 
32
38
  Create one app-owned helper module when you want short helper calls in RSCs, route handlers, or other trusted server code.
@@ -236,7 +242,7 @@ await multilang.updateGlobalByLanguage({
236
242
 
237
243
  ## Keep Shared Fields in Sync
238
244
 
239
- Set `custom.multilang.synchronize` on top-level fields that should be identical across translations, such as author, featured image, or canonical taxonomy.
245
+ Set `custom.multilang.synchronize` on fields that should be identical across translations, such as author, featured image, canonical taxonomy, or a whole nested group.
240
246
 
241
247
  ```ts
242
248
  {
@@ -251,9 +257,9 @@ Set `custom.multilang.synchronize` on top-level fields that should be identical
251
257
  }
252
258
  ```
253
259
 
254
- When an editor changes one of those fields on a saved translation, the plugin updates the same field on the other linked translations.
260
+ When an editor changes one of those fields on a saved translation, the plugin updates the same field on the other linked translations. For `group`, `array`, and `blocks`, `synchronize: true` applies recursively to nested fields.
255
261
 
256
- For `array` and `blocks` fields, synchronization keeps the outer shell aligned only. The row IDs, order, block types, and block names sync across translations, but nested field values inside each row or block stay language-specific.
262
+ Use `custom.multilang.synchronizePosition: true` on `array` or `blocks` to keep only the repeatable shell aligned while preserving localized values inside each row or block.
257
263
 
258
264
  ## Generate Types
259
265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roxxel/payload-multilang",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Polylang-style document localization for Payload CMS",
5
5
  "license": "MIT",
6
6
  "repository": {