@focus-reactive/payload-plugin-translator 0.7.1 → 0.7.2

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.
@@ -11,13 +11,15 @@ export interface BlockLike {
11
11
  }
12
12
  /**
13
13
  * A tab as this layer needs to read it: an optional `name` (present → it opens a data boundary;
14
- * absent → it flattens into the parent scope) and its child `fields`. Payload's `Tab`
15
- * (`NamedTab | UnnamedTab`) is assignable to this.
14
+ * absent → it flattens into the parent scope), its child `fields`, and `localized` (a *named* tab
15
+ * may be `localized`, partitioning its subtree per locale — Payload's sanitizer propagates it the
16
+ * same as a group). Payload's `Tab` (`NamedTab | UnnamedTab`) is assignable to this.
16
17
  *
17
18
  * @public
18
19
  */
19
20
  export interface TabLike {
20
21
  name?: string;
22
+ localized?: boolean;
21
23
  fields: FieldLike[];
22
24
  }
23
25
  /**
@@ -5,9 +5,9 @@ const asObject = (value)=>isObject(value) ? value : {};
5
5
  /**
6
6
  * Reconcile walker: produces the full document shape from source + target, with target
7
7
  * priority (source fills empty target slots). Iteration is driven by `source`; a field with
8
- * no source value is dropped, `id` is stripped from array/block elements (Postgres rejects it
9
- * on update), `blockType` is preserved, and non-object array items / unknown blocks pass
10
- * through unchanged.
8
+ * no source value is dropped, `blockType` is preserved, non-object array items / unknown blocks
9
+ * pass through unchanged, and array/block `id` is kept for shared rows but stripped for per-locale
10
+ * rows (see {@link Cursor.sharedRow} — the data-loss-critical bit).
11
11
  *
12
12
  * Array/block elements are paired with their target counterpart by `id`, not by position (see
13
13
  * {@link matchElementById}); output order always follows `source`.
@@ -15,9 +15,12 @@ const asObject = (value)=>isObject(value) ? value : {};
15
15
  enterObject (field, cursor) {
16
16
  const sourceValue = cursor.source[field.name];
17
17
  if (!isObject(sourceValue)) return "skip"; // undefined / non-object source → drop the field
18
+ // A localized group or named tab partitions its whole subtree per locale → descendants are
19
+ // per-locale rows (Payload's sanitizer propagates `tab.localized` the same as a group).
18
20
  return {
19
21
  source: sourceValue,
20
- target: asObject(cursor.target[field.name])
22
+ target: asObject(cursor.target[field.name]),
23
+ sharedRow: cursor.sharedRow && !field.localized
21
24
  };
22
25
  },
23
26
  enterList (field, cursor) {
@@ -26,6 +29,8 @@ const asObject = (value)=>isObject(value) ? value : {};
26
29
  const targetValue = cursor.target[field.name];
27
30
  const targetArr = Array.isArray(targetValue) ? targetValue : [];
28
31
  const isBlocks = field.type === "blocks";
32
+ // Elements are shared rows only when this container AND every ancestor is non-localized.
33
+ const childShared = cursor.sharedRow && !field.localized;
29
34
  const children = [];
30
35
  sourceValue.forEach((item, index)=>{
31
36
  if (!isObject(item)) return; // non-object element → passthrough (rebuilt in combine)
@@ -35,7 +40,8 @@ const asObject = (value)=>isObject(value) ? value : {};
35
40
  children.push({
36
41
  cursor: {
37
42
  source: item,
38
- target: matchElementById(targetArr, item, isBlocks)
43
+ target: matchElementById(targetArr, item, isBlocks),
44
+ sharedRow: childShared
39
45
  },
40
46
  fields,
41
47
  key: index
@@ -58,13 +64,29 @@ const asObject = (value)=>isObject(value) ? value : {};
58
64
  child.key,
59
65
  child.out
60
66
  ]));
61
- return sourceArr.map((item, index)=>byIndex.has(index) ? byIndex.get(index) : item);
67
+ // Passthrough items (unknown blockType / unresolved fields) never reach the element branch's
68
+ // id guard, so apply the same rule here: strip a per-locale row's id to avoid an insert
69
+ // collision. Elements are shared only when this container and every ancestor is non-localized.
70
+ const childShared = cursor.sharedRow && !container.field.localized;
71
+ return sourceArr.map((item, index)=>{
72
+ if (byIndex.has(index)) return byIndex.get(index);
73
+ if (!childShared && isObject(item) && item.id != null) {
74
+ const { id: _dropped, ...rest } = item;
75
+ return rest;
76
+ }
77
+ return item;
78
+ });
62
79
  }
63
80
  const result = {};
64
81
  for (const child of children)result[child.key] = child.out;
65
- // Block elements keep their blockType; id is intentionally stripped (Postgres rejects it on update).
66
- if (container.kind === "element" && container.field.type === "blocks") {
67
- result.blockType = cursor.source.blockType;
82
+ if (container.kind === "element") {
83
+ // Block elements keep their blockType.
84
+ if (container.field.type === "blocks") result.blockType = cursor.source.blockType;
85
+ // Keep `id` only for a shared (non-localized, no localized ancestor) row so Payload updates it
86
+ // in place; stripping it there makes Payload recreate the row and wipe other locales. Under a
87
+ // localized container/ancestor the rows are per-locale, so the id is dropped to avoid an
88
+ // insert collision with the source locale's row. Guard against writing `id: undefined`.
89
+ if (cursor.sharedRow && cursor.source.id != null) result.id = cursor.source.id;
68
90
  }
69
91
  return result;
70
92
  }
@@ -88,7 +110,8 @@ const asObject = (value)=>isObject(value) ? value : {};
88
110
  */ reconcile(sourceData, targetData) {
89
111
  const root = {
90
112
  source: sourceData,
91
- target: targetData ?? {}
113
+ target: targetData ?? {},
114
+ sharedRow: true
92
115
  };
93
116
  return walkFields(this.schema, root, reconcileWalker) ?? {};
94
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@focus-reactive/payload-plugin-translator",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Translation plugin for Payload CMS 3.x. Automatically translate your localized content using any translation provider.",
5
5
  "type": "module",
6
6
  "license": "MIT",