@braze/sanity-plugin-ai-translation 0.2.22 → 0.2.24
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 +1 -2
- package/dist/index.js +310 -851
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useClient, defineType, defineField, pathToString, definePlugin } from "sanity";
|
|
1
|
+
import { useClient, defineType, defineField, getPublishedId, pathToString, definePlugin } from "sanity";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ErrorOutlineIcon, CheckmarkCircleIcon, EmptyIcon, AddIcon, TrashIcon, WarningOutlineIcon, InfoOutlineIcon, ArrowLeftIcon, EditIcon, TranslateIcon, CheckmarkIcon, CircleIcon, ArrowRightIcon, PanelRightIcon } from "@sanity/icons";
|
|
4
4
|
import { createContext, useContext, useEffect, useState, useReducer, useCallback, useMemo } from "react";
|
|
@@ -6,6 +6,7 @@ import { Card, Flex, Stack, Text, Spinner, Button, Box, Tooltip, Checkbox, Selec
|
|
|
6
6
|
import { useListeningQuery, useOpenInNewPane } from "sanity-plugin-utils";
|
|
7
7
|
import defaults from "lodash.defaults";
|
|
8
8
|
import { toPlainText } from "@portabletext/react";
|
|
9
|
+
import { uuid } from "@sanity/uuid";
|
|
9
10
|
import cloneDeep from "lodash.clonedeep";
|
|
10
11
|
import get from "lodash.get";
|
|
11
12
|
import set from "lodash.set";
|
|
@@ -31,7 +32,7 @@ const TranslationContext = createContext({
|
|
|
31
32
|
"useTranslationContext must be used within a TranslationContextProvider with document and config props"
|
|
32
33
|
);
|
|
33
34
|
return context;
|
|
34
|
-
}, TRANSLATION_METADATA_TYPE = "translation.metadata", undraftId = (id) => id.replace(/^drafts\./, ""),
|
|
35
|
+
}, TRANSLATION_METADATA_TYPE = "translation.metadata", undraftId = (id) => id.replace(/^drafts\./, ""), TRANSLATION_METADATA_QUERY = "*[_type == $translationMetadataType && references($documentId)][0]", useTranslationMetadata = ({ documentId }) => {
|
|
35
36
|
const { dispatch } = useTranslationContext(), { data, loading, error } = useListeningQuery(TRANSLATION_METADATA_QUERY, {
|
|
36
37
|
params: {
|
|
37
38
|
documentId: undraftId(documentId),
|
|
@@ -191,12 +192,7 @@ async function analyzeFeedback({
|
|
|
191
192
|
currentDocument,
|
|
192
193
|
onProgress
|
|
193
194
|
}) {
|
|
194
|
-
|
|
195
|
-
sourceLanguage,
|
|
196
|
-
targetLanguage,
|
|
197
|
-
hasUserFeedback: !!userFeedback,
|
|
198
|
-
shouldIncludeEdits
|
|
199
|
-
}), onProgress?.({ stage: "analyzing", message: "Analyzing feedback and edits..." });
|
|
195
|
+
onProgress?.({ stage: "analyzing", message: "Analyzing feedback and edits..." });
|
|
200
196
|
const analysisPrompt = buildAnalysisPrompt({
|
|
201
197
|
sourceLanguage,
|
|
202
198
|
targetLanguage,
|
|
@@ -205,10 +201,8 @@ async function analyzeFeedback({
|
|
|
205
201
|
sourceSnapshot,
|
|
206
202
|
targetSnapshot,
|
|
207
203
|
currentDocument
|
|
208
|
-
}), analysis = await analyzeWithAI(client, analysisPrompt);
|
|
209
|
-
|
|
210
|
-
const suggestions = convertAnalysisToSuggestions(analysis, sourceLanguage, targetLanguage);
|
|
211
|
-
devLog.info("Generated suggestions", { count: suggestions.length }), onProgress?.({ stage: "analyzing", message: "Validating suggestions..." });
|
|
204
|
+
}), analysis = await analyzeWithAI(client, analysisPrompt), suggestions = convertAnalysisToSuggestions(analysis, sourceLanguage, targetLanguage);
|
|
205
|
+
onProgress?.({ stage: "analyzing", message: "Validating suggestions..." });
|
|
212
206
|
const validatedSuggestions = await Promise.all(
|
|
213
207
|
suggestions.map(async (suggestion) => {
|
|
214
208
|
const validation = await validateSuggestion(client, suggestion);
|
|
@@ -221,7 +215,10 @@ async function analyzeFeedback({
|
|
|
221
215
|
};
|
|
222
216
|
})
|
|
223
217
|
);
|
|
224
|
-
return devLog.info("
|
|
218
|
+
return devLog.info("Feedback analysis complete", {
|
|
219
|
+
sourceLanguage,
|
|
220
|
+
targetLanguage,
|
|
221
|
+
total: validatedSuggestions.length,
|
|
225
222
|
valid: validatedSuggestions.filter((s) => s.validationStatus === "valid").length,
|
|
226
223
|
warnings: validatedSuggestions.filter((s) => s.validationStatus === "warning").length,
|
|
227
224
|
invalid: validatedSuggestions.filter((s) => s.validationStatus === "invalid").length
|
|
@@ -235,24 +232,18 @@ async function applySuggestions({
|
|
|
235
232
|
suggestions,
|
|
236
233
|
onProgress
|
|
237
234
|
}) {
|
|
238
|
-
if (
|
|
239
|
-
sourceLanguage,
|
|
240
|
-
targetLanguage,
|
|
241
|
-
targetLanguageType: typeof targetLanguage,
|
|
242
|
-
targetLanguageValue: targetLanguage,
|
|
243
|
-
count: suggestions.filter((s) => s.approved).length
|
|
244
|
-
}), !targetLanguage)
|
|
235
|
+
if (!targetLanguage)
|
|
245
236
|
throw devLog.error("targetLanguage is undefined in applySuggestions!", {
|
|
246
237
|
sourceLanguage,
|
|
247
238
|
targetLanguage,
|
|
248
239
|
suggestionsCount: suggestions.length
|
|
249
240
|
}), new Error("targetLanguage is required but was undefined");
|
|
250
241
|
const approvedSuggestions = suggestions.filter((s) => s.approved);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
242
|
+
approvedSuggestions.length !== 0 && (onProgress?.({ stage: "applying", message: "Applying updates to translation settings..." }), await applyImprovementsFromSuggestions(client, schemaId, targetLanguage, approvedSuggestions, onProgress), devLog.info("Suggestions applied successfully", {
|
|
243
|
+
sourceLanguage,
|
|
244
|
+
targetLanguage,
|
|
245
|
+
count: approvedSuggestions.length
|
|
246
|
+
}));
|
|
256
247
|
}
|
|
257
248
|
function buildAnalysisPrompt({
|
|
258
249
|
sourceLanguage,
|
|
@@ -470,49 +461,21 @@ function parseAIResponse(responseText) {
|
|
|
470
461
|
}
|
|
471
462
|
}
|
|
472
463
|
async function applyImprovementsFromSuggestions(client, schemaId, targetLanguage, suggestions, onProgress) {
|
|
473
|
-
devLog.info("Applying improvements from suggestions", {
|
|
474
|
-
count: suggestions.length,
|
|
475
|
-
targetLanguage,
|
|
476
|
-
suggestions: suggestions.map((s) => ({
|
|
477
|
-
type: s.type,
|
|
478
|
-
approved: s.approved,
|
|
479
|
-
sourceLocale: s.sourceLocale,
|
|
480
|
-
targetLocale: s.targetLocale,
|
|
481
|
-
sourceValue: s.sourceValue,
|
|
482
|
-
targetValue: s.targetValue
|
|
483
|
-
}))
|
|
484
|
-
});
|
|
485
464
|
const settingsId = "translation-settings";
|
|
486
465
|
await client.fetch("*[_id == $id][0]", { id: settingsId }) || await client.createIfNotExists({
|
|
487
466
|
_id: settingsId,
|
|
488
467
|
_type: "translationSettings"
|
|
489
468
|
});
|
|
490
|
-
const updates = [], globalPrompts = suggestions.filter((s) => s.type === "globalPrompt" && s.description.trim()), localePrompts = suggestions.filter((s) => s.type === "localePrompt" && s.description.trim()), protectedPhrases = suggestions.filter((s) => s.type === "protectedPhrase" && s.description.trim()),
|
|
491
|
-
devLog.info("Glossary terms before filtering", {
|
|
492
|
-
count: glossaryTermsBeforeFilter.length,
|
|
493
|
-
terms: glossaryTermsBeforeFilter.map((s) => ({
|
|
494
|
-
sourceLocale: s.sourceLocale,
|
|
495
|
-
sourceValue: s.sourceValue,
|
|
496
|
-
targetLocale: s.targetLocale,
|
|
497
|
-
targetValue: s.targetValue,
|
|
498
|
-
approved: s.approved
|
|
499
|
-
}))
|
|
500
|
-
});
|
|
501
|
-
const glossaryTerms = suggestions.filter(
|
|
469
|
+
const updates = [], globalPrompts = suggestions.filter((s) => s.type === "globalPrompt" && s.description.trim()), localePrompts = suggestions.filter((s) => s.type === "localePrompt" && s.description.trim()), protectedPhrases = suggestions.filter((s) => s.type === "protectedPhrase" && s.description.trim()), glossaryTerms = suggestions.filter(
|
|
502
470
|
(s) => s.type === "glossaryTerm" && s.sourceLocale?.trim() && s.sourceValue?.trim() && s.targetLocale?.trim() && s.targetValue?.trim()
|
|
503
471
|
);
|
|
504
|
-
if (
|
|
505
|
-
globalPrompts: globalPrompts.length,
|
|
506
|
-
localePrompts: localePrompts.length,
|
|
507
|
-
protectedPhrases: protectedPhrases.length,
|
|
508
|
-
glossaryTerms: glossaryTerms.length
|
|
509
|
-
}), (globalPrompts.length > 0 || localePrompts.length > 0) && (onProgress?.({
|
|
472
|
+
if ((globalPrompts.length > 0 || localePrompts.length > 0) && (onProgress?.({
|
|
510
473
|
stage: "applying",
|
|
511
474
|
message: globalPrompts.length > 0 && localePrompts.length > 0 ? "Updating global and locale translation prompts..." : globalPrompts.length > 0 ? "Updating global translation prompt..." : `Updating ${targetLanguage} translation prompt...`
|
|
512
475
|
}), updates.push(updatePromptsWithTemplate(client, schemaId, settingsId, targetLanguage, globalPrompts, localePrompts))), protectedPhrases.length > 0) {
|
|
513
476
|
onProgress?.({ stage: "applying", message: "Adding protected phrases..." });
|
|
514
477
|
const phrases = protectedPhrases.map((s) => s.description);
|
|
515
|
-
|
|
478
|
+
updates.push(addProtectedPhrases(client, settingsId, phrases));
|
|
516
479
|
}
|
|
517
480
|
if (glossaryTerms.length > 0) {
|
|
518
481
|
onProgress?.({ stage: "applying", message: "Adding glossary terms..." });
|
|
@@ -524,22 +487,17 @@ async function applyImprovementsFromSuggestions(client, schemaId, targetLanguage
|
|
|
524
487
|
targetValue: s.targetValue
|
|
525
488
|
// biome-ignore-end lint/style/noNonNullAssertion: safely handled
|
|
526
489
|
}));
|
|
527
|
-
|
|
490
|
+
updates.push(addGlossaryTerms(client, terms));
|
|
528
491
|
}
|
|
529
492
|
updates.length === 0 && devLog.warn("No valid updates to apply - all suggestions may have been empty"), await Promise.all(updates), onProgress?.({ stage: "applying", message: "Recording changes..." }), await recordChangeHistory(
|
|
530
493
|
client,
|
|
531
494
|
settingsId,
|
|
532
495
|
targetLanguage,
|
|
533
496
|
suggestions.filter((s) => s.approved)
|
|
534
|
-
)
|
|
497
|
+
);
|
|
535
498
|
}
|
|
536
499
|
async function updatePromptsWithTemplate(client, _schemaId, settingsId, targetLanguage, globalPrompts, localePrompts) {
|
|
537
|
-
if (
|
|
538
|
-
settingsId,
|
|
539
|
-
targetLanguage,
|
|
540
|
-
hasGlobalUpdates: globalPrompts.length > 0,
|
|
541
|
-
hasLocaleUpdates: localePrompts.length > 0
|
|
542
|
-
}), !targetLanguage)
|
|
500
|
+
if (!targetLanguage)
|
|
543
501
|
throw devLog.error("targetLanguage is undefined or empty!", {
|
|
544
502
|
targetLanguage,
|
|
545
503
|
globalPrompts: globalPrompts.length,
|
|
@@ -555,11 +513,7 @@ async function updatePromptsWithTemplate(client, _schemaId, settingsId, targetLa
|
|
|
555
513
|
...updates.map((update) => `- ${update}`)
|
|
556
514
|
].filter(Boolean).join(`
|
|
557
515
|
`);
|
|
558
|
-
await client.patch(settingsId).set({ globalPrompt: newGlobalPrompt }).commit()
|
|
559
|
-
previousLength: currentGlobalPrompt.length,
|
|
560
|
-
newLength: newGlobalPrompt.length,
|
|
561
|
-
addedUpdates: updates.length
|
|
562
|
-
});
|
|
516
|
+
await client.patch(settingsId).set({ globalPrompt: newGlobalPrompt }).commit();
|
|
563
517
|
}
|
|
564
518
|
if (localePrompts.length > 0) {
|
|
565
519
|
const updates = localePrompts.map((s) => s.description.trim()).filter(Boolean), existingLocalePrompts = currentSettings?.localePrompts || [], existingLocalePrompt = existingLocalePrompts.find((lp) => lp._key === targetLanguage);
|
|
@@ -572,12 +526,7 @@ async function updatePromptsWithTemplate(client, _schemaId, settingsId, targetLa
|
|
|
572
526
|
...updates.map((update) => `- ${update}`)
|
|
573
527
|
].filter(Boolean).join(`
|
|
574
528
|
`), localeIndex = existingLocalePrompts.findIndex((lp) => lp._key === targetLanguage);
|
|
575
|
-
await client.patch(settingsId).set({ [`localePrompts[${localeIndex}].value`]: newValue }).commit()
|
|
576
|
-
targetLanguage,
|
|
577
|
-
previousLength: currentValue.length,
|
|
578
|
-
newLength: newValue.length,
|
|
579
|
-
addedUpdates: updates.length
|
|
580
|
-
});
|
|
529
|
+
await client.patch(settingsId).set({ [`localePrompts[${localeIndex}].value`]: newValue }).commit();
|
|
581
530
|
} else {
|
|
582
531
|
const newValue = [`## User Feedback (${timestamp})`, ...updates.map((update) => `- ${update}`)].filter(Boolean).join(`
|
|
583
532
|
`);
|
|
@@ -587,22 +536,17 @@ async function updatePromptsWithTemplate(client, _schemaId, settingsId, targetLa
|
|
|
587
536
|
_key: targetLanguage,
|
|
588
537
|
value: newValue
|
|
589
538
|
}
|
|
590
|
-
]).commit()
|
|
591
|
-
targetLanguage,
|
|
592
|
-
length: newValue.length,
|
|
593
|
-
addedUpdates: updates.length
|
|
594
|
-
});
|
|
539
|
+
]).commit();
|
|
595
540
|
}
|
|
596
541
|
}
|
|
597
542
|
devLog.info("Prompts updated successfully", {
|
|
543
|
+
targetLanguage,
|
|
598
544
|
globalUpdated: globalPrompts.length > 0,
|
|
599
545
|
localeUpdated: localePrompts.length > 0
|
|
600
546
|
});
|
|
601
547
|
}
|
|
602
548
|
async function addProtectedPhrases(client, settingsId, phrases) {
|
|
603
|
-
devLog.info("Adding protected phrases", { settingsId, phrasesCount: phrases.length, phrases });
|
|
604
549
|
const currentPhrases = await client.fetch("*[_id == $settingsId][0].protectedPhrases", { settingsId });
|
|
605
|
-
devLog.debug("Fetched current protected phrases", { currentCount: currentPhrases?.length });
|
|
606
550
|
let newPhrases = phrases.filter((phrase) => !currentPhrases?.includes(phrase));
|
|
607
551
|
const conflictingPhrases = [];
|
|
608
552
|
for (const phrase of newPhrases)
|
|
@@ -610,12 +554,7 @@ async function addProtectedPhrases(client, settingsId, phrases) {
|
|
|
610
554
|
'count(*[_type == "translationGlossaryTerm" && count(translations[value == $phrase]) > 0]) > 0',
|
|
611
555
|
{ phrase }
|
|
612
556
|
) && (conflictingPhrases.push(phrase), devLog.warn("Skipping protected phrase due to glossary conflict", { phrase }));
|
|
613
|
-
|
|
614
|
-
devLog.info("No new protected phrases to add (all already exist)");
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
const result = await client.patch(settingsId).setIfMissing({ protectedPhrases: [] }).append("protectedPhrases", newPhrases).commit();
|
|
618
|
-
devLog.info("Protected phrases added", { count: newPhrases.length, phrases: newPhrases, resultId: result._id });
|
|
557
|
+
newPhrases = newPhrases.filter((phrase) => !conflictingPhrases.includes(phrase)), newPhrases.length !== 0 && (await client.patch(settingsId).setIfMissing({ protectedPhrases: [] }).append("protectedPhrases", newPhrases).commit(), devLog.info("Protected phrases added", { count: newPhrases.length }));
|
|
619
558
|
}
|
|
620
559
|
async function addGlossaryTerms(client, terms) {
|
|
621
560
|
const protectedPhrasesLower = (await client.fetch('*[_id == "translation-settings"][0].protectedPhrases'))?.map((p) => p.toLowerCase()) || [], validTerms = terms.filter(({ sourceValue, targetValue }) => sourceValue === targetValue ? (devLog.warn("Skipping glossary term where source === target (should be a protected phrase)", {
|
|
@@ -643,29 +582,13 @@ async function addGlossaryTerms(client, terms) {
|
|
|
643
582
|
);
|
|
644
583
|
if (existingTerm) {
|
|
645
584
|
const existingTargetIndex = existingTerm.translations.findIndex((t) => t._key === targetLocale), existingTargetValue = existingTargetIndex >= 0 ? existingTerm.translations[existingTargetIndex].value : null;
|
|
646
|
-
existingTargetIndex === -1 ?
|
|
585
|
+
existingTargetIndex === -1 ? await client.patch(existingTerm._id).append("translations", [
|
|
647
586
|
{
|
|
648
587
|
_type: "internationalizedArrayStringValue",
|
|
649
588
|
_key: targetLocale,
|
|
650
589
|
value: targetValue
|
|
651
590
|
}
|
|
652
|
-
]).commit()
|
|
653
|
-
sourceLocale,
|
|
654
|
-
sourceValue,
|
|
655
|
-
targetLocale,
|
|
656
|
-
targetValue
|
|
657
|
-
})) : existingTargetValue !== targetValue ? (await client.patch(existingTerm._id).set({ [`translations[${existingTargetIndex}].value`]: targetValue }).commit(), devLog.info("Updated existing glossary term translation", {
|
|
658
|
-
sourceLocale,
|
|
659
|
-
sourceValue,
|
|
660
|
-
targetLocale,
|
|
661
|
-
oldValue: existingTargetValue,
|
|
662
|
-
newValue: targetValue
|
|
663
|
-
})) : devLog.info("Glossary term already has this exact translation", {
|
|
664
|
-
sourceLocale,
|
|
665
|
-
sourceValue,
|
|
666
|
-
targetLocale,
|
|
667
|
-
targetValue
|
|
668
|
-
});
|
|
591
|
+
]).commit() : existingTargetValue !== targetValue && await client.patch(existingTerm._id).set({ [`translations[${existingTargetIndex}].value`]: targetValue }).commit();
|
|
669
592
|
} else
|
|
670
593
|
await client.create({
|
|
671
594
|
_type: "translationGlossaryTerm",
|
|
@@ -681,11 +604,10 @@ async function addGlossaryTerms(client, terms) {
|
|
|
681
604
|
value: targetValue
|
|
682
605
|
}
|
|
683
606
|
]
|
|
684
|
-
})
|
|
607
|
+
});
|
|
685
608
|
}
|
|
686
609
|
devLog.info("Glossary terms processed", {
|
|
687
|
-
|
|
688
|
-
validAdded: validTerms.length,
|
|
610
|
+
processed: validTerms.length,
|
|
689
611
|
skipped: terms.length - validTerms.length
|
|
690
612
|
});
|
|
691
613
|
}
|
|
@@ -698,10 +620,7 @@ async function recordChangeHistory(client, settingsId, targetLanguage, suggestio
|
|
|
698
620
|
description: suggestion.description,
|
|
699
621
|
locale: suggestion.type === "localePrompt" ? targetLanguage : void 0
|
|
700
622
|
}));
|
|
701
|
-
historyEntries.length !== 0 &&
|
|
702
|
-
count: historyEntries.length,
|
|
703
|
-
timestamp
|
|
704
|
-
}));
|
|
623
|
+
historyEntries.length !== 0 && await client.patch(settingsId).setIfMissing({ changeHistory: [] }).append("changeHistory", historyEntries).commit();
|
|
705
624
|
}
|
|
706
625
|
const ICON_SIZE = 64, progressIconMap = {
|
|
707
626
|
in_progress: /* @__PURE__ */ jsx(Spinner, {}),
|
|
@@ -1227,8 +1146,8 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
1227
1146
|
references: action.payload
|
|
1228
1147
|
};
|
|
1229
1148
|
case "SET_TRANSLATION_JOB_STATUS": {
|
|
1230
|
-
const jobKey = `${action.payload._id}_${action.payload.language}`, newTranslationJobs = new Map(context.translationJobs);
|
|
1231
|
-
return newTranslationJobs.set(jobKey,
|
|
1149
|
+
const jobKey = `${action.payload._id}_${action.payload.language}`, newTranslationJobs = new Map(context.translationJobs), existingJob = newTranslationJobs.get(jobKey), mergedJob = defaults(action.payload, existingJob);
|
|
1150
|
+
return newTranslationJobs.set(jobKey, mergedJob), {
|
|
1232
1151
|
...context,
|
|
1233
1152
|
translationJobs: newTranslationJobs
|
|
1234
1153
|
};
|
|
@@ -1755,7 +1674,12 @@ const fetchTranslationPrompts = async ({
|
|
|
1755
1674
|
}
|
|
1756
1675
|
};
|
|
1757
1676
|
return recursiveSearch(obj, []), results;
|
|
1758
|
-
}, localizeReferences = async ({
|
|
1677
|
+
}, localizeReferences = async ({
|
|
1678
|
+
client,
|
|
1679
|
+
documentId,
|
|
1680
|
+
sourceLanguage,
|
|
1681
|
+
languageFieldName
|
|
1682
|
+
}) => {
|
|
1759
1683
|
try {
|
|
1760
1684
|
const doc = await client.fetch("*[_id == $documentId][0]", { documentId });
|
|
1761
1685
|
if (!doc) {
|
|
@@ -1777,25 +1701,25 @@ const fetchTranslationPrompts = async ({
|
|
|
1777
1701
|
const refs = findKeyValues(doc, "_ref");
|
|
1778
1702
|
if (!refs?.length)
|
|
1779
1703
|
return;
|
|
1780
|
-
await updateReferences(documentId, client, refs, targetLanguage);
|
|
1704
|
+
await updateReferences(documentId, client, refs, sourceLanguage, targetLanguage);
|
|
1781
1705
|
} catch (error) {
|
|
1782
1706
|
devLog.error("Error localizing references", error instanceof Error ? error : new Error(String(error)), {
|
|
1783
1707
|
documentId,
|
|
1784
1708
|
component: "localizeReferences"
|
|
1785
1709
|
});
|
|
1786
1710
|
}
|
|
1787
|
-
}, updateReferences = async (documentId, client, refs, targetLanguage) => {
|
|
1711
|
+
}, updateReferences = async (documentId, client, refs, sourceLanguage, targetLanguage) => {
|
|
1788
1712
|
if (!refs?.length)
|
|
1789
1713
|
return [];
|
|
1790
|
-
const refIds = refs.map((r) =>
|
|
1791
|
-
`*[_type == $translationMetadataSchemaType &&
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}[defined(targetId)]`,
|
|
1714
|
+
const refIds = refs.map((r) => getPublishedId(r.value)), translations = await client.fetch(
|
|
1715
|
+
`*[_type == $translationMetadataSchemaType && array::intersects(translations[].value._ref, $refIds)] {
|
|
1716
|
+
"sourceId": translations[_key == $sourceLanguage][0].value._ref,
|
|
1717
|
+
"targetId": translations[_key == $targetLanguage][0].value._ref
|
|
1718
|
+
}`,
|
|
1796
1719
|
{
|
|
1797
1720
|
translationMetadataSchemaType: TRANSLATION_METADATA_TYPE,
|
|
1798
1721
|
refIds,
|
|
1722
|
+
sourceLanguage,
|
|
1799
1723
|
targetLanguage
|
|
1800
1724
|
}
|
|
1801
1725
|
), translationMap = /* @__PURE__ */ new Map();
|
|
@@ -1804,13 +1728,8 @@ const fetchTranslationPrompts = async ({
|
|
|
1804
1728
|
const patch = client.patch(documentId);
|
|
1805
1729
|
let hasChanges = !1;
|
|
1806
1730
|
for (const ref of refs) {
|
|
1807
|
-
const sourceId =
|
|
1808
|
-
newId && (patch.set({ [pathToString(ref.path)]:
|
|
1809
|
-
[`${pathToString(ref.path.slice(0, -1))}._strengthenOnPublish`]: {
|
|
1810
|
-
_type: "reference",
|
|
1811
|
-
_weak: !1
|
|
1812
|
-
}
|
|
1813
|
-
}), hasChanges = !0);
|
|
1731
|
+
const sourceId = getPublishedId(ref.value), newId = translationMap.get(sourceId);
|
|
1732
|
+
newId && (patch.set({ [pathToString(ref.path)]: getPublishedId(newId) }), hasChanges = !0);
|
|
1814
1733
|
}
|
|
1815
1734
|
hasChanges && (await patch.commit(), devLog.info("Updated references in batch", {
|
|
1816
1735
|
documentId,
|
|
@@ -1833,7 +1752,9 @@ const fetchTranslationPrompts = async ({
|
|
|
1833
1752
|
payload
|
|
1834
1753
|
});
|
|
1835
1754
|
}
|
|
1836
|
-
},
|
|
1755
|
+
}, getExistingTranslationId = (translationMetadata, targetLanguage) => translationMetadata?.translations?.length && translationMetadata.translations.find(
|
|
1756
|
+
(translation) => translation._key === targetLanguage
|
|
1757
|
+
)?.value._ref || null, getTranslationMetadata = async ({
|
|
1837
1758
|
client,
|
|
1838
1759
|
sourceDocument,
|
|
1839
1760
|
sourceLanguage
|
|
@@ -1882,7 +1803,7 @@ const fetchTranslationPrompts = async ({
|
|
|
1882
1803
|
_type: "internationalizedArrayReferenceValue",
|
|
1883
1804
|
value: {
|
|
1884
1805
|
_type: "reference",
|
|
1885
|
-
_ref:
|
|
1806
|
+
_ref: getPublishedId(newDocument._id),
|
|
1886
1807
|
_weak: !0,
|
|
1887
1808
|
_strengthenOnPublish: {
|
|
1888
1809
|
_type: "reference",
|
|
@@ -1892,7 +1813,7 @@ const fetchTranslationPrompts = async ({
|
|
|
1892
1813
|
}, newTranslationHistory = {
|
|
1893
1814
|
_key: targetLanguage,
|
|
1894
1815
|
language: targetLanguage,
|
|
1895
|
-
sourceId:
|
|
1816
|
+
sourceId: getPublishedId(sourceDocument._id),
|
|
1896
1817
|
sourceRev: sourceDocument._rev,
|
|
1897
1818
|
sourceLanguage,
|
|
1898
1819
|
sourceSnapshot: JSON.stringify(sourceDocument),
|
|
@@ -1922,70 +1843,51 @@ const fetchTranslationPrompts = async ({
|
|
|
1922
1843
|
status
|
|
1923
1844
|
})
|
|
1924
1845
|
});
|
|
1925
|
-
},
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
}) => {
|
|
1931
|
-
const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage);
|
|
1932
|
-
return existingTranslationId ? {
|
|
1933
|
-
// Update existing translation
|
|
1934
|
-
operation: "createOrReplace",
|
|
1935
|
-
_id: existingTranslationId
|
|
1936
|
-
} : {
|
|
1937
|
-
// Create new translation
|
|
1938
|
-
operation: "create"
|
|
1939
|
-
};
|
|
1940
|
-
}, analyzeDocumentContent = (document) => {
|
|
1941
|
-
let hasCodeBlocks = !1, hasInlineCode = !1, codeBlockCount = 0, inlineCodeCount = 0;
|
|
1942
|
-
const hasMarkdown = !1;
|
|
1943
|
-
let hasPortableText = !1, estimatedFieldCount = 0;
|
|
1944
|
-
const traverse = (value, depth = 0) => {
|
|
1945
|
-
value != null && (depth > 0 && typeof value == "object" && estimatedFieldCount++, Array.isArray(value) ? value.forEach((item) => {
|
|
1946
|
-
item && typeof item == "object" && "_type" in item && item._type === "code" && (hasCodeBlocks = !0, codeBlockCount++, hasPortableText = !0), item && typeof item == "object" && "_type" in item && item._type === "block" && "children" in item && Array.isArray(item.children) && (hasPortableText = !0, item.children.forEach((child) => {
|
|
1947
|
-
child && typeof child == "object" && "marks" in child && Array.isArray(child.marks) && child.marks.includes("code") && (hasInlineCode = !0, inlineCodeCount++);
|
|
1948
|
-
})), traverse(item, depth + 1);
|
|
1949
|
-
}) : typeof value == "object" && Object.values(value).forEach((val) => traverse(val, depth + 1)));
|
|
1950
|
-
};
|
|
1951
|
-
traverse(document);
|
|
1952
|
-
let complexity = "simple";
|
|
1953
|
-
return estimatedFieldCount > 50 ? complexity = "complex" : estimatedFieldCount > 20 && (complexity = "moderate"), {
|
|
1954
|
-
hasCodeBlocks,
|
|
1955
|
-
hasInlineCode,
|
|
1956
|
-
codeBlockCount,
|
|
1957
|
-
inlineCodeCount,
|
|
1958
|
-
hasMarkdown,
|
|
1959
|
-
hasPortableText,
|
|
1960
|
-
complexity,
|
|
1961
|
-
estimatedFieldCount
|
|
1846
|
+
}, callGPT5 = async (apiKey, request) => (request.model || "gpt-5") === "gpt-5" ? await callGPT5ResponsesAPI(apiKey, request) : await callChatCompletionsAPI(apiKey, request), callGPT5ResponsesAPI = async (apiKey, request) => {
|
|
1847
|
+
const model = request.model || "gpt-5", requestBody = {
|
|
1848
|
+
model,
|
|
1849
|
+
input: request.messages
|
|
1850
|
+
// Uses 'input' instead of 'messages'
|
|
1962
1851
|
};
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
})
|
|
1968
|
-
const
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
id: sourceLanugageId,
|
|
1974
|
-
title: sourceLanugageId
|
|
1852
|
+
request.reasoningEffort && (requestBody.reasoning = {
|
|
1853
|
+
effort: request.reasoningEffort,
|
|
1854
|
+
summary: "auto"
|
|
1855
|
+
// Request reasoning summaries
|
|
1856
|
+
});
|
|
1857
|
+
const response = await fetch("https://api.openai.com/v1/responses", {
|
|
1858
|
+
method: "POST",
|
|
1859
|
+
headers: {
|
|
1860
|
+
"Content-Type": "application/json",
|
|
1861
|
+
Authorization: `Bearer ${apiKey}`
|
|
1975
1862
|
},
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1863
|
+
body: JSON.stringify(requestBody)
|
|
1864
|
+
});
|
|
1865
|
+
if (!response.ok) {
|
|
1866
|
+
const error = await response.json();
|
|
1867
|
+
throw new Error(`GPT-5 API error: ${error.error?.message || "Unknown error"}`);
|
|
1868
|
+
}
|
|
1869
|
+
const result = await response.json(), tokensUsed = {
|
|
1870
|
+
prompt: result.usage?.input_tokens || 0,
|
|
1871
|
+
completion: result.usage?.output_tokens || 0,
|
|
1872
|
+
reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
1873
|
+
total: result.usage?.total_tokens || 0
|
|
1874
|
+
}, cost = calculateCost(tokensUsed, model);
|
|
1875
|
+
let content = "";
|
|
1876
|
+
if (result.output && Array.isArray(result.output)) {
|
|
1877
|
+
const messageItem = result.output.find((item) => item.type === "message");
|
|
1878
|
+
messageItem?.content && Array.isArray(messageItem.content) && (content = messageItem.content.find((item) => item.type === "output_text")?.text || "");
|
|
1879
|
+
}
|
|
1880
|
+
return content || (content = result.output_text || result.text || ""), {
|
|
1881
|
+
content,
|
|
1882
|
+
tokensUsed,
|
|
1883
|
+
cost
|
|
1980
1884
|
};
|
|
1981
|
-
},
|
|
1982
|
-
const model = request.model || "gpt-
|
|
1885
|
+
}, callChatCompletionsAPI = async (apiKey, request) => {
|
|
1886
|
+
const model = request.model || "gpt-4o", requestBody = {
|
|
1983
1887
|
model,
|
|
1984
1888
|
messages: request.messages,
|
|
1985
|
-
|
|
1986
|
-
}
|
|
1987
|
-
isGPT5 || (requestBody.temperature = request.temperature ?? 0.3), isGPT5 && (requestBody.reasoning_effort = request.reasoningEffort || "medium", requestBody.verbosity = request.verbosity || "medium");
|
|
1988
|
-
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
1889
|
+
temperature: request.temperature ?? 0.3
|
|
1890
|
+
}, response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
1989
1891
|
method: "POST",
|
|
1990
1892
|
headers: {
|
|
1991
1893
|
"Content-Type": "application/json",
|
|
@@ -1995,88 +1897,30 @@ const fetchTranslationPrompts = async ({
|
|
|
1995
1897
|
});
|
|
1996
1898
|
if (!response.ok) {
|
|
1997
1899
|
const error = await response.json();
|
|
1998
|
-
throw new Error(`
|
|
1900
|
+
throw new Error(`OpenAI API error: ${error.error?.message || "Unknown error"}`);
|
|
1999
1901
|
}
|
|
2000
|
-
if (useStreaming && response.body && request.onReasoningChunk)
|
|
2001
|
-
return await handleStreamingResponse(response, request.onReasoningChunk, model);
|
|
2002
1902
|
const result = await response.json(), usage = result.usage || {}, tokensUsed = {
|
|
2003
1903
|
prompt: usage.prompt_tokens || 0,
|
|
2004
1904
|
completion: usage.completion_tokens || 0,
|
|
2005
1905
|
reasoning: usage.reasoning_tokens || 0,
|
|
2006
|
-
// New in GPT-5
|
|
2007
1906
|
total: usage.total_tokens || 0
|
|
2008
|
-
}, cost =
|
|
2009
|
-
let reasoningSummary, actualContent = messageContent;
|
|
2010
|
-
const reasoningMatch = messageContent.match(/^<reasoning>([\s\S]*?)<\/reasoning>\n\n([\s\S]*)$/i);
|
|
2011
|
-
return reasoningMatch && (reasoningSummary = reasoningMatch[1].trim(), actualContent = reasoningMatch[2]), {
|
|
2012
|
-
content: actualContent,
|
|
2013
|
-
tokensUsed,
|
|
2014
|
-
cost,
|
|
2015
|
-
reasoningSummary
|
|
2016
|
-
};
|
|
2017
|
-
}, handleStreamingResponse = async (response, onReasoningChunk, model) => {
|
|
2018
|
-
if (!response.body)
|
|
2019
|
-
throw new Error("Response body is null");
|
|
2020
|
-
const reader = response.body.getReader(), decoder = new TextDecoder();
|
|
2021
|
-
let fullContent = "", inReasoning = !1, reasoningBuffer = "", contentBuffer = "", tokensUsed = { prompt: 0, completion: 0, reasoning: 0, total: 0 };
|
|
2022
|
-
try {
|
|
2023
|
-
for (; ; ) {
|
|
2024
|
-
const { done, value } = await reader.read();
|
|
2025
|
-
if (done) break;
|
|
2026
|
-
const lines = decoder.decode(value, { stream: !0 }).split(`
|
|
2027
|
-
`).filter((line) => line.trim() !== "");
|
|
2028
|
-
for (const line of lines)
|
|
2029
|
-
if (line.startsWith("data: ")) {
|
|
2030
|
-
const data = line.slice(6);
|
|
2031
|
-
if (data === "[DONE]") continue;
|
|
2032
|
-
try {
|
|
2033
|
-
const parsed = JSON.parse(data);
|
|
2034
|
-
parsed.usage && (tokensUsed = {
|
|
2035
|
-
prompt: parsed.usage.prompt_tokens || 0,
|
|
2036
|
-
completion: parsed.usage.completion_tokens || 0,
|
|
2037
|
-
reasoning: parsed.usage.reasoning_tokens || 0,
|
|
2038
|
-
total: parsed.usage.total_tokens || 0
|
|
2039
|
-
});
|
|
2040
|
-
const delta = parsed.choices?.[0]?.delta?.content;
|
|
2041
|
-
if (delta)
|
|
2042
|
-
if (fullContent += delta, delta.includes("<reasoning>")) {
|
|
2043
|
-
inReasoning = !0;
|
|
2044
|
-
const parts = delta.split("<reasoning>");
|
|
2045
|
-
reasoningBuffer += parts[1] || "";
|
|
2046
|
-
} else if (delta.includes("</reasoning>")) {
|
|
2047
|
-
inReasoning = !1;
|
|
2048
|
-
const parts = delta.split("</reasoning>");
|
|
2049
|
-
reasoningBuffer += parts[0] || "", contentBuffer += parts[1] || "";
|
|
2050
|
-
} else inReasoning ? (reasoningBuffer += delta, onReasoningChunk(reasoningBuffer)) : contentBuffer += delta;
|
|
2051
|
-
} catch {
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
} finally {
|
|
2056
|
-
reader.releaseLock();
|
|
2057
|
-
}
|
|
2058
|
-
const cost = calculateGPT5Cost(tokensUsed, model);
|
|
1907
|
+
}, cost = calculateCost(tokensUsed, model);
|
|
2059
1908
|
return {
|
|
2060
|
-
content:
|
|
1909
|
+
content: result.choices[0].message.content || "",
|
|
2061
1910
|
tokensUsed,
|
|
2062
|
-
cost
|
|
2063
|
-
reasoningSummary: reasoningBuffer || void 0
|
|
1911
|
+
cost
|
|
2064
1912
|
};
|
|
2065
|
-
},
|
|
1913
|
+
}, calculateCost = (tokens, model) => {
|
|
2066
1914
|
const pricing = {
|
|
2067
1915
|
"gpt-5": {
|
|
2068
1916
|
input: 2e-6,
|
|
2069
|
-
// $0.002 per 1k input tokens
|
|
2070
1917
|
output: 1e-5,
|
|
2071
|
-
// $0.010 per 1k output tokens
|
|
2072
1918
|
reasoning: 5e-6
|
|
2073
|
-
// $0.005 per 1k reasoning tokens
|
|
2074
1919
|
},
|
|
2075
1920
|
"gpt-4o": {
|
|
2076
1921
|
input: 5e-6,
|
|
2077
1922
|
output: 14999999999999999e-21,
|
|
2078
1923
|
reasoning: 0
|
|
2079
|
-
// No reasoning tokens in GPT-4
|
|
2080
1924
|
},
|
|
2081
1925
|
"gpt-4-turbo": {
|
|
2082
1926
|
input: 1e-5,
|
|
@@ -2355,434 +2199,35 @@ const fetchTranslationPrompts = async ({
|
|
|
2355
2199
|
type: "metadata_changed"
|
|
2356
2200
|
}) : key !== "text" && validateStructureRecursive(origObj[key], modObj[key], [...path, key], errors));
|
|
2357
2201
|
}
|
|
2358
|
-
},
|
|
2359
|
-
const sections = [
|
|
2360
|
-
{
|
|
2361
|
-
id: "agentDirectives",
|
|
2362
|
-
content: buildAgentDirectivesSection(context),
|
|
2363
|
-
required: !0,
|
|
2364
|
-
priority: 0
|
|
2365
|
-
},
|
|
2366
|
-
{
|
|
2367
|
-
id: "criticalRules",
|
|
2368
|
-
content: buildCriticalRulesSection(context),
|
|
2369
|
-
required: !0,
|
|
2370
|
-
priority: 1
|
|
2371
|
-
}
|
|
2372
|
-
];
|
|
2373
|
-
components.glossaryTerms?.trim() && sections.push({
|
|
2374
|
-
id: "glossaryTerms",
|
|
2375
|
-
content: buildGlossarySection(components.glossaryTerms),
|
|
2376
|
-
required: !1,
|
|
2377
|
-
priority: 3
|
|
2378
|
-
});
|
|
2379
|
-
const styleSection = buildStyleGuideSection(components.globalPrompt, components.localePrompt);
|
|
2380
|
-
styleSection && sections.push({
|
|
2381
|
-
id: "styleGuide",
|
|
2382
|
-
content: styleSection,
|
|
2383
|
-
required: !1,
|
|
2384
|
-
priority: 4
|
|
2385
|
-
}), sections.sort((a, b) => a.priority - b.priority);
|
|
2386
|
-
let styleGuide = "";
|
|
2387
|
-
const omittedSections = [], truncatedSections = [];
|
|
2388
|
-
for (const section of sections) {
|
|
2389
|
-
const prefix = styleGuide.length > 0 ? `
|
|
2390
|
-
|
|
2391
|
-
---
|
|
2392
|
-
|
|
2393
|
-
` : "", availableCharacters = 2e3 - (styleGuide.length + prefix.length);
|
|
2394
|
-
if (availableCharacters <= 0) {
|
|
2395
|
-
if (section.required) {
|
|
2396
|
-
const truncatedContent = truncateSection(section.content, 0);
|
|
2397
|
-
truncatedContent && (styleGuide += prefix + truncatedContent, truncatedSections.push(section.id));
|
|
2398
|
-
} else
|
|
2399
|
-
omittedSections.push(section.id);
|
|
2400
|
-
continue;
|
|
2401
|
-
}
|
|
2402
|
-
if (section.content.length <= availableCharacters) {
|
|
2403
|
-
styleGuide += prefix + section.content;
|
|
2404
|
-
continue;
|
|
2405
|
-
}
|
|
2406
|
-
if (section.required) {
|
|
2407
|
-
const truncatedContent = truncateSection(section.content, availableCharacters);
|
|
2408
|
-
styleGuide += prefix + truncatedContent, truncatedSections.push(section.id);
|
|
2409
|
-
} else
|
|
2410
|
-
omittedSections.push(section.id);
|
|
2411
|
-
}
|
|
2412
|
-
return {
|
|
2413
|
-
styleGuide,
|
|
2414
|
-
omittedSections,
|
|
2415
|
-
truncatedSections
|
|
2416
|
-
};
|
|
2417
|
-
}, truncateSection = (content, limit) => {
|
|
2418
|
-
if (limit <= 0)
|
|
2419
|
-
return "";
|
|
2420
|
-
if (content.length <= limit)
|
|
2421
|
-
return content;
|
|
2422
|
-
if (limit <= 1)
|
|
2423
|
-
return content.slice(0, limit);
|
|
2424
|
-
const ellipsis = "\u2026";
|
|
2425
|
-
if (limit <= ellipsis.length)
|
|
2426
|
-
return content.slice(0, limit);
|
|
2427
|
-
const sliceEnd = limit - ellipsis.length, sliced = content.slice(0, sliceEnd), trimmed = sliced.replace(/\s+\S*$/, "").trimEnd();
|
|
2428
|
-
return `${trimmed.length >= sliceEnd / 2 ? trimmed : sliced}${ellipsis}`;
|
|
2429
|
-
}, buildAgentDirectivesSection = (context) => {
|
|
2430
|
-
const { targetLanguage, sourceLanguage } = context;
|
|
2431
|
-
return `# AGENT WORKFLOW DIRECTIVES
|
|
2432
|
-
|
|
2433
|
-
- Use only the provided ${sourceLanguage || "the source language"} source; never fetch outside context.
|
|
2434
|
-
- Scan each field once, then move directly into translation work.
|
|
2435
|
-
- Plan updates before editing so document structure remains intact.
|
|
2436
|
-
- Default to medium reasoning effort and escalate only for ambiguous passages.
|
|
2437
|
-
- Output just the ${targetLanguage || "the target language"} translation and mirror the source formatting exactly.`;
|
|
2438
|
-
}, buildCriticalRulesSection = (context) => {
|
|
2439
|
-
const { sourceLanguage, targetLanguage } = context, fromLanguage = sourceLanguage || "the source language", toLanguage = targetLanguage || "the target language";
|
|
2440
|
-
return `# CRITICAL TRANSLATION RULES
|
|
2441
|
-
|
|
2442
|
-
Follow these without exception:
|
|
2443
|
-
|
|
2444
|
-
1. Translate from ${fromLanguage} into ${toLanguage}; every output token must be ${toLanguage} unless flagged as protected.
|
|
2445
|
-
2. Preserve meaning exactly\u2014no additions, deletions, or tone shifts.
|
|
2446
|
-
3. Maintain all layout, markdown, rich-text marks, and data structure.
|
|
2447
|
-
4. Never invent metadata or field values that are absent in the source.
|
|
2448
|
-
5. Leave technical tokens (code, IDs, filenames) untouched.
|
|
2449
|
-
6. Keep punctuation, casing, and spacing unless ${toLanguage} grammar forces a change.`;
|
|
2450
|
-
}, buildGlossarySection = (glossaryTerms) => `# TERMINOLOGY RULES
|
|
2451
|
-
|
|
2452
|
-
Apply these mappings immediately after protected phrases and before style preferences.
|
|
2453
|
-
|
|
2454
|
-
${glossaryTerms}`, buildStyleGuideSection = (globalPrompt, localePrompt) => {
|
|
2455
|
-
const components = [];
|
|
2456
|
-
return (globalPrompt || localePrompt) && (components.push("# TRANSLATION STYLE GUIDE (LOWEST PRIORITY)"), components.push(
|
|
2457
|
-
"Apply these directions only after honoring critical rules, protected phrases, and glossary terminology."
|
|
2458
|
-
), components.push("")), globalPrompt && (components.push("## General Guidelines"), components.push(globalPrompt), components.push("")), localePrompt && (components.push("## Language-Specific Guidelines"), components.push(localePrompt)), components.length > 0 ? components.join(`
|
|
2459
|
-
`) : null;
|
|
2460
|
-
}, translateEntireDocument = async ({
|
|
2202
|
+
}, getOpenAITranslation = async ({
|
|
2461
2203
|
dispatch,
|
|
2462
|
-
client,
|
|
2463
|
-
schemaId,
|
|
2464
2204
|
sourceDocument,
|
|
2465
|
-
targetLanguage,
|
|
2466
2205
|
languageFieldName,
|
|
2467
|
-
translationTarget,
|
|
2468
2206
|
translationPrompts,
|
|
2469
|
-
translationMode,
|
|
2470
|
-
openaiConfig,
|
|
2471
|
-
releaseId,
|
|
2472
|
-
isNewTranslation
|
|
2473
|
-
}) => {
|
|
2474
|
-
const logContext = {
|
|
2475
|
-
documentId: sourceDocument._id,
|
|
2476
|
-
targetLanguage,
|
|
2477
|
-
schemaId,
|
|
2478
|
-
operation: translationTarget.operation,
|
|
2479
|
-
targetId: translationTarget._id,
|
|
2480
|
-
translationMode,
|
|
2481
|
-
releaseId,
|
|
2482
|
-
isNewTranslation
|
|
2483
|
-
};
|
|
2484
|
-
devLog.group("translateEntireDocument", logContext), devLog.time("translateEntireDocument");
|
|
2485
|
-
try {
|
|
2486
|
-
let versionedDocumentId, baseDocumentId;
|
|
2487
|
-
if (isNewTranslation) {
|
|
2488
|
-
const { _id, _createdAt, _updatedAt, _rev, ...docWithoutMeta } = sourceDocument, newTranslationDoc = {
|
|
2489
|
-
...docWithoutMeta,
|
|
2490
|
-
_type: sourceDocument._type,
|
|
2491
|
-
[languageFieldName]: targetLanguage
|
|
2492
|
-
};
|
|
2493
|
-
devLog.info("Creating new translation document in release", logContext), baseDocumentId = translationTarget._id || `new-translation-${Date.now()}`, await client.action({
|
|
2494
|
-
actionType: "sanity.action.document.version.create",
|
|
2495
|
-
publishedId: baseDocumentId,
|
|
2496
|
-
document: {
|
|
2497
|
-
...newTranslationDoc,
|
|
2498
|
-
_type: sourceDocument._type,
|
|
2499
|
-
_id: `versions.${releaseId}.${baseDocumentId}`
|
|
2500
|
-
}
|
|
2501
|
-
}), versionedDocumentId = `versions.${releaseId}.${baseDocumentId}`, devLog.info("New translation version created in release", {
|
|
2502
|
-
...logContext,
|
|
2503
|
-
versionedDocumentId,
|
|
2504
|
-
baseDocumentId
|
|
2505
|
-
});
|
|
2506
|
-
} else {
|
|
2507
|
-
const existingTranslationId = translationTarget._id;
|
|
2508
|
-
if (!existingTranslationId)
|
|
2509
|
-
throw new Error("Translation target ID not found for update operation");
|
|
2510
|
-
devLog.info("Creating version of existing translation in release", {
|
|
2511
|
-
...logContext,
|
|
2512
|
-
existingTranslationId
|
|
2513
|
-
});
|
|
2514
|
-
let existingDoc = await client.getDocument(existingTranslationId), actualDocumentId = existingTranslationId;
|
|
2515
|
-
if (!existingDoc) {
|
|
2516
|
-
const alternateId = existingTranslationId.startsWith("drafts.") ? undraftId(existingTranslationId) : draftId(existingTranslationId);
|
|
2517
|
-
devLog.info("Document not found with stored ID, trying alternate ID", {
|
|
2518
|
-
...logContext,
|
|
2519
|
-
storedId: existingTranslationId,
|
|
2520
|
-
alternateId
|
|
2521
|
-
}), existingDoc = await client.getDocument(alternateId), existingDoc && (actualDocumentId = alternateId, devLog.info("Found document with alternate ID", {
|
|
2522
|
-
...logContext,
|
|
2523
|
-
actualDocumentId
|
|
2524
|
-
}));
|
|
2525
|
-
}
|
|
2526
|
-
if (!existingDoc)
|
|
2527
|
-
throw new Error(
|
|
2528
|
-
`Existing translation document not found: ${existingTranslationId} (also tried ${existingTranslationId.startsWith("drafts.") ? undraftId(existingTranslationId) : draftId(existingTranslationId)})`
|
|
2529
|
-
);
|
|
2530
|
-
const publishedId = undraftId(actualDocumentId);
|
|
2531
|
-
await client.action({
|
|
2532
|
-
actionType: "sanity.action.document.version.create",
|
|
2533
|
-
publishedId,
|
|
2534
|
-
document: {
|
|
2535
|
-
...existingDoc,
|
|
2536
|
-
_id: `versions.${releaseId}.${publishedId}`
|
|
2537
|
-
}
|
|
2538
|
-
}), versionedDocumentId = `versions.${releaseId}.${publishedId}`, baseDocumentId = publishedId, devLog.info("Existing translation version created", {
|
|
2539
|
-
...logContext,
|
|
2540
|
-
versionedDocumentId,
|
|
2541
|
-
baseDocumentId
|
|
2542
|
-
});
|
|
2543
|
-
const { _id, _createdAt, _updatedAt, _rev, ...sourceContent } = sourceDocument;
|
|
2544
|
-
await client.createOrReplace({
|
|
2545
|
-
...sourceContent,
|
|
2546
|
-
_id: versionedDocumentId,
|
|
2547
|
-
[languageFieldName]: targetLanguage
|
|
2548
|
-
}), devLog.info("Source content cloned onto version", {
|
|
2549
|
-
...logContext,
|
|
2550
|
-
versionedDocumentId
|
|
2551
|
-
});
|
|
2552
|
-
}
|
|
2553
|
-
let translatedDocument;
|
|
2554
|
-
switch (translationMode) {
|
|
2555
|
-
case "openai-only": {
|
|
2556
|
-
if (!openaiConfig?.enabled || !openaiConfig?.apiKey)
|
|
2557
|
-
throw new Error("OpenAI is not configured but openai-only mode was selected");
|
|
2558
|
-
devLog.info("Using OpenAI-only translation", logContext), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai");
|
|
2559
|
-
const versionedDoc = await client.getDocument(versionedDocumentId);
|
|
2560
|
-
if (!versionedDoc)
|
|
2561
|
-
throw new Error(`Versioned document not found: ${versionedDocumentId}`);
|
|
2562
|
-
const translatedDoc = await translateInPlaceWithOpenAI({
|
|
2563
|
-
dispatch,
|
|
2564
|
-
sourceDocument,
|
|
2565
|
-
targetDocument: versionedDoc,
|
|
2566
|
-
targetLanguage,
|
|
2567
|
-
languageFieldName,
|
|
2568
|
-
translationPrompts,
|
|
2569
|
-
openaiConfig,
|
|
2570
|
-
isReview: !1
|
|
2571
|
-
});
|
|
2572
|
-
translatedDoc && (translatedDocument = await client.createOrReplace({
|
|
2573
|
-
...translatedDoc,
|
|
2574
|
-
_id: versionedDocumentId
|
|
2575
|
-
}));
|
|
2576
|
-
break;
|
|
2577
|
-
}
|
|
2578
|
-
case "sanity-with-review":
|
|
2579
|
-
if (!openaiConfig?.enabled || !openaiConfig?.apiKey)
|
|
2580
|
-
throw new Error("OpenAI is not configured but sanity-with-review mode was selected");
|
|
2581
|
-
if (devLog.info("Using Sanity + OpenAI review", logContext), translatedDocument = await translateWithSanityAgent({
|
|
2582
|
-
dispatch,
|
|
2583
|
-
client,
|
|
2584
|
-
schemaId,
|
|
2585
|
-
sourceDocument,
|
|
2586
|
-
targetLanguage,
|
|
2587
|
-
languageFieldName,
|
|
2588
|
-
versionedDocumentId,
|
|
2589
|
-
translationPrompts
|
|
2590
|
-
}), translatedDocument) {
|
|
2591
|
-
setJobStatus(dispatch, sourceDocument._id, targetLanguage, "reviewing_with_openai");
|
|
2592
|
-
const reviewedDoc = await translateInPlaceWithOpenAI({
|
|
2593
|
-
dispatch,
|
|
2594
|
-
sourceDocument,
|
|
2595
|
-
targetDocument: translatedDocument,
|
|
2596
|
-
targetLanguage,
|
|
2597
|
-
languageFieldName,
|
|
2598
|
-
translationPrompts,
|
|
2599
|
-
openaiConfig,
|
|
2600
|
-
isReview: !0
|
|
2601
|
-
});
|
|
2602
|
-
reviewedDoc && (translatedDocument = await client.createOrReplace({
|
|
2603
|
-
...reviewedDoc,
|
|
2604
|
-
_id: versionedDocumentId
|
|
2605
|
-
}));
|
|
2606
|
-
}
|
|
2607
|
-
break;
|
|
2608
|
-
default:
|
|
2609
|
-
devLog.info("Using Sanity-only translation", logContext), translatedDocument = await translateWithSanityAgent({
|
|
2610
|
-
dispatch,
|
|
2611
|
-
client,
|
|
2612
|
-
schemaId,
|
|
2613
|
-
sourceDocument,
|
|
2614
|
-
targetLanguage,
|
|
2615
|
-
languageFieldName,
|
|
2616
|
-
versionedDocumentId,
|
|
2617
|
-
translationPrompts
|
|
2618
|
-
});
|
|
2619
|
-
break;
|
|
2620
|
-
}
|
|
2621
|
-
return devLog.timeEnd("translateEntireDocument"), devLog.groupEnd(), translatedDocument ? {
|
|
2622
|
-
...translatedDocument,
|
|
2623
|
-
_id: baseDocumentId
|
|
2624
|
-
} : void 0;
|
|
2625
|
-
} catch (error) {
|
|
2626
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2627
|
-
devLog.error(
|
|
2628
|
-
"translateEntireDocument failed",
|
|
2629
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
2630
|
-
logContext
|
|
2631
|
-
), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
2632
|
-
errorMessage
|
|
2633
|
-
}), devLog.timeEnd("translateEntireDocument"), devLog.groupEnd();
|
|
2634
|
-
return;
|
|
2635
|
-
}
|
|
2636
|
-
}, translateWithSanityAgent = async ({
|
|
2637
|
-
dispatch,
|
|
2638
|
-
client,
|
|
2639
|
-
schemaId,
|
|
2640
|
-
sourceDocument,
|
|
2641
2207
|
targetLanguage,
|
|
2642
|
-
|
|
2643
|
-
versionedDocumentId,
|
|
2644
|
-
translationPrompts
|
|
2645
|
-
}) => {
|
|
2646
|
-
const logContext = {
|
|
2647
|
-
documentId: sourceDocument._id,
|
|
2648
|
-
targetLanguage,
|
|
2649
|
-
versionedDocumentId
|
|
2650
|
-
};
|
|
2651
|
-
try {
|
|
2652
|
-
const { toLanguage, fromLanguage } = assembleLanguageSpecs({
|
|
2653
|
-
sourceDocument,
|
|
2654
|
-
languageFieldName,
|
|
2655
|
-
targetLanguage
|
|
2656
|
-
});
|
|
2657
|
-
devLog.debug("Language specs assembled", {
|
|
2658
|
-
...logContext,
|
|
2659
|
-
toLanguage,
|
|
2660
|
-
fromLanguage
|
|
2661
|
-
});
|
|
2662
|
-
const documentAnalysis = analyzeDocumentContent(sourceDocument);
|
|
2663
|
-
devLog.debug("Document analysis completed", {
|
|
2664
|
-
...logContext,
|
|
2665
|
-
analysis: documentAnalysis
|
|
2666
|
-
}), devLog.info("Starting Sanity agent translation", logContext), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating");
|
|
2667
|
-
const { styleGuide, omittedSections, truncatedSections } = assembleTranslationPrompt(
|
|
2668
|
-
{
|
|
2669
|
-
documentAnalysis,
|
|
2670
|
-
isUpdate: !1,
|
|
2671
|
-
// Always fresh translation in a version
|
|
2672
|
-
targetLanguage,
|
|
2673
|
-
sourceLanguage: sourceDocument[languageFieldName]
|
|
2674
|
-
},
|
|
2675
|
-
{
|
|
2676
|
-
globalPrompt: translationPrompts.globalPrompt,
|
|
2677
|
-
localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,
|
|
2678
|
-
glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms,
|
|
2679
|
-
protectedPhrases: translationPrompts.protectedPhrases
|
|
2680
|
-
}
|
|
2681
|
-
);
|
|
2682
|
-
devLog.info("Structured style guide generated", {
|
|
2683
|
-
...logContext,
|
|
2684
|
-
styleGuideLength: styleGuide.length,
|
|
2685
|
-
omittedSections,
|
|
2686
|
-
truncatedSections
|
|
2687
|
-
}), (omittedSections.length > 0 || truncatedSections.length > 0) && devLog.warn("Style guide adjusted to fit character limit", {
|
|
2688
|
-
...logContext,
|
|
2689
|
-
omittedSections,
|
|
2690
|
-
truncatedSections
|
|
2691
|
-
});
|
|
2692
|
-
const newDocument = await client.agent.action.translate({
|
|
2693
|
-
schemaId,
|
|
2694
|
-
toLanguage,
|
|
2695
|
-
fromLanguage,
|
|
2696
|
-
documentId: versionedDocumentId,
|
|
2697
|
-
// Use versioned document ID
|
|
2698
|
-
languageFieldPath: [languageFieldName],
|
|
2699
|
-
styleGuide,
|
|
2700
|
-
protectedPhrases: translationPrompts.protectedPhrases || [],
|
|
2701
|
-
conditionalPaths: {
|
|
2702
|
-
defaultHidden: !1,
|
|
2703
|
-
defaultReadOnly: !1
|
|
2704
|
-
}
|
|
2705
|
-
});
|
|
2706
|
-
return devLog.info("Sanity agent translation completed", {
|
|
2707
|
-
...logContext,
|
|
2708
|
-
newDocumentId: newDocument._id,
|
|
2709
|
-
newDocumentRev: newDocument._rev
|
|
2710
|
-
}), newDocument;
|
|
2711
|
-
} catch (error) {
|
|
2712
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2713
|
-
devLog.error(
|
|
2714
|
-
"Sanity agent translation failed",
|
|
2715
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
2716
|
-
logContext
|
|
2717
|
-
), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
2718
|
-
errorMessage
|
|
2719
|
-
});
|
|
2720
|
-
return;
|
|
2721
|
-
}
|
|
2722
|
-
}, translateInPlaceWithOpenAI = async ({
|
|
2723
|
-
dispatch,
|
|
2724
|
-
sourceDocument,
|
|
2725
|
-
targetDocument,
|
|
2726
|
-
targetLanguage,
|
|
2727
|
-
languageFieldName,
|
|
2728
|
-
translationPrompts,
|
|
2729
|
-
openaiConfig,
|
|
2730
|
-
isReview
|
|
2208
|
+
openaiConfig
|
|
2731
2209
|
}) => {
|
|
2732
2210
|
const sourceLanguage = sourceDocument[languageFieldName], logContext = {
|
|
2733
2211
|
sourceId: sourceDocument._id,
|
|
2734
|
-
|
|
2735
|
-
targetLanguage,
|
|
2736
|
-
sourceLanguage,
|
|
2737
|
-
isReview
|
|
2212
|
+
sourceLanguage
|
|
2738
2213
|
};
|
|
2739
2214
|
try {
|
|
2740
|
-
const sourceText = serializeDocument(sourceDocument),
|
|
2741
|
-
devLog.debug("Documents serialized for OpenAI", {
|
|
2742
|
-
...logContext,
|
|
2743
|
-
sourceLength: sourceText.length,
|
|
2744
|
-
targetLength: targetText.length
|
|
2745
|
-
});
|
|
2746
|
-
const prompt = isReview ? buildReviewPrompt({
|
|
2215
|
+
const sourceText = serializeDocument(sourceDocument), prompt = buildTranslationPrompt$1({
|
|
2747
2216
|
sourceText,
|
|
2748
|
-
translatedText: targetText,
|
|
2749
2217
|
sourceLanguage,
|
|
2750
2218
|
targetLanguage,
|
|
2751
2219
|
translationPrompts
|
|
2752
|
-
})
|
|
2753
|
-
sourceText,
|
|
2754
|
-
sourceLanguage,
|
|
2755
|
-
targetLanguage,
|
|
2756
|
-
translationPrompts
|
|
2757
|
-
});
|
|
2758
|
-
devLog.debug("Calling GPT-5", {
|
|
2759
|
-
...logContext,
|
|
2760
|
-
promptLength: prompt.length
|
|
2761
|
-
});
|
|
2762
|
-
const response = await callGPT5(openaiConfig.apiKey, {
|
|
2220
|
+
}), response = await callGPT5(openaiConfig.apiKey, {
|
|
2763
2221
|
messages: [
|
|
2764
2222
|
{
|
|
2765
|
-
role: "
|
|
2766
|
-
content:
|
|
2223
|
+
role: "developer",
|
|
2224
|
+
content: "You are a translation quality reviewer. Review and refine translations while preserving structure."
|
|
2767
2225
|
},
|
|
2768
2226
|
{ role: "user", content: prompt }
|
|
2769
2227
|
],
|
|
2770
2228
|
model: openaiConfig.model,
|
|
2771
|
-
reasoningEffort: openaiConfig.reasoningEffort
|
|
2772
|
-
|
|
2773
|
-
onReasoningChunk: (reasoning) => {
|
|
2774
|
-
const status2 = isReview ? "reviewing_with_openai" : "translating_with_openai";
|
|
2775
|
-
setJobStatus(dispatch, sourceDocument._id, targetLanguage, status2, {
|
|
2776
|
-
reasoningProgress: reasoning
|
|
2777
|
-
});
|
|
2778
|
-
}
|
|
2779
|
-
});
|
|
2780
|
-
devLog.info("OpenAI operation completed", {
|
|
2781
|
-
...logContext,
|
|
2782
|
-
tokensUsed: response.tokensUsed.total,
|
|
2783
|
-
cost: response.cost
|
|
2784
|
-
});
|
|
2785
|
-
const result = deserializeDocument(targetDocument, response.content), validation = validateStructureIntegrity(targetDocument, result);
|
|
2229
|
+
reasoningEffort: openaiConfig.reasoningEffort
|
|
2230
|
+
}), result = deserializeDocument(sourceDocument, response.content), validation = validateStructureIntegrity(sourceDocument, result);
|
|
2786
2231
|
if (!validation.valid) {
|
|
2787
2232
|
devLog.error("Structure validation failed", {
|
|
2788
2233
|
...logContext,
|
|
@@ -2796,17 +2241,14 @@ ${glossaryTerms}`, buildStyleGuideSection = (globalPrompt, localePrompt) => {
|
|
|
2796
2241
|
`Structure validation failed: ${unexpectedErrors.length} errors. First: ${unexpectedErrors[0].type} at ${unexpectedErrors[0].path.join(".")}`
|
|
2797
2242
|
);
|
|
2798
2243
|
}
|
|
2799
|
-
|
|
2800
|
-
const status = isReview ? "reviewing_with_openai" : "translating_with_openai";
|
|
2801
|
-
return setJobStatus(dispatch, sourceDocument._id, targetLanguage, status, {
|
|
2244
|
+
return setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai", {
|
|
2802
2245
|
tokensUsed: response.tokensUsed.total,
|
|
2803
|
-
estimatedCost: response.cost
|
|
2804
|
-
reasoningSummary: response.reasoningSummary
|
|
2246
|
+
estimatedCost: response.cost
|
|
2805
2247
|
}), result;
|
|
2806
2248
|
} catch (error) {
|
|
2807
2249
|
throw devLog.error("OpenAI translation failed", error instanceof Error ? error : new Error(String(error)), logContext), error;
|
|
2808
2250
|
}
|
|
2809
|
-
}, buildTranslationPrompt = ({
|
|
2251
|
+
}, buildTranslationPrompt$1 = ({
|
|
2810
2252
|
sourceText,
|
|
2811
2253
|
sourceLanguage,
|
|
2812
2254
|
targetLanguage,
|
|
@@ -2853,73 +2295,175 @@ Brief summary of key translation decisions and reasoning
|
|
|
2853
2295
|
</reasoning>
|
|
2854
2296
|
|
|
2855
2297
|
Then return the translated document:`;
|
|
2856
|
-
},
|
|
2857
|
-
|
|
2858
|
-
|
|
2298
|
+
}, assembleLanguageSpecs = ({
|
|
2299
|
+
sourceDocument,
|
|
2300
|
+
languageFieldName,
|
|
2301
|
+
targetLanguage
|
|
2302
|
+
}) => {
|
|
2303
|
+
const sourceLanugageId = sourceDocument[languageFieldName];
|
|
2304
|
+
if (!sourceLanugageId)
|
|
2305
|
+
throw new Error("[assembleLanguages] Source language ID is missing");
|
|
2306
|
+
return {
|
|
2307
|
+
fromLanguage: {
|
|
2308
|
+
id: sourceLanugageId,
|
|
2309
|
+
title: sourceLanugageId
|
|
2310
|
+
},
|
|
2311
|
+
toLanguage: {
|
|
2312
|
+
id: targetLanguage,
|
|
2313
|
+
title: targetLanguage
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
}, buildTranslationPrompt = ({
|
|
2859
2317
|
sourceLanguage,
|
|
2860
2318
|
targetLanguage,
|
|
2861
|
-
|
|
2319
|
+
components,
|
|
2320
|
+
isUpdate = !1
|
|
2862
2321
|
}) => {
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2322
|
+
const sections = [];
|
|
2323
|
+
sections.push(buildAgentDirectivesSection(sourceLanguage, targetLanguage)), sections.push(buildCriticalRulesSection(sourceLanguage, targetLanguage)), isUpdate && sections.push(buildUpdateInstructionsSection()), components.glossaryTerms?.trim() && sections.push(buildGlossarySection(components.glossaryTerms)), components.protectedPhrases?.length && sections.push(buildProtectedPhrasesSection(components.protectedPhrases));
|
|
2324
|
+
const styleSection = buildStyleGuideSection(components.globalPrompt, components.localePrompt);
|
|
2325
|
+
return styleSection && sections.push(styleSection), sections.join(`
|
|
2867
2326
|
|
|
2868
|
-
|
|
2327
|
+
---
|
|
2869
2328
|
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
3. **Protected Phrases**: Are protected phrases preserved?
|
|
2873
|
-
4. **Fluency**: Is it natural and fluent in ${targetLanguage}?
|
|
2874
|
-
5. **Style**: Does it follow the style guide?
|
|
2875
|
-
6. **Completeness**: Is all content translated?
|
|
2329
|
+
`);
|
|
2330
|
+
}, buildAgentDirectivesSection = (sourceLanguage, targetLanguage) => `# AGENT WORKFLOW DIRECTIVES
|
|
2876
2331
|
|
|
2877
|
-
|
|
2332
|
+
- Use only the provided ${sourceLanguage || "the source language"} source; never fetch outside context.
|
|
2333
|
+
- Scan each field once, then move directly into translation work.
|
|
2334
|
+
- Plan updates before editing so document structure remains intact.
|
|
2335
|
+
- Default to medium reasoning effort and escalate only for ambiguous passages.
|
|
2336
|
+
- Output just the ${targetLanguage || "the target language"} translation and mirror the source formatting exactly.`, buildCriticalRulesSection = (sourceLanguage, targetLanguage) => {
|
|
2337
|
+
const fromLanguage = sourceLanguage || "the source language", toLanguage = targetLanguage || "the target language";
|
|
2338
|
+
return `# CRITICAL TRANSLATION RULES
|
|
2878
2339
|
|
|
2879
|
-
|
|
2880
|
-
${globalPrompt}
|
|
2881
|
-
` : ""}
|
|
2882
|
-
${localePrompt ? `### ${targetLanguage} Guidelines
|
|
2883
|
-
${localePrompt}
|
|
2884
|
-
` : ""}
|
|
2885
|
-
${glossaryTerms ? `### Required Terminology
|
|
2886
|
-
${glossaryTerms}
|
|
2887
|
-
` : ""}
|
|
2888
|
-
${protectedPhrases?.length ? `### Protected Phrases (must preserve)
|
|
2889
|
-
${protectedPhrases.join(", ")}
|
|
2890
|
-
` : ""}
|
|
2340
|
+
Follow these without exception:
|
|
2891
2341
|
|
|
2892
|
-
|
|
2342
|
+
1. Translate from ${fromLanguage} into ${toLanguage}; every output token must be ${toLanguage} unless flagged as protected.
|
|
2343
|
+
2. Preserve meaning exactly\u2014no additions, deletions, or tone shifts.
|
|
2344
|
+
3. Maintain all layout, markdown, rich-text marks, and data structure.
|
|
2345
|
+
4. Never invent metadata or field values that are absent in the source.
|
|
2346
|
+
5. Leave technical tokens (code, IDs, filenames) untouched.
|
|
2347
|
+
6. Keep punctuation, casing, and spacing unless ${toLanguage} grammar forces a change.`;
|
|
2348
|
+
}, buildUpdateInstructionsSection = () => `# UPDATING EXISTING TRANSLATION
|
|
2893
2349
|
|
|
2894
|
-
|
|
2895
|
-
${sourceText}
|
|
2896
|
-
\`\`\`
|
|
2350
|
+
This document already has a translation; only adjust fields that changed.
|
|
2897
2351
|
|
|
2898
|
-
|
|
2352
|
+
- Compare the latest source to the existing target text.
|
|
2353
|
+
- Keep prior terminology and style unless the source demands a change.
|
|
2354
|
+
- Edit minimally while guaranteeing accuracy and completeness.`, buildGlossarySection = (glossaryTerms) => `# TERMINOLOGY RULES
|
|
2899
2355
|
|
|
2900
|
-
|
|
2901
|
-
${translatedText}
|
|
2902
|
-
\`\`\`
|
|
2356
|
+
Apply these mappings immediately after protected phrases and before style preferences.
|
|
2903
2357
|
|
|
2904
|
-
|
|
2358
|
+
${glossaryTerms}`, buildProtectedPhrasesSection = (protectedPhrases) => `# PROTECTED PHRASES
|
|
2905
2359
|
|
|
2906
|
-
|
|
2907
|
-
2. Make corrections where needed to improve accuracy, fluency, or adherence to guidelines
|
|
2908
|
-
3. Keep good translations as-is - only fix what needs fixing
|
|
2909
|
-
4. Return the corrected translation in THE EXACT SAME FORMAT
|
|
2910
|
-
5. **CRITICAL**: Only modify text content between tags
|
|
2911
|
-
6. Never add, remove, or change any tags, attributes, or structure
|
|
2912
|
-
|
|
2913
|
-
If you made corrections or have insights about the translation quality, include a brief summary:
|
|
2914
|
-
<reasoning>
|
|
2915
|
-
Brief summary of review findings and corrections made
|
|
2916
|
-
</reasoning>
|
|
2360
|
+
These phrases must NEVER be translated. Keep them exactly as they appear:
|
|
2917
2361
|
|
|
2918
|
-
|
|
2362
|
+
${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localePrompt) => {
|
|
2363
|
+
const components = [];
|
|
2364
|
+
return (globalPrompt || localePrompt) && (components.push("# TRANSLATION STYLE GUIDE (LOWEST PRIORITY)"), components.push(
|
|
2365
|
+
"Apply these directions only after honoring critical rules, protected phrases, and glossary terminology."
|
|
2366
|
+
), components.push("")), globalPrompt && (components.push("## General Guidelines"), components.push(globalPrompt), components.push("")), localePrompt && (components.push("## Language-Specific Guidelines"), components.push(localePrompt)), components.length > 0 ? components.join(`
|
|
2367
|
+
`) : null;
|
|
2368
|
+
}, getSanityAgentTranslation = async ({
|
|
2369
|
+
sourceDocument,
|
|
2370
|
+
targetLanguage,
|
|
2371
|
+
languageFieldName,
|
|
2372
|
+
schemaId,
|
|
2373
|
+
client,
|
|
2374
|
+
translationPrompts
|
|
2375
|
+
}) => {
|
|
2376
|
+
const styleGuide = buildTranslationPrompt({
|
|
2377
|
+
sourceLanguage: sourceDocument[languageFieldName],
|
|
2378
|
+
targetLanguage,
|
|
2379
|
+
isUpdate: !1,
|
|
2380
|
+
// Always fresh translation in a version
|
|
2381
|
+
components: {
|
|
2382
|
+
globalPrompt: translationPrompts.globalPrompt,
|
|
2383
|
+
localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,
|
|
2384
|
+
glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms,
|
|
2385
|
+
protectedPhrases: translationPrompts.protectedPhrases
|
|
2386
|
+
}
|
|
2387
|
+
}), { fromLanguage, toLanguage } = assembleLanguageSpecs({
|
|
2388
|
+
sourceDocument,
|
|
2389
|
+
languageFieldName,
|
|
2390
|
+
targetLanguage
|
|
2391
|
+
});
|
|
2392
|
+
return await client.agent.action.translate({
|
|
2393
|
+
schemaId,
|
|
2394
|
+
toLanguage,
|
|
2395
|
+
fromLanguage,
|
|
2396
|
+
documentId: sourceDocument._id,
|
|
2397
|
+
noWrite: !0,
|
|
2398
|
+
// Do not write to the document, just return the translated content
|
|
2399
|
+
languageFieldPath: [languageFieldName],
|
|
2400
|
+
styleGuide,
|
|
2401
|
+
protectedPhrases: translationPrompts.protectedPhrases,
|
|
2402
|
+
conditionalPaths: {
|
|
2403
|
+
defaultHidden: !1,
|
|
2404
|
+
defaultReadOnly: !1
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
}, handleTranslation = async ({
|
|
2408
|
+
dispatch,
|
|
2409
|
+
client,
|
|
2410
|
+
schemaId,
|
|
2411
|
+
sourceDocument,
|
|
2412
|
+
targetLanguage,
|
|
2413
|
+
existingTranslationId,
|
|
2414
|
+
languageFieldName,
|
|
2415
|
+
translationPrompts,
|
|
2416
|
+
translationMode,
|
|
2417
|
+
openaiConfig,
|
|
2418
|
+
releaseId
|
|
2419
|
+
}) => {
|
|
2420
|
+
let translatedDoc;
|
|
2421
|
+
switch (translationMode) {
|
|
2422
|
+
case "sanity-only": {
|
|
2423
|
+
setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating"), translatedDoc = await getSanityAgentTranslation({
|
|
2424
|
+
sourceDocument,
|
|
2425
|
+
targetLanguage,
|
|
2426
|
+
languageFieldName,
|
|
2427
|
+
schemaId,
|
|
2428
|
+
client,
|
|
2429
|
+
translationPrompts
|
|
2430
|
+
});
|
|
2431
|
+
break;
|
|
2432
|
+
}
|
|
2433
|
+
case "openai-only": {
|
|
2434
|
+
if (!openaiConfig?.enabled || !openaiConfig?.apiKey)
|
|
2435
|
+
throw new Error("OpenAI is not configured but openai-only mode was selected");
|
|
2436
|
+
setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai"), translatedDoc = await getOpenAITranslation({
|
|
2437
|
+
dispatch,
|
|
2438
|
+
sourceDocument,
|
|
2439
|
+
targetLanguage,
|
|
2440
|
+
languageFieldName,
|
|
2441
|
+
translationPrompts,
|
|
2442
|
+
openaiConfig
|
|
2443
|
+
});
|
|
2444
|
+
break;
|
|
2445
|
+
}
|
|
2446
|
+
case "sanity-with-review":
|
|
2447
|
+
throw new Error("Sanity with review translation method is not implemented");
|
|
2448
|
+
default:
|
|
2449
|
+
throw new Error(`Invalid translation mode: ${translationMode}`);
|
|
2450
|
+
}
|
|
2451
|
+
if (!translatedDoc)
|
|
2452
|
+
return null;
|
|
2453
|
+
const targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
|
|
2454
|
+
...translatedDoc,
|
|
2455
|
+
_id: targetDocumentVersionId
|
|
2456
|
+
};
|
|
2457
|
+
return await client.action({
|
|
2458
|
+
actionType: "sanity.action.document.version.create",
|
|
2459
|
+
publishedId: targetDocumentPublishedId,
|
|
2460
|
+
document: newDoc
|
|
2461
|
+
}), newDoc;
|
|
2919
2462
|
}, translateDocument = async ({
|
|
2920
2463
|
dispatch,
|
|
2921
2464
|
client,
|
|
2922
2465
|
sourceDocument,
|
|
2466
|
+
sourceLanguage,
|
|
2923
2467
|
targetLanguage,
|
|
2924
2468
|
languageFieldName,
|
|
2925
2469
|
shouldOverwriteFreshTranslations,
|
|
@@ -2938,62 +2482,37 @@ Then return the corrected translation document:`;
|
|
|
2938
2482
|
};
|
|
2939
2483
|
devLog.group("translateDocument", logContext), devLog.time("translateDocument"), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "preparing_translation");
|
|
2940
2484
|
try {
|
|
2941
|
-
const sourceLanguage = sourceDocument[languageFieldName];
|
|
2942
|
-
if (devLog.debug("Checking source document language", {
|
|
2943
|
-
...logContext,
|
|
2944
|
-
sourceLanguage,
|
|
2945
|
-
languageFieldName
|
|
2946
|
-
}), !sourceLanguage) {
|
|
2947
|
-
const error = `Source document ${sourceDocument._id} does not have a language`;
|
|
2948
|
-
throw devLog.error(error, void 0, logContext), new Error(error);
|
|
2949
|
-
}
|
|
2950
|
-
devLog.debug("Getting translation metadata", logContext);
|
|
2951
2485
|
const translationMetadata = await getTranslationMetadata({
|
|
2952
2486
|
client,
|
|
2953
2487
|
sourceDocument,
|
|
2954
2488
|
sourceLanguage
|
|
2955
2489
|
}), isAlreadyFreshTranslation = translationMetadata.translationHistory?.some(
|
|
2956
2490
|
(history) => history.language === targetLanguage && history.sourceRev === sourceDocument._rev
|
|
2957
|
-
)
|
|
2958
|
-
if (
|
|
2959
|
-
|
|
2960
|
-
isAlreadyFreshTranslation,
|
|
2961
|
-
currentRev: sourceDocument._rev
|
|
2962
|
-
}), !shouldOverwriteFreshTranslations && isAlreadyFreshTranslation) {
|
|
2963
|
-
devLog.info("Skipping fresh translation", logContext), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "skipped", {
|
|
2491
|
+
);
|
|
2492
|
+
if (!shouldOverwriteFreshTranslations && isAlreadyFreshTranslation) {
|
|
2493
|
+
setJobStatus(dispatch, sourceDocument._id, targetLanguage, "skipped", {
|
|
2964
2494
|
targetId: translationMetadata.translations.find((t) => t._key === targetLanguage)?.value._ref
|
|
2965
|
-
})
|
|
2495
|
+
});
|
|
2966
2496
|
return;
|
|
2967
2497
|
}
|
|
2968
|
-
const
|
|
2969
|
-
translationMetadata,
|
|
2970
|
-
targetLanguage
|
|
2971
|
-
}), isNewTranslation = translationTarget.operation === "create";
|
|
2972
|
-
devLog.debug("Translation target determined", {
|
|
2973
|
-
...logContext,
|
|
2974
|
-
operation: translationTarget.operation,
|
|
2975
|
-
isNewTranslation
|
|
2976
|
-
});
|
|
2977
|
-
const newDocument = await translateEntireDocument({
|
|
2498
|
+
const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage), newDocument = await handleTranslation({
|
|
2978
2499
|
dispatch,
|
|
2979
2500
|
client,
|
|
2980
2501
|
schemaId,
|
|
2981
2502
|
sourceDocument,
|
|
2982
2503
|
targetLanguage,
|
|
2504
|
+
existingTranslationId,
|
|
2983
2505
|
languageFieldName,
|
|
2984
|
-
translationTarget,
|
|
2985
2506
|
translationPrompts,
|
|
2986
2507
|
translationMode,
|
|
2987
2508
|
openaiConfig,
|
|
2988
|
-
releaseId
|
|
2989
|
-
isNewTranslation
|
|
2509
|
+
releaseId
|
|
2990
2510
|
});
|
|
2991
|
-
if (!newDocument)
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
}), devLog.debug("Updating translation metadata", logContext), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "updating_metadata", {
|
|
2511
|
+
if (!newDocument) {
|
|
2512
|
+
setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error"), devLog.timeEnd("translateDocument"), devLog.groupEnd();
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
return setJobStatus(dispatch, sourceDocument._id, targetLanguage, "updating_metadata", {
|
|
2997
2516
|
targetId: newDocument._id
|
|
2998
2517
|
}), await updateTranslationMetadata({
|
|
2999
2518
|
client,
|
|
@@ -3002,8 +2521,10 @@ Then return the corrected translation document:`;
|
|
|
3002
2521
|
newDocument,
|
|
3003
2522
|
targetLanguage,
|
|
3004
2523
|
translationMetadata,
|
|
3005
|
-
isNewTranslation
|
|
3006
|
-
}), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "completed"
|
|
2524
|
+
isNewTranslation: !existingTranslationId
|
|
2525
|
+
}), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "completed", {
|
|
2526
|
+
targetId: newDocument._id
|
|
2527
|
+
}), devLog.info("Translation completed successfully", {
|
|
3007
2528
|
...logContext,
|
|
3008
2529
|
newDocumentId: newDocument._id
|
|
3009
2530
|
}), devLog.timeEnd("translateDocument"), devLog.groupEnd(), newDocument._id;
|
|
@@ -3029,40 +2550,32 @@ Then return the corrected translation document:`;
|
|
|
3029
2550
|
}) => {
|
|
3030
2551
|
const logContext = {
|
|
3031
2552
|
documentId: sourceDocument._id,
|
|
2553
|
+
documentRev: sourceDocument._rev,
|
|
3032
2554
|
targetLanguages,
|
|
3033
2555
|
referencesCount: references.length,
|
|
3034
2556
|
maxConcurrentJobs: maxConcurrentTranslationJobs,
|
|
3035
2557
|
translationMode,
|
|
3036
2558
|
shouldOverwriteFreshTranslations
|
|
3037
2559
|
};
|
|
3038
|
-
devLog.group("translateDocumentSet", logContext), devLog.time("translateDocumentSet");
|
|
3039
2560
|
let releaseId;
|
|
3040
2561
|
try {
|
|
3041
2562
|
const sourceLanguage = sourceDocument[languageFieldName];
|
|
3042
2563
|
if (!sourceLanguage)
|
|
3043
2564
|
throw devLog.error("Source language not found"), new Error("Source language not found");
|
|
3044
|
-
maxConcurrentTranslationJobs &&
|
|
3045
|
-
...logContext,
|
|
3046
|
-
newPermits: maxConcurrentTranslationJobs
|
|
3047
|
-
}), translationSemaphore.updatePermits(maxConcurrentTranslationJobs)), devLog.info("Starting document set translation", logContext), dispatch({
|
|
2565
|
+
maxConcurrentTranslationJobs && translationSemaphore.updatePermits(maxConcurrentTranslationJobs), dispatch({
|
|
3048
2566
|
type: "SET_OVERALL_STATUS",
|
|
3049
2567
|
payload: "translating"
|
|
3050
2568
|
});
|
|
3051
|
-
const releaseTitle = `[AI Translation] ${sourceDocument.title || sourceDocument._id.split("-").slice(0, 2).join("-")} (${sourceDocument._type}) ${sourceLanguage} \u2192 ${targetLanguages.join(", ")}`, releaseDescription = `AI translation batch created on ${(/* @__PURE__ */ new Date()).toLocaleDateString()} at ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}
|
|
3052
|
-
devLog.info("Creating Content Release for translation batch", {
|
|
3053
|
-
...logContext,
|
|
3054
|
-
releaseTitle
|
|
3055
|
-
});
|
|
3056
|
-
const { releaseId: newReleaseId } = await client.releases.create({
|
|
2569
|
+
const releaseTitle = `[AI Translation] ${sourceDocument.title || sourceDocument._id.split("-").slice(0, 2).join("-")} (${sourceDocument._type}) ${sourceLanguage} \u2192 ${targetLanguages.join(", ")}`, releaseDescription = `AI translation batch created on ${(/* @__PURE__ */ new Date()).toLocaleDateString()} at ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`, { releaseId: releaseId2 } = await client.releases.create({
|
|
3057
2570
|
metadata: {
|
|
3058
2571
|
title: releaseTitle,
|
|
3059
2572
|
description: releaseDescription,
|
|
3060
|
-
releaseType: "
|
|
2573
|
+
releaseType: "undecided"
|
|
3061
2574
|
}
|
|
3062
2575
|
});
|
|
3063
|
-
|
|
2576
|
+
devLog.info("Content Release created", {
|
|
3064
2577
|
...logContext,
|
|
3065
|
-
releaseId
|
|
2578
|
+
releaseId: releaseId2
|
|
3066
2579
|
});
|
|
3067
2580
|
const sourceDocuments = [
|
|
3068
2581
|
{
|
|
@@ -3072,34 +2585,26 @@ Then return the corrected translation document:`;
|
|
|
3072
2585
|
title: sourceDocument.name || sourceDocument.title
|
|
3073
2586
|
},
|
|
3074
2587
|
...references
|
|
3075
|
-
], sourceDocumentIds = sourceDocuments.map((doc) => doc._id),
|
|
3076
|
-
devLog.debug("Prepared document set for translation", {
|
|
3077
|
-
...logContext,
|
|
3078
|
-
sourceDocumentIds: sourceDocumentIds.length,
|
|
3079
|
-
totalTranslationJobs: sourceDocumentIds.length * targetLanguages.length
|
|
3080
|
-
});
|
|
3081
|
-
const translationPrompts = await fetchTranslationPrompts({
|
|
2588
|
+
], sourceDocumentIds = sourceDocuments.map((doc) => doc._id), versionedTargetDocumentIds = [], translationPrompts = await fetchTranslationPrompts({
|
|
3082
2589
|
client,
|
|
3083
2590
|
sourceLanguage,
|
|
3084
2591
|
targetLanguages,
|
|
3085
2592
|
sourceDocument
|
|
3086
2593
|
});
|
|
3087
|
-
|
|
2594
|
+
setupTranslationJobStatuses(dispatch, sourceDocuments, targetLanguages);
|
|
3088
2595
|
const translationTasks = sourceDocumentIds.flatMap(
|
|
3089
2596
|
(documentId) => targetLanguages.map(async (targetLanguage) => {
|
|
3090
2597
|
try {
|
|
3091
2598
|
await translationSemaphore.withPermit(async () => {
|
|
3092
2599
|
const document = await client.withConfig({
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
});
|
|
3098
|
-
if (document && releaseId) {
|
|
3099
|
-
const targetDocumentId = await translateDocument({
|
|
2600
|
+
perspective: "drafts"
|
|
2601
|
+
}).getDocument(documentId);
|
|
2602
|
+
if (document || devLog.error("\u274C SOURCE DOCUMENT NOT FOUND", { documentId }), document && releaseId2) {
|
|
2603
|
+
const versionedTargetDocumentId = await translateDocument({
|
|
3100
2604
|
dispatch,
|
|
3101
2605
|
client,
|
|
3102
2606
|
sourceDocument: document,
|
|
2607
|
+
sourceLanguage,
|
|
3103
2608
|
targetLanguage,
|
|
3104
2609
|
languageFieldName,
|
|
3105
2610
|
shouldOverwriteFreshTranslations,
|
|
@@ -3107,9 +2612,9 @@ Then return the corrected translation document:`;
|
|
|
3107
2612
|
translationPrompts,
|
|
3108
2613
|
translationMode,
|
|
3109
2614
|
openaiConfig,
|
|
3110
|
-
releaseId
|
|
2615
|
+
releaseId: releaseId2
|
|
3111
2616
|
});
|
|
3112
|
-
|
|
2617
|
+
versionedTargetDocumentId && versionedTargetDocumentIds.push(versionedTargetDocumentId);
|
|
3113
2618
|
}
|
|
3114
2619
|
});
|
|
3115
2620
|
} catch (error) {
|
|
@@ -3125,25 +2630,21 @@ Then return the corrected translation document:`;
|
|
|
3125
2630
|
}
|
|
3126
2631
|
})
|
|
3127
2632
|
), results = await Promise.allSettled(translationTasks), succeeded = results.filter((r) => r.status === "fulfilled").length, failed = results.filter((r) => r.status === "rejected").length;
|
|
3128
|
-
devLog.info("Translation tasks
|
|
2633
|
+
devLog.info("Translation tasks completed", {
|
|
3129
2634
|
...logContext,
|
|
3130
2635
|
total: results.length,
|
|
3131
2636
|
succeeded,
|
|
3132
|
-
failed
|
|
3133
|
-
|
|
3134
|
-
...logContext,
|
|
3135
|
-
targetDocumentIds: targetDocumentIds.length
|
|
2637
|
+
failed,
|
|
2638
|
+
targetDocuments: versionedTargetDocumentIds.length
|
|
3136
2639
|
}), dispatch({
|
|
3137
2640
|
type: "SET_OVERALL_STATUS",
|
|
3138
2641
|
payload: "linking_references"
|
|
3139
|
-
}), devLog.debug("Starting reference localization", {
|
|
3140
|
-
...logContext,
|
|
3141
|
-
documentsToLocalize: targetDocumentIds.length
|
|
3142
2642
|
}), await Promise.all(
|
|
3143
|
-
|
|
2643
|
+
versionedTargetDocumentIds.map(async (documentId) => {
|
|
3144
2644
|
await localizeReferences({
|
|
3145
2645
|
client,
|
|
3146
2646
|
documentId,
|
|
2647
|
+
sourceLanguage,
|
|
3147
2648
|
languageFieldName
|
|
3148
2649
|
});
|
|
3149
2650
|
})
|
|
@@ -3153,26 +2654,13 @@ Then return the corrected translation document:`;
|
|
|
3153
2654
|
...logContext,
|
|
3154
2655
|
succeeded,
|
|
3155
2656
|
failed
|
|
3156
|
-
}), dispatch({ type: "SET_OVERALL_STATUS", payload: "completed" })) :
|
|
2657
|
+
}), dispatch({ type: "SET_OVERALL_STATUS", payload: "completed" })) : dispatch({ type: "SET_OVERALL_STATUS", payload: "completed" }), devLog.timeEnd("translateDocumentSet"), devLog.groupEnd();
|
|
3157
2658
|
} catch (error) {
|
|
3158
|
-
|
|
2659
|
+
throw devLog.error(
|
|
3159
2660
|
"translateDocumentSet failed with unexpected error",
|
|
3160
2661
|
error instanceof Error ? error : new Error(String(error)),
|
|
3161
2662
|
logContext
|
|
3162
|
-
),
|
|
3163
|
-
try {
|
|
3164
|
-
await client.releases.delete({ releaseId }), devLog.warn("Deleted release due to fatal error", {
|
|
3165
|
-
...logContext,
|
|
3166
|
-
releaseId
|
|
3167
|
-
});
|
|
3168
|
-
} catch (cleanupError) {
|
|
3169
|
-
devLog.error(
|
|
3170
|
-
"Failed to cleanup release",
|
|
3171
|
-
cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError)),
|
|
3172
|
-
logContext
|
|
3173
|
-
);
|
|
3174
|
-
}
|
|
3175
|
-
throw devLog.timeEnd("translateDocumentSet"), devLog.groupEnd(), dispatch({ type: "SET_OVERALL_STATUS", payload: "error" }), error;
|
|
2663
|
+
), devLog.timeEnd("translateDocumentSet"), devLog.groupEnd(), dispatch({ type: "SET_OVERALL_STATUS", payload: "error" }), error;
|
|
3176
2664
|
}
|
|
3177
2665
|
}, ActionButtonBar = ({ setIsDialogOpen }) => {
|
|
3178
2666
|
const {
|
|
@@ -3593,24 +3081,12 @@ Then return the corrected translation document:`;
|
|
|
3593
3081
|
" \u2014 ",
|
|
3594
3082
|
job._id.slice(-6)
|
|
3595
3083
|
] }),
|
|
3596
|
-
!["completed", "skipped", "error"].includes(job.status) && /* @__PURE__ */
|
|
3597
|
-
|
|
3598
|
-
job.
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
] }),
|
|
3603
|
-
job.status === "completed" && job.tokensUsed ? /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
3604
|
-
/* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
3605
|
-
job.tokensUsed.toLocaleString(),
|
|
3606
|
-
" tokens (~$",
|
|
3607
|
-
job.estimatedCost?.toFixed(4),
|
|
3608
|
-
")"
|
|
3609
|
-
] }),
|
|
3610
|
-
job.reasoningSummary && /* @__PURE__ */ jsx(Card, { tone: "primary", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3611
|
-
"\u{1F4A1} ",
|
|
3612
|
-
job.reasoningSummary
|
|
3613
|
-
] }) })
|
|
3084
|
+
!["completed", "skipped", "error"].includes(job.status) && /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: getStatusLabel(job.status) }),
|
|
3085
|
+
job.status === "completed" && job.tokensUsed ? /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
3086
|
+
job.tokensUsed.toLocaleString(),
|
|
3087
|
+
" tokens (~$",
|
|
3088
|
+
job.estimatedCost?.toFixed(4),
|
|
3089
|
+
")"
|
|
3614
3090
|
] }) : null,
|
|
3615
3091
|
job.status === "error" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3616
3092
|
"Error: ",
|
|
@@ -3724,26 +3200,9 @@ Then return the corrected translation document:`;
|
|
|
3724
3200
|
"completed"
|
|
3725
3201
|
], STATES_IN_WHICH_TO_SHOW_ERROR = ["error"], TranslationDialog = ({ setIsDialogOpen }) => {
|
|
3726
3202
|
const { overallStatus, document } = useTranslationContext();
|
|
3727
|
-
return devLog.
|
|
3728
|
-
documentId: document._id,
|
|
3729
|
-
overallStatus,
|
|
3730
|
-
component: "TranslationDialog"
|
|
3731
|
-
}), STATES_IN_WHICH_TO_SHOW_CONFIG.includes(overallStatus) ? (devLog.debug("Showing translation config view", {
|
|
3732
|
-
documentId: document._id,
|
|
3733
|
-
overallStatus,
|
|
3734
|
-
component: "TranslationDialog"
|
|
3735
|
-
}), /* @__PURE__ */ jsx(TranslationConfigView, { setIsDialogOpen })) : STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS.includes(overallStatus) ? (devLog.debug("Showing translation progress panel", {
|
|
3736
|
-
documentId: document._id,
|
|
3737
|
-
overallStatus,
|
|
3738
|
-
component: "TranslationDialog"
|
|
3739
|
-
}), /* @__PURE__ */ jsx(TranslationStatusPanel, {})) : STATES_IN_WHICH_TO_SHOW_ERROR.includes(overallStatus) ? (devLog.warn("Translation error state reached", {
|
|
3203
|
+
return STATES_IN_WHICH_TO_SHOW_CONFIG.includes(overallStatus) ? /* @__PURE__ */ jsx(TranslationConfigView, { setIsDialogOpen }) : STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS.includes(overallStatus) ? /* @__PURE__ */ jsx(TranslationStatusPanel, {}) : STATES_IN_WHICH_TO_SHOW_ERROR.includes(overallStatus) ? /* @__PURE__ */ jsx("div", { children: "Error occurred" }) : (devLog.warn("Unhandled translation dialog state", {
|
|
3740
3204
|
documentId: document._id,
|
|
3741
|
-
overallStatus
|
|
3742
|
-
component: "TranslationDialog"
|
|
3743
|
-
}), /* @__PURE__ */ jsx("div", { children: "Error occurred" })) : (devLog.warn("Unhandled translation dialog state", {
|
|
3744
|
-
documentId: document._id,
|
|
3745
|
-
overallStatus,
|
|
3746
|
-
component: "TranslationDialog"
|
|
3205
|
+
overallStatus
|
|
3747
3206
|
}), /* @__PURE__ */ jsx("div", { children: "Unhandled overall state" }));
|
|
3748
3207
|
}, getTranslateAction = (config) => ({ draft, published }) => {
|
|
3749
3208
|
const document = draft ?? published, [isDialogOpen, setIsDialogOpen] = useState(!1);
|