@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
|
|
28
|
-
|
|
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
|
-
|
|
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.
|
|
45
|
-
blockType: field.
|
|
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
|
|
49
|
-
if (!(field.name in
|
|
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(
|
|
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
|
|
152
|
+
const createSynchronizedRow = (sourceRow, previousRow, targetRow, field)=>{
|
|
55
153
|
const row = targetRow ? {
|
|
56
154
|
...targetRow
|
|
57
155
|
} : {};
|
|
58
|
-
if (field.
|
|
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.
|
|
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
|
-
|
|
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
|
|
177
|
+
const synchronizeContainerValue = ({ field, previousSourceValue, sourceValue, targetValue })=>{
|
|
71
178
|
const targetRows = toArray(targetValue);
|
|
72
|
-
const
|
|
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
|
|
202
|
+
return sourceRows.map((sourceRow, index)=>{
|
|
77
203
|
const previousIndex = previousSourceIndexesByKey.get(getRowKey(sourceRow, index));
|
|
78
|
-
return
|
|
204
|
+
return createSynchronizedRow(sourceRow, previousIndex === undefined ? undefined : previousSourceRows[previousIndex], previousIndex === undefined ? undefined : targetRows[previousIndex], field);
|
|
79
205
|
});
|
|
80
206
|
};
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
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 =
|
|
269
|
+
const value = synchronizeContainerValue({
|
|
87
270
|
field,
|
|
88
271
|
previousSourceValue: previous[field.name],
|
|
89
|
-
sourceValue:
|
|
90
|
-
targetValue:
|
|
272
|
+
sourceValue: source[field.name],
|
|
273
|
+
targetValue: target[field.name]
|
|
91
274
|
});
|
|
92
|
-
if (!valuesAreEqual(value,
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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":"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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 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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"}
|
package/docs/configuration.md
CHANGED
|
@@ -166,7 +166,15 @@ Mark fields that should stay shared across translations directly in the field co
|
|
|
166
166
|
}
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|