@braze/sanity-plugin-ai-translation 0.2.72 → 0.2.74

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.
package/dist/index.d.ts CHANGED
@@ -332,6 +332,20 @@ export declare interface PluginConfig {
332
332
  * ```
333
333
  */
334
334
  excludeFields?: string[];
335
+ /**
336
+ * Fields to completely remove from the translated document.
337
+ * Unlike `excludeFields` (which preserves fields with their original values),
338
+ * `unsetFields` strips matched fields entirely so they are absent from the output.
339
+ * Supports the same pattern syntax as `excludeFields`: field names (e.g., 'courseId'),
340
+ * nested paths (e.g., 'meta.internalId'), wildcard arrays (e.g., 'sections[*].legacyId'),
341
+ * and hierarchical matches (e.g., '**.tmpField').
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * unsetFields: ['courseId', 'meta.internalId', 'sections[*].legacyId']
346
+ * ```
347
+ */
348
+ unsetFields?: string[];
335
349
  /**
336
350
  * Optional telemetry configuration for monitoring translation operations.
337
351
  * Provides callbacks for lightweight events and detailed error bundles.
package/dist/index.js CHANGED
@@ -482,8 +482,8 @@ function isNetworkError(error) {
482
482
  return response.status >= 500 || response.status === 429;
483
483
  }
484
484
  if (error instanceof Error) {
485
- const message = error.message;
486
- if (/\b(50[0-9]|429)\b/.test(message))
485
+ const message = error.message.toLowerCase();
486
+ if (/\b(50[0-9]|429)\b/.test(message) || message.includes("rate limit"))
487
487
  return !0;
488
488
  }
489
489
  return !1;
@@ -870,7 +870,14 @@ const fetchTranslationPrompts = async ({
870
870
  languageFieldName
871
871
  }) => {
872
872
  try {
873
- const doc = await client.fetch("*[_id == $documentId][0]", { documentId });
873
+ const doc = await retryWithBackoff(
874
+ () => client.fetch("*[_id == $documentId][0]", { documentId }),
875
+ {
876
+ maxAttempts: 3,
877
+ initialDelayMs: 1e3,
878
+ context: { operation: "localizeReferences.fetchDocument", documentId }
879
+ }
880
+ );
874
881
  if (!doc) {
875
882
  devLog.error("Document not found", new Error("Document not found"), {
876
883
  documentId,
@@ -900,17 +907,24 @@ const fetchTranslationPrompts = async ({
900
907
  }, updateReferences = async (documentId, client, refs, sourceLanguage, targetLanguage) => {
901
908
  if (!refs?.length)
902
909
  return [];
903
- const refIds = refs.map((r) => getPublishedId(r.value)), translations = await client.fetch(
904
- `*[_type == $translationMetadataSchemaType && array::intersects(translations[].value._ref, $refIds)] {
910
+ const refIds = refs.map((r) => getPublishedId(r.value)), translations = await retryWithBackoff(
911
+ () => client.fetch(
912
+ `*[_type == $translationMetadataSchemaType && array::intersects(translations[].value._ref, $refIds)] {
905
913
  "sourceId": translations[_key == $sourceLanguage][0].value._ref,
906
914
  "targetId": translations[_key == $targetLanguage][0].value._ref,
907
915
  "targetType": translations[_key == $sourceLanguage][0].value -> _type
908
916
  }`,
917
+ {
918
+ translationMetadataSchemaType: TRANSLATION_METADATA_TYPE,
919
+ refIds,
920
+ sourceLanguage,
921
+ targetLanguage
922
+ }
923
+ ),
909
924
  {
910
- translationMetadataSchemaType: TRANSLATION_METADATA_TYPE,
911
- refIds,
912
- sourceLanguage,
913
- targetLanguage
925
+ maxAttempts: 3,
926
+ initialDelayMs: 1e3,
927
+ context: { operation: "localizeReferences.fetchTranslations", refCount: refIds.length }
914
928
  }
915
929
  ), translationMap = /* @__PURE__ */ new Map();
916
930
  for (const t of translations)
@@ -948,81 +962,80 @@ const fetchTranslationPrompts = async ({
948
962
  }, updateBackwardReferences = async ({
949
963
  client,
950
964
  releaseId,
951
- sourceDocumentIds,
952
965
  targetLanguages,
953
966
  sourceLanguage,
954
967
  languageFieldName,
955
968
  excludeDocumentIds
956
969
  }) => {
957
- if (!sourceDocumentIds.length || !targetLanguages.length)
958
- return;
959
- const normalizedSourceIds = sourceDocumentIds.map((id) => getPublishedId(id));
960
- devLog.debug("Starting backward reference update", {
961
- releaseId,
962
- sourceDocumentCount: normalizedSourceIds.length,
963
- targetLanguages,
964
- excludeCount: excludeDocumentIds.length,
965
- component: "updateBackwardReferences"
966
- });
967
- try {
968
- for (const targetLanguage of targetLanguages) {
969
- const existingDocsWithStaleRefs = await client.fetch(
970
+ if (targetLanguages.length) {
971
+ devLog.debug("Starting backward reference update", {
972
+ releaseId,
973
+ targetLanguages,
974
+ excludeCount: excludeDocumentIds.length,
975
+ component: "updateBackwardReferences"
976
+ });
977
+ try {
978
+ const existingDocs = await client.fetch(
970
979
  `*[
971
- _id match "versions." + $releaseId + ".*" &&
972
- @[$languageFieldName] == $targetLanguage &&
973
- !(_id in $excludeIds)
974
- ] {
975
- _id,
976
- "hasStaleRefs": count(*[
977
- _id in $sourceDocumentIds &&
978
- ^references(@._id)
979
- ]) > 0
980
- }[hasStaleRefs == true]`,
980
+ sanity::partOfRelease($releaseId) &&
981
+ @[$languageFieldName] in $targetLanguages &&
982
+ !(_id in $excludeIds)
983
+ ]{
984
+ _id,
985
+ "language": @[$languageFieldName]
986
+ }`,
981
987
  {
982
988
  releaseId,
983
- targetLanguage,
989
+ targetLanguages,
984
990
  languageFieldName,
985
- excludeIds: excludeDocumentIds,
986
- sourceDocumentIds: normalizedSourceIds
991
+ excludeIds: excludeDocumentIds
987
992
  }
988
993
  );
989
- if (existingDocsWithStaleRefs.length === 0) {
990
- devLog.debug("No existing documents with stale references found", {
994
+ if (existingDocs.length === 0) {
995
+ devLog.debug("No existing documents found in release for target languages", {
991
996
  releaseId,
992
- targetLanguage,
997
+ targetLanguages,
993
998
  component: "updateBackwardReferences"
994
999
  });
995
- continue;
1000
+ return;
996
1001
  }
997
- devLog.info("Found existing documents with stale references", {
1002
+ devLog.info("Processing existing documents for backward reference updates", {
998
1003
  releaseId,
999
- targetLanguage,
1000
- count: existingDocsWithStaleRefs.length,
1001
- documentIds: existingDocsWithStaleRefs.map((d) => d._id),
1004
+ targetLanguages,
1005
+ count: existingDocs.length,
1002
1006
  component: "updateBackwardReferences"
1003
- }), await Promise.all(
1004
- existingDocsWithStaleRefs.map(async ({ _id }) => {
1007
+ });
1008
+ let updated = 0, errors = 0;
1009
+ for (const { _id } of existingDocs)
1010
+ try {
1005
1011
  await localizeReferences({
1006
1012
  client,
1007
1013
  documentId: _id,
1008
1014
  sourceLanguage,
1009
1015
  languageFieldName
1016
+ }), updated++;
1017
+ } catch (error) {
1018
+ errors++, devLog.warn("Failed to update references for document", {
1019
+ documentId: _id,
1020
+ error: error instanceof Error ? error.message : String(error),
1021
+ component: "updateBackwardReferences"
1010
1022
  });
1011
- })
1012
- ), devLog.info("Updated backward references", {
1023
+ }
1024
+ devLog.info("Backward reference update complete", {
1013
1025
  releaseId,
1014
- targetLanguage,
1015
- updatedCount: existingDocsWithStaleRefs.length,
1026
+ targetLanguages,
1027
+ processed: existingDocs.length,
1028
+ updated,
1029
+ errors,
1030
+ component: "updateBackwardReferences"
1031
+ });
1032
+ } catch (error) {
1033
+ devLog.error("Error updating backward references", error instanceof Error ? error : new Error(String(error)), {
1034
+ releaseId,
1035
+ targetLanguages,
1016
1036
  component: "updateBackwardReferences"
1017
1037
  });
1018
1038
  }
1019
- } catch (error) {
1020
- devLog.error("Error updating backward references", error instanceof Error ? error : new Error(String(error)), {
1021
- releaseId,
1022
- sourceDocumentIds: normalizedSourceIds,
1023
- targetLanguages,
1024
- component: "updateBackwardReferences"
1025
- });
1026
1039
  }
1027
1040
  }, setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages, mainSourceDocumentId) => {
1028
1041
  for (const sourceDocument of sourceDocuments)
@@ -1347,6 +1360,16 @@ const COMMON_ENUM_FIELDS = [
1347
1360
  /** SEO and URL-related fields that often shouldn't be translated */
1348
1361
  seo: ["slug", "handle", "permalink", "canonical", "redirect", "robots", "sitemap"]
1349
1362
  };
1363
+ function unsetDocumentFields(document, unsetFields) {
1364
+ if (!unsetFields.length)
1365
+ return document;
1366
+ const { filteredDocument, excludedValues } = filterExcludedFields(document, unsetFields);
1367
+ return Object.keys(excludedValues).length > 0 && devLog.info("Unset fields from document (permanently removed)", {
1368
+ documentId: document._id || "unknown",
1369
+ unsetFieldCount: Object.keys(excludedValues).length,
1370
+ unsetFields: Object.keys(excludedValues)
1371
+ }), filteredDocument;
1372
+ }
1350
1373
  function applyTransforms({
1351
1374
  document,
1352
1375
  registry,
@@ -2617,11 +2640,17 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2617
2640
  openaiConfig,
2618
2641
  releaseId,
2619
2642
  excludeFields = [],
2643
+ unsetFields = [],
2620
2644
  replaceExisting = !1,
2621
2645
  mainSourceDocumentId,
2622
2646
  telemetryConfig
2623
2647
  }) => {
2624
- const startTime = Date.now(), sourceLanguage = sourceDocument[languageFieldName], safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName);
2648
+ const startTime = Date.now(), sourceLanguage = sourceDocument[languageFieldName], safeUnsetFields = unsetFields.filter((field) => field !== languageFieldName);
2649
+ unsetFields.length !== safeUnsetFields.length && devLog.warn("Language field was in unsetFields - removing it to prevent translation errors", {
2650
+ documentId: sourceDocument._id,
2651
+ languageFieldName
2652
+ });
2653
+ const documentAfterUnset = unsetDocumentFields(sourceDocument, safeUnsetFields), safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName);
2625
2654
  excludeFields.length !== safeExcludeFields.length && devLog.warn("Language field was in excludeFields - removing it to prevent translation errors", {
2626
2655
  documentId: sourceDocument._id,
2627
2656
  languageFieldName,
@@ -2632,10 +2661,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2632
2661
  documentType: sourceDocument._type,
2633
2662
  excludeFields: safeExcludeFields,
2634
2663
  excludeFieldsLength: safeExcludeFields?.length ?? 0,
2635
- sourceHasTitle: "title" in sourceDocument,
2636
- sourceTitleValue: sourceDocument.title
2664
+ sourceHasTitle: "title" in documentAfterUnset,
2665
+ sourceTitleValue: documentAfterUnset.title
2637
2666
  });
2638
- const { filteredDocument, excludedValues } = filterExcludedFields(sourceDocument, safeExcludeFields);
2667
+ const { filteredDocument, excludedValues } = filterExcludedFields(documentAfterUnset, safeExcludeFields);
2639
2668
  devLog.info("After filtering excluded fields", {
2640
2669
  documentId: sourceDocument._id,
2641
2670
  filteredHasTitle: "title" in filteredDocument,
@@ -2858,6 +2887,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2858
2887
  openaiConfig,
2859
2888
  releaseId,
2860
2889
  excludeFields,
2890
+ unsetFields,
2861
2891
  replaceExisting,
2862
2892
  mainSourceDocumentId,
2863
2893
  telemetryConfig
@@ -2887,6 +2917,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2887
2917
  openaiConfig,
2888
2918
  releaseId,
2889
2919
  excludeFields,
2920
+ unsetFields,
2890
2921
  replaceExisting,
2891
2922
  mainSourceDocumentId,
2892
2923
  telemetryConfig
@@ -3002,6 +3033,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3002
3033
  translationMode,
3003
3034
  openaiConfig,
3004
3035
  excludeFields,
3036
+ unsetFields,
3005
3037
  releaseConfig,
3006
3038
  telemetryConfig
3007
3039
  }) => {
@@ -3122,6 +3154,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3122
3154
  openaiConfig,
3123
3155
  releaseId,
3124
3156
  excludeFields,
3157
+ unsetFields,
3125
3158
  replaceExisting: releaseConfig.replaceExisting,
3126
3159
  // Pass main source document ID for active translations tracking
3127
3160
  mainSourceDocumentId: sourceDocument._id,
@@ -3176,10 +3209,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3176
3209
  languageFieldName
3177
3210
  });
3178
3211
  })
3179
- ), releaseConfig.mode === "existing" && releaseId && await updateBackwardReferences({
3212
+ ), devLog.debug("Checking if backward reference update is needed", {
3213
+ releaseMode: releaseConfig.mode,
3214
+ releaseId,
3215
+ willUpdate: releaseConfig.mode === "existing" && !!releaseId,
3216
+ component: "translateDocumentSet"
3217
+ }), releaseConfig.mode === "existing" && releaseId && await updateBackwardReferences({
3180
3218
  client,
3181
3219
  releaseId,
3182
- sourceDocumentIds,
3183
3220
  targetLanguages,
3184
3221
  sourceLanguage,
3185
3222
  languageFieldName,
@@ -3302,6 +3339,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3302
3339
  translationMode,
3303
3340
  openaiConfig: config.openai,
3304
3341
  excludeFields: config.excludeFields,
3342
+ unsetFields: config.unsetFields,
3305
3343
  releaseConfig: {
3306
3344
  ...releaseConfig,
3307
3345
  replaceExisting,
@@ -3324,6 +3362,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3324
3362
  config.maxReferenceDepth,
3325
3363
  config.openai,
3326
3364
  config.excludeFields,
3365
+ config.unsetFields,
3327
3366
  config.telemetry,
3328
3367
  filteredReferences,
3329
3368
  translationMode,