gt-sanity 2.0.21 → 2.1.0

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.js CHANGED
@@ -1,21 +1,21 @@
1
- import { documentInternationalization } from "@sanity/document-internationalization";
2
- import { DocumentInternationalizationMenu, documentInternationalization as documentInternationalization2, useDeleteTranslationAction, useDocumentInternationalizationContext, useDuplicateWithTranslationsAction } from "@sanity/document-internationalization";
1
+ import { GT, getLocaleProperties } from "generaltranslation";
2
+ import { libraryDefaultLocale } from "generaltranslation/internal";
3
+ import { useClient as useClient$1, useSchema, setIfMissing, unset, ArrayOfObjectsItem, MemberItemError, definePlugin } from "sanity";
4
+ import { Link, route } from "sanity/router";
3
5
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
6
+ import o, { useState, useEffect, createContext, useRef, useCallback, useContext, useDebugValue, createElement, useMemo } from "react";
7
+ import { CheckmarkCircleIcon, DownloadIcon, LinkIcon, PublishIcon, TranslateIcon, AddIcon, RemoveCircleIcon } from "@sanity/icons";
4
8
  import { ThemeProvider, ToastProvider, Box, Flex, Spinner, Card, Text, useToast, Label, Grid, Button, Switch, Stack, Tooltip, Dialog, Container, Heading } from "@sanity/ui";
5
9
  import { buildTheme } from "@sanity/ui/theme";
6
- import o, { useState, useEffect, createContext, useRef, useCallback, useContext, useDebugValue, createElement, useMemo } from "react";
7
- import { useClient as useClient$1, useSchema, definePlugin } from "sanity";
8
- import { GT, getLocaleProperties } from "generaltranslation";
9
- import { libraryDefaultLocale } from "generaltranslation/internal";
10
+ import { extractWithPath, arrayToJSONMatchPath } from "@sanity/mutator";
11
+ import { JSONPath } from "jsonpath-plus";
12
+ import JSONPointer from "jsonpointer";
10
13
  import { htmlToBlocks } from "@portabletext/block-tools";
11
14
  import { Schema } from "@sanity/schema";
12
15
  import { toHTML } from "@portabletext/to-html";
13
16
  import merge from "lodash.merge";
14
- import { JSONPath } from "jsonpath-plus";
15
- import JSONPointer from "jsonpointer";
16
- import { extractWithPath, arrayToJSONMatchPath } from "@sanity/mutator";
17
- import { CheckmarkCircleIcon, DownloadIcon, LinkIcon, PublishIcon, TranslateIcon } from "@sanity/icons";
18
- import { Link, route } from "sanity/router";
17
+ import { documentInternationalization } from "@sanity/document-internationalization";
18
+ import { DocumentInternationalizationMenu, documentInternationalization as documentInternationalization2, useDeleteTranslationAction, useDocumentInternationalizationContext, useDuplicateWithTranslationsAction } from "@sanity/document-internationalization";
19
19
  const useClient = () => useClient$1({ apiVersion: "2025-09-15" });
20
20
  function useSecrets(id) {
21
21
  const [loading, setLoading] = useState(!0), [secrets, setSecrets] = useState(null), client = useClient();
@@ -53,9 +53,12 @@ class GTConfig {
53
53
  additionalSerializers;
54
54
  additionalDeserializers;
55
55
  additionalBlockDeserializers;
56
+ translationLevel;
57
+ fieldLevelDocuments;
58
+ fieldLevelTypePrefix;
56
59
  static instance;
57
- constructor(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = []) {
58
- 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;
60
+ constructor(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = [], translationLevel = "document", fieldLevelDocuments = [], fieldLevelTypePrefix = "internationalizedArray") {
61
+ 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;
59
62
  }
60
63
  static getInstance() {
61
64
  return this.instance || (this.instance = new GTConfig(
@@ -75,8 +78,8 @@ class GTConfig {
75
78
  []
76
79
  )), this.instance;
77
80
  }
78
- init(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = []) {
79
- 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;
81
+ init(secretsNamespace, languageField, sourceLocale, locales, singletons, singletonMapping, ignoreFields, dedupeFields, skipFields, translateDocuments, additionalStopTypes = [], additionalSerializers = {}, additionalDeserializers = { types: {} }, additionalBlockDeserializers = [], translationLevel = "document", fieldLevelDocuments = [], fieldLevelTypePrefix = "internationalizedArray") {
82
+ 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;
80
83
  }
81
84
  getSecretsNamespace() {
82
85
  return this.secretsNamespace;
@@ -120,6 +123,15 @@ class GTConfig {
120
123
  getAdditionalBlockDeserializers() {
121
124
  return this.additionalBlockDeserializers;
122
125
  }
126
+ getTranslationLevel() {
127
+ return this.translationLevel;
128
+ }
129
+ getFieldLevelDocuments() {
130
+ return this.fieldLevelDocuments;
131
+ }
132
+ getFieldLevelTypePrefix() {
133
+ return this.fieldLevelTypePrefix;
134
+ }
123
135
  }
124
136
  const pluginConfig = GTConfig.getInstance(), theme = buildTheme(), BaseTranslationWrapper = ({
125
137
  children,
@@ -133,21 +145,21 @@ const pluginConfig = GTConfig.getInstance(), theme = buildTheme(), BaseTranslati
133
145
  !loadingSecrets && secrets && children
134
146
  ] });
135
147
  return /* @__PURE__ */ jsx(ThemeProvider, { theme, children: /* @__PURE__ */ jsx(ToastProvider, { paddingY: 7, children: showContainer ? /* @__PURE__ */ jsx(Box, { padding, children: content }) : content }) });
136
- }, isRecord$4 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), reconcileArray = (origArray, translatedArray) => {
148
+ }, isRecord$5 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), reconcileArray = (origArray, translatedArray) => {
137
149
  if (translatedArray && translatedArray.some((el) => typeof el == "string"))
138
150
  return translatedArray;
139
151
  const combined = JSON.parse(JSON.stringify(origArray));
140
152
  return translatedArray.forEach((translatedItem) => {
141
- if (!isRecord$4(translatedItem) || !translatedItem._key)
153
+ if (!isRecord$5(translatedItem) || !translatedItem._key)
142
154
  return;
143
155
  const foundBlockIdx = origArray.findIndex(
144
- (origBlock) => isRecord$4(origBlock) && origBlock._key === translatedItem._key
156
+ (origBlock) => isRecord$5(origBlock) && origBlock._key === translatedItem._key
145
157
  );
146
158
  foundBlockIdx < 0 ? console.warn(
147
159
  `This block no longer exists on the original document. Was it removed? ${JSON.stringify(
148
160
  translatedItem
149
161
  )}`
150
- ) : isRecord$4(origArray[foundBlockIdx]) && (origArray[foundBlockIdx]._type === "block" || origArray[foundBlockIdx]._type === "span") ? combined[foundBlockIdx] = translatedItem : isRecord$4(origArray[foundBlockIdx]) && (combined[foundBlockIdx] = reconcileObject(
162
+ ) : isRecord$5(origArray[foundBlockIdx]) && (origArray[foundBlockIdx]._type === "block" || origArray[foundBlockIdx]._type === "span") ? combined[foundBlockIdx] = translatedItem : isRecord$5(origArray[foundBlockIdx]) && (combined[foundBlockIdx] = reconcileObject(
151
163
  origArray[foundBlockIdx],
152
164
  translatedItem
153
165
  ));
@@ -160,8 +172,8 @@ const pluginConfig = GTConfig.getInstance(), theme = buildTheme(), BaseTranslati
160
172
  !value || key[0] === "_" || (typeof value == "string" ? updatedObj[key] = value : Array.isArray(value) ? updatedObj[key] = reconcileArray(
161
173
  Array.isArray(origObject[key]) ? origObject[key] : [],
162
174
  value
163
- ) : isRecord$4(value) && (updatedObj[key] = reconcileObject(
164
- isRecord$4(origObject[key]) ? origObject[key] : {},
175
+ ) : isRecord$5(value) && (updatedObj[key] = reconcileObject(
176
+ isRecord$5(origObject[key]) ? origObject[key] : {},
165
177
  value
166
178
  )));
167
179
  }), updatedObj;
@@ -182,7 +194,7 @@ const pluginConfig = GTConfig.getInstance(), theme = buildTheme(), BaseTranslati
182
194
  origVal ?? [],
183
195
  translatedVal
184
196
  ) : typeof translatedVal == "object" && Object.keys(translatedVal).length && (valToPatch = reconcileObject(
185
- isRecord$4(origVal) ? origVal : {},
197
+ isRecord$5(origVal) ? origVal : {},
186
198
  translatedVal
187
199
  ));
188
200
  const destinationPath = [
@@ -201,197 +213,683 @@ const pluginConfig = GTConfig.getInstance(), theme = buildTheme(), BaseTranslati
201
213
  documentLevelMerge,
202
214
  reconcileArray,
203
215
  reconcileObject
204
- }, defaultSchema = Schema.compile({
205
- name: "default",
206
- types: [
207
- {
208
- type: "object",
209
- name: "default",
210
- fields: [
211
- {
212
- name: "block",
213
- type: "array",
214
- of: [{ type: "block" }]
215
- }
216
- ]
217
- }
218
- ]
219
- }), blockContentType = defaultSchema.get("default").fields.find((field) => field.name === "block").type, preprocess = (html) => {
220
- const intermediateBlocks = htmlToBlocks(
221
- `<p>${html}</p>`,
222
- blockContentType
223
- );
224
- if (!intermediateBlocks.length)
225
- throw new Error(`Error parsing string '${html}'`);
226
- return intermediateBlocks[0].children[0].text;
227
216
  };
228
- function attachGTData(html, data, type) {
229
- const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
230
- if (!firstElement)
231
- return html;
232
- const encodedData = encode(JSON.stringify({ [type]: data }));
233
- return firstElement.setAttribute("data-gt-internal", encodedData), firstElement.outerHTML;
217
+ function getPublishedId(documentId) {
218
+ return documentId.startsWith("drafts.") ? documentId.slice(7) : documentId;
234
219
  }
235
- function detachGTData(html) {
236
- const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
237
- if (!firstElement)
238
- return { html };
239
- const encodedData = firstElement.getAttribute("data-gt-internal");
240
- let extractedData;
241
- if (encodedData)
242
- try {
243
- const decodedData = decode(encodedData);
244
- extractedData = JSON.parse(decodedData), firstElement.removeAttribute("data-gt-internal");
245
- } catch (error) {
246
- console.warn("Failed to decode GT internal data:", error);
247
- }
248
- return {
249
- html: firstElement.outerHTML,
250
- data: extractedData
251
- };
220
+ function getDocumentPublishedId(document2) {
221
+ return getPublishedId(document2._id);
252
222
  }
253
- function encode(data) {
254
- const bytes = new TextEncoder().encode(data);
255
- let binary = "";
256
- for (let i = 0; i < bytes.length; i++)
257
- binary += String.fromCharCode(bytes[i]);
258
- return btoa(binary);
223
+ function dedupeDocumentsPreferDraft(documents) {
224
+ const byPublishedId = /* @__PURE__ */ new Map();
225
+ for (const document2 of documents) {
226
+ const publishedId = getDocumentPublishedId(document2);
227
+ (!byPublishedId.get(publishedId) || document2._id.startsWith("drafts.")) && byPublishedId.set(publishedId, document2);
228
+ }
229
+ return Array.from(byPublishedId.values());
259
230
  }
260
- function decode(base64) {
261
- const binary = atob(base64), bytes = new Uint8Array(binary.length);
262
- for (let i = 0; i < binary.length; i++)
263
- bytes[i] = binary.charCodeAt(i);
264
- return new TextDecoder().decode(bytes);
231
+ function createTranslationStatusKey(branchId, documentId, versionId, localeId) {
232
+ const publishedId = getPublishedId(documentId);
233
+ return branchId ? `${branchId}:${publishedId}:${versionId}:${localeId}` : `${publishedId}:${versionId}:${localeId}`;
265
234
  }
266
- const isRecord$3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), defaultStopTypes = [
267
- "reference",
268
- "date",
269
- "datetime",
270
- "file",
271
- "geopoint",
272
- "image",
273
- "number",
274
- "crop",
275
- "hotspot",
276
- "boolean",
277
- "url",
278
- "color",
279
- "code"
280
- ], defaultMarks = {}, defaultPortableTextBlockStyles = {
281
- normal: ({ value, children }) => `<p id="${value._key}">${children}</p>`,
282
- blockquote: ({ value, children }) => `<blockquote id="${value._key}">${children}</blockquote>`,
283
- h1: ({ value, children }) => `<h1 id="${value._key}">${children}</h1>`,
284
- h2: ({ value, children }) => `<h2 id="${value._key}">${children}</h2>`,
285
- h3: ({ value, children }) => `<h3 id="${value._key}">${children}</h3>`,
286
- h4: ({ value, children }) => `<h4 id="${value._key}">${children}</h4>`,
287
- h5: ({ value, children }) => `<h5 id="${value._key}">${children}</h5>`,
288
- h6: ({ value, children }) => `<h6 id="${value._key}">${children}</h6>`
289
- }, defaultLists = {
290
- number: ({ value, children }) => `<ol id="${value._key.replace("-parent", "")}">${children}</ol>`,
291
- bullet: ({ value, children }) => `<ul id="${value._key.replace("-parent", "")}">${children}</ul>`
292
- }, defaultListItem = ({
293
- value,
294
- children
295
- }) => {
296
- const { _key, level } = value;
297
- return `<li id="${(_key || "").replace("-parent", "")}" data-level="${level}">${children}</li>`;
298
- }, unknownBlockFunc = ({ value, children }) => `<p id="${value._key}" data-type="unknown-block-style" data-style="${value.style}">${children}</p>`, customSerializers = {
299
- unknownType: ({ value }) => `<div class="${value._type}"></div>`,
300
- types: {},
301
- marks: defaultMarks,
302
- block: defaultPortableTextBlockStyles,
303
- list: defaultLists,
304
- listItem: defaultListItem,
305
- unknownBlockStyle: unknownBlockFunc
306
- }, customDeserializers = { types: {} }, customBlockDeserializers = [
307
- // handle marks with data-gt-internal
308
- {
309
- deserialize(node2, next2) {
310
- if (node2.nodeType !== 1)
311
- return;
312
- const el = node2;
313
- if (!el.hasChildNodes() || !el.getAttribute("data-gt-internal"))
314
- return;
315
- const { html, data } = detachGTData(el.outerHTML), block = htmlToBlocks(html, blockContentType)[0], children = next2(el.childNodes);
316
- let markDefs = [];
317
- return "markDefs" in block && (markDefs = block.markDefs ?? []), data?.markDef && markDefs.push(data.markDef), Array.isArray(children) && children.forEach((child) => {
318
- if (!isRecord$3(child))
319
- return;
320
- const marks = Array.isArray(child.marks) ? child.marks : [];
321
- child.marks = data?.markDef?._key ? [...marks, data.markDef._key] : marks;
322
- }), {
323
- ...block,
324
- markDefs,
325
- children
326
- };
327
- }
328
- },
329
- //handle undeclared styles
330
- {
331
- deserialize(node2, next2) {
332
- if (node2.nodeType !== 1)
333
- return;
334
- const el = node2;
335
- if (!el.hasChildNodes() || el.getAttribute("data-type") !== "unknown-block-style")
336
- return;
337
- const style = el.getAttribute("data-style") ?? "";
338
- return {
339
- ...htmlToBlocks(el.outerHTML, blockContentType)[0],
340
- style,
341
- children: next2(el.childNodes)
342
- };
343
- }
344
- },
345
- //handle list items
346
- {
347
- deserialize(node2, next2) {
348
- if (node2.nodeType !== 1)
349
- return;
350
- const el = node2;
351
- if (!el.hasChildNodes() || el.tagName.toLowerCase() !== "li")
352
- return;
353
- const tagsToStyle = {
354
- ul: "bullet",
355
- ol: "number"
356
- }, parent = el.parentNode;
357
- if (!parent || !parent.tagName)
358
- return;
359
- const listItem = tagsToStyle[parent.tagName.toLowerCase()];
360
- if (!listItem)
361
- return;
362
- const level = el.getAttribute("data-level") && parseInt(el.getAttribute("data-level") || "0", 10), _key = el.id;
363
- let block = htmlToBlocks(parent.outerHTML, blockContentType)[0];
364
- const customStyle = el.children?.[0]?.getAttribute("data-style");
365
- if (new RegExp(/<("[^"]*"|'[^']*'|[^'">])*>/).test(el.innerHTML)) {
366
- const newBlock = htmlToBlocks(el.innerHTML, blockContentType)[0];
367
- if (newBlock && (block = {
368
- ...block,
369
- ...newBlock,
370
- // @ts-ignore
371
- style: customStyle ?? newBlock.style
372
- }, customStyle))
373
- return block;
235
+ function createStableTranslationKey(branchId, documentId, localeId) {
236
+ const publishedId = getPublishedId(documentId);
237
+ return branchId ? `${branchId}:${publishedId}:${localeId}` : `${publishedId}:${localeId}`;
238
+ }
239
+ const findLatestDraft = (documentId, client) => {
240
+ const publishedId = getPublishedId(documentId), query = "*[_id == $id || _id == $draftId]", params = { id: publishedId, draftId: `drafts.${publishedId}` };
241
+ return client.fetch(query, params).then(
242
+ (docs) => docs.find((doc) => doc._id.startsWith("drafts.")) ?? docs[0]
243
+ );
244
+ }, findDocumentAtRevision = async (documentId, rev, client) => {
245
+ const baseUrl = `/data/history/${client.config().dataset}/documents/${documentId}?revision=${rev}`, url = client.getUrl(baseUrl);
246
+ return await fetch(url, { credentials: "include" }).then((req) => req.json()).then((req) => req.documents && req.documents.length ? req.documents[0] : null);
247
+ };
248
+ function forEachMatchingField(documentId, document2, fields, callback) {
249
+ const applicable = fields.filter(
250
+ (field) => field.documentId === documentId || field.documentId === void 0 || field.documentId === null
251
+ );
252
+ for (const entry of applicable)
253
+ if (entry.fields)
254
+ for (const field of entry.fields) {
255
+ const { property, type } = field;
256
+ try {
257
+ const results = JSONPath({
258
+ json: document2,
259
+ path: property,
260
+ resultType: "all",
261
+ flatten: !0,
262
+ wrap: !0
263
+ });
264
+ results && results.length > 0 && results.forEach(
265
+ (result) => {
266
+ type !== void 0 ? typeof result.value == "object" && result.value !== null && !Array.isArray(result.value) && result.value._type === type && callback(result) : callback(result);
267
+ }
268
+ );
269
+ } catch (error) {
270
+ console.warn(`Invalid JSONPath: ${property}`, error);
271
+ }
374
272
  }
375
- return {
376
- ...block,
377
- level,
378
- _key,
379
- listItem,
380
- children: next2(el.childNodes)
381
- };
382
- }
383
- }
384
- ], META_FIELDS$1 = ["_key", "_type", "_id"], isRecord$2 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), languageObjectFieldFilter = (obj, baseLang) => {
385
- const filterToLangField = (childObj) => {
386
- const filteredObj = {};
387
- return filteredObj[baseLang] = childObj[baseLang], META_FIELDS$1.forEach((field) => {
388
- childObj[field] && (filteredObj[field] = childObj[field]);
389
- }), filteredObj;
390
- }, findBaseLang = (childObj) => {
391
- const filteredObj = {};
392
- META_FIELDS$1.forEach((field) => {
393
- childObj[field] && (filteredObj[field] = childObj[field]);
394
- });
273
+ }
274
+ function deleteMatchingFields(documentId, document2, fields) {
275
+ const arrayRemovals = [];
276
+ forEachMatchingField(documentId, document2, fields, (result) => {
277
+ Array.isArray(result.parent) ? arrayRemovals.push({
278
+ parent: result.parent,
279
+ index: Number(result.parentProperty)
280
+ }) : delete result.parent[result.parentProperty];
281
+ }), arrayRemovals.sort((a, b) => b.index - a.index);
282
+ for (const { parent, index } of arrayRemovals)
283
+ parent.splice(index, 1);
284
+ }
285
+ function applyDocuments(documentId, sourceDocument, targetDocument, ignore, skip = [], dedupe = [], localeId) {
286
+ const mergedDocument = JSON.parse(JSON.stringify(sourceDocument)), clonedTarget = JSON.parse(JSON.stringify(targetDocument));
287
+ for (const [key, value] of Object.entries(clonedTarget))
288
+ mergedDocument[key] = value;
289
+ return forEachMatchingField(documentId, sourceDocument, ignore, (result) => {
290
+ JSONPointer.set(mergedDocument, result.pointer, result.value);
291
+ }), forEachMatchingField(documentId, sourceDocument, dedupe, (result) => {
292
+ JSONPointer.set(
293
+ mergedDocument,
294
+ result.pointer,
295
+ dedupeFieldValue(result.value, localeId)
296
+ );
297
+ }), deleteMatchingFields(documentId, mergedDocument, skip), mergedDocument;
298
+ }
299
+ function dedupeFieldValue(value, localeId) {
300
+ if (!localeId) return value;
301
+ if (typeof value == "string")
302
+ return appendLocaleSuffix(value, localeId);
303
+ if (value && typeof value == "object" && !Array.isArray(value) && typeof value.current == "string") {
304
+ const slug = value;
305
+ return {
306
+ ...slug,
307
+ current: appendLocaleSuffix(slug.current, localeId)
308
+ };
309
+ }
310
+ return value;
311
+ }
312
+ function appendLocaleSuffix(value, localeId) {
313
+ if (!value) return value;
314
+ const suffix = createLocaleSuffix(localeId);
315
+ return !suffix || value.endsWith(suffix) ? value : `${value}${suffix}`;
316
+ }
317
+ function createLocaleSuffix(localeId) {
318
+ const normalized = localeId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
319
+ return normalized ? `-${normalized}` : "";
320
+ }
321
+ function randomKey() {
322
+ return Math.random().toString(36).slice(2, 10);
323
+ }
324
+ async function createI18nDocAndPatchMetadata(sourceDocument, translatedDoc, localeId, client, translationMetadata, sourceDocumentId, languageField = "language") {
325
+ const publishedSourceDocumentId = getPublishedId(sourceDocumentId);
326
+ translatedDoc[languageField] = localeId;
327
+ const existingLocaleKey = translationMetadata.translations.find(
328
+ (translation) => translation.language === localeId
329
+ ), operation = existingLocaleKey ? "replace" : "after", location = existingLocaleKey ? `translations[language == "${localeId}"]` : "translations[-1]", { _updatedAt, _createdAt, ...rest } = translatedDoc, appliedDocument = applyDocuments(
330
+ publishedSourceDocumentId,
331
+ sourceDocument,
332
+ rest,
333
+ pluginConfig.getIgnoreFields(),
334
+ pluginConfig.getSkipFields(),
335
+ pluginConfig.getDedupeFields(),
336
+ localeId
337
+ ), isSingleton = pluginConfig.getSingletons().includes(publishedSourceDocumentId);
338
+ let createDocumentPromise;
339
+ if (isSingleton) {
340
+ const translatedDocId = pluginConfig.getSingletonMapping()(
341
+ publishedSourceDocumentId,
342
+ localeId
343
+ );
344
+ createDocumentPromise = client.create({
345
+ ...appliedDocument,
346
+ _type: rest._type,
347
+ _id: `drafts.${translatedDocId}`
348
+ });
349
+ } else
350
+ createDocumentPromise = client.create({
351
+ ...appliedDocument,
352
+ _type: rest._type,
353
+ _id: "drafts."
354
+ });
355
+ const doc = await createDocumentPromise, _ref = getPublishedId(doc._id);
356
+ await client.transaction().patch(
357
+ translationMetadata._id,
358
+ (p) => p.insert(operation, location, [
359
+ {
360
+ _key: randomKey(),
361
+ language: localeId,
362
+ _type: "internationalizedArrayReferenceValue",
363
+ value: {
364
+ _type: "reference",
365
+ _ref,
366
+ _weak: !0,
367
+ _strengthenOnPublish: {
368
+ type: doc._type
369
+ }
370
+ }
371
+ }
372
+ ])
373
+ ).commit();
374
+ }
375
+ const getOrCreateTranslationMetadata = async (documentId, baseDocument, client, baseLanguage) => {
376
+ const publishedId = getPublishedId(documentId), existingMetadata = await client.fetch(
377
+ `*[
378
+ _type == 'translation.metadata' &&
379
+ translations[language == $baseLanguage][0].value._ref == $id
380
+ ][0]`,
381
+ { baseLanguage, id: publishedId }
382
+ );
383
+ if (existingMetadata)
384
+ return existingMetadata;
385
+ const baseLangEntry = {
386
+ _key: randomKey(),
387
+ language: baseLanguage,
388
+ _type: "internationalizedArrayReferenceValue",
389
+ value: {
390
+ _type: "reference",
391
+ _ref: getPublishedId(baseDocument._id)
392
+ }
393
+ };
394
+ baseDocument._id.startsWith("drafts.") && (baseLangEntry.value = {
395
+ ...baseLangEntry.value,
396
+ _weak: !0,
397
+ //this should reflect doc i18n config when this
398
+ //plugin is able to take that as a config option
399
+ _strengthenOnPublish: {
400
+ type: baseDocument._type
401
+ }
402
+ });
403
+ try {
404
+ return await client.createIfNotExists({
405
+ _id: `translation.metadata.${publishedId}`,
406
+ _type: "translation.metadata",
407
+ translations: [baseLangEntry]
408
+ });
409
+ } catch (error) {
410
+ const metadata = await client.fetch(
411
+ `*[
412
+ _type == 'translation.metadata' &&
413
+ translations[language == $baseLanguage][0].value._ref == $id
414
+ ][0]`,
415
+ { baseLanguage, id: publishedId }
416
+ );
417
+ if (metadata)
418
+ return metadata;
419
+ throw error;
420
+ }
421
+ }, SYSTEM_FIELDS = ["_id", "_rev", "_updatedAt", "language"], isSystemField = (field) => SYSTEM_FIELDS.includes(field);
422
+ async function patchI18nDoc(sourceDocumentId, i18nDocId, sourceDocument, mergedDocument, translatedFields, client, existingDocument) {
423
+ const cleanedMerge = {};
424
+ Object.entries(mergedDocument).forEach(([key, value]) => {
425
+ key in translatedFields && //don't overwrite any existing system values on the i18n doc
426
+ !isSystemField(key) && (cleanedMerge[key] = value);
427
+ });
428
+ const cleanedSourceDocument = {};
429
+ Object.entries(sourceDocument).forEach(([key, value]) => {
430
+ isSystemField(key) || (cleanedSourceDocument[key] = value);
431
+ });
432
+ const appliedDocument = applyDocuments(
433
+ sourceDocumentId,
434
+ cleanedSourceDocument,
435
+ cleanedMerge,
436
+ pluginConfig.getIgnoreFields(),
437
+ pluginConfig.getSkipFields()
438
+ ), dedupeFields = pluginConfig.getDedupeFields();
439
+ deleteMatchingFields(sourceDocumentId, appliedDocument, dedupeFields), existingDocument && forEachMatchingField(
440
+ sourceDocumentId,
441
+ existingDocument,
442
+ dedupeFields,
443
+ (result) => {
444
+ JSONPointer.set(appliedDocument, result.pointer, result.value);
445
+ }
446
+ ), await client.patch(i18nDocId, { set: appliedDocument }).commit();
447
+ }
448
+ const documentLevelPatch = async (docInfo, translatedFields, localeId, client, languageField = "language", mergeWithTargetLocale = !1) => {
449
+ const baseLanguage = pluginConfig.getSourceLocale();
450
+ let baseDoc = null, i18nDoc = null;
451
+ docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
452
+ docInfo.documentId,
453
+ docInfo.versionId,
454
+ client
455
+ )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
456
+ const i18nDocId = (await getOrCreateTranslationMetadata(
457
+ docInfo.documentId,
458
+ baseDoc,
459
+ client,
460
+ baseLanguage
461
+ )).translations.find(
462
+ (translation) => translation.language === localeId
463
+ )?.value?._ref;
464
+ i18nDocId && (i18nDoc = await findLatestDraft(i18nDocId, client)), mergeWithTargetLocale && i18nDoc ? baseDoc = i18nDoc : docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
465
+ docInfo.documentId,
466
+ docInfo.versionId,
467
+ client
468
+ )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
469
+ const merged = BaseDocumentMerger.documentLevelMerge(
470
+ translatedFields,
471
+ baseDoc
472
+ );
473
+ if (i18nDoc)
474
+ await patchI18nDoc(
475
+ docInfo.documentId,
476
+ i18nDoc._id,
477
+ baseDoc,
478
+ merged,
479
+ translatedFields,
480
+ client,
481
+ i18nDoc
482
+ );
483
+ else {
484
+ const freshTranslationMetadata = await getOrCreateTranslationMetadata(
485
+ docInfo.documentId,
486
+ baseDoc,
487
+ client,
488
+ baseLanguage
489
+ ), freshI18nDocId = freshTranslationMetadata.translations.find(
490
+ (translation) => translation.language === localeId
491
+ )?.value?._ref;
492
+ if (freshI18nDocId) {
493
+ const freshI18nDoc = await findLatestDraft(freshI18nDocId, client);
494
+ await patchI18nDoc(
495
+ docInfo.documentId,
496
+ freshI18nDoc._id,
497
+ baseDoc,
498
+ merged,
499
+ translatedFields,
500
+ client,
501
+ freshI18nDoc
502
+ );
503
+ return;
504
+ }
505
+ await createI18nDocAndPatchMetadata(
506
+ baseDoc,
507
+ merged,
508
+ localeId,
509
+ client,
510
+ freshTranslationMetadata,
511
+ docInfo.documentId,
512
+ languageField
513
+ );
514
+ }
515
+ }, DEFAULT_TYPE_PREFIX$1 = "internationalizedArray";
516
+ function hasRecognizedTypePrefix(type) {
517
+ return type.startsWith(DEFAULT_TYPE_PREFIX$1) || type.startsWith(pluginConfig.getFieldLevelTypePrefix());
518
+ }
519
+ const isRecord$4 = (value) => typeof value == "object" && value !== null && !Array.isArray(value);
520
+ function isInternationalizedArrayItem(item) {
521
+ return isRecord$4(item) && typeof item._type == "string" && hasRecognizedTypePrefix(item._type) && item._type.endsWith("Value") && typeof item.language == "string" && "value" in item;
522
+ }
523
+ function isInternationalizedArrayField(value) {
524
+ return Array.isArray(value) && value.length > 0 && value.every(isInternationalizedArrayItem);
525
+ }
526
+ function findLocaleItem(field, locale) {
527
+ return field.find((item) => item.language === locale);
528
+ }
529
+ function upsertLocaleItem(baseArray, translatedValue, targetLocale, sourceLocale) {
530
+ const itemType = (findLocaleItem(baseArray, sourceLocale) ?? baseArray[0])._type, updated = baseArray.map(
531
+ (item) => item.language === targetLocale ? { ...item, value: translatedValue } : { ...item }
532
+ );
533
+ return findLocaleItem(updated, targetLocale) || updated.push({
534
+ _key: randomKey(),
535
+ _type: itemType,
536
+ language: targetLocale,
537
+ value: translatedValue
538
+ }), updated;
539
+ }
540
+ function mergeValue(baseValue, translatedValue, targetLocale, sourceLocale) {
541
+ if (isInternationalizedArrayField(baseValue))
542
+ return upsertLocaleItem(
543
+ baseValue,
544
+ translatedValue,
545
+ targetLocale,
546
+ sourceLocale
547
+ );
548
+ if (Array.isArray(baseValue) && Array.isArray(translatedValue)) {
549
+ let changed = !1;
550
+ const merged = baseValue.map((item) => {
551
+ if (!isRecord$4(item) || typeof item._key != "string")
552
+ return item;
553
+ const translatedItem = translatedValue.find(
554
+ (candidate) => isRecord$4(candidate) && candidate._key === item._key
555
+ );
556
+ if (!translatedItem)
557
+ return item;
558
+ const mergedItem = mergeValue(
559
+ item,
560
+ translatedItem,
561
+ targetLocale,
562
+ sourceLocale
563
+ );
564
+ return mergedItem !== void 0 ? (changed = !0, mergedItem) : item;
565
+ });
566
+ return changed ? merged : void 0;
567
+ }
568
+ if (isRecord$4(baseValue) && isRecord$4(translatedValue)) {
569
+ let changed = !1;
570
+ const merged = { ...baseValue };
571
+ for (const key of Object.keys(translatedValue)) {
572
+ if (key.startsWith("_") || !(key in baseValue))
573
+ continue;
574
+ const mergedChild = mergeValue(
575
+ baseValue[key],
576
+ translatedValue[key],
577
+ targetLocale,
578
+ sourceLocale
579
+ );
580
+ mergedChild !== void 0 && (merged[key] = mergedChild, changed = !0);
581
+ }
582
+ return changed ? merged : void 0;
583
+ }
584
+ }
585
+ function mergeInternationalizedArrays(baseDoc, translatedFields, targetLocale, sourceLocale) {
586
+ const changes = {};
587
+ for (const key of Object.keys(translatedFields)) {
588
+ if (key.startsWith("_") || !(key in baseDoc))
589
+ continue;
590
+ const merged = mergeValue(
591
+ baseDoc[key],
592
+ translatedFields[key],
593
+ targetLocale,
594
+ sourceLocale
595
+ );
596
+ merged !== void 0 && (changes[key] = merged);
597
+ }
598
+ return changes;
599
+ }
600
+ const internationalizedArrayPatch = async (docInfo, translatedFields, localeId, client) => {
601
+ const sourceLocale = pluginConfig.getSourceLocale(), baseDoc = await findLatestDraft(docInfo.documentId, client);
602
+ if (!baseDoc)
603
+ return;
604
+ const changes = mergeInternationalizedArrays(
605
+ baseDoc,
606
+ translatedFields,
607
+ localeId,
608
+ sourceLocale
609
+ );
610
+ if (Object.keys(changes).length === 0)
611
+ return;
612
+ if (baseDoc._id.startsWith("drafts.")) {
613
+ await client.patch(baseDoc._id).set(changes).commit();
614
+ return;
615
+ }
616
+ const draftId = `drafts.${getPublishedId(baseDoc._id)}`;
617
+ await client.transaction().createIfNotExists({ ...baseDoc, _id: draftId }).patch(draftId, (patch) => patch.set(changes)).commit();
618
+ }, defaultSchema = Schema.compile({
619
+ name: "default",
620
+ types: [
621
+ {
622
+ type: "object",
623
+ name: "default",
624
+ fields: [
625
+ {
626
+ name: "block",
627
+ type: "array",
628
+ of: [{ type: "block" }]
629
+ }
630
+ ]
631
+ }
632
+ ]
633
+ }), blockContentType = defaultSchema.get("default").fields.find((field) => field.name === "block").type, preprocess = (html) => {
634
+ const intermediateBlocks = htmlToBlocks(
635
+ `<p>${html}</p>`,
636
+ blockContentType
637
+ );
638
+ if (!intermediateBlocks.length)
639
+ throw new Error(`Error parsing string '${html}'`);
640
+ return intermediateBlocks[0].children[0].text;
641
+ };
642
+ function attachGTData(html, data, type) {
643
+ const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
644
+ if (!firstElement)
645
+ return html;
646
+ const encodedData = encode(JSON.stringify({ [type]: data }));
647
+ return firstElement.setAttribute("data-gt-internal", encodedData), firstElement.outerHTML;
648
+ }
649
+ function detachGTData(html) {
650
+ const firstElement = new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
651
+ if (!firstElement)
652
+ return { html };
653
+ const encodedData = firstElement.getAttribute("data-gt-internal");
654
+ let extractedData;
655
+ if (encodedData)
656
+ try {
657
+ const decodedData = decode(encodedData);
658
+ extractedData = JSON.parse(decodedData), firstElement.removeAttribute("data-gt-internal");
659
+ } catch (error) {
660
+ console.warn("Failed to decode GT internal data:", error);
661
+ }
662
+ return {
663
+ html: firstElement.outerHTML,
664
+ data: extractedData
665
+ };
666
+ }
667
+ function encode(data) {
668
+ const bytes = new TextEncoder().encode(data);
669
+ let binary = "";
670
+ for (let i = 0; i < bytes.length; i++)
671
+ binary += String.fromCharCode(bytes[i]);
672
+ return btoa(binary);
673
+ }
674
+ function decode(base64) {
675
+ const binary = atob(base64), bytes = new Uint8Array(binary.length);
676
+ for (let i = 0; i < binary.length; i++)
677
+ bytes[i] = binary.charCodeAt(i);
678
+ return new TextDecoder().decode(bytes);
679
+ }
680
+ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), defaultStopTypes = [
681
+ "reference",
682
+ "date",
683
+ "datetime",
684
+ "file",
685
+ "geopoint",
686
+ "image",
687
+ "number",
688
+ "crop",
689
+ "hotspot",
690
+ "boolean",
691
+ "url",
692
+ "color",
693
+ "code"
694
+ ], defaultMarks = {}, defaultPortableTextBlockStyles = {
695
+ normal: ({ value, children }) => `<p id="${value._key}">${children}</p>`,
696
+ blockquote: ({ value, children }) => `<blockquote id="${value._key}">${children}</blockquote>`,
697
+ h1: ({ value, children }) => `<h1 id="${value._key}">${children}</h1>`,
698
+ h2: ({ value, children }) => `<h2 id="${value._key}">${children}</h2>`,
699
+ h3: ({ value, children }) => `<h3 id="${value._key}">${children}</h3>`,
700
+ h4: ({ value, children }) => `<h4 id="${value._key}">${children}</h4>`,
701
+ h5: ({ value, children }) => `<h5 id="${value._key}">${children}</h5>`,
702
+ h6: ({ value, children }) => `<h6 id="${value._key}">${children}</h6>`
703
+ }, defaultLists = {
704
+ number: ({ value, children }) => `<ol id="${value._key.replace("-parent", "")}">${children}</ol>`,
705
+ bullet: ({ value, children }) => `<ul id="${value._key.replace("-parent", "")}">${children}</ul>`
706
+ }, defaultListItem = ({
707
+ value,
708
+ children
709
+ }) => {
710
+ const { _key, level } = value;
711
+ return `<li id="${(_key || "").replace("-parent", "")}" data-level="${level}">${children}</li>`;
712
+ }, unknownBlockFunc = ({ value, children }) => `<p id="${value._key}" data-type="unknown-block-style" data-style="${value.style}">${children}</p>`, customSerializers = {
713
+ unknownType: ({ value }) => `<div class="${value._type}"></div>`,
714
+ types: {},
715
+ marks: defaultMarks,
716
+ block: defaultPortableTextBlockStyles,
717
+ list: defaultLists,
718
+ listItem: defaultListItem,
719
+ unknownBlockStyle: unknownBlockFunc
720
+ }, customDeserializers = { types: {} }, customBlockDeserializers = [
721
+ // handle marks with data-gt-internal
722
+ {
723
+ deserialize(node2, next2) {
724
+ if (node2.nodeType !== 1)
725
+ return;
726
+ const el = node2;
727
+ if (!el.hasChildNodes() || !el.getAttribute("data-gt-internal"))
728
+ return;
729
+ const { html, data } = detachGTData(el.outerHTML), block = htmlToBlocks(html, blockContentType)[0], children = next2(el.childNodes);
730
+ let markDefs = [];
731
+ return "markDefs" in block && (markDefs = block.markDefs ?? []), data?.markDef && markDefs.push(data.markDef), Array.isArray(children) && children.forEach((child) => {
732
+ if (!isRecord$3(child))
733
+ return;
734
+ const marks = Array.isArray(child.marks) ? child.marks : [];
735
+ child.marks = data?.markDef?._key ? [...marks, data.markDef._key] : marks;
736
+ }), {
737
+ ...block,
738
+ markDefs,
739
+ children
740
+ };
741
+ }
742
+ },
743
+ //handle undeclared styles
744
+ {
745
+ deserialize(node2, next2) {
746
+ if (node2.nodeType !== 1)
747
+ return;
748
+ const el = node2;
749
+ if (!el.hasChildNodes() || el.getAttribute("data-type") !== "unknown-block-style")
750
+ return;
751
+ const style = el.getAttribute("data-style") ?? "";
752
+ return {
753
+ ...htmlToBlocks(el.outerHTML, blockContentType)[0],
754
+ style,
755
+ children: next2(el.childNodes)
756
+ };
757
+ }
758
+ },
759
+ //handle list items
760
+ {
761
+ deserialize(node2, next2) {
762
+ if (node2.nodeType !== 1)
763
+ return;
764
+ const el = node2;
765
+ if (!el.hasChildNodes() || el.tagName.toLowerCase() !== "li")
766
+ return;
767
+ const tagsToStyle = {
768
+ ul: "bullet",
769
+ ol: "number"
770
+ }, parent = el.parentNode;
771
+ if (!parent || !parent.tagName)
772
+ return;
773
+ const listItem = tagsToStyle[parent.tagName.toLowerCase()];
774
+ if (!listItem)
775
+ return;
776
+ const level = el.getAttribute("data-level") && parseInt(el.getAttribute("data-level") || "0", 10), _key = el.id;
777
+ let block = htmlToBlocks(parent.outerHTML, blockContentType)[0];
778
+ const customStyle = el.children?.[0]?.getAttribute("data-style");
779
+ if (new RegExp(/<("[^"]*"|'[^']*'|[^'">])*>/).test(el.innerHTML)) {
780
+ const newBlock = htmlToBlocks(el.innerHTML, blockContentType)[0];
781
+ if (newBlock && (block = {
782
+ ...block,
783
+ ...newBlock,
784
+ // @ts-ignore
785
+ style: customStyle ?? newBlock.style
786
+ }, customStyle))
787
+ return block;
788
+ }
789
+ return {
790
+ ...block,
791
+ level,
792
+ _key,
793
+ listItem,
794
+ children: next2(el.childNodes)
795
+ };
796
+ }
797
+ }
798
+ ];
799
+ function mergeBlocks(blocks) {
800
+ const mergedBlock = { ...blocks[0] };
801
+ mergedBlock.markDefs = mergedBlock.markDefs ?? [];
802
+ for (const [idx, block] of blocks.entries())
803
+ idx !== 0 && (mergedBlock.children.push(...block.children), mergedBlock.markDefs.push(...block.markDefs ?? []));
804
+ return mergedBlock._type = "block", mergedBlock;
805
+ }
806
+ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
807
+ const output = [];
808
+ return Array.from(arrayHTML.children).forEach((child) => {
809
+ let deserializedObject;
810
+ try {
811
+ if (child.tagName?.toLowerCase() === "span")
812
+ deserializedObject = preprocess(child.innerHTML);
813
+ else if (child.className || child.getAttribute("data-type") === "object")
814
+ deserializedObject = deserializeObject(
815
+ child,
816
+ deserializers,
817
+ blockDeserializers
818
+ ), deserializedObject && typeof deserializedObject == "object" && !Array.isArray(deserializedObject) && (deserializedObject._key = child.id);
819
+ else {
820
+ const blocks = htmlToBlocks(child.outerHTML, blockContentType, {
821
+ rules: blockDeserializers
822
+ });
823
+ deserializedObject = mergeBlocks(
824
+ blocks
825
+ ), deserializedObject._key = child.id;
826
+ }
827
+ } catch (e) {
828
+ console.debug(
829
+ `Tried to deserialize block: ${child.outerHTML} in an array but failed to identify it! Error: ${e}`
830
+ );
831
+ }
832
+ output.push(deserializedObject);
833
+ }), output;
834
+ }, deserializeObject = (objectHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
835
+ const deserialize = deserializers.types?.[objectHTML.className];
836
+ if (deserialize)
837
+ return deserialize(objectHTML);
838
+ const output = {};
839
+ return objectHTML.className && (output._type = objectHTML.className), Array.from(objectHTML.children).forEach((child) => {
840
+ if (child.tagName?.toLowerCase() === "span")
841
+ output[child.className] = preprocess(child.innerHTML);
842
+ else if (child.getAttribute("data-level") === "field") {
843
+ const deserialized = deserializeHTML(
844
+ child.outerHTML,
845
+ deserializers,
846
+ blockDeserializers
847
+ );
848
+ deserialized && Object.keys(deserialized).length ? output[child.className] = deserialized : console.debug(
849
+ `Deserializer: Skipping empty or unreadable HTML: ${child.outerHTML}`
850
+ );
851
+ } else child.getAttribute("data-type") === "array" && (output[child.className] = deserializeArray(
852
+ child,
853
+ deserializers,
854
+ blockDeserializers
855
+ ));
856
+ }), output;
857
+ }, deserializeHTML = (html, deserializers, blockDeserializers) => {
858
+ let HTMLnode = new DOMParser().parseFromString(html, "text/html").body.children[0];
859
+ if (HTMLnode?.getAttribute("data-level") === "field" && (HTMLnode = HTMLnode.children[0]), !HTMLnode)
860
+ return {};
861
+ let output;
862
+ const deserialize = deserializers.types?.[HTMLnode.className];
863
+ 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(
864
+ `Tried to deserialize block ${HTMLnode.outerHTML} but failed to identify it!`
865
+ )), output;
866
+ }, deserializeDocument$1 = (serializedDoc, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
867
+ const metadata = {}, head = new DOMParser().parseFromString(serializedDoc, "text/html").head;
868
+ return Array.from(head.children).forEach((metaTag) => {
869
+ const validTags = ["_id", "_rev", "_type"], metaName = metaTag.getAttribute("name");
870
+ metaName && validTags.includes(metaName) && (metadata[metaName] = metaTag.getAttribute("content"));
871
+ }), {
872
+ ...deserializeHTML(
873
+ serializedDoc,
874
+ deserializers,
875
+ blockDeserializers
876
+ ),
877
+ ...metadata
878
+ };
879
+ }, BaseDocumentDeserializer = {
880
+ deserializeDocument: deserializeDocument$1,
881
+ deserializeHTML
882
+ }, META_FIELDS$1 = ["_key", "_type", "_id"], isRecord$2 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), languageObjectFieldFilter = (obj, baseLang) => {
883
+ const filterToLangField = (childObj) => {
884
+ const filteredObj = {};
885
+ return filteredObj[baseLang] = childObj[baseLang], META_FIELDS$1.forEach((field) => {
886
+ childObj[field] && (filteredObj[field] = childObj[field]);
887
+ }), filteredObj;
888
+ }, findBaseLang = (childObj) => {
889
+ const filteredObj = {};
890
+ META_FIELDS$1.forEach((field) => {
891
+ childObj[field] && (filteredObj[field] = childObj[field]);
892
+ });
395
893
  for (const key in childObj)
396
894
  if (childObj.hasOwnProperty(key)) {
397
895
  const value = childObj[key];
@@ -518,240 +1016,78 @@ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Arr
518
1016
  );
519
1017
  return schema && schema.fields ? fieldFilter(block, schema.fields, stopTypes) : block;
520
1018
  }).map((obj) => typeof obj == "string" ? `<span>${obj}</span>` : serializeObject(obj, stopTypes, serializers));
521
- return `<div class="${fieldName}" data-type="array">${output.join("")}</div>`;
522
- };
523
- return {
524
- serializeDocument: (doc, translationLevel = "document", baseLang = "en", stopTypes = defaultStopTypes, serializers = customSerializers) => {
525
- const schema = getSchema(doc._type);
526
- let filteredObj = {};
527
- translationLevel === "field" ? filteredObj = languageObjectFieldFilter(doc, baseLang) : filteredObj = fieldFilter(doc, schema?.fields ?? [], stopTypes);
528
- const serializedFields = {};
529
- for (const key in filteredObj) {
530
- if (filteredObj.hasOwnProperty(key) === !1) continue;
531
- const value = filteredObj[key];
532
- if (typeof value == "string")
533
- serializedFields[key] = value;
534
- else if (Array.isArray(value))
535
- serializedFields[key] = serializeArray(
536
- value.filter(
537
- (item) => typeof item == "string" || isRecord$1(item)
538
- ),
539
- key,
540
- stopTypes,
541
- serializers
542
- );
543
- else if (value && isRecord$1(value) && !stopTypes.find((stopType) => stopType == value?._type)) {
544
- const serialized = serializeObject(
545
- value,
546
- stopTypes,
547
- serializers
548
- );
549
- serializedFields[key] = `<div class="${key}" data-level='field'>${serialized}</div>`;
550
- }
551
- }
552
- const rawHTMLBody = document.createElement("body");
553
- rawHTMLBody.innerHTML = serializeObject(
554
- serializedFields,
555
- stopTypes,
556
- serializers
557
- );
558
- const rawHTMLHead = document.createElement("head");
559
- ["_id", "_type", "_rev"].forEach((field) => {
560
- const metaEl = document.createElement("meta");
561
- metaEl.setAttribute("name", field), metaEl.setAttribute("content", doc[field]), rawHTMLHead.appendChild(metaEl);
562
- });
563
- const versionMeta = document.createElement("meta");
564
- versionMeta.setAttribute("name", "version"), versionMeta.setAttribute("content", "3"), rawHTMLHead.appendChild(versionMeta);
565
- const rawHTML = document.createElement("html");
566
- return rawHTML.appendChild(rawHTMLHead), rawHTML.appendChild(rawHTMLBody), {
567
- name: doc._id,
568
- content: rawHTML.outerHTML
569
- };
570
- },
571
- fieldFilter,
572
- languageObjectFieldFilter,
573
- serializeArray,
574
- serializeObject
575
- };
576
- };
577
- function mergeBlocks(blocks) {
578
- const mergedBlock = { ...blocks[0] };
579
- mergedBlock.markDefs = mergedBlock.markDefs ?? [];
580
- for (const [idx, block] of blocks.entries())
581
- idx !== 0 && (mergedBlock.children.push(...block.children), mergedBlock.markDefs.push(...block.markDefs ?? []));
582
- return mergedBlock._type = "block", mergedBlock;
583
- }
584
- const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
585
- const output = [];
586
- return Array.from(arrayHTML.children).forEach((child) => {
587
- let deserializedObject;
588
- try {
589
- if (child.tagName?.toLowerCase() === "span")
590
- deserializedObject = preprocess(child.innerHTML);
591
- else if (child.className || child.getAttribute("data-type") === "object")
592
- deserializedObject = deserializeObject(
593
- child,
594
- deserializers,
595
- blockDeserializers
596
- ), deserializedObject && typeof deserializedObject == "object" && !Array.isArray(deserializedObject) && (deserializedObject._key = child.id);
597
- else {
598
- const blocks = htmlToBlocks(child.outerHTML, blockContentType, {
599
- rules: blockDeserializers
600
- });
601
- deserializedObject = mergeBlocks(
602
- blocks
603
- ), deserializedObject._key = child.id;
604
- }
605
- } catch (e) {
606
- console.debug(
607
- `Tried to deserialize block: ${child.outerHTML} in an array but failed to identify it! Error: ${e}`
608
- );
609
- }
610
- output.push(deserializedObject);
611
- }), output;
612
- }, deserializeObject = (objectHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
613
- const deserialize = deserializers.types?.[objectHTML.className];
614
- if (deserialize)
615
- return deserialize(objectHTML);
616
- const output = {};
617
- return objectHTML.className && (output._type = objectHTML.className), Array.from(objectHTML.children).forEach((child) => {
618
- if (child.tagName?.toLowerCase() === "span")
619
- output[child.className] = preprocess(child.innerHTML);
620
- else if (child.getAttribute("data-level") === "field") {
621
- const deserialized = deserializeHTML(
622
- child.outerHTML,
623
- deserializers,
624
- blockDeserializers
625
- );
626
- deserialized && Object.keys(deserialized).length ? output[child.className] = deserialized : console.debug(
627
- `Deserializer: Skipping empty or unreadable HTML: ${child.outerHTML}`
628
- );
629
- } else child.getAttribute("data-type") === "array" && (output[child.className] = deserializeArray(
630
- child,
631
- deserializers,
632
- blockDeserializers
633
- ));
634
- }), output;
635
- }, deserializeHTML = (html, deserializers, blockDeserializers) => {
636
- let HTMLnode = new DOMParser().parseFromString(html, "text/html").body.children[0];
637
- if (HTMLnode?.getAttribute("data-level") === "field" && (HTMLnode = HTMLnode.children[0]), !HTMLnode)
638
- return {};
639
- let output;
640
- const deserialize = deserializers.types?.[HTMLnode.className];
641
- 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(
642
- `Tried to deserialize block ${HTMLnode.outerHTML} but failed to identify it!`
643
- )), output;
644
- }, deserializeDocument$1 = (serializedDoc, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
645
- const metadata = {}, head = new DOMParser().parseFromString(serializedDoc, "text/html").head;
646
- return Array.from(head.children).forEach((metaTag) => {
647
- const validTags = ["_id", "_rev", "_type"], metaName = metaTag.getAttribute("name");
648
- metaName && validTags.includes(metaName) && (metadata[metaName] = metaTag.getAttribute("content"));
649
- }), {
650
- ...deserializeHTML(
651
- serializedDoc,
652
- deserializers,
653
- blockDeserializers
654
- ),
655
- ...metadata
656
- };
657
- }, BaseDocumentDeserializer = {
658
- deserializeDocument: deserializeDocument$1,
659
- deserializeHTML
660
- };
661
- function forEachMatchingField(documentId, document2, fields, callback) {
662
- const applicable = fields.filter(
663
- (field) => field.documentId === documentId || field.documentId === void 0 || field.documentId === null
664
- );
665
- for (const entry of applicable)
666
- if (entry.fields)
667
- for (const field of entry.fields) {
668
- const { property, type } = field;
669
- try {
670
- const results = JSONPath({
671
- json: document2,
672
- path: property,
673
- resultType: "all",
674
- flatten: !0,
675
- wrap: !0
676
- });
677
- results && results.length > 0 && results.forEach(
678
- (result) => {
679
- type !== void 0 ? typeof result.value == "object" && result.value !== null && !Array.isArray(result.value) && result.value._type === type && callback(result) : callback(result);
680
- }
681
- );
682
- } catch (error) {
683
- console.warn(`Invalid JSONPath: ${property}`, error);
684
- }
685
- }
686
- }
687
- function deleteMatchingFields(documentId, document2, fields) {
688
- const arrayRemovals = [];
689
- forEachMatchingField(documentId, document2, fields, (result) => {
690
- Array.isArray(result.parent) ? arrayRemovals.push({
691
- parent: result.parent,
692
- index: Number(result.parentProperty)
693
- }) : delete result.parent[result.parentProperty];
694
- }), arrayRemovals.sort((a, b) => b.index - a.index);
695
- for (const { parent, index } of arrayRemovals)
696
- parent.splice(index, 1);
697
- }
698
- function applyDocuments(documentId, sourceDocument, targetDocument, ignore, skip = [], dedupe = [], localeId) {
699
- const mergedDocument = JSON.parse(JSON.stringify(sourceDocument)), clonedTarget = JSON.parse(JSON.stringify(targetDocument));
700
- for (const [key, value] of Object.entries(clonedTarget))
701
- mergedDocument[key] = value;
702
- return forEachMatchingField(documentId, sourceDocument, ignore, (result) => {
703
- JSONPointer.set(mergedDocument, result.pointer, result.value);
704
- }), forEachMatchingField(documentId, sourceDocument, dedupe, (result) => {
705
- JSONPointer.set(
706
- mergedDocument,
707
- result.pointer,
708
- dedupeFieldValue(result.value, localeId)
709
- );
710
- }), deleteMatchingFields(documentId, mergedDocument, skip), mergedDocument;
711
- }
712
- function dedupeFieldValue(value, localeId) {
713
- if (!localeId) return value;
714
- if (typeof value == "string")
715
- return appendLocaleSuffix(value, localeId);
716
- if (value && typeof value == "object" && !Array.isArray(value) && typeof value.current == "string") {
717
- const slug = value;
718
- return {
719
- ...slug,
720
- current: appendLocaleSuffix(slug.current, localeId)
721
- };
1019
+ return `<div class="${fieldName}" data-type="array">${output.join("")}</div>`;
1020
+ };
1021
+ return {
1022
+ serializeDocument: (doc, translationLevel = "document", baseLang = "en", stopTypes = defaultStopTypes, serializers = customSerializers) => {
1023
+ const schema = getSchema(doc._type);
1024
+ let filteredObj = {};
1025
+ translationLevel === "field" ? filteredObj = languageObjectFieldFilter(doc, baseLang) : filteredObj = fieldFilter(doc, schema?.fields ?? [], stopTypes);
1026
+ const serializedFields = {};
1027
+ for (const key in filteredObj) {
1028
+ if (filteredObj.hasOwnProperty(key) === !1) continue;
1029
+ const value = filteredObj[key];
1030
+ if (typeof value == "string")
1031
+ serializedFields[key] = value;
1032
+ else if (Array.isArray(value))
1033
+ serializedFields[key] = serializeArray(
1034
+ value.filter(
1035
+ (item) => typeof item == "string" || isRecord$1(item)
1036
+ ),
1037
+ key,
1038
+ stopTypes,
1039
+ serializers
1040
+ );
1041
+ else if (value && isRecord$1(value) && !stopTypes.find((stopType) => stopType == value?._type)) {
1042
+ const serialized = serializeObject(
1043
+ value,
1044
+ stopTypes,
1045
+ serializers
1046
+ );
1047
+ serializedFields[key] = `<div class="${key}" data-level='field'>${serialized}</div>`;
1048
+ }
1049
+ }
1050
+ const rawHTMLBody = document.createElement("body");
1051
+ rawHTMLBody.innerHTML = serializeObject(
1052
+ serializedFields,
1053
+ stopTypes,
1054
+ serializers
1055
+ );
1056
+ const rawHTMLHead = document.createElement("head");
1057
+ ["_id", "_type", "_rev"].forEach((field) => {
1058
+ const metaEl = document.createElement("meta");
1059
+ metaEl.setAttribute("name", field), metaEl.setAttribute("content", doc[field]), rawHTMLHead.appendChild(metaEl);
1060
+ });
1061
+ const versionMeta = document.createElement("meta");
1062
+ versionMeta.setAttribute("name", "version"), versionMeta.setAttribute("content", "3"), rawHTMLHead.appendChild(versionMeta);
1063
+ const rawHTML = document.createElement("html");
1064
+ return rawHTML.appendChild(rawHTMLHead), rawHTML.appendChild(rawHTMLBody), {
1065
+ name: doc._id,
1066
+ content: rawHTML.outerHTML
1067
+ };
1068
+ },
1069
+ fieldFilter,
1070
+ languageObjectFieldFilter,
1071
+ serializeArray,
1072
+ serializeObject
1073
+ };
1074
+ };
1075
+ function collapseToSourceLocale(value, sourceLocale) {
1076
+ if (isInternationalizedArrayField(value)) {
1077
+ const sourceItem = findLocaleItem(value, sourceLocale);
1078
+ return sourceItem ? collapseToSourceLocale(sourceItem.value, sourceLocale) : void 0;
722
1079
  }
723
- return value;
724
- }
725
- function appendLocaleSuffix(value, localeId) {
726
- if (!value) return value;
727
- const suffix = createLocaleSuffix(localeId);
728
- return !suffix || value.endsWith(suffix) ? value : `${value}${suffix}`;
729
- }
730
- function createLocaleSuffix(localeId) {
731
- const normalized = localeId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
732
- return normalized ? `-${normalized}` : "";
733
- }
734
- function getPublishedId(documentId) {
735
- return documentId.startsWith("drafts.") ? documentId.slice(7) : documentId;
736
- }
737
- function getDocumentPublishedId(document2) {
738
- return getPublishedId(document2._id);
739
- }
740
- function dedupeDocumentsPreferDraft(documents) {
741
- const byPublishedId = /* @__PURE__ */ new Map();
742
- for (const document2 of documents) {
743
- const publishedId = getDocumentPublishedId(document2);
744
- (!byPublishedId.get(publishedId) || document2._id.startsWith("drafts.")) && byPublishedId.set(publishedId, document2);
1080
+ if (Array.isArray(value))
1081
+ return value.map((item) => collapseToSourceLocale(item, sourceLocale)).filter((item) => item !== void 0);
1082
+ if (isRecord$4(value)) {
1083
+ const collapsed = {};
1084
+ for (const key of Object.keys(value)) {
1085
+ const collapsedValue = collapseToSourceLocale(value[key], sourceLocale);
1086
+ collapsedValue !== void 0 && (collapsed[key] = collapsedValue);
1087
+ }
1088
+ return collapsed;
745
1089
  }
746
- return Array.from(byPublishedId.values());
747
- }
748
- function createTranslationStatusKey(branchId, documentId, versionId, localeId) {
749
- const publishedId = getPublishedId(documentId);
750
- return branchId ? `${branchId}:${publishedId}:${versionId}:${localeId}` : `${publishedId}:${versionId}:${localeId}`;
751
- }
752
- function createStableTranslationKey(branchId, documentId, localeId) {
753
- const publishedId = getPublishedId(documentId);
754
- return branchId ? `${branchId}:${publishedId}:${localeId}` : `${publishedId}:${localeId}`;
1090
+ return value;
755
1091
  }
756
1092
  function deserializeDocument(document2) {
757
1093
  const deserializers = merge(
@@ -767,21 +1103,25 @@ function deserializeDocument(document2) {
767
1103
  blockDeserializers
768
1104
  );
769
1105
  }
770
- function serializeDocument(document2, schema, baseLanguage) {
1106
+ function serializeDocument(document2, schema, baseLanguage, level = "document") {
771
1107
  const stopTypes = [
772
1108
  ...defaultStopTypes,
773
1109
  ...pluginConfig.getAdditionalStopTypes()
774
1110
  ], serializers = merge(
775
1111
  customSerializers,
776
1112
  pluginConfig.getAdditionalSerializers()
777
- ), docToSerialize = stripIgnoredFields(
1113
+ );
1114
+ let docToSerialize = stripIgnoredFields(
778
1115
  document2,
779
1116
  pluginConfig.getIgnoreFields(),
780
1117
  pluginConfig.getDedupeFields()
781
- );
782
- return BaseDocumentSerializer(schema).serializeDocument(
1118
+ ), innerLevel = level;
1119
+ return level === "internationalizedArray" && (docToSerialize = collapseToSourceLocale(
1120
+ docToSerialize,
1121
+ baseLanguage
1122
+ ), innerLevel = "document"), BaseDocumentSerializer(schema).serializeDocument(
783
1123
  docToSerialize,
784
- "document",
1124
+ innerLevel,
785
1125
  baseLanguage,
786
1126
  stopTypes,
787
1127
  serializers
@@ -797,6 +1137,32 @@ function stripIgnoredFields(document2, ignoreFields, dedupeFields) {
797
1137
  fieldsToStrip
798
1138
  ), strippedDoc;
799
1139
  }
1140
+ const documentAdapter = {
1141
+ level: "document",
1142
+ serialize: (document2, schema, baseLanguage) => serializeDocument(document2, schema, baseLanguage, "document"),
1143
+ patch: (docInfo, deserialized, localeId, client, mergeWithTargetLocale) => documentLevelPatch(
1144
+ docInfo,
1145
+ deserialized,
1146
+ localeId,
1147
+ client,
1148
+ pluginConfig.getLanguageField(),
1149
+ mergeWithTargetLocale
1150
+ )
1151
+ }, internationalizedArrayAdapter = {
1152
+ level: "internationalizedArray",
1153
+ serialize: (document2, schema, baseLanguage) => serializeDocument(document2, schema, baseLanguage, "internationalizedArray"),
1154
+ patch: (docInfo, deserialized, localeId, client) => internationalizedArrayPatch(docInfo, deserialized, localeId, client)
1155
+ };
1156
+ function matchesFieldLevel(type) {
1157
+ return type ? pluginConfig.getFieldLevelDocuments().some((filter2) => filter2.type === type) : !1;
1158
+ }
1159
+ function getTranslationStrategyForType(type) {
1160
+ const level = pluginConfig.getTranslationLevel();
1161
+ return level === "internationalizedArray" || level === "mixed" && matchesFieldLevel(type) ? internationalizedArrayAdapter : documentAdapter;
1162
+ }
1163
+ function getTranslationStrategy(document2) {
1164
+ return getTranslationStrategyForType(document2._type);
1165
+ }
800
1166
  async function uploadFiles(documents, secrets) {
801
1167
  return overrideConfig(secrets), await gt.uploadSourceFiles(
802
1168
  documents.map(({ info, serializedDocument }) => ({
@@ -831,283 +1197,79 @@ async function initProject(uploadResult, options, secrets) {
831
1197
  if (status[0].status === "completed") {
832
1198
  setupCompleted = !0;
833
1199
  break;
834
- }
835
- if (status[0].status === "failed") {
836
- setupFailedMessage = status[0].error?.message || "Unknown error";
837
- break;
838
- }
839
- if (Date.now() - start > setupTimeoutMs) {
840
- setupFailedMessage = "Timed out while waiting for setup generation";
841
- break;
842
- }
843
- await new Promise((r) => setTimeout(r, pollInterval));
844
- }
845
- console.log(setupCompleted ? "Setup successfully completed" : `Setup ${setupFailedMessage ? "failed" : "timed out"} \u2014 proceeding without setup${setupFailedMessage ? ` (${setupFailedMessage})` : ""}`);
846
- }
847
- return !0;
848
- }
849
- async function createJobs(uploadResult, localeIds, secrets) {
850
- return overrideConfig(secrets), await gt.enqueueFiles(uploadResult.uploadedFiles, {
851
- sourceLocale: gt.sourceLocale || libraryDefaultLocale,
852
- targetLocales: localeIds
853
- });
854
- }
855
- async function downloadTranslations(files, secrets, maxRetries = 3, retryDelay = 1e3) {
856
- overrideConfig(secrets);
857
- let retries = 0;
858
- for (; retries <= maxRetries; )
859
- try {
860
- return (await gt.downloadFileBatch(
861
- files.map((file) => ({
862
- fileId: file.fileId,
863
- branchId: file.branchId,
864
- versionId: file.versionId,
865
- locale: file.locale
866
- }))
867
- )).files || [];
868
- } catch {
869
- retries++, await new Promise((resolve) => setTimeout(resolve, retryDelay));
870
- }
871
- return [];
872
- }
873
- async function checkTranslationStatus(fileQueryData, downloadStatus, secrets) {
874
- overrideConfig(secrets);
875
- try {
876
- const currentQueryData = fileQueryData.filter((item) => {
877
- const statusKey = createTranslationStatusKey(
878
- item.branchId,
879
- item.fileId,
880
- item.versionId,
881
- item.locale
882
- ), stableKey = createStableTranslationKey(
883
- item.branchId,
884
- item.fileId,
885
- item.locale
886
- );
887
- 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);
888
- });
889
- return currentQueryData.length === 0 ? !0 : ((await gt.queryFileData({
890
- translatedFiles: currentQueryData
891
- })).translatedFiles || []).filter(
892
- (translation) => translation.completedAt
893
- );
894
- } catch (error) {
895
- return console.error("Error checking translation status", error), [];
896
- }
897
- }
898
- const findLatestDraft = (documentId, client) => {
899
- const publishedId = getPublishedId(documentId), query = "*[_id == $id || _id == $draftId]", params = { id: publishedId, draftId: `drafts.${publishedId}` };
900
- return client.fetch(query, params).then(
901
- (docs) => docs.find((doc) => doc._id.startsWith("drafts.")) ?? docs[0]
902
- );
903
- }, findDocumentAtRevision = async (documentId, rev, client) => {
904
- const baseUrl = `/data/history/${client.config().dataset}/documents/${documentId}?revision=${rev}`, url = client.getUrl(baseUrl);
905
- return await fetch(url, { credentials: "include" }).then((req) => req.json()).then((req) => req.documents && req.documents.length ? req.documents[0] : null);
906
- };
907
- function randomKey() {
908
- return Math.random().toString(36).slice(2, 10);
909
- }
910
- async function createI18nDocAndPatchMetadata(sourceDocument, translatedDoc, localeId, client, translationMetadata, sourceDocumentId, languageField = "language") {
911
- const publishedSourceDocumentId = getPublishedId(sourceDocumentId);
912
- translatedDoc[languageField] = localeId;
913
- const existingLocaleKey = translationMetadata.translations.find(
914
- (translation) => translation.language === localeId
915
- ), operation = existingLocaleKey ? "replace" : "after", location = existingLocaleKey ? `translations[language == "${localeId}"]` : "translations[-1]", { _updatedAt, _createdAt, ...rest } = translatedDoc, appliedDocument = applyDocuments(
916
- publishedSourceDocumentId,
917
- sourceDocument,
918
- rest,
919
- pluginConfig.getIgnoreFields(),
920
- pluginConfig.getSkipFields(),
921
- pluginConfig.getDedupeFields(),
922
- localeId
923
- ), isSingleton = pluginConfig.getSingletons().includes(publishedSourceDocumentId);
924
- let createDocumentPromise;
925
- if (isSingleton) {
926
- const translatedDocId = pluginConfig.getSingletonMapping()(
927
- publishedSourceDocumentId,
928
- localeId
929
- );
930
- createDocumentPromise = client.create({
931
- ...appliedDocument,
932
- _type: rest._type,
933
- _id: `drafts.${translatedDocId}`
934
- });
935
- } else
936
- createDocumentPromise = client.create({
937
- ...appliedDocument,
938
- _type: rest._type,
939
- _id: "drafts."
940
- });
941
- const doc = await createDocumentPromise, _ref = getPublishedId(doc._id);
942
- await client.transaction().patch(
943
- translationMetadata._id,
944
- (p) => p.insert(operation, location, [
945
- {
946
- _key: randomKey(),
947
- language: localeId,
948
- _type: "internationalizedArrayReferenceValue",
949
- value: {
950
- _type: "reference",
951
- _ref,
952
- _weak: !0,
953
- _strengthenOnPublish: {
954
- type: doc._type
955
- }
956
- }
957
- }
958
- ])
959
- ).commit();
960
- }
961
- const getOrCreateTranslationMetadata = async (documentId, baseDocument, client, baseLanguage) => {
962
- const publishedId = getPublishedId(documentId), existingMetadata = await client.fetch(
963
- `*[
964
- _type == 'translation.metadata' &&
965
- translations[language == $baseLanguage][0].value._ref == $id
966
- ][0]`,
967
- { baseLanguage, id: publishedId }
968
- );
969
- if (existingMetadata)
970
- return existingMetadata;
971
- const baseLangEntry = {
972
- _key: randomKey(),
973
- language: baseLanguage,
974
- _type: "internationalizedArrayReferenceValue",
975
- value: {
976
- _type: "reference",
977
- _ref: getPublishedId(baseDocument._id)
978
- }
979
- };
980
- baseDocument._id.startsWith("drafts.") && (baseLangEntry.value = {
981
- ...baseLangEntry.value,
982
- _weak: !0,
983
- //this should reflect doc i18n config when this
984
- //plugin is able to take that as a config option
985
- _strengthenOnPublish: {
986
- type: baseDocument._type
1200
+ }
1201
+ if (status[0].status === "failed") {
1202
+ setupFailedMessage = status[0].error?.message || "Unknown error";
1203
+ break;
1204
+ }
1205
+ if (Date.now() - start > setupTimeoutMs) {
1206
+ setupFailedMessage = "Timed out while waiting for setup generation";
1207
+ break;
1208
+ }
1209
+ await new Promise((r) => setTimeout(r, pollInterval));
987
1210
  }
988
- });
989
- try {
990
- return await client.createIfNotExists({
991
- _id: `translation.metadata.${publishedId}`,
992
- _type: "translation.metadata",
993
- translations: [baseLangEntry]
994
- });
995
- } catch (error) {
996
- const metadata = await client.fetch(
997
- `*[
998
- _type == 'translation.metadata' &&
999
- translations[language == $baseLanguage][0].value._ref == $id
1000
- ][0]`,
1001
- { baseLanguage, id: publishedId }
1002
- );
1003
- if (metadata)
1004
- return metadata;
1005
- throw error;
1211
+ console.log(setupCompleted ? "Setup successfully completed" : `Setup ${setupFailedMessage ? "failed" : "timed out"} \u2014 proceeding without setup${setupFailedMessage ? ` (${setupFailedMessage})` : ""}`);
1006
1212
  }
1007
- }, SYSTEM_FIELDS = ["_id", "_rev", "_updatedAt", "language"], isSystemField = (field) => SYSTEM_FIELDS.includes(field);
1008
- async function patchI18nDoc(sourceDocumentId, i18nDocId, sourceDocument, mergedDocument, translatedFields, client, existingDocument) {
1009
- const cleanedMerge = {};
1010
- Object.entries(mergedDocument).forEach(([key, value]) => {
1011
- key in translatedFields && //don't overwrite any existing system values on the i18n doc
1012
- !isSystemField(key) && (cleanedMerge[key] = value);
1013
- });
1014
- const cleanedSourceDocument = {};
1015
- Object.entries(sourceDocument).forEach(([key, value]) => {
1016
- isSystemField(key) || (cleanedSourceDocument[key] = value);
1213
+ return !0;
1214
+ }
1215
+ async function createJobs(uploadResult, localeIds, secrets) {
1216
+ return overrideConfig(secrets), await gt.enqueueFiles(uploadResult.uploadedFiles, {
1217
+ sourceLocale: gt.sourceLocale || libraryDefaultLocale,
1218
+ targetLocales: localeIds
1017
1219
  });
1018
- const appliedDocument = applyDocuments(
1019
- sourceDocumentId,
1020
- cleanedSourceDocument,
1021
- cleanedMerge,
1022
- pluginConfig.getIgnoreFields(),
1023
- pluginConfig.getSkipFields()
1024
- ), dedupeFields = pluginConfig.getDedupeFields();
1025
- deleteMatchingFields(sourceDocumentId, appliedDocument, dedupeFields), existingDocument && forEachMatchingField(
1026
- sourceDocumentId,
1027
- existingDocument,
1028
- dedupeFields,
1029
- (result) => {
1030
- JSONPointer.set(appliedDocument, result.pointer, result.value);
1220
+ }
1221
+ async function downloadTranslations(files, secrets, maxRetries = 3, retryDelay = 1e3) {
1222
+ overrideConfig(secrets);
1223
+ let retries = 0;
1224
+ for (; retries <= maxRetries; )
1225
+ try {
1226
+ return (await gt.downloadFileBatch(
1227
+ files.map((file) => ({
1228
+ fileId: file.fileId,
1229
+ branchId: file.branchId,
1230
+ versionId: file.versionId,
1231
+ locale: file.locale
1232
+ }))
1233
+ )).files || [];
1234
+ } catch {
1235
+ retries++, await new Promise((resolve) => setTimeout(resolve, retryDelay));
1031
1236
  }
1032
- ), await client.patch(i18nDocId, { set: appliedDocument }).commit();
1237
+ return [];
1033
1238
  }
1034
- const documentLevelPatch = async (docInfo, translatedFields, localeId, client, languageField = "language", mergeWithTargetLocale = !1) => {
1035
- const baseLanguage = pluginConfig.getSourceLocale();
1036
- let baseDoc = null, i18nDoc = null;
1037
- docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
1038
- docInfo.documentId,
1039
- docInfo.versionId,
1040
- client
1041
- )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
1042
- const i18nDocId = (await getOrCreateTranslationMetadata(
1043
- docInfo.documentId,
1044
- baseDoc,
1045
- client,
1046
- baseLanguage
1047
- )).translations.find(
1048
- (translation) => translation.language === localeId
1049
- )?.value?._ref;
1050
- i18nDocId && (i18nDoc = await findLatestDraft(i18nDocId, client)), mergeWithTargetLocale && i18nDoc ? baseDoc = i18nDoc : docInfo.documentId && docInfo.versionId && (baseDoc = await findDocumentAtRevision(
1051
- docInfo.documentId,
1052
- docInfo.versionId,
1053
- client
1054
- )), baseDoc || (baseDoc = await findLatestDraft(docInfo.documentId, client));
1055
- const merged = BaseDocumentMerger.documentLevelMerge(
1056
- translatedFields,
1057
- baseDoc
1058
- );
1059
- if (i18nDoc)
1060
- await patchI18nDoc(
1061
- docInfo.documentId,
1062
- i18nDoc._id,
1063
- baseDoc,
1064
- merged,
1065
- translatedFields,
1066
- client,
1067
- i18nDoc
1068
- );
1069
- else {
1070
- const freshTranslationMetadata = await getOrCreateTranslationMetadata(
1071
- docInfo.documentId,
1072
- baseDoc,
1073
- client,
1074
- baseLanguage
1075
- ), freshI18nDocId = freshTranslationMetadata.translations.find(
1076
- (translation) => translation.language === localeId
1077
- )?.value?._ref;
1078
- if (freshI18nDocId) {
1079
- const freshI18nDoc = await findLatestDraft(freshI18nDocId, client);
1080
- await patchI18nDoc(
1081
- docInfo.documentId,
1082
- freshI18nDoc._id,
1083
- baseDoc,
1084
- merged,
1085
- translatedFields,
1086
- client,
1087
- freshI18nDoc
1239
+ async function checkTranslationStatus(fileQueryData, downloadStatus, secrets) {
1240
+ overrideConfig(secrets);
1241
+ try {
1242
+ const currentQueryData = fileQueryData.filter((item) => {
1243
+ const statusKey = createTranslationStatusKey(
1244
+ item.branchId,
1245
+ item.fileId,
1246
+ item.versionId,
1247
+ item.locale
1248
+ ), stableKey = createStableTranslationKey(
1249
+ item.branchId,
1250
+ item.fileId,
1251
+ item.locale
1088
1252
  );
1089
- return;
1090
- }
1091
- await createI18nDocAndPatchMetadata(
1092
- baseDoc,
1093
- merged,
1094
- localeId,
1095
- client,
1096
- freshTranslationMetadata,
1097
- docInfo.documentId,
1098
- languageField
1253
+ 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);
1254
+ });
1255
+ return currentQueryData.length === 0 ? !0 : ((await gt.queryFileData({
1256
+ translatedFiles: currentQueryData
1257
+ })).translatedFiles || []).filter(
1258
+ (translation) => translation.completedAt
1099
1259
  );
1260
+ } catch (error) {
1261
+ return console.error("Error checking translation status", error), [];
1100
1262
  }
1101
- };
1263
+ }
1102
1264
  async function importDocument(docInfo, localeId, document2, context, mergeWithTargetLocale = !1) {
1103
1265
  const { client } = context, deserialized = deserializeDocument(document2);
1104
- return documentLevelPatch(
1266
+ return getTranslationStrategyForType(
1267
+ deserialized._type
1268
+ ).patch(
1105
1269
  docInfo,
1106
- // versionId is not used here, since we just use the _rev id in the deserialized HTML itself
1107
1270
  deserialized,
1108
1271
  localeId,
1109
1272
  client,
1110
- pluginConfig.getLanguageField(),
1111
1273
  mergeWithTargetLocale
1112
1274
  );
1113
1275
  }
@@ -1235,11 +1397,13 @@ async function processImportBatch(items, options = {}) {
1235
1397
  ), item.key),
1236
1398
  {
1237
1399
  ...options,
1238
- getConcurrencyKey: (item) => createStableTranslationKey(
1239
- void 0,
1240
- item.docInfo.documentId,
1241
- item.locale
1242
- ),
1400
+ // Serialize all locales of the same document when imports may patch the
1401
+ // source document in place: internationalized-array imports do a
1402
+ // read-merge-set, so concurrent locale imports would clobber each other
1403
+ // (last write wins). Document-level imports write to separate per-locale
1404
+ // documents and stay parallel. In 'mixed' mode the per-document strategy
1405
+ // is only known after deserialization, so serialize conservatively.
1406
+ getConcurrencyKey: (item) => pluginConfig.getTranslationLevel() === "document" ? void 0 : getPublishedId(item.docInfo.documentId),
1243
1407
  onItemSuccess: (item, key) => {
1244
1408
  successfulImports.push(key), options.onItemSuccess?.(item, key);
1245
1409
  },
@@ -1335,7 +1499,34 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1335
1499
  localeId: locale,
1336
1500
  description: gt.getLocaleProperties(locale).name,
1337
1501
  enabled: !0
1338
- })), TranslationsContext = createContext(null), useTranslations = () => {
1502
+ })), TranslationsContext = createContext(null), getUploadedVersionsStorageKey = (projectId, dataset) => `gt-sanity:uploadedVersions:${projectId ?? ""}:${dataset ?? ""}`;
1503
+ function readUploadedVersions(storageKey) {
1504
+ try {
1505
+ const raw = window.sessionStorage.getItem(storageKey);
1506
+ if (!raw) return /* @__PURE__ */ new Map();
1507
+ const parsed = JSON.parse(raw);
1508
+ return isPlainRecord(parsed) ? new Map(
1509
+ Object.entries(parsed).filter(
1510
+ (entry) => typeof entry[1] == "string"
1511
+ )
1512
+ ) : /* @__PURE__ */ new Map();
1513
+ } catch {
1514
+ return /* @__PURE__ */ new Map();
1515
+ }
1516
+ }
1517
+ function writeUploadedVersions(storageKey, versions) {
1518
+ try {
1519
+ window.sessionStorage.setItem(
1520
+ storageKey,
1521
+ JSON.stringify(Object.fromEntries(versions))
1522
+ );
1523
+ } catch {
1524
+ }
1525
+ }
1526
+ function isPlainRecord(value) {
1527
+ return typeof value == "object" && value !== null && !Array.isArray(value);
1528
+ }
1529
+ const useTranslations = () => {
1339
1530
  const context = useContext(TranslationsContext);
1340
1531
  if (!context)
1341
1532
  throw new Error("useTranslations must be used within TranslationsProvider");
@@ -1356,13 +1547,21 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1356
1547
  downloaded: /* @__PURE__ */ new Set(),
1357
1548
  failed: /* @__PURE__ */ new Set(),
1358
1549
  skipped: /* @__PURE__ */ new Set()
1359
- }), downloadStatusRef = useRef(downloadStatus), [translationStatuses, setTranslationStatuses] = useState(/* @__PURE__ */ new Map()), [isRefreshing, setIsRefreshing] = useState(!1), client = useClient(), schema = useSchema(), translationContext = { client, schema }, toast = useToast(), { loading: loadingSecrets, secrets } = useSecrets(
1550
+ }), downloadStatusRef = useRef(downloadStatus), [translationStatuses, setTranslationStatuses] = useState(/* @__PURE__ */ new Map()), [isRefreshing, setIsRefreshing] = useState(!1), client = useClient(), { projectId, dataset } = client.config(), uploadedVersionsStorageKey = getUploadedVersionsStorageKey(
1551
+ projectId,
1552
+ dataset
1553
+ ), [uploadedVersions, setUploadedVersions] = useState(
1554
+ () => readUploadedVersions(uploadedVersionsStorageKey)
1555
+ ), schema = useSchema(), translationContext = { client, schema }, toast = useToast(), { loading: loadingSecrets, secrets } = useSecrets(
1360
1556
  pluginConfig.getSecretsNamespace()
1361
1557
  ), [branchId, setBranchId] = useState(void 0);
1362
1558
  useEffect(() => {
1363
1559
  downloadStatusRef.current = downloadStatus;
1364
1560
  }, [downloadStatus]);
1365
- const fetchDocuments = useCallback(async () => {
1561
+ const getVersionId = useCallback(
1562
+ (document2) => uploadedVersions.get(getDocumentPublishedId(document2)) ?? document2._rev,
1563
+ [uploadedVersions]
1564
+ ), fetchDocuments = useCallback(async () => {
1366
1565
  setLoadingDocuments(!0);
1367
1566
  try {
1368
1567
  if (singleDocument) {
@@ -1434,7 +1633,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1434
1633
  const availableLocaleIds = locales.filter((locale) => locale.enabled !== !1).map((locale) => locale.localeId), transformedDocuments = documents.map((doc) => {
1435
1634
  const { [pluginConfig.getLanguageField()]: _2, ...cleanDoc } = doc, baseLanguage = pluginConfig.getSourceLocale();
1436
1635
  try {
1437
- const serialized = serializeDocument(
1636
+ const serialized = getTranslationStrategy(doc).serialize(
1438
1637
  cleanDoc,
1439
1638
  schema,
1440
1639
  baseLanguage
@@ -1451,7 +1650,11 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1451
1650
  }
1452
1651
  return null;
1453
1652
  }).filter((doc) => doc !== null), uploadResult = await uploadFiles(transformedDocuments, secrets);
1454
- await initProject(uploadResult, { timeout: 600 }, secrets), await createJobs(uploadResult, availableLocaleIds, secrets), toast.push({
1653
+ await initProject(uploadResult, { timeout: 600 }, secrets), await createJobs(uploadResult, availableLocaleIds, secrets);
1654
+ const nextUploadedVersions = new Map(uploadedVersions);
1655
+ for (const { info } of transformedDocuments)
1656
+ info.versionId && nextUploadedVersions.set(info.documentId, info.versionId);
1657
+ writeUploadedVersions(uploadedVersionsStorageKey, nextUploadedVersions), setUploadedVersions(nextUploadedVersions), toast.push({
1455
1658
  title: `Translation tasks created for ${documents.length} documents`,
1456
1659
  status: "success",
1457
1660
  closable: !0
@@ -1466,7 +1669,14 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1466
1669
  setIsBusy(!1);
1467
1670
  }
1468
1671
  }
1469
- }, [secrets, documents, locales, schema]), handleImportAll = useCallback(async () => {
1672
+ }, [
1673
+ secrets,
1674
+ documents,
1675
+ locales,
1676
+ schema,
1677
+ uploadedVersions,
1678
+ uploadedVersionsStorageKey
1679
+ ]), handleImportAll = useCallback(async () => {
1470
1680
  if (!(!secrets || documents.length === 0 || !branchId)) {
1471
1681
  setIsBusy(!0);
1472
1682
  try {
@@ -1652,7 +1862,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1652
1862
  for (const localeId of availableLocaleIds) {
1653
1863
  const documentId = getDocumentPublishedId(doc);
1654
1864
  fileQueryData.push({
1655
- versionId: doc._rev,
1865
+ versionId: getVersionId(doc),
1656
1866
  fileId: documentId,
1657
1867
  branchId,
1658
1868
  locale: localeId
@@ -1667,7 +1877,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1667
1877
  const newStatuses = /* @__PURE__ */ new Map();
1668
1878
  for (const doc of documents)
1669
1879
  for (const localeId of availableLocaleIds) {
1670
- const documentId = getDocumentPublishedId(doc), versionId = doc._rev, key = createTranslationStatusKey(
1880
+ const documentId = getDocumentPublishedId(doc), versionId = getVersionId(doc), key = createTranslationStatusKey(
1671
1881
  branchId,
1672
1882
  documentId,
1673
1883
  versionId,
@@ -1710,7 +1920,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1710
1920
  setIsRefreshing(!1);
1711
1921
  }
1712
1922
  }
1713
- }, [secrets, documents, locales, branchId]), handleImportDocument = useCallback(
1923
+ }, [secrets, documents, locales, branchId, getVersionId]), handleImportDocument = useCallback(
1714
1924
  async (documentId, versionId, localeId) => {
1715
1925
  if (!secrets) return;
1716
1926
  const key = createTranslationStatusKey(
@@ -1972,6 +2182,7 @@ const getLocales = async (_secrets) => pluginConfig.getLocales().map((locale) =>
1972
2182
  loadingSecrets,
1973
2183
  secrets,
1974
2184
  branchId,
2185
+ getVersionId,
1975
2186
  // Actions
1976
2187
  setLocales,
1977
2188
  setAutoRefresh,
@@ -3258,7 +3469,8 @@ const WrapText = dt(Box)`
3258
3469
  autoPatchReferences,
3259
3470
  setAutoPatchReferences,
3260
3471
  autoPublish,
3261
- setAutoPublish
3472
+ setAutoPublish,
3473
+ getVersionId
3262
3474
  } = useTranslations(), [isImporting, setIsImporting] = useState(!1), [isPublishing, setIsPublishing] = useState(!1), toast = useToast(), document2 = documents[0], currentDocumentLanguage = useMemo(() => {
3263
3475
  if (!document2) return null;
3264
3476
  const languageField = pluginConfig.getLanguageField();
@@ -3268,15 +3480,15 @@ const WrapText = dt(Box)`
3268
3480
  return locales.filter(
3269
3481
  (locale) => locale.enabled !== !1 && locale.localeId !== sourceLocale
3270
3482
  );
3271
- }, [locales]), documentId = useMemo(() => document2 ? getDocumentPublishedId(document2) : null, [document2]), handleImportTranslations = useCallback(
3483
+ }, [locales]), documentId = useMemo(() => document2 ? getDocumentPublishedId(document2) : null, [document2]), versionId = useMemo(() => document2 ? getVersionId(document2) : null, [document2, getVersionId]), handleImportTranslations = useCallback(
3272
3484
  async (options = {}) => {
3273
3485
  const { autoOnly = !1 } = options;
3274
- if (isImporting || !documentId || autoOnly && !autoImport) return;
3486
+ if (isImporting || !documentId || !versionId || autoOnly && !autoImport) return;
3275
3487
  const readyTranslations = availableLocales.filter((locale) => {
3276
3488
  const key = createTranslationStatusKey(
3277
3489
  branchId,
3278
3490
  documentId,
3279
- document2._rev,
3491
+ versionId,
3280
3492
  locale.localeId
3281
3493
  );
3282
3494
  return translationStatuses.get(key)?.isReady && !importedTranslations.has(key);
@@ -3284,11 +3496,9 @@ const WrapText = dt(Box)`
3284
3496
  if (readyTranslations.length !== 0) {
3285
3497
  setIsImporting(!0);
3286
3498
  try {
3287
- await Promise.all(
3288
- readyTranslations.map(
3289
- (locale) => handleImportDocument(documentId, document2._rev, locale.localeId)
3290
- )
3291
- ), autoPatchReferences && await handlePatchDocumentReferences(), autoPublish && await handlePublishAllTranslations();
3499
+ for (const locale of readyTranslations)
3500
+ await handleImportDocument(documentId, versionId, locale.localeId);
3501
+ autoPatchReferences && await handlePatchDocumentReferences(), autoPublish && await handlePublishAllTranslations();
3292
3502
  } finally {
3293
3503
  setIsImporting(!1);
3294
3504
  }
@@ -3298,6 +3508,7 @@ const WrapText = dt(Box)`
3298
3508
  autoImport,
3299
3509
  isImporting,
3300
3510
  documentId,
3511
+ versionId,
3301
3512
  availableLocales,
3302
3513
  translationStatuses,
3303
3514
  importedTranslations,
@@ -3372,7 +3583,7 @@ const WrapText = dt(Box)`
3372
3583
  }
3373
3584
  )
3374
3585
  ] }),
3375
- documentId && availableLocales.length > 0 && /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3586
+ documentId && versionId && availableLocales.length > 0 && /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3376
3587
  /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
3377
3588
  /* @__PURE__ */ jsx(Text, { as: "h2", weight: "semibold", size: 2, children: "Translation Status" }),
3378
3589
  /* @__PURE__ */ jsxs(Flex, { gap: 3, align: "center", children: [
@@ -3402,7 +3613,7 @@ const WrapText = dt(Box)`
3402
3613
  const key = createTranslationStatusKey(
3403
3614
  branchId,
3404
3615
  documentId,
3405
- document2._rev,
3616
+ versionId,
3406
3617
  locale.localeId
3407
3618
  ), status = translationStatuses.get(key), progress = status?.progress || 0, isImported = importedTranslations.has(key);
3408
3619
  return /* @__PURE__ */ jsx(
@@ -3414,7 +3625,7 @@ const WrapText = dt(Box)`
3414
3625
  importFile: async () => {
3415
3626
  !isImported && status?.isReady && await handleImportDocument(
3416
3627
  documentId,
3417
- document2._rev,
3628
+ versionId,
3418
3629
  locale.localeId
3419
3630
  );
3420
3631
  }
@@ -3437,7 +3648,7 @@ const WrapText = dt(Box)`
3437
3648
  const key = createTranslationStatusKey(
3438
3649
  branchId,
3439
3650
  documentId,
3440
- document2._rev,
3651
+ versionId,
3441
3652
  locale.localeId
3442
3653
  );
3443
3654
  return !translationStatuses.get(key)?.isReady || importedTranslations.has(key);
@@ -3464,7 +3675,7 @@ const WrapText = dt(Box)`
3464
3675
  const key = createTranslationStatusKey(
3465
3676
  branchId,
3466
3677
  documentId,
3467
- document2._rev,
3678
+ versionId,
3468
3679
  locale.localeId
3469
3680
  );
3470
3681
  return importedTranslations.has(key);
@@ -3474,7 +3685,7 @@ const WrapText = dt(Box)`
3474
3685
  const key = createTranslationStatusKey(
3475
3686
  branchId,
3476
3687
  documentId,
3477
- document2._rev,
3688
+ versionId,
3478
3689
  locale.localeId
3479
3690
  );
3480
3691
  return translationStatuses.get(key)?.isReady;
@@ -3562,9 +3773,6 @@ const WrapText = dt(Box)`
3562
3773
  /* @__PURE__ */ jsx("code", { children: pluginConfig.getSourceLocale() }),
3563
3774
  " documents."
3564
3775
  ] }) });
3565
- }, TranslationTab = (props) => {
3566
- const { displayed } = props.document;
3567
- return /* @__PURE__ */ jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsx(TranslationsProvider, { singleDocument: displayed, children: /* @__PURE__ */ jsx(TranslationView, {}) }) });
3568
3776
  }, translateAction = (props) => {
3569
3777
  const [dialogOpen, setDialogOpen] = useState(!1), document2 = props.draft || props.published;
3570
3778
  return {
@@ -3579,7 +3787,228 @@ const WrapText = dt(Box)`
3579
3787
  content: /* @__PURE__ */ jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsx(TranslationsProvider, { singleDocument: document2, children: /* @__PURE__ */ jsx(TranslationView, {}) }) })
3580
3788
  } : null
3581
3789
  };
3582
- }, TranslationsTable = () => {
3790
+ };
3791
+ function readGTOptions(schemaType) {
3792
+ return schemaType?.options?.gtInternationalizedArray;
3793
+ }
3794
+ function toneFromValidation(validation) {
3795
+ if (validation?.length) {
3796
+ if (validation.some((marker) => marker.level === "error")) return "critical";
3797
+ if (validation.some((marker) => marker.level === "warning")) return "caution";
3798
+ }
3799
+ }
3800
+ function RemoveLocaleButton(props) {
3801
+ const { isSource, readOnly, onRemove } = props, button = (
3802
+ // The span keeps the tooltip working when the button is disabled
3803
+ // (disabled buttons don't emit pointer events).
3804
+ /* @__PURE__ */ jsx("span", { style: { paddingBottom: "2px" }, children: /* @__PURE__ */ jsx(
3805
+ Button,
3806
+ {
3807
+ mode: "bleed",
3808
+ icon: RemoveCircleIcon,
3809
+ tone: "critical",
3810
+ disabled: readOnly || isSource,
3811
+ onClick: onRemove
3812
+ }
3813
+ ) })
3814
+ );
3815
+ return isSource ? /* @__PURE__ */ jsx(
3816
+ Tooltip,
3817
+ {
3818
+ animate: !0,
3819
+ portal: !0,
3820
+ placement: "top",
3821
+ fallbackPlacements: ["right", "left"],
3822
+ content: /* @__PURE__ */ jsx(Text, { muted: !0, size: 1, children: "Can't remove the source language" }),
3823
+ children: button
3824
+ }
3825
+ ) : button;
3826
+ }
3827
+ function InternationalizedValueItem(props) {
3828
+ 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 = useCallback(
3829
+ (patch) => {
3830
+ if (!Array.isArray(patch)) {
3831
+ onChange(patch);
3832
+ return;
3833
+ }
3834
+ const currentValue = value?.value;
3835
+ if (!((currentValue == null || Array.isArray(currentValue) && currentValue.length === 0) && patch.some(
3836
+ (p) => p.type === "insert" && Array.isArray(p.path) && p.path.length > 0 && (p.path[0] === "value" || typeof p.path[0] == "number")
3837
+ ))) {
3838
+ onChange(patch);
3839
+ return;
3840
+ }
3841
+ const rerooted = patch.map(
3842
+ (p) => p.type === "insert" && Array.isArray(p.path) && p.path[0] !== "value" ? { ...p, path: ["value", ...p.path] } : p
3843
+ );
3844
+ onChange(
3845
+ currentValue === void 0 ? [setIfMissing([], ["value"]), ...rerooted] : rerooted
3846
+ );
3847
+ },
3848
+ [onChange, value?.value]
3849
+ ), members = useMemo(
3850
+ () => inputProps.members.filter(
3851
+ (member) => member.kind === "field" && member.name === "value"
3852
+ ).map((member) => ({
3853
+ ...member,
3854
+ field: {
3855
+ ...member.field,
3856
+ schemaType: {
3857
+ ...member.field.schemaType,
3858
+ title: /* @__PURE__ */ jsx(Label, { muted: !0, size: 1, children: languageLabel })
3859
+ }
3860
+ }
3861
+ })),
3862
+ [inputProps.members, languageLabel]
3863
+ ), handleRemove = useCallback(() => {
3864
+ onChange(unset());
3865
+ }, [onChange]);
3866
+ return /* @__PURE__ */ jsx(Card, { paddingTop: 2, tone: toneFromValidation(validation), children: /* @__PURE__ */ jsxs(Flex, { align: "flex-end", gap: 2, children: [
3867
+ /* @__PURE__ */ jsx(Box, { flex: 1, children: inputProps.renderInput({
3868
+ ...inputProps,
3869
+ members,
3870
+ onChange: wrappedOnChange
3871
+ // renderInput's parameter is typed as the base InputProps, which
3872
+ // doesn't know about object members.
3873
+ }) }),
3874
+ /* @__PURE__ */ jsx(
3875
+ RemoveLocaleButton,
3876
+ {
3877
+ isSource,
3878
+ readOnly: !!inputProps.readOnly,
3879
+ onRemove: handleRemove
3880
+ }
3881
+ )
3882
+ ] }) });
3883
+ }
3884
+ function InternationalizedArrayInput(props) {
3885
+ const { members, schemaType, value, readOnly, onItemAppend } = props, gtOptions = readGTOptions(schemaType), itemType = schemaType.of[0]?.name, handleAdd = useCallback(
3886
+ (language) => {
3887
+ const item = { _key: randomKey(), _type: itemType, language };
3888
+ onItemAppend(item);
3889
+ },
3890
+ [onItemAppend, itemType]
3891
+ );
3892
+ if (!gtOptions)
3893
+ return props.renderDefault(props);
3894
+ const allLocales = [gtOptions.sourceLocale, ...gtOptions.locales], existing = new Set(
3895
+ (value ?? []).map((item) => item.language).filter(Boolean)
3896
+ ), missing = allLocales.filter((locale) => !existing.has(locale));
3897
+ return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3898
+ members.length > 0 ? /* @__PURE__ */ jsx(Stack, { space: 2, children: members.map(
3899
+ (member) => member.kind === "item" ? /* @__PURE__ */ jsx(ArrayOfObjectsItem, { ...props, member }, member.key) : /* @__PURE__ */ jsx(MemberItemError, { member }, member.key)
3900
+ ) }) : /* @__PURE__ */ jsx(Card, { border: !0, tone: "transparent", padding: 3, radius: 2, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This internationalized field currently has no translations." }) }),
3901
+ !readOnly && missing.length > 0 ? /* @__PURE__ */ jsx(Grid, { columns: Math.min(missing.length, 4), gap: 1, children: missing.map((locale) => /* @__PURE__ */ jsx(
3902
+ Button,
3903
+ {
3904
+ icon: AddIcon,
3905
+ mode: "ghost",
3906
+ text: gtOptions.titles?.[locale] ?? locale,
3907
+ onClick: () => handleAdd(locale)
3908
+ },
3909
+ locale
3910
+ )) }) : null
3911
+ ] });
3912
+ }
3913
+ const DEFAULT_TYPE_PREFIX = "internationalizedArray";
3914
+ function resolveComponents(overrides) {
3915
+ const input = overrides?.input === !1 ? void 0 : overrides?.input ?? InternationalizedArrayInput, item = overrides?.item === !1 ? void 0 : overrides?.item ?? InternationalizedValueItem, defaultField = input === InternationalizedArrayInput ? (
3916
+ // Reset the field level so inline per-locale inputs don't inherit
3917
+ // nested-object indentation.
3918
+ ((fieldProps) => fieldProps.renderDefault({ ...fieldProps, level: 0 }))
3919
+ ) : void 0, field = overrides?.field === !1 ? void 0 : overrides?.field ?? defaultField;
3920
+ return { input, item, field };
3921
+ }
3922
+ function capitalize(value) {
3923
+ return value.charAt(0).toUpperCase() + value.slice(1);
3924
+ }
3925
+ function fieldTypeName(fieldType) {
3926
+ return typeof fieldType == "string" ? fieldType : fieldType.name;
3927
+ }
3928
+ function valueField(fieldType) {
3929
+ if (fieldType === "string" || fieldType === "text")
3930
+ return { name: "value", type: fieldType, title: "Value" };
3931
+ if (fieldType === "block")
3932
+ return {
3933
+ name: "value",
3934
+ type: "array",
3935
+ title: "Value",
3936
+ of: [{ type: "block" }]
3937
+ };
3938
+ const { name: _name, ...rest } = fieldType;
3939
+ return { name: "value", title: "Value", ...rest };
3940
+ }
3941
+ function makeLanguageTitle(options) {
3942
+ const { languageTitles, getLanguageTitle, sourceLocale } = options;
3943
+ return (locale) => getLanguageTitle?.(locale) ?? languageTitles?.[locale] ?? getLocaleProperties(locale, sourceLocale).name ?? locale;
3944
+ }
3945
+ function buildTypesForPrefix(prefix2, options) {
3946
+ const { sourceLocale, locales, fieldTypes } = options, languageTitle = makeLanguageTitle(options), components = resolveComponents(options.components);
3947
+ return fieldTypes.map((fieldType) => {
3948
+ const typeName = `${prefix2}${capitalize(fieldTypeName(fieldType))}`, valueTypeName = `${typeName}Value`, gtInternationalizedArray = {
3949
+ sourceLocale,
3950
+ locales,
3951
+ titles: Object.fromEntries(
3952
+ [sourceLocale, ...locales].map((locale) => [
3953
+ locale,
3954
+ languageTitle(locale)
3955
+ ])
3956
+ )
3957
+ }, valueObject = {
3958
+ type: "object",
3959
+ name: valueTypeName,
3960
+ ...components.item ? { components: { item: components.item } } : {},
3961
+ fields: [
3962
+ {
3963
+ name: "language",
3964
+ type: "string",
3965
+ title: "Language",
3966
+ readOnly: !0,
3967
+ // GT's inline item shows the locale as the value field's label;
3968
+ // with a custom or detached item, keep the field visible so the
3969
+ // locale is still discoverable in default/dialog rendering.
3970
+ hidden: components.item === InternationalizedValueItem
3971
+ },
3972
+ valueField(fieldType)
3973
+ ],
3974
+ options: { gtInternationalizedArray },
3975
+ preview: {
3976
+ select: { language: "language", value: "value" },
3977
+ prepare(selection) {
3978
+ const localeLabel = selection.language ? languageTitle(selection.language) : "Unknown";
3979
+ return {
3980
+ title: typeof selection.value == "string" ? selection.value : localeLabel,
3981
+ subtitle: localeLabel
3982
+ };
3983
+ }
3984
+ }
3985
+ }, arrayComponents = {
3986
+ ...components.input ? { input: components.input } : {},
3987
+ ...components.field ? { field: components.field } : {}
3988
+ };
3989
+ return {
3990
+ name: typeName,
3991
+ type: "array",
3992
+ ...Object.keys(arrayComponents).length ? { components: arrayComponents } : {},
3993
+ of: [valueObject],
3994
+ options: { gtInternationalizedArray }
3995
+ };
3996
+ });
3997
+ }
3998
+ function createInternationalizedArrayTypes(options) {
3999
+ const prefix2 = options.typePrefix ?? DEFAULT_TYPE_PREFIX, includeCompatibilityTypes = options.includeCompatibilityTypes ?? !0, types = buildTypesForPrefix(prefix2, options);
4000
+ return includeCompatibilityTypes && prefix2 !== DEFAULT_TYPE_PREFIX && types.push(...buildTypesForPrefix(DEFAULT_TYPE_PREFIX, options)), types;
4001
+ }
4002
+ function resolveFieldLevelConfig(config) {
4003
+ return {
4004
+ enabled: !1,
4005
+ fieldTypes: ["string", "text"],
4006
+ typePrefix: DEFAULT_TYPE_PREFIX,
4007
+ includeCompatibilityTypes: !0,
4008
+ ...config
4009
+ };
4010
+ }
4011
+ const TranslationsTable = () => {
3583
4012
  const {
3584
4013
  documents,
3585
4014
  locales,
@@ -3588,7 +4017,8 @@ const WrapText = dt(Box)`
3588
4017
  downloadStatus,
3589
4018
  importedTranslations,
3590
4019
  handleImportDocument,
3591
- branchId
4020
+ branchId,
4021
+ getVersionId
3592
4022
  } = useTranslations();
3593
4023
  return loadingDocuments ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsx(Spinner, {}) }) : /* @__PURE__ */ jsx(Box, { style: { maxHeight: "60vh", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 2, children: documents.map((document2) => /* @__PURE__ */ jsx(Card, { shadow: 1, padding: 3, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3594
4024
  /* @__PURE__ */ jsx(Flex, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxs(Box, { flex: 1, children: [
@@ -3596,10 +4026,10 @@ const WrapText = dt(Box)`
3596
4026
  /* @__PURE__ */ jsx(Text, { size: 0, muted: !0, style: { marginTop: "2px" }, children: document2._type })
3597
4027
  ] }) }),
3598
4028
  /* @__PURE__ */ jsx(Stack, { space: 2, children: locales.length > 0 ? locales.filter((locale) => locale.enabled !== !1).map((locale) => {
3599
- const documentId = getDocumentPublishedId(document2), key = createTranslationStatusKey(
4029
+ const documentId = getDocumentPublishedId(document2), versionId = getVersionId(document2), key = createTranslationStatusKey(
3600
4030
  branchId,
3601
4031
  documentId,
3602
- document2._rev,
4032
+ versionId,
3603
4033
  locale.localeId
3604
4034
  ), status = translationStatuses.get(key), isDownloaded = downloadStatus.downloaded.has(key), isImported = importedTranslations.has(key);
3605
4035
  return /* @__PURE__ */ jsx(
@@ -3611,12 +4041,12 @@ const WrapText = dt(Box)`
3611
4041
  importFile: async () => {
3612
4042
  await handleImportDocument(
3613
4043
  documentId,
3614
- document2._rev,
4044
+ versionId,
3615
4045
  locale.localeId
3616
4046
  );
3617
4047
  }
3618
4048
  },
3619
- `${document2._id}-${document2._rev}-${locale.localeId}`
4049
+ `${document2._id}-${versionId}-${locale.localeId}`
3620
4050
  );
3621
4051
  }) : /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No locales configured" }) })
3622
4052
  ] }) }, document2._id)) }) });
@@ -3970,7 +4400,10 @@ const WrapText = dt(Box)`
3970
4400
  }
3971
4401
  )
3972
4402
  ] });
3973
- }, TranslationsTool = () => /* @__PURE__ */ jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsx(TranslationsProvider, { children: /* @__PURE__ */ jsx(TranslationsToolContent, {}) }) }), gtPlugin = definePlugin(
4403
+ }, TranslationsTool = () => /* @__PURE__ */ jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsx(TranslationsProvider, { children: /* @__PURE__ */ jsx(TranslationsToolContent, {}) }) }), TranslationTab = (props) => {
4404
+ const { displayed } = props.document;
4405
+ return /* @__PURE__ */ jsx(BaseTranslationWrapper, { showContainer: !1, children: /* @__PURE__ */ jsx(TranslationsProvider, { singleDocument: displayed, children: /* @__PURE__ */ jsx(TranslationView, {}) }) });
4406
+ }, gtPlugin = definePlugin(
3974
4407
  ({
3975
4408
  languageField = "language",
3976
4409
  sourceLocale,
@@ -3990,11 +4423,16 @@ const WrapText = dt(Box)`
3990
4423
  additionalSerializers = {},
3991
4424
  additionalDeserializers = {},
3992
4425
  additionalBlockDeserializers = [],
3993
- showDocumentInternationalization = !0
4426
+ showDocumentInternationalization = !0,
4427
+ internationalizedArray,
4428
+ fieldLevelLocalization,
4429
+ translationLevel = "document",
4430
+ fieldLevelDocuments
3994
4431
  }) => {
3995
- const resolvedSourceLocale = sourceLocale ?? defaultLocale ?? libraryDefaultLocale;
3996
- let normalizedTranslateDocuments;
3997
- translateDocuments && (normalizedTranslateDocuments = translateDocuments.map((entry) => typeof entry == "string" ? { type: entry } : entry).filter((filter2) => filter2.documentId || filter2.type)), pluginConfig.init(
4432
+ const resolvedSourceLocale = sourceLocale ?? defaultLocale ?? 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(
4433
+ internationalizedArray ?? fieldLevelLocalization
4434
+ );
4435
+ pluginConfig.init(
3998
4436
  secretsNamespace,
3999
4437
  languageField,
4000
4438
  resolvedSourceLocale,
@@ -4009,17 +4447,22 @@ const WrapText = dt(Box)`
4009
4447
  additionalStopTypes,
4010
4448
  additionalSerializers,
4011
4449
  additionalDeserializers,
4012
- additionalBlockDeserializers
4450
+ additionalBlockDeserializers,
4451
+ translationLevel,
4452
+ normalizedFieldLevelDocuments,
4453
+ fieldLevelConfig.typePrefix
4013
4454
  ), gt.setConfig({
4014
4455
  sourceLocale: resolvedSourceLocale,
4015
4456
  customMapping,
4016
4457
  apiKey,
4017
4458
  projectId
4018
4459
  });
4460
+ const arrayLocalizedTypes = /* @__PURE__ */ new Set();
4461
+ 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));
4019
4462
  const plugins = [];
4020
4463
  if (showDocumentInternationalization) {
4021
- const schemaTypes = normalizedTranslateDocuments?.map((filter2) => filter2.type).filter((type) => !!type) ?? [];
4022
- if (schemaTypes.length > 0) {
4464
+ const schemaTypes2 = normalizedTranslateDocuments?.map((filter2) => filter2.type).filter((type) => !!type).filter((type) => !arrayLocalizedTypes.has(type)) ?? [];
4465
+ if (schemaTypes2.length > 0) {
4023
4466
  const supportedLanguages = [resolvedSourceLocale, ...locales].map((locale) => {
4024
4467
  const props = getLocaleProperties(locale, resolvedSourceLocale);
4025
4468
  return { id: locale, title: props.name };
@@ -4027,15 +4470,28 @@ const WrapText = dt(Box)`
4027
4470
  plugins.push(
4028
4471
  documentInternationalization({
4029
4472
  supportedLanguages,
4030
- schemaTypes,
4473
+ schemaTypes: schemaTypes2,
4031
4474
  languageField
4032
4475
  })
4033
4476
  );
4034
4477
  }
4035
4478
  }
4479
+ const schemaTypes = fieldLevelConfig.enabled ? createInternationalizedArrayTypes({
4480
+ sourceLocale: resolvedSourceLocale,
4481
+ locales,
4482
+ fieldTypes: fieldLevelConfig.fieldTypes,
4483
+ languageTitles: fieldLevelConfig.languageTitles,
4484
+ getLanguageTitle: fieldLevelConfig.getLanguageTitle,
4485
+ typePrefix: fieldLevelConfig.typePrefix,
4486
+ includeCompatibilityTypes: fieldLevelConfig.includeCompatibilityTypes,
4487
+ components: fieldLevelConfig.components
4488
+ }) : [];
4036
4489
  return {
4037
4490
  name: "gt-sanity",
4038
4491
  plugins,
4492
+ schema: {
4493
+ types: schemaTypes
4494
+ },
4039
4495
  tools: [
4040
4496
  {
4041
4497
  name: "translations",
@@ -4057,6 +4513,7 @@ export {
4057
4513
  DocumentInternationalizationMenu,
4058
4514
  TranslationTab as TranslationsTab,
4059
4515
  attachGTData,
4516
+ createInternationalizedArrayTypes,
4060
4517
  customSerializers,
4061
4518
  defaultStopTypes,
4062
4519
  detachGTData,