gt-sanity 2.0.21 → 2.1.1

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.
Files changed (44) hide show
  1. package/dist/index.cjs +1220 -763
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +113 -1
  4. package/dist/index.d.ts +113 -1
  5. package/dist/index.js +1211 -754
  6. package/dist/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/adapter/core.ts +27 -2
  9. package/src/adapter/types.ts +9 -0
  10. package/src/components/TranslationsProvider.tsx +92 -6
  11. package/src/components/page/TranslationsTable.tsx +5 -3
  12. package/src/components/shared/SingleDocumentView.tsx +5 -3
  13. package/src/components/tab/TranslationView.tsx +24 -14
  14. package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +1 -1
  15. package/src/configuration/baseFieldLevelConfig.ts +1 -1
  16. package/src/configuration/internationalizedArrayConfig/internationalizedArrayPatch.ts +59 -0
  17. package/src/index.ts +114 -58
  18. package/src/schema/InternationalizedArrayInput.tsx +278 -0
  19. package/src/schema/__tests__/createInternationalizedArrayTypes.test.ts +169 -0
  20. package/src/schema/createInternationalizedArrayTypes.ts +209 -0
  21. package/src/schema/types.ts +84 -0
  22. package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +3 -3
  23. package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +1 -1
  24. package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +1 -1
  25. package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +1 -1
  26. package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +2 -2
  27. package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +4 -2
  28. package/src/serialization/__tests__/global.setup.ts +6 -0
  29. package/src/serialization/__tests__/helpers.ts +2 -1
  30. package/src/serialization/internationalizedArray/__tests__/internationalizedArray.test.ts +265 -0
  31. package/src/serialization/internationalizedArray/__tests__/serializeRoundTrip.test.ts +83 -0
  32. package/src/serialization/internationalizedArray/collapse.ts +52 -0
  33. package/src/serialization/internationalizedArray/detect.ts +81 -0
  34. package/src/serialization/internationalizedArray/merge.ts +149 -0
  35. package/src/serialization/types.ts +5 -1
  36. package/src/translation/__tests__/strategy.test.ts +47 -0
  37. package/src/translation/importDocument.ts +9 -5
  38. package/src/translation/strategy.ts +92 -0
  39. package/src/translation/uploadFiles.ts +1 -1
  40. package/src/types.ts +1 -1
  41. package/src/utils/__tests__/batchProcessor.test.ts +61 -2
  42. package/src/utils/batchProcessor.ts +11 -6
  43. package/src/utils/serialize.ts +24 -7
  44. package/src/serialization/index.ts +0 -16
package/dist/index.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var documentInternationalization = require("@sanity/document-internationalization"), jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), theme$1 = require("@sanity/ui/theme"), o = require("react"), sanity = require("sanity"), generaltranslation = require("generaltranslation"), internal = require("generaltranslation/internal"), blockTools = require("@portabletext/block-tools"), schema = require("@sanity/schema"), toHtml = require("@portabletext/to-html"), merge = require("lodash.merge"), jsonpathPlus = require("jsonpath-plus"), JSONPointer = require("jsonpointer"), mutator = require("@sanity/mutator"), icons = require("@sanity/icons"), router = require("sanity/router");
3
+ var generaltranslation = require("generaltranslation"), internal = require("generaltranslation/internal"), sanity = require("sanity"), router = require("sanity/router"), jsxRuntime = require("react/jsx-runtime"), o = require("react"), icons = require("@sanity/icons"), ui = require("@sanity/ui"), theme$1 = require("@sanity/ui/theme"), mutator = require("@sanity/mutator"), jsonpathPlus = require("jsonpath-plus"), JSONPointer = require("jsonpointer"), blockTools = require("@portabletext/block-tools"), schema = require("@sanity/schema"), toHtml = require("@portabletext/to-html"), merge = require("lodash.merge"), documentInternationalization = require("@sanity/document-internationalization");
4
4
  function _interopDefaultCompat(e) {
5
5
  return e && typeof e == "object" && "default" in e ? e : { default: e };
6
6
  }
7
- var o__default = /* @__PURE__ */ _interopDefaultCompat(o), merge__default = /* @__PURE__ */ _interopDefaultCompat(merge), JSONPointer__default = /* @__PURE__ */ _interopDefaultCompat(JSONPointer);
7
+ var o__default = /* @__PURE__ */ _interopDefaultCompat(o), JSONPointer__default = /* @__PURE__ */ _interopDefaultCompat(JSONPointer), merge__default = /* @__PURE__ */ _interopDefaultCompat(merge);
8
8
  const useClient = () => sanity.useClient({ apiVersion: "2025-09-15" });
9
9
  function useSecrets(id) {
10
10
  const [loading, setLoading] = o.useState(!0), [secrets, setSecrets] = o.useState(null), client = useClient();
@@ -42,9 +42,12 @@ class GTConfig {
42
42
  additionalSerializers;
43
43
  additionalDeserializers;
44
44
  additionalBlockDeserializers;
45
+ translationLevel;
46
+ fieldLevelDocuments;
47
+ fieldLevelTypePrefix;
45
48
  static instance;
46
- constructor(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = []) {
47
- this.secretsNamespace = secretsNamespace, this.languageField = languageField, this.sourceLocale = sourceLocale, this.locales = locales, this.singletons = singletons, this.singletonMapping = singletonMapping, this.ignoreFields = ignoreFields, this.dedupeFields = dedupeFields, this.skipFields = skipFields, this.translateDocuments = translateDocuments, this.additionalStopTypes = additionalStopTypes, this.additionalSerializers = additionalSerializers, this.additionalDeserializers = additionalDeserializers, this.additionalBlockDeserializers = additionalBlockDeserializers;
49
+ constructor(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = [], translationLevel = "document", fieldLevelDocuments = [], fieldLevelTypePrefix = "internationalizedArray") {
50
+ this.secretsNamespace = secretsNamespace, this.languageField = languageField, this.sourceLocale = sourceLocale, this.locales = locales, this.singletons = singletons, this.singletonMapping = singletonMapping, this.ignoreFields = ignoreFields, this.dedupeFields = dedupeFields, this.skipFields = skipFields, this.translateDocuments = translateDocuments, this.additionalStopTypes = additionalStopTypes, this.additionalSerializers = additionalSerializers, this.additionalDeserializers = additionalDeserializers, this.additionalBlockDeserializers = additionalBlockDeserializers, this.translationLevel = translationLevel, this.fieldLevelDocuments = fieldLevelDocuments, this.fieldLevelTypePrefix = fieldLevelTypePrefix;
48
51
  }
49
52
  static getInstance() {
50
53
  return this.instance || (this.instance = new GTConfig(
@@ -64,8 +67,8 @@ class GTConfig {
64
67
  []
65
68
  )), this.instance;
66
69
  }
67
- init(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = []) {
68
- this.secretsNamespace = secretsNamespace, this.languageField = languageField, this.sourceLocale = sourceLocale, this.locales = locales, this.singletons = singletons, this.singletonMapping = singletonMapping, this.ignoreFields = ignoreFields, this.dedupeFields = dedupeFields, this.skipFields = skipFields, this.translateDocuments = translateDocuments, this.additionalStopTypes = additionalStopTypes, this.additionalSerializers = additionalSerializers, this.additionalDeserializers = additionalDeserializers, this.additionalBlockDeserializers = additionalBlockDeserializers;
70
+ init(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = [], translationLevel = "document", fieldLevelDocuments = [], fieldLevelTypePrefix = "internationalizedArray") {
71
+ this.secretsNamespace = secretsNamespace, this.languageField = languageField, this.sourceLocale = sourceLocale, this.locales = locales, this.singletons = singletons, this.singletonMapping = singletonMapping, this.ignoreFields = ignoreFields, this.dedupeFields = dedupeFields, this.skipFields = skipFields, this.translateDocuments = translateDocuments, this.additionalStopTypes = additionalStopTypes, this.additionalSerializers = additionalSerializers, this.additionalDeserializers = additionalDeserializers, this.additionalBlockDeserializers = additionalBlockDeserializers, this.translationLevel = translationLevel, this.fieldLevelDocuments = fieldLevelDocuments, this.fieldLevelTypePrefix = fieldLevelTypePrefix;
69
72
  }
70
73
  getSecretsNamespace() {
71
74
  return this.secretsNamespace;
@@ -109,6 +112,15 @@ class GTConfig {
109
112
  getAdditionalBlockDeserializers() {
110
113
  return this.additionalBlockDeserializers;
111
114
  }
115
+ getTranslationLevel() {
116
+ return this.translationLevel;
117
+ }
118
+ getFieldLevelDocuments() {
119
+ return this.fieldLevelDocuments;
120
+ }
121
+ getFieldLevelTypePrefix() {
122
+ return this.fieldLevelTypePrefix;
123
+ }
112
124
  }
113
125
  const pluginConfig = GTConfig.getInstance(), theme = theme$1.buildTheme(), BaseTranslationWrapper = ({
114
126
  children,
@@ -122,21 +134,21 @@ const pluginConfig = GTConfig.getInstance(), theme = theme$1.buildTheme(), BaseT
122
134
  !loadingSecrets && secrets && children
123
135
  ] });
124
136
  return /* @__PURE__ */ jsxRuntime.jsx(ui.ThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(ui.ToastProvider, { paddingY: 7, children: showContainer ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding, children: content }) : content }) });
125
- }, isRecord$4 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), reconcileArray = (origArray, translatedArray) => {
137
+ }, isRecord$5 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), reconcileArray = (origArray, translatedArray) => {
126
138
  if (translatedArray && translatedArray.some((el) => typeof el == "string"))
127
139
  return translatedArray;
128
140
  const combined = JSON.parse(JSON.stringify(origArray));
129
141
  return translatedArray.forEach((translatedItem) => {
130
- if (!isRecord$4(translatedItem) || !translatedItem._key)
142
+ if (!isRecord$5(translatedItem) || !translatedItem._key)
131
143
  return;
132
144
  const foundBlockIdx = origArray.findIndex(
133
- (origBlock) => isRecord$4(origBlock) && origBlock._key === translatedItem._key
145
+ (origBlock) => isRecord$5(origBlock) && origBlock._key === translatedItem._key
134
146
  );
135
147
  foundBlockIdx < 0 ? console.warn(
136
148
  `This block no longer exists on the original document. Was it removed? ${JSON.stringify(
137
149
  translatedItem
138
150
  )}`
139
- ) : isRecord$4(origArray[foundBlockIdx]) && (origArray[foundBlockIdx]._type === "block" || origArray[foundBlockIdx]._type === "span") ? combined[foundBlockIdx] = translatedItem : isRecord$4(origArray[foundBlockIdx]) && (combined[foundBlockIdx] = reconcileObject(
151
+ ) : isRecord$5(origArray[foundBlockIdx]) && (origArray[foundBlockIdx]._type === "block" || origArray[foundBlockIdx]._type === "span") ? combined[foundBlockIdx] = translatedItem : isRecord$5(origArray[foundBlockIdx]) && (combined[foundBlockIdx] = reconcileObject(
140
152
  origArray[foundBlockIdx],
141
153
  translatedItem
142
154
  ));
@@ -149,8 +161,8 @@ const pluginConfig = GTConfig.getInstance(), theme = theme$1.buildTheme(), BaseT
149
161
  !value || key[0] === "_" || (typeof value == "string" ? updatedObj[key] = value : Array.isArray(value) ? updatedObj[key] = reconcileArray(
150
162
  Array.isArray(origObject[key]) ? origObject[key] : [],
151
163
  value
152
- ) : isRecord$4(value) && (updatedObj[key] = reconcileObject(
153
- isRecord$4(origObject[key]) ? origObject[key] : {},
164
+ ) : isRecord$5(value) && (updatedObj[key] = reconcileObject(
165
+ isRecord$5(origObject[key]) ? origObject[key] : {},
154
166
  value
155
167
  )));
156
168
  }), updatedObj;
@@ -171,7 +183,7 @@ const pluginConfig = GTConfig.getInstance(), theme = theme$1.buildTheme(), BaseT
171
183
  origVal ?? [],
172
184
  translatedVal
173
185
  ) : typeof translatedVal == "object" && Object.keys(translatedVal).length && (valToPatch = reconcileObject(
174
- isRecord$4(origVal) ? origVal : {},
186
+ isRecord$5(origVal) ? origVal : {},
175
187
  translatedVal
176
188
  ));
177
189
  const destinationPath = [
@@ -190,215 +202,701 @@ const pluginConfig = GTConfig.getInstance(), theme = theme$1.buildTheme(), BaseT
190
202
  documentLevelMerge,
191
203
  reconcileArray,
192
204
  reconcileObject
193
- }, defaultSchema = schema.Schema.compile({
194
- name: "default",
195
- types: [
196
- {
197
- type: "object",
198
- name: "default",
199
- fields: [
200
- {
201
- name: "block",
202
- type: "array",
203
- of: [{ type: "block" }]
204
- }
205
- ]
206
- }
207
- ]
208
- }), blockContentType = defaultSchema.get("default").fields.find((field) => field.name === "block").type, preprocess = (html) => {
209
- const intermediateBlocks = blockTools.htmlToBlocks(
210
- `<p>${html}</p>`,
211
- blockContentType
212
- );
213
- if (!intermediateBlocks.length)
214
- throw new Error(`Error parsing string '${html}'`);
215
- return intermediateBlocks[0].children[0].text;
216
205
  };
217
- function attachGTData(html, data, type) {
218
- const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
219
- if (!firstElement)
220
- return html;
221
- const encodedData = encode(JSON.stringify({ [type]: data }));
222
- return firstElement.setAttribute("data-gt-internal", encodedData), firstElement.outerHTML;
206
+ function getPublishedId(documentId) {
207
+ return documentId.startsWith("drafts.") ? documentId.slice(7) : documentId;
223
208
  }
224
- function detachGTData(html) {
225
- const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
226
- if (!firstElement)
227
- return { html };
228
- const encodedData = firstElement.getAttribute("data-gt-internal");
229
- let extractedData;
230
- if (encodedData)
231
- try {
232
- const decodedData = decode(encodedData);
233
- extractedData = JSON.parse(decodedData), firstElement.removeAttribute("data-gt-internal");
234
- } catch (error) {
235
- console.warn("Failed to decode GT internal data:", error);
236
- }
237
- return {
238
- html: firstElement.outerHTML,
239
- data: extractedData
240
- };
209
+ function getDocumentPublishedId(document2) {
210
+ return getPublishedId(document2._id);
241
211
  }
242
- function encode(data) {
243
- const bytes = new TextEncoder().encode(data);
244
- let binary = "";
245
- for (let i = 0; i < bytes.length; i++)
246
- binary += String.fromCharCode(bytes[i]);
247
- return btoa(binary);
212
+ function dedupeDocumentsPreferDraft(documents) {
213
+ const byPublishedId = /* @__PURE__ */ new Map();
214
+ for (const document2 of documents) {
215
+ const publishedId = getDocumentPublishedId(document2);
216
+ (!byPublishedId.get(publishedId) || document2._id.startsWith("drafts.")) && byPublishedId.set(publishedId, document2);
217
+ }
218
+ return Array.from(byPublishedId.values());
248
219
  }
249
- function decode(base64) {
250
- const binary = atob(base64), bytes = new Uint8Array(binary.length);
251
- for (let i = 0; i < binary.length; i++)
252
- bytes[i] = binary.charCodeAt(i);
253
- return new TextDecoder().decode(bytes);
220
+ function createTranslationStatusKey(branchId, documentId, versionId, localeId) {
221
+ const publishedId = getPublishedId(documentId);
222
+ return branchId ? `${branchId}:${publishedId}:${versionId}:${localeId}` : `${publishedId}:${versionId}:${localeId}`;
254
223
  }
255
- const isRecord$3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), defaultStopTypes = [
256
- "reference",
257
- "date",
258
- "datetime",
259
- "file",
260
- "geopoint",
261
- "image",
262
- "number",
263
- "crop",
264
- "hotspot",
265
- "boolean",
266
- "url",
267
- "color",
268
- "code"
269
- ], defaultMarks = {}, defaultPortableTextBlockStyles = {
270
- normal: ({ value, children }) => `<p id="${value._key}">${children}</p>`,
271
- blockquote: ({ value, children }) => `<blockquote id="${value._key}">${children}</blockquote>`,
272
- h1: ({ value, children }) => `<h1 id="${value._key}">${children}</h1>`,
273
- h2: ({ value, children }) => `<h2 id="${value._key}">${children}</h2>`,
274
- h3: ({ value, children }) => `<h3 id="${value._key}">${children}</h3>`,
275
- h4: ({ value, children }) => `<h4 id="${value._key}">${children}</h4>`,
276
- h5: ({ value, children }) => `<h5 id="${value._key}">${children}</h5>`,
277
- h6: ({ value, children }) => `<h6 id="${value._key}">${children}</h6>`
278
- }, defaultLists = {
279
- number: ({ value, children }) => `<ol id="${value._key.replace("-parent", "")}">${children}</ol>`,
280
- bullet: ({ value, children }) => `<ul id="${value._key.replace("-parent", "")}">${children}</ul>`
281
- }, defaultListItem = ({
282
- value,
283
- children
284
- }) => {
285
- const { _key, level } = value;
286
- return `<li id="${(_key || "").replace("-parent", "")}" data-level="${level}">${children}</li>`;
287
- }, unknownBlockFunc = ({ value, children }) => `<p id="${value._key}" data-type="unknown-block-style" data-style="${value.style}">${children}</p>`, customSerializers = {
288
- unknownType: ({ value }) => `<div class="${value._type}"></div>`,
289
- types: {},
290
- marks: defaultMarks,
291
- block: defaultPortableTextBlockStyles,
292
- list: defaultLists,
293
- listItem: defaultListItem,
294
- unknownBlockStyle: unknownBlockFunc
295
- }, customDeserializers = { types: {} }, customBlockDeserializers = [
296
- // handle marks with data-gt-internal
297
- {
298
- deserialize(node2, next2) {
299
- if (node2.nodeType !== 1)
300
- return;
301
- const el = node2;
302
- if (!el.hasChildNodes() || !el.getAttribute("data-gt-internal"))
303
- return;
304
- const { html, data } = detachGTData(el.outerHTML), block = blockTools.htmlToBlocks(html, blockContentType)[0], children = next2(el.childNodes);
305
- let markDefs = [];
306
- return "markDefs" in block && (markDefs = block.markDefs ?? []), data?.markDef && markDefs.push(data.markDef), Array.isArray(children) && children.forEach((child) => {
307
- if (!isRecord$3(child))
308
- return;
309
- const marks = Array.isArray(child.marks) ? child.marks : [];
310
- child.marks = data?.markDef?._key ? [...marks, data.markDef._key] : marks;
311
- }), {
312
- ...block,
313
- markDefs,
314
- children
315
- };
316
- }
317
- },
318
- //handle undeclared styles
319
- {
320
- deserialize(node2, next2) {
321
- if (node2.nodeType !== 1)
322
- return;
323
- const el = node2;
324
- if (!el.hasChildNodes() || el.getAttribute("data-type") !== "unknown-block-style")
325
- return;
326
- const style = el.getAttribute("data-style") ?? "";
327
- return {
328
- ...blockTools.htmlToBlocks(el.outerHTML, blockContentType)[0],
329
- style,
330
- children: next2(el.childNodes)
331
- };
332
- }
333
- },
334
- //handle list items
335
- {
336
- deserialize(node2, next2) {
337
- if (node2.nodeType !== 1)
338
- return;
339
- const el = node2;
340
- if (!el.hasChildNodes() || el.tagName.toLowerCase() !== "li")
341
- return;
342
- const tagsToStyle = {
343
- ul: "bullet",
344
- ol: "number"
345
- }, parent = el.parentNode;
346
- if (!parent || !parent.tagName)
347
- return;
348
- const listItem = tagsToStyle[parent.tagName.toLowerCase()];
349
- if (!listItem)
350
- return;
351
- const level = el.getAttribute("data-level") && parseInt(el.getAttribute("data-level") || "0", 10), _key = el.id;
352
- let block = blockTools.htmlToBlocks(parent.outerHTML, blockContentType)[0];
353
- const customStyle = el.children?.[0]?.getAttribute("data-style");
354
- if (new RegExp(/<("[^"]*"|'[^']*'|[^'">])*>/).test(el.innerHTML)) {
355
- const newBlock = blockTools.htmlToBlocks(el.innerHTML, blockContentType)[0];
356
- if (newBlock && (block = {
357
- ...block,
358
- ...newBlock,
359
- // @ts-ignore
360
- style: customStyle ?? newBlock.style
361
- }, customStyle))
362
- return block;
224
+ function createStableTranslationKey(branchId, documentId, localeId) {
225
+ const publishedId = getPublishedId(documentId);
226
+ return branchId ? `${branchId}:${publishedId}:${localeId}` : `${publishedId}:${localeId}`;
227
+ }
228
+ const findLatestDraft = (documentId, client) => {
229
+ const publishedId = getPublishedId(documentId), query = "*[_id == $id || _id == $draftId]", params = { id: publishedId, draftId: `drafts.${publishedId}` };
230
+ return client.fetch(query, params).then(
231
+ (docs) => docs.find((doc) => doc._id.startsWith("drafts.")) ?? docs[0]
232
+ );
233
+ }, findDocumentAtRevision = async (documentId, rev, client) => {
234
+ const baseUrl = `/data/history/${client.config().dataset}/documents/${documentId}?revision=${rev}`, url = client.getUrl(baseUrl);
235
+ return await fetch(url, { credentials: "include" }).then((req) => req.json()).then((req) => req.documents && req.documents.length ? req.documents[0] : null);
236
+ };
237
+ function forEachMatchingField(documentId, document2, fields, callback) {
238
+ const applicable = fields.filter(
239
+ (field) => field.documentId === documentId || field.documentId === void 0 || field.documentId === null
240
+ );
241
+ for (const entry of applicable)
242
+ if (entry.fields)
243
+ for (const field of entry.fields) {
244
+ const { property, type } = field;
245
+ try {
246
+ const results = jsonpathPlus.JSONPath({
247
+ json: document2,
248
+ path: property,
249
+ resultType: "all",
250
+ flatten: !0,
251
+ wrap: !0
252
+ });
253
+ results && results.length > 0 && results.forEach(
254
+ (result) => {
255
+ type !== void 0 ? typeof result.value == "object" && result.value !== null && !Array.isArray(result.value) && result.value._type === type && callback(result) : callback(result);
256
+ }
257
+ );
258
+ } catch (error) {
259
+ console.warn(`Invalid JSONPath: ${property}`, error);
260
+ }
363
261
  }
364
- return {
365
- ...block,
366
- level,
367
- _key,
368
- listItem,
369
- children: next2(el.childNodes)
370
- };
371
- }
372
- }
373
- ], META_FIELDS$1 = ["_key", "_type", "_id"], isRecord$2 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), languageObjectFieldFilter = (obj, baseLang) => {
374
- const filterToLangField = (childObj) => {
375
- const filteredObj = {};
376
- return filteredObj[baseLang] = childObj[baseLang], META_FIELDS$1.forEach((field) => {
377
- childObj[field] && (filteredObj[field] = childObj[field]);
378
- }), filteredObj;
379
- }, findBaseLang = (childObj) => {
380
- const filteredObj = {};
381
- META_FIELDS$1.forEach((field) => {
382
- childObj[field] && (filteredObj[field] = childObj[field]);
383
- });
384
- for (const key in childObj)
385
- if (childObj.hasOwnProperty(key)) {
386
- const value = childObj[key];
387
- if (isRecord$2(value) && Object.hasOwn(value, baseLang))
388
- filteredObj[key] = filterToLangField(value);
389
- else if (Array.isArray(value) && value.length && typeof value[0] == "object") {
390
- const validLangObjects = value.reduce(
391
- (validArr, objInArray) => {
392
- if (!isRecord$2(objInArray))
393
- return validArr;
394
- if (objInArray._type === "block")
395
- validArr.push(objInArray);
396
- else if (Object.hasOwn(objInArray, baseLang))
397
- validArr.push(filterToLangField(objInArray));
398
- else {
399
- const filtered = findBaseLang(objInArray);
400
- Object.keys(filtered).filter(
401
- (objInArrayKey) => META_FIELDS$1.indexOf(objInArrayKey) === -1
262
+ }
263
+ function deleteMatchingFields(documentId, document2, fields) {
264
+ const arrayRemovals = [];
265
+ forEachMatchingField(documentId, document2, fields, (result) => {
266
+ Array.isArray(result.parent) ? arrayRemovals.push({
267
+ parent: result.parent,
268
+ index: Number(result.parentProperty)
269
+ }) : delete result.parent[result.parentProperty];
270
+ }), arrayRemovals.sort((a, b) => b.index - a.index);
271
+ for (const { parent, index } of arrayRemovals)
272
+ parent.splice(index, 1);
273
+ }
274
+ function applyDocuments(documentId, sourceDocument, targetDocument, ignore, skip = [], dedupe = [], localeId) {
275
+ const mergedDocument = JSON.parse(JSON.stringify(sourceDocument)), clonedTarget = JSON.parse(JSON.stringify(targetDocument));
276
+ for (const [key, value] of Object.entries(clonedTarget))
277
+ mergedDocument[key] = value;
278
+ return forEachMatchingField(documentId, sourceDocument, ignore, (result) => {
279
+ JSONPointer__default.default.set(mergedDocument, result.pointer, result.value);
280
+ }), forEachMatchingField(documentId, sourceDocument, dedupe, (result) => {
281
+ JSONPointer__default.default.set(
282
+ mergedDocument,
283
+ result.pointer,
284
+ dedupeFieldValue(result.value, localeId)
285
+ );
286
+ }), deleteMatchingFields(documentId, mergedDocument, skip), mergedDocument;
287
+ }
288
+ function dedupeFieldValue(value, localeId) {
289
+ if (!localeId) return value;
290
+ if (typeof value == "string")
291
+ return appendLocaleSuffix(value, localeId);
292
+ if (value && typeof value == "object" && !Array.isArray(value) && typeof value.current == "string") {
293
+ const slug = value;
294
+ return {
295
+ ...slug,
296
+ current: appendLocaleSuffix(slug.current, localeId)
297
+ };
298
+ }
299
+ return value;
300
+ }
301
+ function appendLocaleSuffix(value, localeId) {
302
+ if (!value) return value;
303
+ const suffix = createLocaleSuffix(localeId);
304
+ return !suffix || value.endsWith(suffix) ? value : `${value}${suffix}`;
305
+ }
306
+ function createLocaleSuffix(localeId) {
307
+ const normalized = localeId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
308
+ return normalized ? `-${normalized}` : "";
309
+ }
310
+ function randomKey() {
311
+ return Math.random().toString(36).slice(2, 10);
312
+ }
313
+ async function createI18nDocAndPatchMetadata(sourceDocument, translatedDoc, localeId, client, translationMetadata, sourceDocumentId, languageField = "language") {
314
+ const publishedSourceDocumentId = getPublishedId(sourceDocumentId);
315
+ translatedDoc[languageField] = localeId;
316
+ const existingLocaleKey = translationMetadata.translations.find(
317
+ (translation) => translation.language === localeId
318
+ ), operation = existingLocaleKey ? "replace" : "after", location = existingLocaleKey ? `translations[language == "${localeId}"]` : "translations[-1]", { _updatedAt, _createdAt, ...rest } = translatedDoc, appliedDocument = applyDocuments(
319
+ publishedSourceDocumentId,
320
+ sourceDocument,
321
+ rest,
322
+ pluginConfig.getIgnoreFields(),
323
+ pluginConfig.getSkipFields(),
324
+ pluginConfig.getDedupeFields(),
325
+ localeId
326
+ ), isSingleton = pluginConfig.getSingletons().includes(publishedSourceDocumentId);
327
+ let createDocumentPromise;
328
+ if (isSingleton) {
329
+ const translatedDocId = pluginConfig.getSingletonMapping()(
330
+ publishedSourceDocumentId,
331
+ localeId
332
+ );
333
+ createDocumentPromise = client.create({
334
+ ...appliedDocument,
335
+ _type: rest._type,
336
+ _id: `drafts.${translatedDocId}`
337
+ });
338
+ } else
339
+ createDocumentPromise = client.create({
340
+ ...appliedDocument,
341
+ _type: rest._type,
342
+ _id: "drafts."
343
+ });
344
+ const doc = await createDocumentPromise, _ref = getPublishedId(doc._id);
345
+ await client.transaction().patch(
346
+ translationMetadata._id,
347
+ (p) => p.insert(operation, location, [
348
+ {
349
+ _key: randomKey(),
350
+ language: localeId,
351
+ _type: "internationalizedArrayReferenceValue",
352
+ value: {
353
+ _type: "reference",
354
+ _ref,
355
+ _weak: !0,
356
+ _strengthenOnPublish: {
357
+ type: doc._type
358
+ }
359
+ }
360
+ }
361
+ ])
362
+ ).commit();
363
+ }
364
+ const getOrCreateTranslationMetadata = async (documentId, baseDocument, client, baseLanguage) => {
365
+ const publishedId = getPublishedId(documentId), existingMetadata = await client.fetch(
366
+ `*[
367
+ _type == 'translation.metadata' &&
368
+ translations[language == $baseLanguage][0].value._ref == $id
369
+ ][0]`,
370
+ { baseLanguage, id: publishedId }
371
+ );
372
+ if (existingMetadata)
373
+ return existingMetadata;
374
+ const baseLangEntry = {
375
+ _key: randomKey(),
376
+ language: baseLanguage,
377
+ _type: "internationalizedArrayReferenceValue",
378
+ value: {
379
+ _type: "reference",
380
+ _ref: getPublishedId(baseDocument._id)
381
+ }
382
+ };
383
+ baseDocument._id.startsWith("drafts.") && (baseLangEntry.value = {
384
+ ...baseLangEntry.value,
385
+ _weak: !0,
386
+ //this should reflect doc i18n config when this
387
+ //plugin is able to take that as a config option
388
+ _strengthenOnPublish: {
389
+ type: baseDocument._type
390
+ }
391
+ });
392
+ try {
393
+ return await client.createIfNotExists({
394
+ _id: `translation.metadata.${publishedId}`,
395
+ _type: "translation.metadata",
396
+ translations: [baseLangEntry]
397
+ });
398
+ } catch (error) {
399
+ const metadata = await client.fetch(
400
+ `*[
401
+ _type == 'translation.metadata' &&
402
+ translations[language == $baseLanguage][0].value._ref == $id
403
+ ][0]`,
404
+ { baseLanguage, id: publishedId }
405
+ );
406
+ if (metadata)
407
+ return metadata;
408
+ throw error;
409
+ }
410
+ }, SYSTEM_FIELDS = ["_id", "_rev", "_updatedAt", "language"], isSystemField = (field) => SYSTEM_FIELDS.includes(field);
411
+ async function patchI18nDoc(sourceDocumentId, i18nDocId, sourceDocument, mergedDocument, translatedFields, client, existingDocument) {
412
+ const cleanedMerge = {};
413
+ Object.entries(mergedDocument).forEach(([key, value]) => {
414
+ key in translatedFields && //don't overwrite any existing system values on the i18n doc
415
+ !isSystemField(key) && (cleanedMerge[key] = value);
416
+ });
417
+ const cleanedSourceDocument = {};
418
+ Object.entries(sourceDocument).forEach(([key, value]) => {
419
+ isSystemField(key) || (cleanedSourceDocument[key] = value);
420
+ });
421
+ const appliedDocument = applyDocuments(
422
+ sourceDocumentId,
423
+ cleanedSourceDocument,
424
+ cleanedMerge,
425
+ pluginConfig.getIgnoreFields(),
426
+ pluginConfig.getSkipFields()
427
+ ), dedupeFields = pluginConfig.getDedupeFields();
428
+ deleteMatchingFields(sourceDocumentId, appliedDocument, dedupeFields), existingDocument && forEachMatchingField(
429
+ sourceDocumentId,
430
+ existingDocument,
431
+ dedupeFields,
432
+ (result) => {
433
+ JSONPointer__default.default.set(appliedDocument, result.pointer, result.value);
434
+ }
435
+ ), await client.patch(i18nDocId, { set: appliedDocument }).commit();
436
+ }
437
+ const documentLevelPatch = async (docInfo, translatedFields, localeId, client, languageField = "language", mergeWithTargetLocale = !1) => {
438
+ const baseLanguage = pluginConfig.getSourceLocale();
439
+ let baseDoc = null, i18nDoc = null;
440
+ docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
441
+ docInfo.documentId,
442
+ docInfo.versionId,
443
+ client
444
+ )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
445
+ const i18nDocId = (await getOrCreateTranslationMetadata(
446
+ docInfo.documentId,
447
+ baseDoc,
448
+ client,
449
+ baseLanguage
450
+ )).translations.find(
451
+ (translation) => translation.language === localeId
452
+ )?.value?._ref;
453
+ i18nDocId && (i18nDoc = await findLatestDraft(i18nDocId, client)), mergeWithTargetLocale && i18nDoc ? baseDoc = i18nDoc : docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
454
+ docInfo.documentId,
455
+ docInfo.versionId,
456
+ client
457
+ )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
458
+ const merged = BaseDocumentMerger.documentLevelMerge(
459
+ translatedFields,
460
+ baseDoc
461
+ );
462
+ if (i18nDoc)
463
+ await patchI18nDoc(
464
+ docInfo.documentId,
465
+ i18nDoc._id,
466
+ baseDoc,
467
+ merged,
468
+ translatedFields,
469
+ client,
470
+ i18nDoc
471
+ );
472
+ else {
473
+ const freshTranslationMetadata = await getOrCreateTranslationMetadata(
474
+ docInfo.documentId,
475
+ baseDoc,
476
+ client,
477
+ baseLanguage
478
+ ), freshI18nDocId = freshTranslationMetadata.translations.find(
479
+ (translation) => translation.language === localeId
480
+ )?.value?._ref;
481
+ if (freshI18nDocId) {
482
+ const freshI18nDoc = await findLatestDraft(freshI18nDocId, client);
483
+ await patchI18nDoc(
484
+ docInfo.documentId,
485
+ freshI18nDoc._id,
486
+ baseDoc,
487
+ merged,
488
+ translatedFields,
489
+ client,
490
+ freshI18nDoc
491
+ );
492
+ return;
493
+ }
494
+ await createI18nDocAndPatchMetadata(
495
+ baseDoc,
496
+ merged,
497
+ localeId,
498
+ client,
499
+ freshTranslationMetadata,
500
+ docInfo.documentId,
501
+ languageField
502
+ );
503
+ }
504
+ }, DEFAULT_TYPE_PREFIX$1 = "internationalizedArray";
505
+ function hasRecognizedTypePrefix(type) {
506
+ return type.startsWith(DEFAULT_TYPE_PREFIX$1) || type.startsWith(pluginConfig.getFieldLevelTypePrefix());
507
+ }
508
+ const isRecord$4 = (value) => typeof value == "object" && value !== null && !Array.isArray(value);
509
+ function isInternationalizedArrayItem(item) {
510
+ return isRecord$4(item) && typeof item._type == "string" && hasRecognizedTypePrefix(item._type) && item._type.endsWith("Value") && typeof item.language == "string" && "value" in item;
511
+ }
512
+ function isInternationalizedArrayField(value) {
513
+ return Array.isArray(value) && value.length > 0 && value.every(isInternationalizedArrayItem);
514
+ }
515
+ function findLocaleItem(field, locale) {
516
+ return field.find((item) => item.language === locale);
517
+ }
518
+ function upsertLocaleItem(baseArray, translatedValue, targetLocale, sourceLocale) {
519
+ const itemType = (findLocaleItem(baseArray, sourceLocale) ?? baseArray[0])._type, updated = baseArray.map(
520
+ (item) => item.language === targetLocale ? { ...item, value: translatedValue } : { ...item }
521
+ );
522
+ return findLocaleItem(updated, targetLocale) || updated.push({
523
+ _key: randomKey(),
524
+ _type: itemType,
525
+ language: targetLocale,
526
+ value: translatedValue
527
+ }), updated;
528
+ }
529
+ function mergeValue(baseValue, translatedValue, targetLocale, sourceLocale) {
530
+ if (isInternationalizedArrayField(baseValue))
531
+ return upsertLocaleItem(
532
+ baseValue,
533
+ translatedValue,
534
+ targetLocale,
535
+ sourceLocale
536
+ );
537
+ if (Array.isArray(baseValue) && Array.isArray(translatedValue)) {
538
+ let changed = !1;
539
+ const merged = baseValue.map((item) => {
540
+ if (!isRecord$4(item) || typeof item._key != "string")
541
+ return item;
542
+ const translatedItem = translatedValue.find(
543
+ (candidate) => isRecord$4(candidate) && candidate._key === item._key
544
+ );
545
+ if (!translatedItem)
546
+ return item;
547
+ const mergedItem = mergeValue(
548
+ item,
549
+ translatedItem,
550
+ targetLocale,
551
+ sourceLocale
552
+ );
553
+ return mergedItem !== void 0 ? (changed = !0, mergedItem) : item;
554
+ });
555
+ return changed ? merged : void 0;
556
+ }
557
+ if (isRecord$4(baseValue) && isRecord$4(translatedValue)) {
558
+ let changed = !1;
559
+ const merged = { ...baseValue };
560
+ for (const key of Object.keys(translatedValue)) {
561
+ if (key.startsWith("_") || !(key in baseValue))
562
+ continue;
563
+ const mergedChild = mergeValue(
564
+ baseValue[key],
565
+ translatedValue[key],
566
+ targetLocale,
567
+ sourceLocale
568
+ );
569
+ mergedChild !== void 0 && (merged[key] = mergedChild, changed = !0);
570
+ }
571
+ return changed ? merged : void 0;
572
+ }
573
+ }
574
+ function mergeInternationalizedArrays(baseDoc, translatedFields, targetLocale, sourceLocale) {
575
+ const changes = {};
576
+ for (const key of Object.keys(translatedFields)) {
577
+ if (key.startsWith("_") || !(key in baseDoc))
578
+ continue;
579
+ const merged = mergeValue(
580
+ baseDoc[key],
581
+ translatedFields[key],
582
+ targetLocale,
583
+ sourceLocale
584
+ );
585
+ merged !== void 0 && (changes[key] = merged);
586
+ }
587
+ return changes;
588
+ }
589
+ const internationalizedArrayPatch = async (docInfo, translatedFields, localeId, client) => {
590
+ const sourceLocale = pluginConfig.getSourceLocale(), baseDoc = await findLatestDraft(docInfo.documentId, client);
591
+ if (!baseDoc)
592
+ return;
593
+ const changes = mergeInternationalizedArrays(
594
+ baseDoc,
595
+ translatedFields,
596
+ localeId,
597
+ sourceLocale
598
+ );
599
+ if (Object.keys(changes).length === 0)
600
+ return;
601
+ if (baseDoc._id.startsWith("drafts.")) {
602
+ await client.patch(baseDoc._id).set(changes).commit();
603
+ return;
604
+ }
605
+ const draftId = `drafts.${getPublishedId(baseDoc._id)}`;
606
+ await client.transaction().createIfNotExists({ ...baseDoc, _id: draftId }).patch(draftId, (patch) => patch.set(changes)).commit();
607
+ }, defaultSchema = schema.Schema.compile({
608
+ name: "default",
609
+ types: [
610
+ {
611
+ type: "object",
612
+ name: "default",
613
+ fields: [
614
+ {
615
+ name: "block",
616
+ type: "array",
617
+ of: [{ type: "block" }]
618
+ }
619
+ ]
620
+ }
621
+ ]
622
+ }), blockContentType = defaultSchema.get("default").fields.find((field) => field.name === "block").type, preprocess = (html) => {
623
+ const intermediateBlocks = blockTools.htmlToBlocks(
624
+ `<p>${html}</p>`,
625
+ blockContentType
626
+ );
627
+ if (!intermediateBlocks.length)
628
+ throw new Error(`Error parsing string '${html}'`);
629
+ return intermediateBlocks[0].children[0].text;
630
+ };
631
+ function attachGTData(html, data, type) {
632
+ const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
633
+ if (!firstElement)
634
+ return html;
635
+ const encodedData = encode(JSON.stringify({ [type]: data }));
636
+ return firstElement.setAttribute("data-gt-internal", encodedData), firstElement.outerHTML;
637
+ }
638
+ function detachGTData(html) {
639
+ const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
640
+ if (!firstElement)
641
+ return { html };
642
+ const encodedData = firstElement.getAttribute("data-gt-internal");
643
+ let extractedData;
644
+ if (encodedData)
645
+ try {
646
+ const decodedData = decode(encodedData);
647
+ extractedData = JSON.parse(decodedData), firstElement.removeAttribute("data-gt-internal");
648
+ } catch (error) {
649
+ console.warn("Failed to decode GT internal data:", error);
650
+ }
651
+ return {
652
+ html: firstElement.outerHTML,
653
+ data: extractedData
654
+ };
655
+ }
656
+ function encode(data) {
657
+ const bytes = new TextEncoder().encode(data);
658
+ let binary = "";
659
+ for (let i = 0; i < bytes.length; i++)
660
+ binary += String.fromCharCode(bytes[i]);
661
+ return btoa(binary);
662
+ }
663
+ function decode(base64) {
664
+ const binary = atob(base64), bytes = new Uint8Array(binary.length);
665
+ for (let i = 0; i < binary.length; i++)
666
+ bytes[i] = binary.charCodeAt(i);
667
+ return new TextDecoder().decode(bytes);
668
+ }
669
+ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), defaultStopTypes = [
670
+ "reference",
671
+ "date",
672
+ "datetime",
673
+ "file",
674
+ "geopoint",
675
+ "image",
676
+ "number",
677
+ "crop",
678
+ "hotspot",
679
+ "boolean",
680
+ "url",
681
+ "color",
682
+ "code"
683
+ ], defaultMarks = {}, defaultPortableTextBlockStyles = {
684
+ normal: ({ value, children }) => `<p id="${value._key}">${children}</p>`,
685
+ blockquote: ({ value, children }) => `<blockquote id="${value._key}">${children}</blockquote>`,
686
+ h1: ({ value, children }) => `<h1 id="${value._key}">${children}</h1>`,
687
+ h2: ({ value, children }) => `<h2 id="${value._key}">${children}</h2>`,
688
+ h3: ({ value, children }) => `<h3 id="${value._key}">${children}</h3>`,
689
+ h4: ({ value, children }) => `<h4 id="${value._key}">${children}</h4>`,
690
+ h5: ({ value, children }) => `<h5 id="${value._key}">${children}</h5>`,
691
+ h6: ({ value, children }) => `<h6 id="${value._key}">${children}</h6>`
692
+ }, defaultLists = {
693
+ number: ({ value, children }) => `<ol id="${value._key.replace("-parent", "")}">${children}</ol>`,
694
+ bullet: ({ value, children }) => `<ul id="${value._key.replace("-parent", "")}">${children}</ul>`
695
+ }, defaultListItem = ({
696
+ value,
697
+ children
698
+ }) => {
699
+ const { _key, level } = value;
700
+ return `<li id="${(_key || "").replace("-parent", "")}" data-level="${level}">${children}</li>`;
701
+ }, unknownBlockFunc = ({ value, children }) => `<p id="${value._key}" data-type="unknown-block-style" data-style="${value.style}">${children}</p>`, customSerializers = {
702
+ unknownType: ({ value }) => `<div class="${value._type}"></div>`,
703
+ types: {},
704
+ marks: defaultMarks,
705
+ block: defaultPortableTextBlockStyles,
706
+ list: defaultLists,
707
+ listItem: defaultListItem,
708
+ unknownBlockStyle: unknownBlockFunc
709
+ }, customDeserializers = { types: {} }, customBlockDeserializers = [
710
+ // handle marks with data-gt-internal
711
+ {
712
+ deserialize(node2, next2) {
713
+ if (node2.nodeType !== 1)
714
+ return;
715
+ const el = node2;
716
+ if (!el.hasChildNodes() || !el.getAttribute("data-gt-internal"))
717
+ return;
718
+ const { html, data } = detachGTData(el.outerHTML), block = blockTools.htmlToBlocks(html, blockContentType)[0], children = next2(el.childNodes);
719
+ let markDefs = [];
720
+ return "markDefs" in block && (markDefs = block.markDefs ?? []), data?.markDef && markDefs.push(data.markDef), Array.isArray(children) && children.forEach((child) => {
721
+ if (!isRecord$3(child))
722
+ return;
723
+ const marks = Array.isArray(child.marks) ? child.marks : [];
724
+ child.marks = data?.markDef?._key ? [...marks, data.markDef._key] : marks;
725
+ }), {
726
+ ...block,
727
+ markDefs,
728
+ children
729
+ };
730
+ }
731
+ },
732
+ //handle undeclared styles
733
+ {
734
+ deserialize(node2, next2) {
735
+ if (node2.nodeType !== 1)
736
+ return;
737
+ const el = node2;
738
+ if (!el.hasChildNodes() || el.getAttribute("data-type") !== "unknown-block-style")
739
+ return;
740
+ const style = el.getAttribute("data-style") ?? "";
741
+ return {
742
+ ...blockTools.htmlToBlocks(el.outerHTML, blockContentType)[0],
743
+ style,
744
+ children: next2(el.childNodes)
745
+ };
746
+ }
747
+ },
748
+ //handle list items
749
+ {
750
+ deserialize(node2, next2) {
751
+ if (node2.nodeType !== 1)
752
+ return;
753
+ const el = node2;
754
+ if (!el.hasChildNodes() || el.tagName.toLowerCase() !== "li")
755
+ return;
756
+ const tagsToStyle = {
757
+ ul: "bullet",
758
+ ol: "number"
759
+ }, parent = el.parentNode;
760
+ if (!parent || !parent.tagName)
761
+ return;
762
+ const listItem = tagsToStyle[parent.tagName.toLowerCase()];
763
+ if (!listItem)
764
+ return;
765
+ const level = el.getAttribute("data-level") && parseInt(el.getAttribute("data-level") || "0", 10), _key = el.id;
766
+ let block = blockTools.htmlToBlocks(parent.outerHTML, blockContentType)[0];
767
+ const customStyle = el.children?.[0]?.getAttribute("data-style");
768
+ if (new RegExp(/<("[^"]*"|'[^']*'|[^'">])*>/).test(el.innerHTML)) {
769
+ const newBlock = blockTools.htmlToBlocks(el.innerHTML, blockContentType)[0];
770
+ if (newBlock && (block = {
771
+ ...block,
772
+ ...newBlock,
773
+ // @ts-ignore
774
+ style: customStyle ?? newBlock.style
775
+ }, customStyle))
776
+ return block;
777
+ }
778
+ return {
779
+ ...block,
780
+ level,
781
+ _key,
782
+ listItem,
783
+ children: next2(el.childNodes)
784
+ };
785
+ }
786
+ }
787
+ ];
788
+ function mergeBlocks(blocks) {
789
+ const mergedBlock = { ...blocks[0] };
790
+ mergedBlock.markDefs = mergedBlock.markDefs ?? [];
791
+ for (const [idx, block] of blocks.entries())
792
+ idx !== 0 && (mergedBlock.children.push(...block.children), mergedBlock.markDefs.push(...block.markDefs ?? []));
793
+ return mergedBlock._type = "block", mergedBlock;
794
+ }
795
+ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
796
+ const output = [];
797
+ return Array.from(arrayHTML.children).forEach((child) => {
798
+ let deserializedObject;
799
+ try {
800
+ if (child.tagName?.toLowerCase() === "span")
801
+ deserializedObject = preprocess(child.innerHTML);
802
+ else if (child.className || child.getAttribute("data-type") === "object")
803
+ deserializedObject = deserializeObject(
804
+ child,
805
+ deserializers,
806
+ blockDeserializers
807
+ ), deserializedObject && typeof deserializedObject == "object" && !Array.isArray(deserializedObject) && (deserializedObject._key = child.id);
808
+ else {
809
+ const blocks = blockTools.htmlToBlocks(child.outerHTML, blockContentType, {
810
+ rules: blockDeserializers
811
+ });
812
+ deserializedObject = mergeBlocks(
813
+ blocks
814
+ ), deserializedObject._key = child.id;
815
+ }
816
+ } catch (e) {
817
+ console.debug(
818
+ `Tried to deserialize block: ${child.outerHTML} in an array but failed to identify it! Error: ${e}`
819
+ );
820
+ }
821
+ output.push(deserializedObject);
822
+ }), output;
823
+ }, deserializeObject = (objectHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
824
+ const deserialize = deserializers.types?.[objectHTML.className];
825
+ if (deserialize)
826
+ return deserialize(objectHTML);
827
+ const output = {};
828
+ return objectHTML.className && (output._type = objectHTML.className), Array.from(objectHTML.children).forEach((child) => {
829
+ if (child.tagName?.toLowerCase() === "span")
830
+ output[child.className] = preprocess(child.innerHTML);
831
+ else if (child.getAttribute("data-level") === "field") {
832
+ const deserialized = deserializeHTML(
833
+ child.outerHTML,
834
+ deserializers,
835
+ blockDeserializers
836
+ );
837
+ deserialized && Object.keys(deserialized).length ? output[child.className] = deserialized : console.debug(
838
+ `Deserializer: Skipping empty or unreadable HTML: ${child.outerHTML}`
839
+ );
840
+ } else child.getAttribute("data-type") === "array" && (output[child.className] = deserializeArray(
841
+ child,
842
+ deserializers,
843
+ blockDeserializers
844
+ ));
845
+ }), output;
846
+ }, deserializeHTML = (html, deserializers, blockDeserializers) => {
847
+ let HTMLnode = new DOMParser().parseFromString(html, "text/html").body.children[0];
848
+ if (HTMLnode?.getAttribute("data-level") === "field" && (HTMLnode = HTMLnode.children[0]), !HTMLnode)
849
+ return {};
850
+ let output;
851
+ const deserialize = deserializers.types?.[HTMLnode.className];
852
+ return deserialize ? output = deserialize(HTMLnode) : HTMLnode.getAttribute("data-type") === "object" ? output = deserializeObject(HTMLnode, deserializers, blockDeserializers) : HTMLnode.getAttribute("data-type") === "array" ? output = deserializeArray(HTMLnode, deserializers, blockDeserializers) : (output = {}, console.debug(
853
+ `Tried to deserialize block ${HTMLnode.outerHTML} but failed to identify it!`
854
+ )), output;
855
+ }, deserializeDocument$1 = (serializedDoc, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
856
+ const metadata = {}, head = new DOMParser().parseFromString(serializedDoc, "text/html").head;
857
+ return Array.from(head.children).forEach((metaTag) => {
858
+ const validTags = ["_id", "_rev", "_type"], metaName = metaTag.getAttribute("name");
859
+ metaName && validTags.includes(metaName) && (metadata[metaName] = metaTag.getAttribute("content"));
860
+ }), {
861
+ ...deserializeHTML(
862
+ serializedDoc,
863
+ deserializers,
864
+ blockDeserializers
865
+ ),
866
+ ...metadata
867
+ };
868
+ }, BaseDocumentDeserializer = {
869
+ deserializeDocument: deserializeDocument$1,
870
+ deserializeHTML
871
+ }, META_FIELDS$1 = ["_key", "_type", "_id"], isRecord$2 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), languageObjectFieldFilter = (obj, baseLang) => {
872
+ const filterToLangField = (childObj) => {
873
+ const filteredObj = {};
874
+ return filteredObj[baseLang] = childObj[baseLang], META_FIELDS$1.forEach((field) => {
875
+ childObj[field] && (filteredObj[field] = childObj[field]);
876
+ }), filteredObj;
877
+ }, findBaseLang = (childObj) => {
878
+ const filteredObj = {};
879
+ META_FIELDS$1.forEach((field) => {
880
+ childObj[field] && (filteredObj[field] = childObj[field]);
881
+ });
882
+ for (const key in childObj)
883
+ if (childObj.hasOwnProperty(key)) {
884
+ const value = childObj[key];
885
+ if (isRecord$2(value) && Object.hasOwn(value, baseLang))
886
+ filteredObj[key] = filterToLangField(value);
887
+ else if (Array.isArray(value) && value.length && typeof value[0] == "object") {
888
+ const validLangObjects = value.reduce(
889
+ (validArr, objInArray) => {
890
+ if (!isRecord$2(objInArray))
891
+ return validArr;
892
+ if (objInArray._type === "block")
893
+ validArr.push(objInArray);
894
+ else if (Object.hasOwn(objInArray, baseLang))
895
+ validArr.push(filterToLangField(objInArray));
896
+ else {
897
+ const filtered = findBaseLang(objInArray);
898
+ Object.keys(filtered).filter(
899
+ (objInArrayKey) => META_FIELDS$1.indexOf(objInArrayKey) === -1
402
900
  ).length && validArr.push(filtered);
403
901
  }
404
902
  return validArr;
@@ -507,240 +1005,78 @@ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Arr
507
1005
  );
508
1006
  return schema2 && schema2.fields ? fieldFilter(block, schema2.fields, stopTypes) : block;
509
1007
  }).map((obj) => typeof obj == "string" ? `<span>${obj}</span>` : serializeObject(obj, stopTypes, serializers));
510
- return `<div class="${fieldName}" data-type="array">${output.join("")}</div>`;
511
- };
512
- return {
513
- serializeDocument: (doc, translationLevel = "document", baseLang = "en", stopTypes = defaultStopTypes, serializers = customSerializers) => {
514
- const schema2 = getSchema(doc._type);
515
- let filteredObj = {};
516
- translationLevel === "field" ? filteredObj = languageObjectFieldFilter(doc, baseLang) : filteredObj = fieldFilter(doc, schema2?.fields ?? [], stopTypes);
517
- const serializedFields = {};
518
- for (const key in filteredObj) {
519
- if (filteredObj.hasOwnProperty(key) === !1) continue;
520
- const value = filteredObj[key];
521
- if (typeof value == "string")
522
- serializedFields[key] = value;
523
- else if (Array.isArray(value))
524
- serializedFields[key] = serializeArray(
525
- value.filter(
526
- (item) => typeof item == "string" || isRecord$1(item)
527
- ),
528
- key,
529
- stopTypes,
530
- serializers
531
- );
532
- else if (value && isRecord$1(value) && !stopTypes.find((stopType) => stopType == value?._type)) {
533
- const serialized = serializeObject(
534
- value,
535
- stopTypes,
536
- serializers
537
- );
538
- serializedFields[key] = `<div class="${key}" data-level='field'>${serialized}</div>`;
539
- }
540
- }
541
- const rawHTMLBody = document.createElement("body");
542
- rawHTMLBody.innerHTML = serializeObject(
543
- serializedFields,
544
- stopTypes,
545
- serializers
546
- );
547
- const rawHTMLHead = document.createElement("head");
548
- ["_id", "_type", "_rev"].forEach((field) => {
549
- const metaEl = document.createElement("meta");
550
- metaEl.setAttribute("name", field), metaEl.setAttribute("content", doc[field]), rawHTMLHead.appendChild(metaEl);
551
- });
552
- const versionMeta = document.createElement("meta");
553
- versionMeta.setAttribute("name", "version"), versionMeta.setAttribute("content", "3"), rawHTMLHead.appendChild(versionMeta);
554
- const rawHTML = document.createElement("html");
555
- return rawHTML.appendChild(rawHTMLHead), rawHTML.appendChild(rawHTMLBody), {
556
- name: doc._id,
557
- content: rawHTML.outerHTML
558
- };
559
- },
560
- fieldFilter,
561
- languageObjectFieldFilter,
562
- serializeArray,
563
- serializeObject
564
- };
565
- };
566
- function mergeBlocks(blocks) {
567
- const mergedBlock = { ...blocks[0] };
568
- mergedBlock.markDefs = mergedBlock.markDefs ?? [];
569
- for (const [idx, block] of blocks.entries())
570
- idx !== 0 && (mergedBlock.children.push(...block.children), mergedBlock.markDefs.push(...block.markDefs ?? []));
571
- return mergedBlock._type = "block", mergedBlock;
572
- }
573
- const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
574
- const output = [];
575
- return Array.from(arrayHTML.children).forEach((child) => {
576
- let deserializedObject;
577
- try {
578
- if (child.tagName?.toLowerCase() === "span")
579
- deserializedObject = preprocess(child.innerHTML);
580
- else if (child.className || child.getAttribute("data-type") === "object")
581
- deserializedObject = deserializeObject(
582
- child,
583
- deserializers,
584
- blockDeserializers
585
- ), deserializedObject && typeof deserializedObject == "object" && !Array.isArray(deserializedObject) && (deserializedObject._key = child.id);
586
- else {
587
- const blocks = blockTools.htmlToBlocks(child.outerHTML, blockContentType, {
588
- rules: blockDeserializers
589
- });
590
- deserializedObject = mergeBlocks(
591
- blocks
592
- ), deserializedObject._key = child.id;
593
- }
594
- } catch (e) {
595
- console.debug(
596
- `Tried to deserialize block: ${child.outerHTML} in an array but failed to identify it! Error: ${e}`
597
- );
598
- }
599
- output.push(deserializedObject);
600
- }), output;
601
- }, deserializeObject = (objectHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
602
- const deserialize = deserializers.types?.[objectHTML.className];
603
- if (deserialize)
604
- return deserialize(objectHTML);
605
- const output = {};
606
- return objectHTML.className && (output._type = objectHTML.className), Array.from(objectHTML.children).forEach((child) => {
607
- if (child.tagName?.toLowerCase() === "span")
608
- output[child.className] = preprocess(child.innerHTML);
609
- else if (child.getAttribute("data-level") === "field") {
610
- const deserialized = deserializeHTML(
611
- child.outerHTML,
612
- deserializers,
613
- blockDeserializers
614
- );
615
- deserialized && Object.keys(deserialized).length ? output[child.className] = deserialized : console.debug(
616
- `Deserializer: Skipping empty or unreadable HTML: ${child.outerHTML}`
617
- );
618
- } else child.getAttribute("data-type") === "array" && (output[child.className] = deserializeArray(
619
- child,
620
- deserializers,
621
- blockDeserializers
622
- ));
623
- }), output;
624
- }, deserializeHTML = (html, deserializers, blockDeserializers) => {
625
- let HTMLnode = new DOMParser().parseFromString(html, "text/html").body.children[0];
626
- if (HTMLnode?.getAttribute("data-level") === "field" && (HTMLnode = HTMLnode.children[0]), !HTMLnode)
627
- return {};
628
- let output;
629
- const deserialize = deserializers.types?.[HTMLnode.className];
630
- return deserialize ? output = deserialize(HTMLnode) : HTMLnode.getAttribute("data-type") === "object" ? output = deserializeObject(HTMLnode, deserializers, blockDeserializers) : HTMLnode.getAttribute("data-type") === "array" ? output = deserializeArray(HTMLnode, deserializers, blockDeserializers) : (output = {}, console.debug(
631
- `Tried to deserialize block ${HTMLnode.outerHTML} but failed to identify it!`
632
- )), output;
633
- }, deserializeDocument$1 = (serializedDoc, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
634
- const metadata = {}, head = new DOMParser().parseFromString(serializedDoc, "text/html").head;
635
- return Array.from(head.children).forEach((metaTag) => {
636
- const validTags = ["_id", "_rev", "_type"], metaName = metaTag.getAttribute("name");
637
- metaName && validTags.includes(metaName) && (metadata[metaName] = metaTag.getAttribute("content"));
638
- }), {
639
- ...deserializeHTML(
640
- serializedDoc,
641
- deserializers,
642
- blockDeserializers
643
- ),
644
- ...metadata
645
- };
646
- }, BaseDocumentDeserializer = {
647
- deserializeDocument: deserializeDocument$1,
648
- deserializeHTML
649
- };
650
- function forEachMatchingField(documentId, document2, fields, callback) {
651
- const applicable = fields.filter(
652
- (field) => field.documentId === documentId || field.documentId === void 0 || field.documentId === null
653
- );
654
- for (const entry of applicable)
655
- if (entry.fields)
656
- for (const field of entry.fields) {
657
- const { property, type } = field;
658
- try {
659
- const results = jsonpathPlus.JSONPath({
660
- json: document2,
661
- path: property,
662
- resultType: "all",
663
- flatten: !0,
664
- wrap: !0
665
- });
666
- results && results.length > 0 && results.forEach(
667
- (result) => {
668
- type !== void 0 ? typeof result.value == "object" && result.value !== null && !Array.isArray(result.value) && result.value._type === type && callback(result) : callback(result);
669
- }
670
- );
671
- } catch (error) {
672
- console.warn(`Invalid JSONPath: ${property}`, error);
673
- }
674
- }
675
- }
676
- function deleteMatchingFields(documentId, document2, fields) {
677
- const arrayRemovals = [];
678
- forEachMatchingField(documentId, document2, fields, (result) => {
679
- Array.isArray(result.parent) ? arrayRemovals.push({
680
- parent: result.parent,
681
- index: Number(result.parentProperty)
682
- }) : delete result.parent[result.parentProperty];
683
- }), arrayRemovals.sort((a, b) => b.index - a.index);
684
- for (const { parent, index } of arrayRemovals)
685
- parent.splice(index, 1);
686
- }
687
- function applyDocuments(documentId, sourceDocument, targetDocument, ignore, skip = [], dedupe = [], localeId) {
688
- const mergedDocument = JSON.parse(JSON.stringify(sourceDocument)), clonedTarget = JSON.parse(JSON.stringify(targetDocument));
689
- for (const [key, value] of Object.entries(clonedTarget))
690
- mergedDocument[key] = value;
691
- return forEachMatchingField(documentId, sourceDocument, ignore, (result) => {
692
- JSONPointer__default.default.set(mergedDocument, result.pointer, result.value);
693
- }), forEachMatchingField(documentId, sourceDocument, dedupe, (result) => {
694
- JSONPointer__default.default.set(
695
- mergedDocument,
696
- result.pointer,
697
- dedupeFieldValue(result.value, localeId)
698
- );
699
- }), deleteMatchingFields(documentId, mergedDocument, skip), mergedDocument;
700
- }
701
- function dedupeFieldValue(value, localeId) {
702
- if (!localeId) return value;
703
- if (typeof value == "string")
704
- return appendLocaleSuffix(value, localeId);
705
- if (value && typeof value == "object" && !Array.isArray(value) && typeof value.current == "string") {
706
- const slug = value;
707
- return {
708
- ...slug,
709
- current: appendLocaleSuffix(slug.current, localeId)
710
- };
1008
+ return `<div class="${fieldName}" data-type="array">${output.join("")}</div>`;
1009
+ };
1010
+ return {
1011
+ serializeDocument: (doc, translationLevel = "document", baseLang = "en", stopTypes = defaultStopTypes, serializers = customSerializers) => {
1012
+ const schema2 = getSchema(doc._type);
1013
+ let filteredObj = {};
1014
+ translationLevel === "field" ? filteredObj = languageObjectFieldFilter(doc, baseLang) : filteredObj = fieldFilter(doc, schema2?.fields ?? [], stopTypes);
1015
+ const serializedFields = {};
1016
+ for (const key in filteredObj) {
1017
+ if (filteredObj.hasOwnProperty(key) === !1) continue;
1018
+ const value = filteredObj[key];
1019
+ if (typeof value == "string")
1020
+ serializedFields[key] = value;
1021
+ else if (Array.isArray(value))
1022
+ serializedFields[key] = serializeArray(
1023
+ value.filter(
1024
+ (item) => typeof item == "string" || isRecord$1(item)
1025
+ ),
1026
+ key,
1027
+ stopTypes,
1028
+ serializers
1029
+ );
1030
+ else if (value && isRecord$1(value) && !stopTypes.find((stopType) => stopType == value?._type)) {
1031
+ const serialized = serializeObject(
1032
+ value,
1033
+ stopTypes,
1034
+ serializers
1035
+ );
1036
+ serializedFields[key] = `<div class="${key}" data-level='field'>${serialized}</div>`;
1037
+ }
1038
+ }
1039
+ const rawHTMLBody = document.createElement("body");
1040
+ rawHTMLBody.innerHTML = serializeObject(
1041
+ serializedFields,
1042
+ stopTypes,
1043
+ serializers
1044
+ );
1045
+ const rawHTMLHead = document.createElement("head");
1046
+ ["_id", "_type", "_rev"].forEach((field) => {
1047
+ const metaEl = document.createElement("meta");
1048
+ metaEl.setAttribute("name", field), metaEl.setAttribute("content", doc[field]), rawHTMLHead.appendChild(metaEl);
1049
+ });
1050
+ const versionMeta = document.createElement("meta");
1051
+ versionMeta.setAttribute("name", "version"), versionMeta.setAttribute("content", "3"), rawHTMLHead.appendChild(versionMeta);
1052
+ const rawHTML = document.createElement("html");
1053
+ return rawHTML.appendChild(rawHTMLHead), rawHTML.appendChild(rawHTMLBody), {
1054
+ name: doc._id,
1055
+ content: rawHTML.outerHTML
1056
+ };
1057
+ },
1058
+ fieldFilter,
1059
+ languageObjectFieldFilter,
1060
+ serializeArray,
1061
+ serializeObject
1062
+ };
1063
+ };
1064
+ function collapseToSourceLocale(value, sourceLocale) {
1065
+ if (isInternationalizedArrayField(value)) {
1066
+ const sourceItem = findLocaleItem(value, sourceLocale);
1067
+ return sourceItem ? collapseToSourceLocale(sourceItem.value, sourceLocale) : void 0;
711
1068
  }
712
- return value;
713
- }
714
- function appendLocaleSuffix(value, localeId) {
715
- if (!value) return value;
716
- const suffix = createLocaleSuffix(localeId);
717
- return !suffix || value.endsWith(suffix) ? value : `${value}${suffix}`;
718
- }
719
- function createLocaleSuffix(localeId) {
720
- const normalized = localeId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
721
- return normalized ? `-${normalized}` : "";
722
- }
723
- function getPublishedId(documentId) {
724
- return documentId.startsWith("drafts.") ? documentId.slice(7) : documentId;
725
- }
726
- function getDocumentPublishedId(document2) {
727
- return getPublishedId(document2._id);
728
- }
729
- function dedupeDocumentsPreferDraft(documents) {
730
- const byPublishedId = /* @__PURE__ */ new Map();
731
- for (const document2 of documents) {
732
- const publishedId = getDocumentPublishedId(document2);
733
- (!byPublishedId.get(publishedId) || document2._id.startsWith("drafts.")) && byPublishedId.set(publishedId, document2);
1069
+ if (Array.isArray(value))
1070
+ return value.map((item) => collapseToSourceLocale(item, sourceLocale)).filter((item) => item !== void 0);
1071
+ if (isRecord$4(value)) {
1072
+ const collapsed = {};
1073
+ for (const key of Object.keys(value)) {
1074
+ const collapsedValue = collapseToSourceLocale(value[key], sourceLocale);
1075
+ collapsedValue !== void 0 && (collapsed[key] = collapsedValue);
1076
+ }
1077
+ return collapsed;
734
1078
  }
735
- return Array.from(byPublishedId.values());
736
- }
737
- function createTranslationStatusKey(branchId, documentId, versionId, localeId) {
738
- const publishedId = getPublishedId(documentId);
739
- return branchId ? `${branchId}:${publishedId}:${versionId}:${localeId}` : `${publishedId}:${versionId}:${localeId}`;
740
- }
741
- function createStableTranslationKey(branchId, documentId, localeId) {
742
- const publishedId = getPublishedId(documentId);
743
- return branchId ? `${branchId}:${publishedId}:${localeId}` : `${publishedId}:${localeId}`;
1079
+ return value;
744
1080
  }
745
1081
  function deserializeDocument(document2) {
746
1082
  const deserializers = merge__default.default(
@@ -756,21 +1092,25 @@ function deserializeDocument(document2) {
756
1092
  blockDeserializers
757
1093
  );
758
1094
  }
759
- function serializeDocument(document2, schema2, baseLanguage) {
1095
+ function serializeDocument(document2, schema2, baseLanguage, level = "document") {
760
1096
  const stopTypes = [
761
1097
  ...defaultStopTypes,
762
1098
  ...pluginConfig.getAdditionalStopTypes()
763
1099
  ], serializers = merge__default.default(
764
1100
  customSerializers,
765
1101
  pluginConfig.getAdditionalSerializers()
766
- ), docToSerialize = stripIgnoredFields(
1102
+ );
1103
+ let docToSerialize = stripIgnoredFields(
767
1104
  document2,
768
1105
  pluginConfig.getIgnoreFields(),
769
1106
  pluginConfig.getDedupeFields()
770
- );
771
- return BaseDocumentSerializer(schema2).serializeDocument(
1107
+ ), innerLevel = level;
1108
+ return level === "internationalizedArray" && (docToSerialize = collapseToSourceLocale(
1109
+ docToSerialize,
1110
+ baseLanguage
1111
+ ), innerLevel = "document"), BaseDocumentSerializer(schema2).serializeDocument(
772
1112
  docToSerialize,
773
- "document",
1113
+ innerLevel,
774
1114
  baseLanguage,
775
1115
  stopTypes,
776
1116
  serializers
@@ -786,6 +1126,32 @@ function stripIgnoredFields(document2, ignoreFields, dedupeFields) {
786
1126
  fieldsToStrip
787
1127
  ), strippedDoc;
788
1128
  }
1129
+ const documentAdapter = {
1130
+ level: "document",
1131
+ serialize: (document2, schema2, baseLanguage) => serializeDocument(document2, schema2, baseLanguage, "document"),
1132
+ patch: (docInfo, deserialized, localeId, client, mergeWithTargetLocale) => documentLevelPatch(
1133
+ docInfo,
1134
+ deserialized,
1135
+ localeId,
1136
+ client,
1137
+ pluginConfig.getLanguageField(),
1138
+ mergeWithTargetLocale
1139
+ )
1140
+ }, internationalizedArrayAdapter = {
1141
+ level: "internationalizedArray",
1142
+ serialize: (document2, schema2, baseLanguage) => serializeDocument(document2, schema2, baseLanguage, "internationalizedArray"),
1143
+ patch: (docInfo, deserialized, localeId, client) => internationalizedArrayPatch(docInfo, deserialized, localeId, client)
1144
+ };
1145
+ function matchesFieldLevel(type) {
1146
+ return type ? pluginConfig.getFieldLevelDocuments().some((filter2) => filter2.type === type) : !1;
1147
+ }
1148
+ function getTranslationStrategyForType(type) {
1149
+ const level = pluginConfig.getTranslationLevel();
1150
+ return level === "internationalizedArray" || level === "mixed" && matchesFieldLevel(type) ? internationalizedArrayAdapter : documentAdapter;
1151
+ }
1152
+ function getTranslationStrategy(document2) {
1153
+ return getTranslationStrategyForType(document2._type);
1154
+ }
789
1155
  async function uploadFiles(documents, secrets) {
790
1156
  return overrideConfig(secrets), await gt.uploadSourceFiles(
791
1157
  documents.map(({ info, serializedDocument }) => ({
@@ -820,283 +1186,79 @@ async function initProject(uploadResult, options, secrets) {
820
1186
  if (status[0].status === "completed") {
821
1187
  setupCompleted = !0;
822
1188
  break;
823
- }
824
- if (status[0].status === "failed") {
825
- setupFailedMessage = status[0].error?.message || "Unknown error";
826
- break;
827
- }
828
- if (Date.now() - start > setupTimeoutMs) {
829
- setupFailedMessage = "Timed out while waiting for setup generation";
830
- break;
831
- }
832
- await new Promise((r) => setTimeout(r, pollInterval));
833
- }
834
- console.log(setupCompleted ? "Setup successfully completed" : `Setup ${setupFailedMessage ? "failed" : "timed out"} \u2014 proceeding without setup${setupFailedMessage ? ` (${setupFailedMessage})` : ""}`);
835
- }
836
- return !0;
837
- }
838
- async function createJobs(uploadResult, localeIds, secrets) {
839
- return overrideConfig(secrets), await gt.enqueueFiles(uploadResult.uploadedFiles, {
840
- sourceLocale: gt.sourceLocale || internal.libraryDefaultLocale,
841
- targetLocales: localeIds
842
- });
843
- }
844
- async function downloadTranslations(files, secrets, maxRetries = 3, retryDelay = 1e3) {
845
- overrideConfig(secrets);
846
- let retries = 0;
847
- for (; retries <= maxRetries; )
848
- try {
849
- return (await gt.downloadFileBatch(
850
- files.map((file) => ({
851
- fileId: file.fileId,
852
- branchId: file.branchId,
853
- versionId: file.versionId,
854
- locale: file.locale
855
- }))
856
- )).files || [];
857
- } catch {
858
- retries++, await new Promise((resolve) => setTimeout(resolve, retryDelay));
859
- }
860
- return [];
861
- }
862
- async function checkTranslationStatus(fileQueryData, downloadStatus, secrets) {
863
- overrideConfig(secrets);
864
- try {
865
- const currentQueryData = fileQueryData.filter((item) => {
866
- const statusKey = createTranslationStatusKey(
867
- item.branchId,
868
- item.fileId,
869
- item.versionId,
870
- item.locale
871
- ), stableKey = createStableTranslationKey(
872
- item.branchId,
873
- item.fileId,
874
- item.locale
875
- );
876
- return !downloadStatus.downloaded.has(statusKey) && !downloadStatus.downloaded.has(stableKey) && !downloadStatus.failed.has(statusKey) && !downloadStatus.failed.has(stableKey) && !downloadStatus.skipped.has(statusKey) && !downloadStatus.skipped.has(stableKey);
877
- });
878
- return currentQueryData.length === 0 ? !0 : ((await gt.queryFileData({
879
- translatedFiles: currentQueryData
880
- })).translatedFiles || []).filter(
881
- (translation) => translation.completedAt
882
- );
883
- } catch (error) {
884
- return console.error("Error checking translation status", error), [];
885
- }
886
- }
887
- const findLatestDraft = (documentId, client) => {
888
- const publishedId = getPublishedId(documentId), query = "*[_id == $id || _id == $draftId]", params = { id: publishedId, draftId: `drafts.${publishedId}` };
889
- return client.fetch(query, params).then(
890
- (docs) => docs.find((doc) => doc._id.startsWith("drafts.")) ?? docs[0]
891
- );
892
- }, findDocumentAtRevision = async (documentId, rev, client) => {
893
- const baseUrl = `/data/history/${client.config().dataset}/documents/${documentId}?revision=${rev}`, url = client.getUrl(baseUrl);
894
- return await fetch(url, { credentials: "include" }).then((req) => req.json()).then((req) => req.documents && req.documents.length ? req.documents[0] : null);
895
- };
896
- function randomKey() {
897
- return Math.random().toString(36).slice(2, 10);
898
- }
899
- async function createI18nDocAndPatchMetadata(sourceDocument, translatedDoc, localeId, client, translationMetadata, sourceDocumentId, languageField = "language") {
900
- const publishedSourceDocumentId = getPublishedId(sourceDocumentId);
901
- translatedDoc[languageField] = localeId;
902
- const existingLocaleKey = translationMetadata.translations.find(
903
- (translation) => translation.language === localeId
904
- ), operation = existingLocaleKey ? "replace" : "after", location = existingLocaleKey ? `translations[language == "${localeId}"]` : "translations[-1]", { _updatedAt, _createdAt, ...rest } = translatedDoc, appliedDocument = applyDocuments(
905
- publishedSourceDocumentId,
906
- sourceDocument,
907
- rest,
908
- pluginConfig.getIgnoreFields(),
909
- pluginConfig.getSkipFields(),
910
- pluginConfig.getDedupeFields(),
911
- localeId
912
- ), isSingleton = pluginConfig.getSingletons().includes(publishedSourceDocumentId);
913
- let createDocumentPromise;
914
- if (isSingleton) {
915
- const translatedDocId = pluginConfig.getSingletonMapping()(
916
- publishedSourceDocumentId,
917
- localeId
918
- );
919
- createDocumentPromise = client.create({
920
- ...appliedDocument,
921
- _type: rest._type,
922
- _id: `drafts.${translatedDocId}`
923
- });
924
- } else
925
- createDocumentPromise = client.create({
926
- ...appliedDocument,
927
- _type: rest._type,
928
- _id: "drafts."
929
- });
930
- const doc = await createDocumentPromise, _ref = getPublishedId(doc._id);
931
- await client.transaction().patch(
932
- translationMetadata._id,
933
- (p) => p.insert(operation, location, [
934
- {
935
- _key: randomKey(),
936
- language: localeId,
937
- _type: "internationalizedArrayReferenceValue",
938
- value: {
939
- _type: "reference",
940
- _ref,
941
- _weak: !0,
942
- _strengthenOnPublish: {
943
- type: doc._type
944
- }
945
- }
946
- }
947
- ])
948
- ).commit();
949
- }
950
- const getOrCreateTranslationMetadata = async (documentId, baseDocument, client, baseLanguage) => {
951
- const publishedId = getPublishedId(documentId), existingMetadata = await client.fetch(
952
- `*[
953
- _type == 'translation.metadata' &&
954
- translations[language == $baseLanguage][0].value._ref == $id
955
- ][0]`,
956
- { baseLanguage, id: publishedId }
957
- );
958
- if (existingMetadata)
959
- return existingMetadata;
960
- const baseLangEntry = {
961
- _key: randomKey(),
962
- language: baseLanguage,
963
- _type: "internationalizedArrayReferenceValue",
964
- value: {
965
- _type: "reference",
966
- _ref: getPublishedId(baseDocument._id)
967
- }
968
- };
969
- baseDocument._id.startsWith("drafts.") && (baseLangEntry.value = {
970
- ...baseLangEntry.value,
971
- _weak: !0,
972
- //this should reflect doc i18n config when this
973
- //plugin is able to take that as a config option
974
- _strengthenOnPublish: {
975
- type: baseDocument._type
1189
+ }
1190
+ if (status[0].status === "failed") {
1191
+ setupFailedMessage = status[0].error?.message || "Unknown error";
1192
+ break;
1193
+ }
1194
+ if (Date.now() - start > setupTimeoutMs) {
1195
+ setupFailedMessage = "Timed out while waiting for setup generation";
1196
+ break;
1197
+ }
1198
+ await new Promise((r) => setTimeout(r, pollInterval));
976
1199
  }
977
- });
978
- try {
979
- return await client.createIfNotExists({
980
- _id: `translation.metadata.${publishedId}`,
981
- _type: "translation.metadata",
982
- translations: [baseLangEntry]
983
- });
984
- } catch (error) {
985
- const metadata = await client.fetch(
986
- `*[
987
- _type == 'translation.metadata' &&
988
- translations[language == $baseLanguage][0].value._ref == $id
989
- ][0]`,
990
- { baseLanguage, id: publishedId }
991
- );
992
- if (metadata)
993
- return metadata;
994
- throw error;
1200
+ console.log(setupCompleted ? "Setup successfully completed" : `Setup ${setupFailedMessage ? "failed" : "timed out"} \u2014 proceeding without setup${setupFailedMessage ? ` (${setupFailedMessage})` : ""}`);
995
1201
  }
996
- }, SYSTEM_FIELDS = ["_id", "_rev", "_updatedAt", "language"], isSystemField = (field) => SYSTEM_FIELDS.includes(field);
997
- async function patchI18nDoc(sourceDocumentId, i18nDocId, sourceDocument, mergedDocument, translatedFields, client, existingDocument) {
998
- const cleanedMerge = {};
999
- Object.entries(mergedDocument).forEach(([key, value]) => {
1000
- key in translatedFields && //don't overwrite any existing system values on the i18n doc
1001
- !isSystemField(key) && (cleanedMerge[key] = value);
1002
- });
1003
- const cleanedSourceDocument = {};
1004
- Object.entries(sourceDocument).forEach(([key, value]) => {
1005
- isSystemField(key) || (cleanedSourceDocument[key] = value);
1202
+ return !0;
1203
+ }
1204
+ async function createJobs(uploadResult, localeIds, secrets) {
1205
+ return overrideConfig(secrets), await gt.enqueueFiles(uploadResult.uploadedFiles, {
1206
+ sourceLocale: gt.sourceLocale || internal.libraryDefaultLocale,
1207
+ targetLocales: localeIds
1006
1208
  });
1007
- const appliedDocument = applyDocuments(
1008
- sourceDocumentId,
1009
- cleanedSourceDocument,
1010
- cleanedMerge,
1011
- pluginConfig.getIgnoreFields(),
1012
- pluginConfig.getSkipFields()
1013
- ), dedupeFields = pluginConfig.getDedupeFields();
1014
- deleteMatchingFields(sourceDocumentId, appliedDocument, dedupeFields), existingDocument && forEachMatchingField(
1015
- sourceDocumentId,
1016
- existingDocument,
1017
- dedupeFields,
1018
- (result) => {
1019
- JSONPointer__default.default.set(appliedDocument, result.pointer, result.value);
1209
+ }
1210
+ async function downloadTranslations(files, secrets, maxRetries = 3, retryDelay = 1e3) {
1211
+ overrideConfig(secrets);
1212
+ let retries = 0;
1213
+ for (; retries <= maxRetries; )
1214
+ try {
1215
+ return (await gt.downloadFileBatch(
1216
+ files.map((file) => ({
1217
+ fileId: file.fileId,
1218
+ branchId: file.branchId,
1219
+ versionId: file.versionId,
1220
+ locale: file.locale
1221
+ }))
1222
+ )).files || [];
1223
+ } catch {
1224
+ retries++, await new Promise((resolve) => setTimeout(resolve, retryDelay));
1020
1225
  }
1021
- ), await client.patch(i18nDocId, { set: appliedDocument }).commit();
1226
+ return [];
1022
1227
  }
1023
- const documentLevelPatch = async (docInfo, translatedFields, localeId, client, languageField = "language", mergeWithTargetLocale = !1) => {
1024
- const baseLanguage = pluginConfig.getSourceLocale();
1025
- let baseDoc = null, i18nDoc = null;
1026
- docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
1027
- docInfo.documentId,
1028
- docInfo.versionId,
1029
- client
1030
- )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
1031
- const i18nDocId = (await getOrCreateTranslationMetadata(
1032
- docInfo.documentId,
1033
- baseDoc,
1034
- client,
1035
- baseLanguage
1036
- )).translations.find(
1037
- (translation) => translation.language === localeId
1038
- )?.value?._ref;
1039
- i18nDocId && (i18nDoc = await findLatestDraft(i18nDocId, client)), mergeWithTargetLocale && i18nDoc ? baseDoc = i18nDoc : docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
1040
- docInfo.documentId,
1041
- docInfo.versionId,
1042
- client
1043
- )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
1044
- const merged = BaseDocumentMerger.documentLevelMerge(
1045
- translatedFields,
1046
- baseDoc
1047
- );
1048
- if (i18nDoc)
1049
- await patchI18nDoc(
1050
- docInfo.documentId,
1051
- i18nDoc._id,
1052
- baseDoc,
1053
- merged,
1054
- translatedFields,
1055
- client,
1056
- i18nDoc
1057
- );
1058
- else {
1059
- const freshTranslationMetadata = await getOrCreateTranslationMetadata(
1060
- docInfo.documentId,
1061
- baseDoc,
1062
- client,
1063
- baseLanguage
1064
- ), freshI18nDocId = freshTranslationMetadata.translations.find(
1065
- (translation) => translation.language === localeId
1066
- )?.value?._ref;
1067
- if (freshI18nDocId) {
1068
- const freshI18nDoc = await findLatestDraft(freshI18nDocId, client);
1069
- await patchI18nDoc(
1070
- docInfo.documentId,
1071
- freshI18nDoc._id,
1072
- baseDoc,
1073
- merged,
1074
- translatedFields,
1075
- client,
1076
- freshI18nDoc
1228
+ async function checkTranslationStatus(fileQueryData, downloadStatus, secrets) {
1229
+ overrideConfig(secrets);
1230
+ try {
1231
+ const currentQueryData = fileQueryData.filter((item) => {
1232
+ const statusKey = createTranslationStatusKey(
1233
+ item.branchId,
1234
+ item.fileId,
1235
+ item.versionId,
1236
+ item.locale
1237
+ ), stableKey = createStableTranslationKey(
1238
+ item.branchId,
1239
+ item.fileId,
1240
+ item.locale
1077
1241
  );
1078
- return;
1079
- }
1080
- await createI18nDocAndPatchMetadata(
1081
- baseDoc,
1082
- merged,
1083
- localeId,
1084
- client,
1085
- freshTranslationMetadata,
1086
- docInfo.documentId,
1087
- languageField
1242
+ return !downloadStatus.downloaded.has(statusKey) && !downloadStatus.downloaded.has(stableKey) && !downloadStatus.failed.has(statusKey) && !downloadStatus.failed.has(stableKey) && !downloadStatus.skipped.has(statusKey) && !downloadStatus.skipped.has(stableKey);
1243
+ });
1244
+ return currentQueryData.length === 0 ? !0 : ((await gt.queryFileData({
1245
+ translatedFiles: currentQueryData
1246
+ })).translatedFiles || []).filter(
1247
+ (translation) => translation.completedAt
1088
1248
  );
1249
+ } catch (error) {
1250
+ return console.error("Error checking translation status", error), [];
1089
1251
  }
1090
- };
1252
+ }
1091
1253
  async function importDocument(docInfo, localeId, document2, context, mergeWithTargetLocale = !1) {
1092
1254
  const { client } = context, deserialized = deserializeDocument(document2);
1093
- return documentLevelPatch(
1255
+ return getTranslationStrategyForType(
1256
+ deserialized._type
1257
+ ).patch(
1094
1258
  docInfo,
1095
- // versionId is not used here, since we just use the _rev id in the deserialized HTML itself
1096
1259
  deserialized,
1097
1260
  localeId,
1098
1261
  client,
1099
- pluginConfig.getLanguageField(),
1100
1262
  mergeWithTargetLocale
1101
1263
  );
1102
1264
  }
@@ -1224,11 +1386,13 @@ async function processImportBatch(items, options = {}) {
1224
1386
  ), item.key),
1225
1387
  {
1226
1388
  ...options,
1227
- getConcurrencyKey: (item) => createStableTranslationKey(
1228
- void 0,
1229
- item.docInfo.documentId,
1230
- item.locale
1231
- ),
1389
+ // Serialize all locales of the same document when imports may patch the
1390
+ // source document in place: internationalized-array imports do a
1391
+ // read-merge-set, so concurrent locale imports would clobber each other
1392
+ // (last write wins). Document-level imports write to separate per-locale
1393
+ // documents and stay parallel. In 'mixed' mode the per-document strategy
1394
+ // is only known after deserialization, so serialize conservatively.
1395
+ getConcurrencyKey: (item) => pluginConfig.getTranslationLevel() === "document" ? void 0 : getPublishedId(item.docInfo.documentId),
1232
1396
  onItemSuccess: (item, key) => {
1233
1397
  successfulImports.push(key), options.onItemSuccess?.(item, key);
1234
1398
  },
@@ -1324,7 +1488,34 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1324
1488
  localeId: locale,
1325
1489
  description: gt.getLocaleProperties(locale).name,
1326
1490
  enabled: !0
1327
- })), TranslationsContext = o.createContext(null), useTranslations = () => {
1491
+ })), TranslationsContext = o.createContext(null), getUploadedVersionsStorageKey = (projectId, dataset) => `gt-sanity:uploadedVersions:${projectId ?? ""}:${dataset ?? ""}`;
1492
+ function readUploadedVersions(storageKey) {
1493
+ try {
1494
+ const raw = window.sessionStorage.getItem(storageKey);
1495
+ if (!raw) return /* @__PURE__ */ new Map();
1496
+ const parsed = JSON.parse(raw);
1497
+ return isPlainRecord(parsed) ? new Map(
1498
+ Object.entries(parsed).filter(
1499
+ (entry) => typeof entry[1] == "string"
1500
+ )
1501
+ ) : /* @__PURE__ */ new Map();
1502
+ } catch {
1503
+ return /* @__PURE__ */ new Map();
1504
+ }
1505
+ }
1506
+ function writeUploadedVersions(storageKey, versions) {
1507
+ try {
1508
+ window.sessionStorage.setItem(
1509
+ storageKey,
1510
+ JSON.stringify(Object.fromEntries(versions))
1511
+ );
1512
+ } catch {
1513
+ }
1514
+ }
1515
+ function isPlainRecord(value) {
1516
+ return typeof value == "object" && value !== null && !Array.isArray(value);
1517
+ }
1518
+ const useTranslations = () => {
1328
1519
  const context = o.useContext(TranslationsContext);
1329
1520
  if (!context)
1330
1521
  throw new Error("useTranslations must be used within TranslationsProvider");
@@ -1345,13 +1536,21 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1345
1536
  downloaded: /* @__PURE__ */ new Set(),
1346
1537
  failed: /* @__PURE__ */ new Set(),
1347
1538
  skipped: /* @__PURE__ */ new Set()
1348
- }), downloadStatusRef = o.useRef(downloadStatus), [translationStatuses, setTranslationStatuses] = o.useState(/* @__PURE__ */ new Map()), [isRefreshing, setIsRefreshing] = o.useState(!1), client = useClient(), schema2 = sanity.useSchema(), translationContext = { client, schema: schema2 }, toast = ui.useToast(), { loading: loadingSecrets, secrets } = useSecrets(
1539
+ }), downloadStatusRef = o.useRef(downloadStatus), [translationStatuses, setTranslationStatuses] = o.useState(/* @__PURE__ */ new Map()), [isRefreshing, setIsRefreshing] = o.useState(!1), client = useClient(), { projectId, dataset } = client.config(), uploadedVersionsStorageKey = getUploadedVersionsStorageKey(
1540
+ projectId,
1541
+ dataset
1542
+ ), [uploadedVersions, setUploadedVersions] = o.useState(
1543
+ () => readUploadedVersions(uploadedVersionsStorageKey)
1544
+ ), schema2 = sanity.useSchema(), translationContext = { client, schema: schema2 }, toast = ui.useToast(), { loading: loadingSecrets, secrets } = useSecrets(
1349
1545
  pluginConfig.getSecretsNamespace()
1350
1546
  ), [branchId, setBranchId] = o.useState(void 0);
1351
1547
  o.useEffect(() => {
1352
1548
  downloadStatusRef.current = downloadStatus;
1353
1549
  }, [downloadStatus]);
1354
- const fetchDocuments = o.useCallback(async () => {
1550
+ const getVersionId = o.useCallback(
1551
+ (document2) => uploadedVersions.get(getDocumentPublishedId(document2)) ?? document2._rev,
1552
+ [uploadedVersions]
1553
+ ), fetchDocuments = o.useCallback(async () => {
1355
1554
  setLoadingDocuments(!0);
1356
1555
  try {
1357
1556
  if (singleDocument) {
@@ -1423,7 +1622,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1423
1622
  const availableLocaleIds = locales.filter((locale) => locale.enabled !== !1).map((locale) => locale.localeId), transformedDocuments = documents.map((doc) => {
1424
1623
  const { [pluginConfig.getLanguageField()]: _2, ...cleanDoc } = doc, baseLanguage = pluginConfig.getSourceLocale();
1425
1624
  try {
1426
- const serialized = serializeDocument(
1625
+ const serialized = getTranslationStrategy(doc).serialize(
1427
1626
  cleanDoc,
1428
1627
  schema2,
1429
1628
  baseLanguage
@@ -1440,7 +1639,11 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1440
1639
  }
1441
1640
  return null;
1442
1641
  }).filter((doc) => doc !== null), uploadResult = await uploadFiles(transformedDocuments, secrets);
1443
- await initProject(uploadResult, { timeout: 600 }, secrets), await createJobs(uploadResult, availableLocaleIds, secrets), toast.push({
1642
+ await initProject(uploadResult, { timeout: 600 }, secrets), await createJobs(uploadResult, availableLocaleIds, secrets);
1643
+ const nextUploadedVersions = new Map(uploadedVersions);
1644
+ for (const { info } of transformedDocuments)
1645
+ info.versionId && nextUploadedVersions.set(info.documentId, info.versionId);
1646
+ writeUploadedVersions(uploadedVersionsStorageKey, nextUploadedVersions), setUploadedVersions(nextUploadedVersions), toast.push({
1444
1647
  title: `Translation tasks created for ${documents.length} documents`,
1445
1648
  status: "success",
1446
1649
  closable: !0
@@ -1455,7 +1658,14 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1455
1658
  setIsBusy(!1);
1456
1659
  }
1457
1660
  }
1458
- }, [secrets, documents, locales, schema2]), handleImportAll = o.useCallback(async () => {
1661
+ }, [
1662
+ secrets,
1663
+ documents,
1664
+ locales,
1665
+ schema2,
1666
+ uploadedVersions,
1667
+ uploadedVersionsStorageKey
1668
+ ]), handleImportAll = o.useCallback(async () => {
1459
1669
  if (!(!secrets || documents.length === 0 || !branchId)) {
1460
1670
  setIsBusy(!0);
1461
1671
  try {
@@ -1641,7 +1851,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1641
1851
  for (const localeId of availableLocaleIds) {
1642
1852
  const documentId = getDocumentPublishedId(doc);
1643
1853
  fileQueryData.push({
1644
- versionId: doc._rev,
1854
+ versionId: getVersionId(doc),
1645
1855
  fileId: documentId,
1646
1856
  branchId,
1647
1857
  locale: localeId
@@ -1656,7 +1866,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1656
1866
  const newStatuses = /* @__PURE__ */ new Map();
1657
1867
  for (const doc of documents)
1658
1868
  for (const localeId of availableLocaleIds) {
1659
- const documentId = getDocumentPublishedId(doc), versionId = doc._rev, key = createTranslationStatusKey(
1869
+ const documentId = getDocumentPublishedId(doc), versionId = getVersionId(doc), key = createTranslationStatusKey(
1660
1870
  branchId,
1661
1871
  documentId,
1662
1872
  versionId,
@@ -1699,7 +1909,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1699
1909
  setIsRefreshing(!1);
1700
1910
  }
1701
1911
  }
1702
- }, [secrets, documents, locales, branchId]), handleImportDocument = o.useCallback(
1912
+ }, [secrets, documents, locales, branchId, getVersionId]), handleImportDocument = o.useCallback(
1703
1913
  async (documentId, versionId, localeId) => {
1704
1914
  if (!secrets) return;
1705
1915
  const key = createTranslationStatusKey(
@@ -1961,6 +2171,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1961
2171
  loadingSecrets,
1962
2172
  secrets,
1963
2173
  branchId,
2174
+ getVersionId,
1964
2175
  // Actions
1965
2176
  setLocales,
1966
2177
  setAutoRefresh,
@@ -3247,7 +3458,8 @@ const WrapText = dt(ui.Box)`
3247
3458
  autoPatchReferences,
3248
3459
  setAutoPatchReferences,
3249
3460
  autoPublish,
3250
- setAutoPublish
3461
+ setAutoPublish,
3462
+ getVersionId
3251
3463
  } = useTranslations(), [isImporting, setIsImporting] = o.useState(!1), [isPublishing, setIsPublishing] = o.useState(!1), toast = ui.useToast(), document2 = documents[0], currentDocumentLanguage = o.useMemo(() => {
3252
3464
  if (!document2) return null;
3253
3465
  const languageField = pluginConfig.getLanguageField();
@@ -3257,15 +3469,15 @@ const WrapText = dt(ui.Box)`
3257
3469
  return locales.filter(
3258
3470
  (locale) => locale.enabled !== !1 && locale.localeId !== sourceLocale
3259
3471
  );
3260
- }, [locales]), documentId = o.useMemo(() => document2 ? getDocumentPublishedId(document2) : null, [document2]), handleImportTranslations = o.useCallback(
3472
+ }, [locales]), documentId = o.useMemo(() => document2 ? getDocumentPublishedId(document2) : null, [document2]), versionId = o.useMemo(() => document2 ? getVersionId(document2) : null, [document2, getVersionId]), handleImportTranslations = o.useCallback(
3261
3473
  async (options = {}) => {
3262
3474
  const { autoOnly = !1 } = options;
3263
- if (isImporting || !documentId || autoOnly && !autoImport) return;
3475
+ if (isImporting || !documentId || !versionId || autoOnly && !autoImport) return;
3264
3476
  const readyTranslations = availableLocales.filter((locale) => {
3265
3477
  const key = createTranslationStatusKey(
3266
3478
  branchId,
3267
3479
  documentId,
3268
- document2._rev,
3480
+ versionId,
3269
3481
  locale.localeId
3270
3482
  );
3271
3483
  return translationStatuses.get(key)?.isReady && !importedTranslations.has(key);
@@ -3273,11 +3485,9 @@ const WrapText = dt(ui.Box)`
3273
3485
  if (readyTranslations.length !== 0) {
3274
3486
  setIsImporting(!0);
3275
3487
  try {
3276
- await Promise.all(
3277
- readyTranslations.map(
3278
- (locale) => handleImportDocument(documentId, document2._rev, locale.localeId)
3279
- )
3280
- ), autoPatchReferences && await handlePatchDocumentReferences(), autoPublish && await handlePublishAllTranslations();
3488
+ for (const locale of readyTranslations)
3489
+ await handleImportDocument(documentId, versionId, locale.localeId);
3490
+ autoPatchReferences && await handlePatchDocumentReferences(), autoPublish && await handlePublishAllTranslations();
3281
3491
  } finally {
3282
3492
  setIsImporting(!1);
3283
3493
  }
@@ -3287,6 +3497,7 @@ const WrapText = dt(ui.Box)`
3287
3497
  autoImport,
3288
3498
  isImporting,
3289
3499
  documentId,
3500
+ versionId,
3290
3501
  availableLocales,
3291
3502
  translationStatuses,
3292
3503
  importedTranslations,
@@ -3361,7 +3572,7 @@ const WrapText = dt(ui.Box)`
3361
3572
  }
3362
3573
  )
3363
3574
  ] }),
3364
- documentId && availableLocales.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, children: [
3575
+ documentId && versionId && availableLocales.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, children: [
3365
3576
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", justify: "space-between", children: [
3366
3577
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "h2", weight: "semibold", size: 2, children: "Translation Status" }),
3367
3578
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, align: "center", children: [
@@ -3391,7 +3602,7 @@ const WrapText = dt(ui.Box)`
3391
3602
  const key = createTranslationStatusKey(
3392
3603
  branchId,
3393
3604
  documentId,
3394
- document2._rev,
3605
+ versionId,
3395
3606
  locale.localeId
3396
3607
  ), status = translationStatuses.get(key), progress = status?.progress || 0, isImported = importedTranslations.has(key);
3397
3608
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3403,7 +3614,7 @@ const WrapText = dt(ui.Box)`
3403
3614
  importFile: async () => {
3404
3615
  !isImported && status?.isReady && await handleImportDocument(
3405
3616
  documentId,
3406
- document2._rev,
3617
+ versionId,
3407
3618
  locale.localeId
3408
3619
  );
3409
3620
  }
@@ -3426,7 +3637,7 @@ const WrapText = dt(ui.Box)`
3426
3637
  const key = createTranslationStatusKey(
3427
3638
  branchId,
3428
3639
  documentId,
3429
- document2._rev,
3640
+ versionId,
3430
3641
  locale.localeId
3431
3642
  );
3432
3643
  return !translationStatuses.get(key)?.isReady || importedTranslations.has(key);
@@ -3453,7 +3664,7 @@ const WrapText = dt(ui.Box)`
3453
3664
  const key = createTranslationStatusKey(
3454
3665
  branchId,
3455
3666
  documentId,
3456
- document2._rev,
3667
+ versionId,
3457
3668
  locale.localeId
3458
3669
  );
3459
3670
  return importedTranslations.has(key);
@@ -3463,7 +3674,7 @@ const WrapText = dt(ui.Box)`
3463
3674
  const key = createTranslationStatusKey(
3464
3675
  branchId,
3465
3676
  documentId,
3466
- document2._rev,
3677
+ versionId,
3467
3678
  locale.localeId
3468
3679
  );
3469
3680
  return translationStatuses.get(key)?.isReady;
@@ -3551,9 +3762,6 @@ const WrapText = dt(ui.Box)`
3551
3762
  /* @__PURE__ */ jsxRuntime.jsx("code", { children: pluginConfig.getSourceLocale() }),
3552
3763
  " documents."
3553
3764
  ] }) });
3554
- }, TranslationTab = (props) => {
3555
- const { displayed } = props.document;
3556
- return /* @__PURE__ */ jsxRuntime.jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { singleDocument: displayed, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationView, {}) }) });
3557
3765
  }, translateAction = (props) => {
3558
3766
  const [dialogOpen, setDialogOpen] = o.useState(!1), document2 = props.draft || props.published;
3559
3767
  return {
@@ -3568,7 +3776,228 @@ const WrapText = dt(ui.Box)`
3568
3776
  content: /* @__PURE__ */ jsxRuntime.jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { singleDocument: document2, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationView, {}) }) })
3569
3777
  } : null
3570
3778
  };
3571
- }, TranslationsTable = () => {
3779
+ };
3780
+ function readGTOptions(schemaType) {
3781
+ return schemaType?.options?.gtInternationalizedArray;
3782
+ }
3783
+ function toneFromValidation(validation) {
3784
+ if (validation?.length) {
3785
+ if (validation.some((marker) => marker.level === "error")) return "critical";
3786
+ if (validation.some((marker) => marker.level === "warning")) return "caution";
3787
+ }
3788
+ }
3789
+ function RemoveLocaleButton(props) {
3790
+ const { isSource, readOnly, onRemove } = props, button = (
3791
+ // The span keeps the tooltip working when the button is disabled
3792
+ // (disabled buttons don't emit pointer events).
3793
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { paddingBottom: "2px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
3794
+ ui.Button,
3795
+ {
3796
+ mode: "bleed",
3797
+ icon: icons.RemoveCircleIcon,
3798
+ tone: "critical",
3799
+ disabled: readOnly || isSource,
3800
+ onClick: onRemove
3801
+ }
3802
+ ) })
3803
+ );
3804
+ return isSource ? /* @__PURE__ */ jsxRuntime.jsx(
3805
+ ui.Tooltip,
3806
+ {
3807
+ animate: !0,
3808
+ portal: !0,
3809
+ placement: "top",
3810
+ fallbackPlacements: ["right", "left"],
3811
+ content: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, size: 1, children: "Can't remove the source language" }),
3812
+ children: button
3813
+ }
3814
+ ) : button;
3815
+ }
3816
+ function InternationalizedValueItem(props) {
3817
+ const { inputProps, parentSchemaType, schemaType, validation } = props, value = props.value, { onChange } = inputProps, gtOptions = readGTOptions(schemaType) ?? readGTOptions(parentSchemaType), language = value?.language, languageLabel = language ? gtOptions?.titles?.[language] ?? language : "Unknown language", isSource = !!language && language === gtOptions?.sourceLocale, wrappedOnChange = o.useCallback(
3818
+ (patch) => {
3819
+ if (!Array.isArray(patch)) {
3820
+ onChange(patch);
3821
+ return;
3822
+ }
3823
+ const currentValue = value?.value;
3824
+ if (!((currentValue == null || Array.isArray(currentValue) && currentValue.length === 0) && patch.some(
3825
+ (p) => p.type === "insert" && Array.isArray(p.path) && p.path.length > 0 && (p.path[0] === "value" || typeof p.path[0] == "number")
3826
+ ))) {
3827
+ onChange(patch);
3828
+ return;
3829
+ }
3830
+ const rerooted = patch.map(
3831
+ (p) => p.type === "insert" && Array.isArray(p.path) && p.path[0] !== "value" ? { ...p, path: ["value", ...p.path] } : p
3832
+ );
3833
+ onChange(
3834
+ currentValue === void 0 ? [sanity.setIfMissing([], ["value"]), ...rerooted] : rerooted
3835
+ );
3836
+ },
3837
+ [onChange, value?.value]
3838
+ ), members = o.useMemo(
3839
+ () => inputProps.members.filter(
3840
+ (member) => member.kind === "field" && member.name === "value"
3841
+ ).map((member) => ({
3842
+ ...member,
3843
+ field: {
3844
+ ...member.field,
3845
+ schemaType: {
3846
+ ...member.field.schemaType,
3847
+ title: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { muted: !0, size: 1, children: languageLabel })
3848
+ }
3849
+ }
3850
+ })),
3851
+ [inputProps.members, languageLabel]
3852
+ ), handleRemove = o.useCallback(() => {
3853
+ onChange(sanity.unset());
3854
+ }, [onChange]);
3855
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { paddingTop: 2, tone: toneFromValidation(validation), children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "flex-end", gap: 2, children: [
3856
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { flex: 1, children: inputProps.renderInput({
3857
+ ...inputProps,
3858
+ members,
3859
+ onChange: wrappedOnChange
3860
+ // renderInput's parameter is typed as the base InputProps, which
3861
+ // doesn't know about object members.
3862
+ }) }),
3863
+ /* @__PURE__ */ jsxRuntime.jsx(
3864
+ RemoveLocaleButton,
3865
+ {
3866
+ isSource,
3867
+ readOnly: !!inputProps.readOnly,
3868
+ onRemove: handleRemove
3869
+ }
3870
+ )
3871
+ ] }) });
3872
+ }
3873
+ function InternationalizedArrayInput(props) {
3874
+ const { members, schemaType, value, readOnly, onItemAppend } = props, gtOptions = readGTOptions(schemaType), itemType = schemaType.of[0]?.name, handleAdd = o.useCallback(
3875
+ (language) => {
3876
+ const item = { _key: randomKey(), _type: itemType, language };
3877
+ onItemAppend(item);
3878
+ },
3879
+ [onItemAppend, itemType]
3880
+ );
3881
+ if (!gtOptions)
3882
+ return props.renderDefault(props);
3883
+ const allLocales = [gtOptions.sourceLocale, ...gtOptions.locales], existing = new Set(
3884
+ (value ?? []).map((item) => item.language).filter(Boolean)
3885
+ ), missing = allLocales.filter((locale) => !existing.has(locale));
3886
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
3887
+ members.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 2, children: members.map(
3888
+ (member) => member.kind === "item" ? /* @__PURE__ */ jsxRuntime.jsx(sanity.ArrayOfObjectsItem, { ...props, member }, member.key) : /* @__PURE__ */ jsxRuntime.jsx(sanity.MemberItemError, { member }, member.key)
3889
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { border: !0, tone: "transparent", padding: 3, radius: 2, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "This internationalized field currently has no translations." }) }),
3890
+ !readOnly && missing.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Grid, { columns: Math.min(missing.length, 4), gap: 1, children: missing.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(
3891
+ ui.Button,
3892
+ {
3893
+ icon: icons.AddIcon,
3894
+ mode: "ghost",
3895
+ text: gtOptions.titles?.[locale] ?? locale,
3896
+ onClick: () => handleAdd(locale)
3897
+ },
3898
+ locale
3899
+ )) }) : null
3900
+ ] });
3901
+ }
3902
+ const DEFAULT_TYPE_PREFIX = "internationalizedArray";
3903
+ function resolveComponents(overrides) {
3904
+ const input = overrides?.input === !1 ? void 0 : overrides?.input ?? InternationalizedArrayInput, item = overrides?.item === !1 ? void 0 : overrides?.item ?? InternationalizedValueItem, defaultField = input === InternationalizedArrayInput ? (
3905
+ // Reset the field level so inline per-locale inputs don't inherit
3906
+ // nested-object indentation.
3907
+ ((fieldProps) => fieldProps.renderDefault({ ...fieldProps, level: 0 }))
3908
+ ) : void 0, field = overrides?.field === !1 ? void 0 : overrides?.field ?? defaultField;
3909
+ return { input, item, field };
3910
+ }
3911
+ function capitalize(value) {
3912
+ return value.charAt(0).toUpperCase() + value.slice(1);
3913
+ }
3914
+ function fieldTypeName(fieldType) {
3915
+ return typeof fieldType == "string" ? fieldType : fieldType.name;
3916
+ }
3917
+ function valueField(fieldType) {
3918
+ if (fieldType === "string" || fieldType === "text")
3919
+ return { name: "value", type: fieldType, title: "Value" };
3920
+ if (fieldType === "block")
3921
+ return {
3922
+ name: "value",
3923
+ type: "array",
3924
+ title: "Value",
3925
+ of: [{ type: "block" }]
3926
+ };
3927
+ const { name: _name, ...rest } = fieldType;
3928
+ return { name: "value", title: "Value", ...rest };
3929
+ }
3930
+ function makeLanguageTitle(options) {
3931
+ const { languageTitles, getLanguageTitle, sourceLocale } = options;
3932
+ return (locale) => getLanguageTitle?.(locale) ?? languageTitles?.[locale] ?? generaltranslation.getLocaleProperties(locale, sourceLocale).name ?? locale;
3933
+ }
3934
+ function buildTypesForPrefix(prefix2, options) {
3935
+ const { sourceLocale, locales, fieldTypes } = options, languageTitle = makeLanguageTitle(options), components = resolveComponents(options.components);
3936
+ return fieldTypes.map((fieldType) => {
3937
+ const typeName = `${prefix2}${capitalize(fieldTypeName(fieldType))}`, valueTypeName = `${typeName}Value`, gtInternationalizedArray = {
3938
+ sourceLocale,
3939
+ locales,
3940
+ titles: Object.fromEntries(
3941
+ [sourceLocale, ...locales].map((locale) => [
3942
+ locale,
3943
+ languageTitle(locale)
3944
+ ])
3945
+ )
3946
+ }, valueObject = {
3947
+ type: "object",
3948
+ name: valueTypeName,
3949
+ ...components.item ? { components: { item: components.item } } : {},
3950
+ fields: [
3951
+ {
3952
+ name: "language",
3953
+ type: "string",
3954
+ title: "Language",
3955
+ readOnly: !0,
3956
+ // GT's inline item shows the locale as the value field's label;
3957
+ // with a custom or detached item, keep the field visible so the
3958
+ // locale is still discoverable in default/dialog rendering.
3959
+ hidden: components.item === InternationalizedValueItem
3960
+ },
3961
+ valueField(fieldType)
3962
+ ],
3963
+ options: { gtInternationalizedArray },
3964
+ preview: {
3965
+ select: { language: "language", value: "value" },
3966
+ prepare(selection) {
3967
+ const localeLabel = selection.language ? languageTitle(selection.language) : "Unknown";
3968
+ return {
3969
+ title: typeof selection.value == "string" ? selection.value : localeLabel,
3970
+ subtitle: localeLabel
3971
+ };
3972
+ }
3973
+ }
3974
+ }, arrayComponents = {
3975
+ ...components.input ? { input: components.input } : {},
3976
+ ...components.field ? { field: components.field } : {}
3977
+ };
3978
+ return {
3979
+ name: typeName,
3980
+ type: "array",
3981
+ ...Object.keys(arrayComponents).length ? { components: arrayComponents } : {},
3982
+ of: [valueObject],
3983
+ options: { gtInternationalizedArray }
3984
+ };
3985
+ });
3986
+ }
3987
+ function createInternationalizedArrayTypes(options) {
3988
+ const prefix2 = options.typePrefix ?? DEFAULT_TYPE_PREFIX, includeCompatibilityTypes = options.includeCompatibilityTypes ?? !0, types = buildTypesForPrefix(prefix2, options);
3989
+ return includeCompatibilityTypes && prefix2 !== DEFAULT_TYPE_PREFIX && types.push(...buildTypesForPrefix(DEFAULT_TYPE_PREFIX, options)), types;
3990
+ }
3991
+ function resolveFieldLevelConfig(config) {
3992
+ return {
3993
+ enabled: !1,
3994
+ fieldTypes: ["string", "text"],
3995
+ typePrefix: DEFAULT_TYPE_PREFIX,
3996
+ includeCompatibilityTypes: !0,
3997
+ ...config
3998
+ };
3999
+ }
4000
+ const TranslationsTable = () => {
3572
4001
  const {
3573
4002
  documents,
3574
4003
  locales,
@@ -3577,7 +4006,8 @@ const WrapText = dt(ui.Box)`
3577
4006
  downloadStatus,
3578
4007
  importedTranslations,
3579
4008
  handleImportDocument,
3580
- branchId
4009
+ branchId,
4010
+ getVersionId
3581
4011
  } = useTranslations();
3582
4012
  return loadingDocuments ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { style: { maxHeight: "60vh", overflowY: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 2, children: documents.map((document2) => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { shadow: 1, padding: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
3583
4013
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, { flex: 1, children: [
@@ -3585,10 +4015,10 @@ const WrapText = dt(ui.Box)`
3585
4015
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 0, muted: !0, style: { marginTop: "2px" }, children: document2._type })
3586
4016
  ] }) }),
3587
4017
  /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 2, children: locales.length > 0 ? locales.filter((locale) => locale.enabled !== !1).map((locale) => {
3588
- const documentId = getDocumentPublishedId(document2), key = createTranslationStatusKey(
4018
+ const documentId = getDocumentPublishedId(document2), versionId = getVersionId(document2), key = createTranslationStatusKey(
3589
4019
  branchId,
3590
4020
  documentId,
3591
- document2._rev,
4021
+ versionId,
3592
4022
  locale.localeId
3593
4023
  ), status = translationStatuses.get(key), isDownloaded = downloadStatus.downloaded.has(key), isImported = importedTranslations.has(key);
3594
4024
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3600,12 +4030,12 @@ const WrapText = dt(ui.Box)`
3600
4030
  importFile: async () => {
3601
4031
  await handleImportDocument(
3602
4032
  documentId,
3603
- document2._rev,
4033
+ versionId,
3604
4034
  locale.localeId
3605
4035
  );
3606
4036
  }
3607
4037
  },
3608
- `${document2._id}-${document2._rev}-${locale.localeId}`
4038
+ `${document2._id}-${versionId}-${locale.localeId}`
3609
4039
  );
3610
4040
  }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "No locales configured" }) })
3611
4041
  ] }) }, document2._id)) }) });
@@ -3959,7 +4389,10 @@ const WrapText = dt(ui.Box)`
3959
4389
  }
3960
4390
  )
3961
4391
  ] });
3962
- }, TranslationsTool = () => /* @__PURE__ */ jsxRuntime.jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsToolContent, {}) }) }), gtPlugin = sanity.definePlugin(
4392
+ }, TranslationsTool = () => /* @__PURE__ */ jsxRuntime.jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsToolContent, {}) }) }), TranslationTab = (props) => {
4393
+ const { displayed } = props.document;
4394
+ return /* @__PURE__ */ jsxRuntime.jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { singleDocument: displayed, children: /* @__PURE__ */ jsxRuntime.jsx(TranslationView, {}) }) });
4395
+ }, gtPlugin = sanity.definePlugin(
3963
4396
  ({
3964
4397
  languageField = "language",
3965
4398
  sourceLocale,
@@ -3979,11 +4412,16 @@ const WrapText = dt(ui.Box)`
3979
4412
  additionalSerializers = {},
3980
4413
  additionalDeserializers = {},
3981
4414
  additionalBlockDeserializers = [],
3982
- showDocumentInternationalization = !0
4415
+ showDocumentInternationalization = !0,
4416
+ internationalizedArray,
4417
+ fieldLevelLocalization,
4418
+ translationLevel = "document",
4419
+ fieldLevelDocuments
3983
4420
  }) => {
3984
- const resolvedSourceLocale = sourceLocale ?? defaultLocale ?? internal.libraryDefaultLocale;
3985
- let normalizedTranslateDocuments;
3986
- translateDocuments && (normalizedTranslateDocuments = translateDocuments.map((entry) => typeof entry == "string" ? { type: entry } : entry).filter((filter2) => filter2.documentId || filter2.type)), pluginConfig.init(
4421
+ const resolvedSourceLocale = sourceLocale ?? defaultLocale ?? internal.libraryDefaultLocale, normalizeFilters = (entries) => entries?.map((entry) => typeof entry == "string" ? { type: entry } : entry).filter((filter2) => filter2.documentId || filter2.type), normalizedTranslateDocuments = normalizeFilters(translateDocuments), normalizedFieldLevelDocuments = normalizeFilters(fieldLevelDocuments) ?? [], fieldLevelConfig = resolveFieldLevelConfig(
4422
+ internationalizedArray ?? fieldLevelLocalization
4423
+ );
4424
+ pluginConfig.init(
3987
4425
  secretsNamespace,
3988
4426
  languageField,
3989
4427
  resolvedSourceLocale,
@@ -3998,17 +4436,22 @@ const WrapText = dt(ui.Box)`
3998
4436
  additionalStopTypes,
3999
4437
  additionalSerializers,
4000
4438
  additionalDeserializers,
4001
- additionalBlockDeserializers
4439
+ additionalBlockDeserializers,
4440
+ translationLevel,
4441
+ normalizedFieldLevelDocuments,
4442
+ fieldLevelConfig.typePrefix
4002
4443
  ), gt.setConfig({
4003
4444
  sourceLocale: resolvedSourceLocale,
4004
4445
  customMapping,
4005
4446
  apiKey,
4006
4447
  projectId
4007
4448
  });
4449
+ const arrayLocalizedTypes = /* @__PURE__ */ new Set();
4450
+ translationLevel === "internationalizedArray" ? normalizedTranslateDocuments?.map((filter2) => filter2.type).filter((type) => !!type).forEach((type) => arrayLocalizedTypes.add(type)) : translationLevel === "mixed" && normalizedFieldLevelDocuments.map((filter2) => filter2.type).filter((type) => !!type).forEach((type) => arrayLocalizedTypes.add(type));
4008
4451
  const plugins = [];
4009
4452
  if (showDocumentInternationalization) {
4010
- const schemaTypes = normalizedTranslateDocuments?.map((filter2) => filter2.type).filter((type) => !!type) ?? [];
4011
- if (schemaTypes.length > 0) {
4453
+ const schemaTypes2 = normalizedTranslateDocuments?.map((filter2) => filter2.type).filter((type) => !!type).filter((type) => !arrayLocalizedTypes.has(type)) ?? [];
4454
+ if (schemaTypes2.length > 0) {
4012
4455
  const supportedLanguages = [resolvedSourceLocale, ...locales].map((locale) => {
4013
4456
  const props = generaltranslation.getLocaleProperties(locale, resolvedSourceLocale);
4014
4457
  return { id: locale, title: props.name };
@@ -4016,15 +4459,28 @@ const WrapText = dt(ui.Box)`
4016
4459
  plugins.push(
4017
4460
  documentInternationalization.documentInternationalization({
4018
4461
  supportedLanguages,
4019
- schemaTypes,
4462
+ schemaTypes: schemaTypes2,
4020
4463
  languageField
4021
4464
  })
4022
4465
  );
4023
4466
  }
4024
4467
  }
4468
+ const schemaTypes = fieldLevelConfig.enabled ? createInternationalizedArrayTypes({
4469
+ sourceLocale: resolvedSourceLocale,
4470
+ locales,
4471
+ fieldTypes: fieldLevelConfig.fieldTypes,
4472
+ languageTitles: fieldLevelConfig.languageTitles,
4473
+ getLanguageTitle: fieldLevelConfig.getLanguageTitle,
4474
+ typePrefix: fieldLevelConfig.typePrefix,
4475
+ includeCompatibilityTypes: fieldLevelConfig.includeCompatibilityTypes,
4476
+ components: fieldLevelConfig.components
4477
+ }) : [];
4025
4478
  return {
4026
4479
  name: "gt-sanity",
4027
4480
  plugins,
4481
+ schema: {
4482
+ types: schemaTypes
4483
+ },
4028
4484
  tools: [
4029
4485
  {
4030
4486
  name: "translations",
@@ -4074,6 +4530,7 @@ exports.BaseDocumentMerger = BaseDocumentMerger;
4074
4530
  exports.BaseDocumentSerializer = BaseDocumentSerializer;
4075
4531
  exports.TranslationsTab = TranslationTab;
4076
4532
  exports.attachGTData = attachGTData;
4533
+ exports.createInternationalizedArrayTypes = createInternationalizedArrayTypes;
4077
4534
  exports.customSerializers = customSerializers;
4078
4535
  exports.defaultStopTypes = defaultStopTypes;
4079
4536
  exports.detachGTData = detachGTData;