@braze/sanity-plugin-ai-translation 0.1.6 → 0.1.75
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.js +222 -164
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -118,17 +118,24 @@ async function applySuggestions({
|
|
|
118
118
|
suggestions,
|
|
119
119
|
onProgress
|
|
120
120
|
}) {
|
|
121
|
-
devLog.info("Applying suggestions", {
|
|
121
|
+
if (devLog.info("Applying suggestions", {
|
|
122
122
|
sourceLanguage,
|
|
123
123
|
targetLanguage,
|
|
124
|
+
targetLanguageType: typeof targetLanguage,
|
|
125
|
+
targetLanguageValue: targetLanguage,
|
|
124
126
|
count: suggestions.filter((s) => s.approved).length
|
|
125
|
-
})
|
|
127
|
+
}), !targetLanguage)
|
|
128
|
+
throw devLog.error("targetLanguage is undefined in applySuggestions!", {
|
|
129
|
+
sourceLanguage,
|
|
130
|
+
targetLanguage,
|
|
131
|
+
suggestionsCount: suggestions.length
|
|
132
|
+
}), new Error("targetLanguage is required but was undefined");
|
|
126
133
|
const approvedSuggestions = suggestions.filter((s) => s.approved);
|
|
127
134
|
if (approvedSuggestions.length === 0) {
|
|
128
135
|
devLog.info("No approved suggestions to apply");
|
|
129
136
|
return;
|
|
130
137
|
}
|
|
131
|
-
onProgress?.({ stage: "applying", message: "Applying updates to translation settings..." }), await applyImprovementsFromSuggestions(client, schemaId,
|
|
138
|
+
onProgress?.({ stage: "applying", message: "Applying updates to translation settings..." }), await applyImprovementsFromSuggestions(client, schemaId, targetLanguage, approvedSuggestions, onProgress), devLog.info("Suggestions applied successfully");
|
|
132
139
|
}
|
|
133
140
|
function buildAnalysisPrompt({
|
|
134
141
|
sourceLanguage,
|
|
@@ -203,7 +210,7 @@ function buildAnalysisPrompt({
|
|
|
203
210
|
' "globalPromptUpdates": "string | null - ONLY if this applies to ALL language pairs",',
|
|
204
211
|
` "localePromptUpdates": "string | null - ONLY if this is specific to ${sourceLanguage}\u2192${targetLanguage}",`,
|
|
205
212
|
' "protectedPhrasesToAdd": ["ONLY brand names, products, proper nouns that should NEVER translate"],',
|
|
206
|
-
' "glossaryTermsToAdd": [{"sourceValue": "term", "targetValue": "translation"} - ONLY specific term mappings],',
|
|
213
|
+
' "glossaryTermsToAdd": [{"sourceLocale": "sourceLocale","sourceValue": "term", "targetLocale": "targetLocale", "targetValue": "translation"} - ONLY specific term mappings],',
|
|
207
214
|
' "reasoning": "string - Explain your analysis and which mechanism you chose for each issue"',
|
|
208
215
|
"}",
|
|
209
216
|
"",
|
|
@@ -240,7 +247,9 @@ function convertAnalysisToSuggestions(analysis) {
|
|
|
240
247
|
type: "glossaryTerm",
|
|
241
248
|
description: `${term.sourceValue} \u2192 ${term.targetValue}`,
|
|
242
249
|
approved: !0,
|
|
250
|
+
sourceLocale: term.sourceLocale,
|
|
243
251
|
sourceValue: term.sourceValue,
|
|
252
|
+
targetLocale: term.targetLocale,
|
|
244
253
|
targetValue: term.targetValue
|
|
245
254
|
});
|
|
246
255
|
return suggestions;
|
|
@@ -286,7 +295,7 @@ function parseAIResponse(responseText) {
|
|
|
286
295
|
throw devLog.error("Error parsing AI response", { error, responseText }), new Error("Failed to parse AI response as JSON");
|
|
287
296
|
}
|
|
288
297
|
}
|
|
289
|
-
async function applyImprovementsFromSuggestions(client,
|
|
298
|
+
async function applyImprovementsFromSuggestions(client, schemaId, targetLanguage, suggestions, onProgress) {
|
|
290
299
|
devLog.info("Applying improvements from suggestions", { count: suggestions.length });
|
|
291
300
|
const settingsId = "translation-settings";
|
|
292
301
|
await client.fetch("*[_id == $id][0]", { id: settingsId }) || await client.createIfNotExists({
|
|
@@ -294,28 +303,17 @@ async function applyImprovementsFromSuggestions(client, sourceLanguage, targetLa
|
|
|
294
303
|
_type: "translationSettings"
|
|
295
304
|
});
|
|
296
305
|
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(
|
|
297
|
-
(s) => s.type === "glossaryTerm" && s.sourceValue?.trim() && s.targetValue?.trim()
|
|
306
|
+
(s) => s.type === "glossaryTerm" && s.sourceLocale?.trim() && s.sourceValue?.trim() && s.targetLocale?.trim() && s.targetValue?.trim()
|
|
298
307
|
);
|
|
299
308
|
if (devLog.info("Filtered suggestions by type", {
|
|
300
309
|
globalPrompts: globalPrompts.length,
|
|
301
310
|
localePrompts: localePrompts.length,
|
|
302
311
|
protectedPhrases: protectedPhrases.length,
|
|
303
312
|
glossaryTerms: glossaryTerms.length
|
|
304
|
-
}), globalPrompts.length > 0) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
`);
|
|
309
|
-
devLog.debug("Applying global prompt update", { combinedUpdate }), updates.push(updateGlobalPrompt(client, settingsId, combinedUpdate));
|
|
310
|
-
}
|
|
311
|
-
if (localePrompts.length > 0) {
|
|
312
|
-
onProgress?.({ stage: "applying", message: `Updating ${targetLanguage} translation prompt...` });
|
|
313
|
-
const combinedUpdate = localePrompts.map((s) => s.description).join(`
|
|
314
|
-
|
|
315
|
-
`);
|
|
316
|
-
devLog.debug("Applying locale prompt update", { targetLanguage, combinedUpdate }), updates.push(updateLocalePrompt(client, settingsId, targetLanguage, combinedUpdate));
|
|
317
|
-
}
|
|
318
|
-
if (protectedPhrases.length > 0) {
|
|
313
|
+
}), (globalPrompts.length > 0 || localePrompts.length > 0) && (onProgress?.({
|
|
314
|
+
stage: "applying",
|
|
315
|
+
message: globalPrompts.length > 0 && localePrompts.length > 0 ? "Updating global and locale translation prompts..." : globalPrompts.length > 0 ? "Updating global translation prompt..." : `Updating ${targetLanguage} translation prompt...`
|
|
316
|
+
}), updates.push(updatePromptsWithTransform(client, schemaId, settingsId, targetLanguage, globalPrompts, localePrompts))), protectedPhrases.length > 0) {
|
|
319
317
|
onProgress?.({ stage: "applying", message: "Adding protected phrases..." });
|
|
320
318
|
const phrases = protectedPhrases.map((s) => s.description);
|
|
321
319
|
devLog.debug("Applying protected phrases", { phrases }), updates.push(addProtectedPhrases(client, settingsId, phrases));
|
|
@@ -324,77 +322,118 @@ async function applyImprovementsFromSuggestions(client, sourceLanguage, targetLa
|
|
|
324
322
|
onProgress?.({ stage: "applying", message: "Adding glossary terms..." });
|
|
325
323
|
const terms = glossaryTerms.map((s) => ({
|
|
326
324
|
// biome-ignore-start lint/style/noNonNullAssertion: safely handled
|
|
325
|
+
sourceLocale: s.sourceLocale,
|
|
327
326
|
sourceValue: s.sourceValue,
|
|
327
|
+
targetLocale: s.targetLocale,
|
|
328
328
|
targetValue: s.targetValue
|
|
329
329
|
// biome-ignore-end lint/style/noNonNullAssertion: safely handled
|
|
330
330
|
}));
|
|
331
|
-
devLog.debug("Applying glossary terms", { terms }), updates.push(addGlossaryTerms(client, terms
|
|
331
|
+
devLog.debug("Applying glossary terms", { terms }), updates.push(addGlossaryTerms(client, terms));
|
|
332
332
|
}
|
|
333
333
|
updates.length === 0 && devLog.warn("No valid updates to apply - all suggestions may have been empty"), await Promise.all(updates), devLog.info("All improvements applied successfully");
|
|
334
334
|
}
|
|
335
|
-
async function
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
335
|
+
async function updatePromptsWithTransform(client, schemaId, settingsId, targetLanguage, globalPrompts, localePrompts) {
|
|
336
|
+
if (devLog.info("Updating prompts with transform action", {
|
|
337
|
+
settingsId,
|
|
338
|
+
targetLanguage,
|
|
339
|
+
targetLanguageType: typeof targetLanguage,
|
|
340
|
+
targetLanguageValue: targetLanguage,
|
|
341
|
+
hasGlobalUpdates: globalPrompts.length > 0,
|
|
342
|
+
hasLocaleUpdates: localePrompts.length > 0
|
|
343
|
+
}), !targetLanguage)
|
|
344
|
+
throw devLog.error("targetLanguage is undefined or empty!", {
|
|
345
|
+
targetLanguage,
|
|
346
|
+
globalPrompts: globalPrompts.length,
|
|
347
|
+
localePrompts: localePrompts.length
|
|
348
|
+
}), new Error("targetLanguage is required but was undefined");
|
|
349
|
+
const targets = [];
|
|
350
|
+
if (globalPrompts.length > 0) {
|
|
351
|
+
const combinedGlobalUpdate = globalPrompts.map((s) => s.description).join(`
|
|
339
352
|
|
|
340
|
-
|
|
341
|
-
|
|
353
|
+
`);
|
|
354
|
+
targets.push({
|
|
355
|
+
path: "globalPrompt",
|
|
356
|
+
instruction: `You are updating a translation prompt document that contains rules and guidance for future AI translations.
|
|
342
357
|
|
|
343
|
-
NEW GUIDANCE
|
|
344
|
-
${newGuidance}
|
|
358
|
+
IMPORTANT: The text below is NEW GUIDANCE that should be INCORPORATED INTO the existing prompt as additional rules/instructions for future translations. DO NOT apply or execute these rules on the prompt text itself.
|
|
345
359
|
|
|
346
|
-
|
|
347
|
-
- Read the current prompt carefully
|
|
348
|
-
- Integrate the new guidance into the prompt in a natural, coherent way
|
|
349
|
-
- If the new guidance conflicts with existing content, update or replace the conflicting parts
|
|
350
|
-
- If the new guidance is already covered, do not add redundant text
|
|
351
|
-
- Maintain the structure and tone of the prompt
|
|
352
|
-
- Output ONLY the updated prompt text, with no preamble or explanation
|
|
360
|
+
Your task: Merge the new guidance below into the existing prompt text. Treat the new guidance as rules that will guide future translations, not as transformations to apply to the current text.
|
|
353
361
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const existingPrompt = currentLocalePrompts?.find((lp) => lp.locale === locale);
|
|
382
|
-
if (devLog.debug("Found existing prompt for locale", { locale, hasExisting: !!existingPrompt }), existingPrompt?.prompt) {
|
|
383
|
-
const newPrompt = await mergePromptWithAI(client, existingPrompt.prompt, updates, locale), updatedPrompts = currentLocalePrompts.map(
|
|
384
|
-
(lp) => lp.locale === locale ? { ...lp, prompt: newPrompt.trim() } : lp
|
|
385
|
-
), result = await client.patch(settingsId).set({ localePrompts: updatedPrompts }).commit();
|
|
386
|
-
devLog.info("Locale prompt updated (merged)", { locale, resultId: result._id });
|
|
387
|
-
} else {
|
|
388
|
-
const result = await client.patch(settingsId).setIfMissing({ localePrompts: [] }).append("localePrompts", [
|
|
362
|
+
Integration guidelines:
|
|
363
|
+
- Add the new guidance as additional instructions/rules in the prompt
|
|
364
|
+
- If it conflicts with existing guidance, update or replace the conflicting parts
|
|
365
|
+
- If it's already covered, don't add redundant text
|
|
366
|
+
- Maintain clear structure and consistent tone
|
|
367
|
+
- Preserve all existing guidance that is still relevant
|
|
368
|
+
- Keep the prompt written as instructions for a translator (e.g., "When translating, you should..." not "The translation has been...")
|
|
369
|
+
|
|
370
|
+
New guidance to incorporate:
|
|
371
|
+
|
|
372
|
+
\`\`\`
|
|
373
|
+
${combinedGlobalUpdate}
|
|
374
|
+
\`\`\``
|
|
375
|
+
}), devLog.debug("Added global prompt target", { combinedUpdate: combinedGlobalUpdate });
|
|
376
|
+
}
|
|
377
|
+
if (localePrompts.length > 0) {
|
|
378
|
+
const combinedLocaleUpdate = localePrompts.map((s) => s.description).join(`
|
|
379
|
+
|
|
380
|
+
`), existingLocalePrompts = await client.fetch(
|
|
381
|
+
"*[_id == $settingsId][0].localePrompts",
|
|
382
|
+
{ settingsId }
|
|
383
|
+
), hasExistingLocalePrompt = existingLocalePrompts?.some((lp) => lp._key === targetLanguage);
|
|
384
|
+
devLog.debug("Checking existing locale prompt", {
|
|
385
|
+
targetLanguage,
|
|
386
|
+
hasExistingLocalePrompt,
|
|
387
|
+
existingCount: existingLocalePrompts?.length || 0
|
|
388
|
+
}), hasExistingLocalePrompt || (devLog.info("Creating new locale prompt entry", { targetLanguage }), await client.patch(settingsId).setIfMissing({ localePrompts: [] }).append("localePrompts", [
|
|
389
389
|
{
|
|
390
|
-
_type: "
|
|
391
|
-
_key:
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
_type: "internationalizedArrayTextValue",
|
|
391
|
+
_key: targetLanguage,
|
|
392
|
+
value: ""
|
|
393
|
+
// Start with empty value, transform will fill it
|
|
394
394
|
}
|
|
395
|
-
]).commit();
|
|
396
|
-
|
|
395
|
+
]).commit());
|
|
396
|
+
const localePromptPath = ["localePrompts", { _key: targetLanguage }, "value"];
|
|
397
|
+
targets.push({
|
|
398
|
+
// @ts-expect-error
|
|
399
|
+
path: localePromptPath,
|
|
400
|
+
instruction: `You are updating a translation prompt document that contains rules and guidance for future AI translations.
|
|
401
|
+
|
|
402
|
+
IMPORTANT: The text below is NEW GUIDANCE that should be INCORPORATED INTO the existing prompt as additional rules/instructions for future translations. DO NOT apply or execute these rules on the prompt text itself.
|
|
403
|
+
|
|
404
|
+
Your task: Merge the new guidance below into the existing prompt text. Treat the new guidance as rules that will guide future translations, not as transformations to apply to the current text.
|
|
405
|
+
|
|
406
|
+
Integration guidelines:
|
|
407
|
+
- Add the new guidance as additional instructions/rules in the prompt
|
|
408
|
+
- If it conflicts with existing guidance, update or replace the conflicting parts
|
|
409
|
+
- If it's already covered, don't add redundant text
|
|
410
|
+
- Maintain clear structure and consistent tone
|
|
411
|
+
- Preserve all existing guidance that is still relevant
|
|
412
|
+
- Keep the prompt written as instructions for a translator (e.g., "When translating, you should..." not "The translation has been...")
|
|
413
|
+
|
|
414
|
+
New guidance to incorporate:
|
|
415
|
+
|
|
416
|
+
\`\`\`
|
|
417
|
+
${combinedLocaleUpdate}
|
|
418
|
+
\`\`\``
|
|
419
|
+
}), devLog.debug("Added locale prompt target", {
|
|
420
|
+
targetLanguage,
|
|
421
|
+
localePromptPath,
|
|
422
|
+
combinedUpdate: combinedLocaleUpdate
|
|
423
|
+
});
|
|
397
424
|
}
|
|
425
|
+
const result = await client.agent.action.transform({
|
|
426
|
+
schemaId,
|
|
427
|
+
documentId: settingsId,
|
|
428
|
+
instruction: "Update translation prompts based on user feedback to improve future translations.",
|
|
429
|
+
target: targets,
|
|
430
|
+
forcePublishedWrite: !0
|
|
431
|
+
});
|
|
432
|
+
devLog.info("Prompts updated via transform action", {
|
|
433
|
+
resultId: result._id,
|
|
434
|
+
globalUpdated: globalPrompts.length > 0,
|
|
435
|
+
localeUpdated: localePrompts.length > 0
|
|
436
|
+
});
|
|
398
437
|
}
|
|
399
438
|
async function addProtectedPhrases(client, settingsId, phrases) {
|
|
400
439
|
devLog.info("Adding protected phrases", { settingsId, phrasesCount: phrases.length, phrases });
|
|
@@ -408,7 +447,7 @@ async function addProtectedPhrases(client, settingsId, phrases) {
|
|
|
408
447
|
const result = await client.patch(settingsId).setIfMissing({ protectedPhrases: [] }).append("protectedPhrases", newPhrases).commit();
|
|
409
448
|
devLog.info("Protected phrases added", { count: newPhrases.length, phrases: newPhrases, resultId: result._id });
|
|
410
449
|
}
|
|
411
|
-
async function addGlossaryTerms(client, terms
|
|
450
|
+
async function addGlossaryTerms(client, terms) {
|
|
412
451
|
const validTerms = terms.filter(({ sourceValue, targetValue }) => sourceValue === targetValue ? (devLog.warn("Skipping glossary term where source === target (should be a protected phrase)", {
|
|
413
452
|
sourceValue,
|
|
414
453
|
targetValue
|
|
@@ -417,38 +456,37 @@ async function addGlossaryTerms(client, terms, sourceLocale, targetLocale) {
|
|
|
417
456
|
devLog.info("No valid glossary terms to add");
|
|
418
457
|
return;
|
|
419
458
|
}
|
|
420
|
-
for (const { sourceValue, targetValue } of validTerms) {
|
|
459
|
+
for (const { sourceLocale, sourceValue, targetLocale, targetValue } of validTerms) {
|
|
421
460
|
const existingTerm = await client.fetch(
|
|
422
|
-
`*[_type == "translationGlossaryTerm"
|
|
423
|
-
&& count(translations[locale == $sourceLocale && value == $sourceValue]) > 0
|
|
461
|
+
`*[_type == "translationGlossaryTerm" && count(translations[_key == $sourceLocale && value == $sourceValue]) > 0
|
|
424
462
|
][0] {
|
|
425
463
|
_id,
|
|
426
464
|
translations
|
|
427
465
|
}`,
|
|
428
466
|
{ sourceLocale, sourceValue }
|
|
429
467
|
);
|
|
430
|
-
existingTerm ? existingTerm.translations.some(
|
|
431
|
-
|
|
432
|
-
|
|
468
|
+
existingTerm ? existingTerm.translations.some((t) => t._key === targetLocale) ? devLog.info("Glossary term already has this translation", {
|
|
469
|
+
sourceLocale,
|
|
470
|
+
sourceValue,
|
|
471
|
+
targetLocale,
|
|
472
|
+
targetValue
|
|
473
|
+
}) : (await client.patch(existingTerm._id).append("translations", [
|
|
433
474
|
{
|
|
434
|
-
_type: "
|
|
475
|
+
_type: "internationalizedArrayStringValue",
|
|
435
476
|
_key: targetLocale,
|
|
436
|
-
locale: targetLocale,
|
|
437
477
|
value: targetValue
|
|
438
478
|
}
|
|
439
|
-
]).commit(), devLog.info("Updated existing glossary term", {
|
|
479
|
+
]).commit(), devLog.info("Updated existing glossary term", { sourceLocale, sourceValue, targetLocale, targetValue })) : (await client.create({
|
|
440
480
|
_type: "translationGlossaryTerm",
|
|
441
481
|
translations: [
|
|
442
482
|
{
|
|
443
|
-
_type: "
|
|
483
|
+
_type: "internationalizedArrayStringValue",
|
|
444
484
|
_key: sourceLocale,
|
|
445
|
-
locale: sourceLocale,
|
|
446
485
|
value: sourceValue
|
|
447
486
|
},
|
|
448
487
|
{
|
|
449
|
-
_type: "
|
|
488
|
+
_type: "internationalizedArrayStringValue",
|
|
450
489
|
_key: targetLocale,
|
|
451
|
-
locale: targetLocale,
|
|
452
490
|
value: targetValue
|
|
453
491
|
}
|
|
454
492
|
]
|
|
@@ -729,7 +767,7 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
729
767
|
client,
|
|
730
768
|
schemaId: config.schemaId,
|
|
731
769
|
sourceLanguage: translationHistory2.sourceLanguage || "en-us",
|
|
732
|
-
targetLanguage:
|
|
770
|
+
targetLanguage: translationHistory2.language,
|
|
733
771
|
suggestions,
|
|
734
772
|
onProgress: ({ stage, message }) => {
|
|
735
773
|
setProcessingState(stage), setChangeSummary(message);
|
|
@@ -972,24 +1010,7 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
972
1010
|
defineField({
|
|
973
1011
|
name: "translations",
|
|
974
1012
|
title: "Translations",
|
|
975
|
-
type: "
|
|
976
|
-
of: [
|
|
977
|
-
{
|
|
978
|
-
name: "translation",
|
|
979
|
-
title: "Translation",
|
|
980
|
-
type: "object",
|
|
981
|
-
fields: [
|
|
982
|
-
defineField({
|
|
983
|
-
name: "locale",
|
|
984
|
-
type: "string"
|
|
985
|
-
}),
|
|
986
|
-
defineField({
|
|
987
|
-
name: "value",
|
|
988
|
-
type: "string"
|
|
989
|
-
})
|
|
990
|
-
]
|
|
991
|
-
}
|
|
992
|
-
]
|
|
1013
|
+
type: "internationalizedArrayString"
|
|
993
1014
|
})
|
|
994
1015
|
],
|
|
995
1016
|
preview: {
|
|
@@ -1050,21 +1071,6 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
1050
1071
|
]
|
|
1051
1072
|
}
|
|
1052
1073
|
]
|
|
1053
|
-
}), localePrompt = defineField({
|
|
1054
|
-
name: "localePrompt",
|
|
1055
|
-
title: "Locale Prompt",
|
|
1056
|
-
type: "object",
|
|
1057
|
-
fields: [
|
|
1058
|
-
defineField({
|
|
1059
|
-
name: "locale",
|
|
1060
|
-
type: "string"
|
|
1061
|
-
}),
|
|
1062
|
-
defineField({
|
|
1063
|
-
name: "prompt",
|
|
1064
|
-
type: "text",
|
|
1065
|
-
rows: 5
|
|
1066
|
-
})
|
|
1067
|
-
]
|
|
1068
1074
|
}), translationSettings = defineType({
|
|
1069
1075
|
name: "translationSettings",
|
|
1070
1076
|
title: "Translation Settings",
|
|
@@ -1082,8 +1088,7 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
1082
1088
|
name: "localePrompts",
|
|
1083
1089
|
title: "Locale Prompts",
|
|
1084
1090
|
description: "Prompts that will be given to the Sanity AI Agent for each locale.",
|
|
1085
|
-
type: "
|
|
1086
|
-
of: [localePrompt]
|
|
1091
|
+
type: "internationalizedArrayText"
|
|
1087
1092
|
}),
|
|
1088
1093
|
defineField({
|
|
1089
1094
|
name: "protectedPhrases",
|
|
@@ -1120,9 +1125,9 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
1120
1125
|
) || [], glossaryTermsByLocale = {};
|
|
1121
1126
|
for (const targetLanguage of targetLanguages) {
|
|
1122
1127
|
const relevantTranslations = translationGlossaryTerms.filter(
|
|
1123
|
-
(termTranslations) => termTranslations.some((translation) => translation.
|
|
1128
|
+
(termTranslations) => termTranslations.some((translation) => translation._key === targetLanguage)
|
|
1124
1129
|
).map((termTranslations) => {
|
|
1125
|
-
const from = termTranslations.find((translation) => translation.
|
|
1130
|
+
const from = termTranslations.find((translation) => translation._key === sourceLanguage)?.value, to = termTranslations.find((translation) => translation._key === targetLanguage)?.value;
|
|
1126
1131
|
return !from || !to ? null : { from, to };
|
|
1127
1132
|
}).filter((item) => item !== null);
|
|
1128
1133
|
glossaryTermsByLocale[targetLanguage] = relevantTranslations;
|
|
@@ -1134,14 +1139,16 @@ const ICON_SIZE = 64, progressIconMap = {
|
|
|
1134
1139
|
targetLanguages.map((locale) => [
|
|
1135
1140
|
locale,
|
|
1136
1141
|
{
|
|
1137
|
-
prompt: translationSettings2?.localePrompts?.find((lp) => lp.
|
|
1142
|
+
prompt: translationSettings2?.localePrompts?.find((lp) => lp._key === locale)?.value,
|
|
1138
1143
|
glossaryTerms: formatGlossaryTermInstruction(glossaryTermsByLocale[locale])
|
|
1139
1144
|
}
|
|
1140
1145
|
])
|
|
1141
1146
|
)
|
|
1142
1147
|
};
|
|
1143
1148
|
}, formatGlossaryTermInstruction = (terms) => {
|
|
1144
|
-
const instructions = [
|
|
1149
|
+
const instructions = [
|
|
1150
|
+
"Translate the following terms exactly as described below. When a term includes a colon, the suffix following the colon describes how the term should be translated for the female gender."
|
|
1151
|
+
];
|
|
1145
1152
|
return terms.forEach(({ from, to }) => {
|
|
1146
1153
|
instructions.push(`"${from}" -> "${to}"`);
|
|
1147
1154
|
}), instructions.join(`
|
|
@@ -1376,7 +1383,11 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
1376
1383
|
}
|
|
1377
1384
|
});
|
|
1378
1385
|
} catch (error) {
|
|
1379
|
-
|
|
1386
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1387
|
+
setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
1388
|
+
errorMessage
|
|
1389
|
+
}), console.error("Error translating document:", error);
|
|
1390
|
+
return;
|
|
1380
1391
|
}
|
|
1381
1392
|
}, assembleLanguageSpecs = ({
|
|
1382
1393
|
sourceDocument,
|
|
@@ -1472,11 +1483,15 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
1472
1483
|
newDocumentRev: newDocument._rev
|
|
1473
1484
|
}), devLog.timeEnd("translateEntireDocument"), devLog.groupEnd(), newDocument;
|
|
1474
1485
|
} catch (error) {
|
|
1475
|
-
|
|
1486
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1487
|
+
devLog.error(
|
|
1476
1488
|
"translateEntireDocument failed",
|
|
1477
1489
|
error instanceof Error ? error : new Error(String(error)),
|
|
1478
1490
|
logContext
|
|
1479
|
-
),
|
|
1491
|
+
), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
1492
|
+
errorMessage
|
|
1493
|
+
}), devLog.timeEnd("translateEntireDocument"), devLog.groupEnd();
|
|
1494
|
+
return;
|
|
1480
1495
|
}
|
|
1481
1496
|
}, updateTranslationMetadata = async ({
|
|
1482
1497
|
client,
|
|
@@ -1637,7 +1652,11 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
1637
1652
|
newDocumentId: newDocument._id
|
|
1638
1653
|
}), devLog.timeEnd("translateDocument"), devLog.groupEnd(), newDocument._id;
|
|
1639
1654
|
} catch (error) {
|
|
1640
|
-
|
|
1655
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1656
|
+
devLog.error("Translation failed", error instanceof Error ? error : new Error(String(error)), logContext), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
1657
|
+
errorMessage
|
|
1658
|
+
}), devLog.timeEnd("translateDocument"), devLog.groupEnd();
|
|
1659
|
+
return;
|
|
1641
1660
|
}
|
|
1642
1661
|
}, patchTranslationTarget = async ({
|
|
1643
1662
|
client,
|
|
@@ -1717,31 +1736,48 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
1717
1736
|
devLog.info("Translation prompts", translationPrompts), setupTranslationJobStatuses(dispatch, sourceDocuments, targetLanguages);
|
|
1718
1737
|
const translationTasks = sourceDocumentIds.flatMap(
|
|
1719
1738
|
(documentId) => targetLanguages.map(async (targetLanguage) => {
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
id
|
|
1726
|
-
|
|
1727
|
-
if (document) {
|
|
1728
|
-
const targetDocumentId = await translateDocument({
|
|
1729
|
-
dispatch,
|
|
1730
|
-
client,
|
|
1731
|
-
sourceDocument: document,
|
|
1732
|
-
targetLanguage,
|
|
1733
|
-
languageFieldName,
|
|
1734
|
-
shouldOverwriteFreshTranslations,
|
|
1735
|
-
schemaId,
|
|
1736
|
-
shouldTranslateDiffs,
|
|
1737
|
-
translationPrompts
|
|
1739
|
+
try {
|
|
1740
|
+
await translationSemaphore.withPermit(async () => {
|
|
1741
|
+
const document = await client.withConfig({
|
|
1742
|
+
// This enables us to detect documents that might be stale due to unpublished changes
|
|
1743
|
+
perspective: "raw"
|
|
1744
|
+
}).fetch("*[_id == $id][0]", {
|
|
1745
|
+
id: documentId
|
|
1738
1746
|
});
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1747
|
+
if (document) {
|
|
1748
|
+
const targetDocumentId = await translateDocument({
|
|
1749
|
+
dispatch,
|
|
1750
|
+
client,
|
|
1751
|
+
sourceDocument: document,
|
|
1752
|
+
targetLanguage,
|
|
1753
|
+
languageFieldName,
|
|
1754
|
+
shouldOverwriteFreshTranslations,
|
|
1755
|
+
schemaId,
|
|
1756
|
+
shouldTranslateDiffs,
|
|
1757
|
+
translationPrompts
|
|
1758
|
+
});
|
|
1759
|
+
targetDocumentId && targetDocumentIds.push(targetDocumentId);
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
devLog.error(
|
|
1764
|
+
"Individual translation task failed",
|
|
1765
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1766
|
+
{
|
|
1767
|
+
...logContext,
|
|
1768
|
+
failedDocumentId: documentId,
|
|
1769
|
+
failedTargetLanguage: targetLanguage
|
|
1770
|
+
}
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1742
1773
|
})
|
|
1743
|
-
);
|
|
1744
|
-
|
|
1774
|
+
), results = await Promise.allSettled(translationTasks), succeeded = results.filter((r) => r.status === "fulfilled").length, failed = results.filter((r) => r.status === "rejected").length;
|
|
1775
|
+
devLog.info("Translation tasks summary", {
|
|
1776
|
+
...logContext,
|
|
1777
|
+
total: results.length,
|
|
1778
|
+
succeeded,
|
|
1779
|
+
failed
|
|
1780
|
+
}), devLog.info("All translation tasks completed", {
|
|
1745
1781
|
...logContext,
|
|
1746
1782
|
targetDocumentIds: targetDocumentIds.length
|
|
1747
1783
|
}), dispatch({
|
|
@@ -1758,9 +1794,19 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
1758
1794
|
languageFieldName
|
|
1759
1795
|
});
|
|
1760
1796
|
})
|
|
1761
|
-
)
|
|
1797
|
+
);
|
|
1798
|
+
const hasAnyFailures = failed > 0;
|
|
1799
|
+
hasAnyFailures && succeeded === 0 ? (devLog.error("All translation tasks failed", void 0, logContext), dispatch({ type: "SET_OVERALL_STATUS", payload: "error" })) : hasAnyFailures ? (devLog.warn("Translation completed with some failures", {
|
|
1800
|
+
...logContext,
|
|
1801
|
+
succeeded,
|
|
1802
|
+
failed
|
|
1803
|
+
}), dispatch({ type: "SET_OVERALL_STATUS", payload: "completed" })) : (devLog.info("Document set translation completed successfully", logContext), dispatch({ type: "SET_OVERALL_STATUS", payload: "completed" })), devLog.timeEnd("translateDocumentSet"), devLog.groupEnd();
|
|
1762
1804
|
} catch (error) {
|
|
1763
|
-
|
|
1805
|
+
throw devLog.error(
|
|
1806
|
+
"translateDocumentSet failed with unexpected error",
|
|
1807
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1808
|
+
logContext
|
|
1809
|
+
), devLog.timeEnd("translateDocumentSet"), devLog.groupEnd(), dispatch({ type: "SET_OVERALL_STATUS", payload: "error" }), error;
|
|
1764
1810
|
}
|
|
1765
1811
|
}, ActionButtonBar = ({ setIsDialogOpen }) => {
|
|
1766
1812
|
const {
|
|
@@ -2079,7 +2125,7 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
2079
2125
|
/* @__PURE__ */ jsx(Label, { children: "Translations" }),
|
|
2080
2126
|
/* @__PURE__ */ jsx(Card, { padding: 2, tone: "transparent", children: /* @__PURE__ */ jsx(Stack, { space: 2, children: /* @__PURE__ */ jsx("ul", { style: { listStyleType: "none", paddingInlineStart: "0" }, children: jobs.map((job) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
2081
2127
|
/* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
2082
|
-
/* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", style: { width: "24px", height: "24px" }, children: ["completed", "skipped"].includes(job.status) ? /* @__PURE__ */ jsx(CheckmarkIcon, { fontSize: 24 }) : job.status === "queued" ? /* @__PURE__ */ jsx(CircleIcon, { fontSize: 24 }) : /* @__PURE__ */ jsx(Spinner, {}) }),
|
|
2128
|
+
/* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", style: { width: "24px", height: "24px" }, children: ["completed", "skipped"].includes(job.status) ? /* @__PURE__ */ jsx(CheckmarkIcon, { fontSize: 24 }) : job.status === "error" ? /* @__PURE__ */ jsx(Box, { style: { color: "var(--card-critical-fg-color)" }, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 24 }) }) : job.status === "queued" ? /* @__PURE__ */ jsx(CircleIcon, { fontSize: 24 }) : /* @__PURE__ */ jsx(Spinner, {}) }),
|
|
2083
2129
|
/* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2084
2130
|
/* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
2085
2131
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: job.title || "Untitled Document" }),
|
|
@@ -2090,11 +2136,16 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
2090
2136
|
job._type,
|
|
2091
2137
|
" \u2014 ",
|
|
2092
2138
|
job._id.slice(-6)
|
|
2093
|
-
] })
|
|
2139
|
+
] }),
|
|
2140
|
+
job.status === "error" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
2141
|
+
"Error: ",
|
|
2142
|
+
job.errorMessage
|
|
2143
|
+
] }) }) : null
|
|
2094
2144
|
] })
|
|
2095
2145
|
] }),
|
|
2096
2146
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
|
|
2097
2147
|
job.status === "skipped" ? /* @__PURE__ */ jsx(SkippedBadge, {}) : null,
|
|
2148
|
+
job.status === "error" ? /* @__PURE__ */ jsx(ErrorBadge, {}) : null,
|
|
2098
2149
|
job.targetId ? /* @__PURE__ */ jsx(
|
|
2099
2150
|
TranslationOpenerButton,
|
|
2100
2151
|
{
|
|
@@ -2158,6 +2209,13 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
2158
2209
|
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "There was already a fresh translation for this document and language." }) }),
|
|
2159
2210
|
children: /* @__PURE__ */ jsx(Badge, { tone: "neutral", children: "Skipped" })
|
|
2160
2211
|
}
|
|
2212
|
+
), ErrorBadge = () => /* @__PURE__ */ jsx(
|
|
2213
|
+
Tooltip,
|
|
2214
|
+
{
|
|
2215
|
+
portal: !0,
|
|
2216
|
+
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This translation failed. Check the error message for details." }) }),
|
|
2217
|
+
children: /* @__PURE__ */ jsx(Badge, { tone: "critical", children: "Failed" })
|
|
2218
|
+
}
|
|
2161
2219
|
), STATES_IN_WHICH_TO_SHOW_CONFIG = ["fetching_metadata", "idle"], STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS = [
|
|
2162
2220
|
"translating",
|
|
2163
2221
|
"linking_references",
|
|
@@ -2194,7 +2252,7 @@ const setupTranslationJobStatuses = (dispatch, sourceDocuments, targetLanguages)
|
|
|
2194
2252
|
documentType: document?._type,
|
|
2195
2253
|
translatableTypes: config.translatableSchemaTypes
|
|
2196
2254
|
}), null) : config ? {
|
|
2197
|
-
label: "Translate
|
|
2255
|
+
label: "Translate",
|
|
2198
2256
|
icon: TranslateIcon,
|
|
2199
2257
|
onHandle: () => {
|
|
2200
2258
|
devLog.info("Translation dialog opened", {
|