@roxxel/payload-multilang 0.0.6 → 0.0.8

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,170 @@ 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)=>{
55
- const row = targetRow ? {
152
+ const createSynchronizedRow = (sourceRow, previousRow, targetRow, field)=>{
153
+ const sourceBlockType = field.kind === 'blocks' ? getStringValue(sourceRow.blockType) : undefined;
154
+ const previousBlockType = field.kind === 'blocks' && previousRow ? getStringValue(previousRow.blockType) : undefined;
155
+ const blockTypeChanged = field.kind === 'blocks' && field.synchronizePosition && previousRow !== undefined && sourceBlockType !== previousBlockType;
156
+ const row = targetRow && !blockTypeChanged ? {
56
157
  ...targetRow
57
158
  } : {};
58
- if (field.type === 'blocks' && 'blockType' in sourceRow) {
159
+ if (field.kind === 'blocks') {
160
+ row.id = randomUUID();
161
+ }
162
+ if (field.synchronizePosition && field.kind === 'blocks' && 'blockType' in sourceRow) {
59
163
  row.blockType = sourceRow.blockType;
60
164
  }
61
- if (field.type === 'blocks') {
165
+ if (field.synchronizePosition && field.kind === 'blocks') {
62
166
  if ('blockName' in sourceRow) {
63
167
  row.blockName = sourceRow.blockName;
64
168
  } else {
65
169
  delete row.blockName;
66
170
  }
67
171
  }
68
- return row;
172
+ const fields = field.kind === 'array' ? field.fields : getBlockFieldsForRow(field, sourceRow);
173
+ return synchronizeData({
174
+ fields,
175
+ previous: previousRow || {},
176
+ source: sourceRow,
177
+ target: row
178
+ });
69
179
  };
70
- const synchronizeShellValue = ({ field, previousSourceValue, sourceValue, targetValue })=>{
180
+ const synchronizeContainerValue = ({ field, previousSourceValue, sourceValue, targetValue })=>{
71
181
  const targetRows = toArray(targetValue);
72
- const previousSourceIndexesByKey = new Map(toArray(previousSourceValue).map((row, index)=>[
182
+ const previousSourceRows = toArray(previousSourceValue);
183
+ const sourceRows = toArray(sourceValue);
184
+ if (!field.synchronizePosition) {
185
+ const sourceRowsByPreviousKey = new Map(sourceRows.map((row, index)=>{
186
+ const previousRow = previousSourceRows[index];
187
+ const key = previousRow ? getRowKey(previousRow, index) : getRowKey(row, index);
188
+ return [
189
+ key,
190
+ row
191
+ ];
192
+ }));
193
+ return targetRows.map((targetRow, index)=>{
194
+ const previousRow = previousSourceRows[index];
195
+ const sourceRow = previousRow && sourceRowsByPreviousKey.get(getRowKey(previousRow, index)) || sourceRows[index];
196
+ return sourceRow ? createSynchronizedRow(sourceRow, previousRow, targetRow, field) : {
197
+ ...targetRow
198
+ };
199
+ });
200
+ }
201
+ const previousSourceIndexesByKey = new Map(previousSourceRows.map((row, index)=>[
73
202
  getRowKey(row, index),
74
203
  index
75
204
  ]));
76
- return toArray(sourceValue).map((sourceRow, index)=>{
205
+ return sourceRows.map((sourceRow, index)=>{
77
206
  const previousIndex = previousSourceIndexesByKey.get(getRowKey(sourceRow, index));
78
- return createShellRow(sourceRow, previousIndex === undefined ? undefined : targetRows[previousIndex], field);
207
+ return createSynchronizedRow(sourceRow, previousIndex === undefined ? undefined : previousSourceRows[previousIndex], previousIndex === undefined ? undefined : targetRows[previousIndex], field);
79
208
  });
80
209
  };
81
- const getSynchronizedDataForTranslation = ({ fields, nextDoc, previous, translation })=>fields.reduce((acc, field)=>{
82
- if (field.strategy === 'value') {
83
- acc[field.name] = nextDoc[field.name];
210
+ const descriptorChanged = ({ field, previous, source })=>{
211
+ if (field.kind === 'value') {
212
+ return !valuesAreEqual(source[field.name], previous[field.name]);
213
+ }
214
+ if (field.kind === 'group') {
215
+ return descriptorsChanged({
216
+ fields: field.fields,
217
+ previous: asDocument(previous[field.name]),
218
+ source: asDocument(source[field.name])
219
+ });
220
+ }
221
+ if (!(field.name in source)) {
222
+ return false;
223
+ }
224
+ if (positionChanged(field, source, previous)) {
225
+ return true;
226
+ }
227
+ const previousRows = toArray(previous[field.name]);
228
+ const previousRowsByKey = new Map(previousRows.map((row, index)=>[
229
+ getRowKey(row, index),
230
+ row
231
+ ]));
232
+ return toArray(source[field.name]).some((sourceRow, index)=>{
233
+ const previousRow = previousRowsByKey.get(getRowKey(sourceRow, index)) || previousRows[index];
234
+ const fields = field.kind === 'array' ? field.fields : getBlockFieldsForRow(field, sourceRow);
235
+ return descriptorsChanged({
236
+ fields,
237
+ previous: previousRow || {},
238
+ source: sourceRow
239
+ });
240
+ });
241
+ };
242
+ const descriptorsChanged = ({ fields, previous, source })=>fields.some((field)=>descriptorChanged({
243
+ field,
244
+ previous,
245
+ source
246
+ }));
247
+ const synchronizeData = ({ fields, previous, source, target })=>fields.reduce((acc, field)=>{
248
+ if (field.kind === 'value') {
249
+ if (field.name in source) {
250
+ acc[field.name] = source[field.name];
251
+ } else {
252
+ delete acc[field.name];
253
+ }
254
+ return acc;
255
+ }
256
+ if (field.kind === 'group') {
257
+ if (field.name in source) {
258
+ const value = synchronizeData({
259
+ fields: field.fields,
260
+ previous: asDocument(previous[field.name]),
261
+ source: asDocument(source[field.name]),
262
+ target: asDocument(acc[field.name])
263
+ });
264
+ if (!valuesAreEqual(value, acc[field.name])) {
265
+ acc[field.name] = value;
266
+ }
267
+ } else {
268
+ delete acc[field.name];
269
+ }
84
270
  return acc;
85
271
  }
86
- const value = synchronizeShellValue({
272
+ const value = synchronizeContainerValue({
87
273
  field,
88
274
  previousSourceValue: previous[field.name],
89
- sourceValue: nextDoc[field.name],
90
- targetValue: translation[field.name]
275
+ sourceValue: source[field.name],
276
+ targetValue: target[field.name]
91
277
  });
92
- if (!valuesAreEqual(value, translation[field.name])) {
278
+ if (!valuesAreEqual(value, target[field.name])) {
93
279
  acc[field.name] = value;
94
280
  }
95
281
  return acc;
282
+ }, {
283
+ ...target
284
+ });
285
+ const getSynchronizedDataForTranslation = ({ fields, nextDoc, previous, translation })=>{
286
+ const synchronized = synchronizeData({
287
+ fields,
288
+ previous,
289
+ source: nextDoc,
290
+ target: translation
291
+ });
292
+ return fields.reduce((acc, field)=>{
293
+ if (!valuesAreEqual(synchronized[field.name], translation[field.name])) {
294
+ acc[field.name] = synchronized[field.name];
295
+ }
296
+ return acc;
96
297
  }, {});
298
+ };
97
299
  const isConfiguredLanguage = (collection, language)=>Boolean(language && collection.languages.some((configuredLanguage)=>configuredLanguage.code === language));
98
300
  const getDuplicateLanguageIDs = async ({ collection, excludeID, group, language, req })=>{
99
301
  const and = [
@@ -183,12 +385,11 @@ export const createSynchronizedFieldsAfterChangeHook = ({ collection, fields })=
183
385
  if (!group || !currentID) {
184
386
  return doc;
185
387
  }
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
- });
388
+ const changedFields = synchronizedFields.filter((field)=>descriptorChanged({
389
+ field,
390
+ previous,
391
+ source: nextDoc
392
+ }));
192
393
  if (changedFields.length === 0) {
193
394
  return doc;
194
395
  }
@@ -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":"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"}
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 sourceBlockType = field.kind === 'blocks' ? getStringValue(sourceRow.blockType) : undefined\n const previousBlockType =\n field.kind === 'blocks' && previousRow\n ? getStringValue(previousRow.blockType)\n : undefined\n const blockTypeChanged =\n field.kind === 'blocks' &&\n field.synchronizePosition &&\n previousRow !== undefined &&\n sourceBlockType !== previousBlockType\n const row = targetRow && !blockTypeChanged ? { ...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","sourceBlockType","previousBlockType","blockTypeChanged","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 the repeatable structure should follow the source document. This synchronizes row or block order, plus block type and block name for blocks. Descendant fields marked with `custom.multilang.synchronize: true` are still copied, while unmarked nested fields remain localized.
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 the repeatable shell. Descendant fields marked with `custom.multilang.synchronize: true` are copied across translations, while unmarked nested fields stay localized.
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 the repeatable shell aligned. Descendant fields marked with `custom.multilang.synchronize: true` are copied across translations, while unmarked nested fields stay localized.
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.8",
4
4
  "description": "Polylang-style document localization for Payload CMS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,6 +41,7 @@
41
41
  "dev:generate-importmap": "pnpm dev:payload generate:importmap",
42
42
  "dev:generate-types": "pnpm dev:payload generate:types",
43
43
  "dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
44
+ "dev:seed": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload seed",
44
45
  "generate:importmap": "pnpm dev:generate-importmap",
45
46
  "generate:types": "pnpm dev:generate-types",
46
47
  "lint": "eslint",