@braze/sanity-plugin-ai-translation 0.2.51 → 0.2.52

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
@@ -446,6 +446,21 @@ declare interface TranslatableVTTCaptions {
446
446
  _originalMetadata: VTTCaptionMetadata[];
447
447
  }
448
448
 
449
+ /**
450
+ * Special type to track transformed Wistia captions.
451
+ * This combines metadata separation for token efficiency with compact text format for translation.
452
+ */
453
+ declare interface TranslatableWistiaCaptions {
454
+ _type: "translatable.wistia.captions";
455
+ translationText: string;
456
+ _originalMetadata: WistiaCaptionsMetadata;
457
+ _srtCues: Array<{
458
+ index: number;
459
+ startTime: string;
460
+ endTime: string;
461
+ }>;
462
+ }
463
+
449
464
  /**
450
465
  * Schema for translation history
451
466
  *
@@ -497,4 +512,40 @@ export declare const vttCaptionTransform: FieldTransform<
497
512
  TranslatableVTTCaptions
498
513
  >;
499
514
 
515
+ /**
516
+ * Represents a Wistia captions object with SRT content and metadata.
517
+ */
518
+ declare interface WistiaCaptions {
519
+ captionContent?: string;
520
+ captionFileName?: string;
521
+ isoLanguage?: string;
522
+ captionUploadStatus?: string;
523
+ captionUploadMessage?: string;
524
+ [key: string]: string | undefined;
525
+ }
526
+
527
+ /**
528
+ * Wistia captions metadata without captionContent, used for preservation during translation.
529
+ */
530
+ declare interface WistiaCaptionsMetadata {
531
+ captionFileName?: string;
532
+ isoLanguage?: string;
533
+ captionUploadStatus?: string;
534
+ captionUploadMessage?: string;
535
+ _originalIndex: number;
536
+ [key: string]: string | number | undefined;
537
+ }
538
+
539
+ /**
540
+ * Transform for handling Wistia captions in documents using SRT format.
541
+ * This stores metadata separately for token efficiency while formatting
542
+ * text content in a compact indexed format for better translation results.
543
+ *
544
+ * @public
545
+ */
546
+ export declare const wistiaCaptionTransform: FieldTransform<
547
+ WistiaCaptions,
548
+ TranslatableWistiaCaptions
549
+ >;
550
+
500
551
  export {};
package/dist/index.js CHANGED
@@ -80,7 +80,9 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
80
80
  }, updateActiveTranslationJob = (documentId, job) => {
81
81
  const entry = activeTranslations.get(documentId);
82
82
  if (entry) {
83
- const jobKey = `${job._id}_${job.language}`, existingJob = entry.state.translationJobs.get(jobKey), mergedJob = existingJob ? { ...existingJob, ...job } : job;
83
+ const jobKey = `${job._id}_${job.language}`, existingJob = entry.state.translationJobs.get(jobKey), jobWithDefinedValues = Object.fromEntries(
84
+ Object.entries(job).filter(([_, value]) => value !== void 0)
85
+ ), mergedJob = existingJob ? { ...existingJob, ...jobWithDefinedValues } : jobWithDefinedValues;
84
86
  entry.state.translationJobs.set(jobKey, mergedJob), notifySubscribers(entry);
85
87
  }
86
88
  }, getActiveTranslation = (documentId) => activeTranslations.get(documentId)?.state ?? null, subscribeToActiveTranslation = (documentId, callback) => {
@@ -3799,6 +3801,277 @@ function deserializeVTTCaptionText(translationText, originalMetadata) {
3799
3801
  resultExample: result.length > 0 ? result[0] : null
3800
3802
  }), result;
3801
3803
  }
3804
+ const LOCALE_TO_ISO = {
3805
+ // English variants
3806
+ "en-us": "eng",
3807
+ "en-US": "eng",
3808
+ "en-gb": "eng",
3809
+ "en-GB": "eng",
3810
+ "en-ca": "eng",
3811
+ "en-CA": "eng",
3812
+ "en-au": "eng",
3813
+ "en-AU": "eng",
3814
+ // Spanish variants
3815
+ es: "spa",
3816
+ "es-es": "spa",
3817
+ "es-ES": "spa",
3818
+ "es-mx": "spa",
3819
+ "es-MX": "spa",
3820
+ // French variants
3821
+ fr: "fra",
3822
+ "fr-fr": "fra",
3823
+ "fr-FR": "fra",
3824
+ "fr-ca": "fra",
3825
+ "fr-CA": "fra",
3826
+ // German variants
3827
+ de: "ger",
3828
+ "de-de": "ger",
3829
+ "de-DE": "ger",
3830
+ "de-at": "ger",
3831
+ "de-AT": "ger",
3832
+ // Italian variants
3833
+ it: "ita",
3834
+ "it-it": "ita",
3835
+ "it-IT": "ita",
3836
+ // Portuguese variants
3837
+ pt: "por",
3838
+ "pt-br": "por",
3839
+ "pt-BR": "por",
3840
+ "pt-pt": "por",
3841
+ "pt-PT": "por",
3842
+ // Russian variants
3843
+ ru: "rus",
3844
+ "ru-ru": "rus",
3845
+ "ru-RU": "rus",
3846
+ // Japanese variants
3847
+ ja: "jpn",
3848
+ "ja-jp": "jpn",
3849
+ "ja-JP": "jpn",
3850
+ // Korean variants
3851
+ ko: "kor",
3852
+ "ko-kr": "kor",
3853
+ "ko-KR": "kor",
3854
+ // Chinese variants
3855
+ zh: "chi",
3856
+ "zh-cn": "chi",
3857
+ "zh-CN": "chi",
3858
+ "zh-tw": "chi",
3859
+ "zh-TW": "chi",
3860
+ "zh-hk": "chi",
3861
+ "zh-HK": "chi"
3862
+ }, normalizeToIsoLanguageCode = (code) => {
3863
+ const normalizedCode = code.toLowerCase();
3864
+ return LOCALE_TO_ISO[normalizedCode];
3865
+ }, wistiaCaptionTransform = {
3866
+ matcher: {
3867
+ fieldName: "wistiaCaptions"
3868
+ },
3869
+ toTranslatable: (wistiaCaptions, context) => {
3870
+ if (devLog.info("Wistia captions toTranslatable - input:", {
3871
+ fieldPath: context.fieldPath,
3872
+ hasCaptionContent: !!wistiaCaptions?.captionContent,
3873
+ captionContentLength: wistiaCaptions?.captionContent?.length ?? 0,
3874
+ isoLanguage: wistiaCaptions?.isoLanguage
3875
+ }), !wistiaCaptions || !wistiaCaptions.captionContent)
3876
+ return devLog.debug("Wistia caption field is missing or has no content, returning empty transform", {
3877
+ fieldPath: context.fieldPath,
3878
+ value: wistiaCaptions
3879
+ }), {
3880
+ _type: "translatable.wistia.captions",
3881
+ translationText: "",
3882
+ _originalMetadata: {
3883
+ captionFileName: wistiaCaptions?.captionFileName,
3884
+ isoLanguage: wistiaCaptions?.isoLanguage,
3885
+ captionUploadStatus: wistiaCaptions?.captionUploadStatus,
3886
+ captionUploadMessage: wistiaCaptions?.captionUploadMessage,
3887
+ _originalIndex: 0
3888
+ },
3889
+ _srtCues: []
3890
+ };
3891
+ devLog.info("Applying Wistia caption transform", {
3892
+ fieldPath: context.fieldPath,
3893
+ captionContentPreview: wistiaCaptions.captionContent.substring(0, 200)
3894
+ });
3895
+ const srtCues = parseSRTContent(wistiaCaptions.captionContent);
3896
+ devLog.debug("Parsed SRT cues:", {
3897
+ fieldPath: context.fieldPath,
3898
+ cueCount: srtCues.length,
3899
+ firstCue: srtCues.length > 0 ? srtCues[0] : null
3900
+ });
3901
+ const translationText = serializeWistiaCaptionText(srtCues), originalMetadata = {
3902
+ captionFileName: wistiaCaptions.captionFileName,
3903
+ isoLanguage: wistiaCaptions.isoLanguage,
3904
+ captionUploadStatus: wistiaCaptions.captionUploadStatus,
3905
+ captionUploadMessage: wistiaCaptions.captionUploadMessage,
3906
+ _originalIndex: 0
3907
+ }, srtCuesMetadata = srtCues.map((cue) => ({
3908
+ index: cue.index,
3909
+ startTime: cue.startTime,
3910
+ endTime: cue.endTime
3911
+ }));
3912
+ return devLog.info("Wistia captions serialized:", {
3913
+ fieldPath: context.fieldPath,
3914
+ translationTextLength: translationText.length,
3915
+ translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? "..." : ""),
3916
+ cueCount: srtCues.length
3917
+ }), {
3918
+ _type: "translatable.wistia.captions",
3919
+ translationText,
3920
+ _originalMetadata: originalMetadata,
3921
+ _srtCues: srtCuesMetadata
3922
+ };
3923
+ },
3924
+ fromTranslatable: (translatedValue, originalValue, context) => {
3925
+ if (devLog.info("Wistia captions fromTranslatable - input:", {
3926
+ fieldPath: context.fieldPath,
3927
+ valueType: typeof translatedValue,
3928
+ isCorrectType: translatedValue._type === "translatable.wistia.captions",
3929
+ hasText: !!translatedValue.translationText,
3930
+ hasMetadata: !!translatedValue._originalMetadata,
3931
+ hasCues: Array.isArray(translatedValue._srtCues)
3932
+ }), !translatedValue || translatedValue._type !== "translatable.wistia.captions" || !translatedValue._originalMetadata || !Array.isArray(translatedValue._srtCues))
3933
+ return devLog.debug("Value is not a translatable Wistia caption object, returning original", {
3934
+ fieldPath: context.fieldPath,
3935
+ valueType: typeof translatedValue,
3936
+ translatedValue
3937
+ }), originalValue || {};
3938
+ devLog.info("Processing translated Wistia captions", {
3939
+ fieldPath: context.fieldPath,
3940
+ cueCount: translatedValue._srtCues.length
3941
+ });
3942
+ const { translationText, _originalMetadata, _srtCues } = translatedValue;
3943
+ devLog.info("Wistia captions translationText:", {
3944
+ fieldPath: context.fieldPath,
3945
+ translationTextLength: translationText.length,
3946
+ translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? "..." : "")
3947
+ });
3948
+ const reconstructedCaptionContent = deserializeWistiaCaptionText(translationText, _srtCues), documentLanguage = getDocumentLanguage(context.document);
3949
+ let updatedIsoLanguage = _originalMetadata.isoLanguage;
3950
+ if (documentLanguage) {
3951
+ const isoCode = normalizeToIsoLanguageCode(documentLanguage);
3952
+ isoCode ? (updatedIsoLanguage = isoCode, devLog.debug("Updated isoLanguage based on document language", {
3953
+ fieldPath: context.fieldPath,
3954
+ documentLanguage,
3955
+ isoCode
3956
+ })) : devLog.debug("Could not convert document language to ISO code, preserving original", {
3957
+ fieldPath: context.fieldPath,
3958
+ documentLanguage,
3959
+ originalIsoLanguage: _originalMetadata.isoLanguage
3960
+ });
3961
+ } else
3962
+ devLog.debug("Document language field not found, preserving original isoLanguage", {
3963
+ fieldPath: context.fieldPath,
3964
+ originalIsoLanguage: _originalMetadata.isoLanguage
3965
+ });
3966
+ const result = {
3967
+ captionContent: reconstructedCaptionContent,
3968
+ captionFileName: _originalMetadata.captionFileName,
3969
+ isoLanguage: updatedIsoLanguage,
3970
+ captionUploadStatus: _originalMetadata.captionUploadStatus,
3971
+ captionUploadMessage: _originalMetadata.captionUploadMessage
3972
+ };
3973
+ return devLog.info("Wistia captions final result:", {
3974
+ fieldPath: context.fieldPath,
3975
+ hasCaptionContent: !!result.captionContent,
3976
+ captionContentLength: result.captionContent?.length ?? 0,
3977
+ isoLanguage: result.isoLanguage
3978
+ }), result;
3979
+ }
3980
+ };
3981
+ function parseSRTContent(content) {
3982
+ if (devLog.debug("parseSRTContent - input:", {
3983
+ contentLength: content.length,
3984
+ contentPreview: content.substring(0, 100) + (content.length > 100 ? "..." : "")
3985
+ }), !content || content.trim() === "")
3986
+ return [];
3987
+ const cues = [], cueBlocks = content.split(/\n\s*\n/);
3988
+ for (const block of cueBlocks) {
3989
+ const lines = block.trim().split(`
3990
+ `);
3991
+ if (lines.length < 3)
3992
+ continue;
3993
+ const cueNumber = Number.parseInt(lines[0].trim(), 10);
3994
+ if (Number.isNaN(cueNumber)) {
3995
+ devLog.warn("Invalid SRT cue number", { line: lines[0] });
3996
+ continue;
3997
+ }
3998
+ const timestampLine = lines[1].trim(), timestampMatch = timestampLine.match(/^(\d{2}:\d{2}:\d{2}[,.]\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2}[,.]\d{3})$/);
3999
+ if (!timestampMatch) {
4000
+ devLog.warn("Invalid SRT timestamp format", { line: timestampLine });
4001
+ continue;
4002
+ }
4003
+ const startTime = timestampMatch[1].replace(".", ","), endTime = timestampMatch[2].replace(".", ","), text = lines.slice(2).join(`
4004
+ `).trim();
4005
+ text && cues.push({
4006
+ index: cueNumber,
4007
+ startTime,
4008
+ endTime,
4009
+ text
4010
+ });
4011
+ }
4012
+ return devLog.debug("parseSRTContent - output:", {
4013
+ cueCount: cues.length,
4014
+ firstCue: cues.length > 0 ? cues[0] : null
4015
+ }), cues;
4016
+ }
4017
+ function serializeWistiaCaptionText(cues) {
4018
+ if (devLog.debug("serializeWistiaCaptionText - input:", {
4019
+ cuesCount: cues.length,
4020
+ firstCue: cues.length > 0 ? cues[0] : null
4021
+ }), !Array.isArray(cues) || cues.length === 0)
4022
+ return "";
4023
+ const result = cues.map((cue) => (devLog.debug(`Processing cue ${cue.index}:`, {
4024
+ text: cue.text,
4025
+ hasText: typeof cue.text == "string"
4026
+ }), `[${cue.index}] ${cue.text}`)).join(`
4027
+ `);
4028
+ return devLog.debug("serializeWistiaCaptionText - output:", {
4029
+ resultLength: result.length,
4030
+ resultPreview: result.substring(0, 100) + (result.length > 100 ? "..." : "")
4031
+ }), result;
4032
+ }
4033
+ function deserializeWistiaCaptionText(translationText, srtCuesMetadata) {
4034
+ if (devLog.debug("deserializeWistiaCaptionText - input:", {
4035
+ translationTextLength: translationText.length,
4036
+ translationTextPreview: translationText.substring(0, 100) + (translationText.length > 100 ? "..." : ""),
4037
+ metadataCount: srtCuesMetadata.length,
4038
+ metadataExample: srtCuesMetadata.length > 0 ? srtCuesMetadata[0] : null
4039
+ }), !translationText || translationText.trim() === "")
4040
+ return "";
4041
+ const result = [], lines = translationText.split(`
4042
+ `), lineRegex = /^\[(\d+)\]\s*(.*)$/, metadataByIndex = /* @__PURE__ */ new Map();
4043
+ for (const metadata of srtCuesMetadata)
4044
+ metadataByIndex.set(metadata.index, { startTime: metadata.startTime, endTime: metadata.endTime });
4045
+ for (const line of lines) {
4046
+ const match = line.match(lineRegex);
4047
+ if (!match) {
4048
+ devLog.warn("Failed to parse caption line", { line });
4049
+ continue;
4050
+ }
4051
+ const index = Number.parseInt(match[1], 10), text = match[2];
4052
+ devLog.debug("Found caption:", {
4053
+ index,
4054
+ text
4055
+ });
4056
+ const metadata = metadataByIndex.get(index);
4057
+ metadata ? (devLog.debug(`Found matching metadata for index ${index}`), result.push(`${index}`), result.push(`${metadata.startTime} --> ${metadata.endTime}`), result.push(text), result.push("")) : devLog.warn("Missing metadata for caption", { index });
4058
+ }
4059
+ result.length > 0 && result[result.length - 1] === "" && result.pop();
4060
+ const reconstructed = result.join(`
4061
+ `);
4062
+ return devLog.debug("deserializeWistiaCaptionText - output:", {
4063
+ resultLength: reconstructed.length,
4064
+ resultPreview: reconstructed.substring(0, 200) + (reconstructed.length > 200 ? "..." : "")
4065
+ }), reconstructed;
4066
+ }
4067
+ function getDocumentLanguage(document) {
4068
+ if (!document || typeof document != "object")
4069
+ return;
4070
+ const languageFieldNames = ["language", "lang", "locale"];
4071
+ for (const fieldName of languageFieldNames)
4072
+ if (fieldName in document && typeof document[fieldName] == "string")
4073
+ return document[fieldName];
4074
+ }
3802
4075
  const aiTranslationPlugin = definePlugin((config) => (config.fieldTransforms?.length && (devLog.info(`Registering ${config.fieldTransforms.length} custom field transforms`), config.fieldTransforms.forEach((transform) => {
3803
4076
  globalTransformRegistry.register(transform);
3804
4077
  })), {
@@ -3818,6 +4091,7 @@ export {
3818
4091
  restoreExcludedFields,
3819
4092
  richTextTransform,
3820
4093
  translationHistorySchema,
3821
- vttCaptionTransform
4094
+ vttCaptionTransform,
4095
+ wistiaCaptionTransform
3822
4096
  };
3823
4097
  //# sourceMappingURL=index.js.map