@justanarthur/payload-plugin-translator 1.3.21 → 3.0.3

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/jobs.js CHANGED
@@ -1,807 +1,11 @@
1
- // src/jobs/constants.ts
2
- var TRANSLATE_TASK_SLUG = "translateEntityToLocale";
3
- var TRANSLATE_WORKFLOW_SLUG = "translateEntityToLocales";
4
-
5
- // src/jobs/createAutoTranslateCollectionHook.ts
6
- function createAutoTranslateCollectionHook(options) {
7
- const {
8
- collectionSlug,
9
- defaultLocale: defaultLocaleOption,
10
- targetLocales: targetLocalesOption,
11
- onlyOnPublished = true,
12
- workflowSlug = TRANSLATE_WORKFLOW_SLUG
13
- } = options;
14
- const afterChange = async ({ doc, req }) => {
15
- if (shouldSkipAutoTranslate(req.context))
16
- return doc;
17
- const typed = doc;
18
- const requestLocale = readRequestLocale(req);
19
- const defaultLocale = defaultLocaleOption ?? readConfigDefaultLocale(req) ?? "";
20
- if (!requestLocale || !defaultLocale || requestLocale !== defaultLocale)
21
- return doc;
22
- if (!req.user)
23
- return doc;
24
- if (onlyOnPublished && typed._status && typed._status !== "published")
25
- return doc;
26
- const targetLocales = targetLocalesOption ?? readTargetLocales(req, defaultLocale);
27
- if (targetLocales.length === 0)
28
- return doc;
29
- const resolverKey = options.resolverKey ?? readFirstResolverKey(req);
30
- if (!resolverKey) {
31
- req.payload.logger.error({
32
- msg: `auto-translate: no resolver key available for collection ${collectionSlug} — pass \`resolverKey\` to \`createAutoTranslateCollectionHook\` or configure \`translator.resolvers\` in \`translator({...})\``
33
- });
34
- return doc;
35
- }
36
- const updatedAt = typed.updatedAt ?? new Date().toISOString();
37
- for (const toLocale of targetLocales) {
38
- if (toLocale === defaultLocale)
39
- continue;
40
- try {
41
- const job = await req.payload.jobs.queue({
42
- workflow: workflowSlug,
43
- input: {
44
- id: typed.id,
45
- updatedAt,
46
- collection: collectionSlug,
47
- fromLocale: defaultLocale,
48
- resolver: resolverKey || undefined,
49
- toLocale
50
- }
51
- });
52
- req.payload.logger.info({
53
- msg: `auto-translate: queued translation of ${collectionSlug}#${typed.id} → ${toLocale} (job ${job.id})`
54
- });
55
- } catch (error) {
56
- req.payload.logger.error({
57
- msg: `auto-translate: failed to queue ${collectionSlug}#${typed.id} → ${toLocale}: ${String(error)}`
58
- });
59
- }
60
- }
61
- return doc;
62
- };
63
- return { afterChange };
64
- }
65
- function shouldSkipAutoTranslate(context) {
66
- return Boolean(context?.disableAutoTranslate);
67
- }
68
- function readRequestLocale(req) {
69
- if (!req || typeof req !== "object")
70
- return "";
71
- const locale = req.locale;
72
- return typeof locale === "string" && locale.length > 0 ? locale : "";
73
- }
74
- function readConfigDefaultLocale(req) {
75
- if (!req || typeof req !== "object")
76
- return "";
77
- const config = req.payload?.config;
78
- const def = config?.localization?.defaultLocale;
79
- return typeof def === "string" && def.length > 0 ? def : "";
80
- }
81
- function readTargetLocales(req, defaultLocale) {
82
- if (!req || typeof req !== "object")
83
- return [];
84
- const list = req.payload?.config?.localization?.locales;
85
- if (!Array.isArray(list) || list.length === 0)
86
- return [];
87
- const out = [];
88
- for (const entry of list) {
89
- const code = typeof entry === "string" ? entry : entry?.code;
90
- if (typeof code === "string" && code.length > 0 && code !== defaultLocale) {
91
- out.push(code);
92
- }
93
- }
94
- return out;
95
- }
96
- function readFirstResolverKey(req) {
97
- if (!req || typeof req !== "object")
98
- return "";
99
- const custom = req.payload?.config?.custom?.translator?.resolvers;
100
- if (!Array.isArray(custom) || custom.length === 0)
101
- return "";
102
- const first = custom[0];
103
- return typeof first?.key === "string" ? first.key : "";
104
- }
105
-
106
- // src/jobs/createAutoTranslateGlobalHook.ts
107
- function createAutoTranslateGlobalHook(options) {
108
- const {
109
- globalSlug,
110
- defaultLocale: defaultLocaleOption,
111
- targetLocales: targetLocalesOption,
112
- workflowSlug = TRANSLATE_WORKFLOW_SLUG
113
- } = options;
114
- return async ({ doc, req }) => {
115
- if (shouldSkipAutoTranslate2(req.context))
116
- return doc;
117
- const typed = doc;
118
- const requestLocale = readRequestLocale2(req);
119
- const defaultLocale = defaultLocaleOption ?? readConfigDefaultLocale2(req) ?? "";
120
- if (!requestLocale || !defaultLocale || requestLocale !== defaultLocale)
121
- return doc;
122
- if (!req.user)
123
- return doc;
124
- const targetLocales = targetLocalesOption ?? readTargetLocales2(req, defaultLocale);
125
- if (targetLocales.length === 0)
126
- return doc;
127
- const resolverKey = options.resolverKey ?? readFirstResolverKey2(req);
128
- if (!resolverKey) {
129
- req.payload.logger.error({
130
- msg: `auto-translate: no resolver key available for global ${globalSlug} — pass \`resolverKey\` to \`createAutoTranslateGlobalHook\` or configure \`translator.resolvers\` in \`translator({...})\``
131
- });
132
- return doc;
133
- }
134
- const updatedAt = typed.updatedAt ?? new Date().toISOString();
135
- for (const toLocale of targetLocales) {
136
- if (toLocale === defaultLocale)
137
- continue;
138
- try {
139
- const job = await req.payload.jobs.queue({
140
- workflow: workflowSlug,
141
- input: {
142
- updatedAt,
143
- global: globalSlug,
144
- fromLocale: defaultLocale,
145
- resolver: resolverKey || undefined,
146
- toLocale
147
- }
148
- });
149
- req.payload.logger.info({
150
- msg: `auto-translate: queued translation of ${globalSlug} → ${toLocale} (job ${job.id})`
151
- });
152
- } catch (error) {
153
- req.payload.logger.error({
154
- msg: `auto-translate: failed to queue ${globalSlug} → ${toLocale}: ${String(error)}`
155
- });
156
- }
157
- }
158
- return doc;
159
- };
160
- }
161
- function shouldSkipAutoTranslate2(context) {
162
- return Boolean(context?.disableAutoTranslate);
163
- }
164
- function readRequestLocale2(req) {
165
- if (!req || typeof req !== "object")
166
- return "";
167
- const locale = req.locale;
168
- return typeof locale === "string" && locale.length > 0 ? locale : "";
169
- }
170
- function readConfigDefaultLocale2(req) {
171
- if (!req || typeof req !== "object")
172
- return "";
173
- const config = req.payload?.config;
174
- const def = config?.localization?.defaultLocale;
175
- return typeof def === "string" && def.length > 0 ? def : "";
176
- }
177
- function readTargetLocales2(req, defaultLocale) {
178
- if (!req || typeof req !== "object")
179
- return [];
180
- const list = req.payload?.config?.localization?.locales;
181
- if (!Array.isArray(list) || list.length === 0)
182
- return [];
183
- const out = [];
184
- for (const entry of list) {
185
- const code = typeof entry === "string" ? entry : entry?.code;
186
- if (typeof code === "string" && code.length > 0 && code !== defaultLocale) {
187
- out.push(code);
188
- }
189
- }
190
- return out;
191
- }
192
- function readFirstResolverKey2(req) {
193
- if (!req || typeof req !== "object")
194
- return "";
195
- const custom = req.payload?.config?.custom?.translator?.resolvers;
196
- if (!Array.isArray(custom) || custom.length === 0)
197
- return "";
198
- const first = custom[0];
199
- return typeof first?.key === "string" ? first.key : "";
200
- }
201
-
202
- // src/translate/operation.ts
203
- import he from "he";
204
- import { APIError as APIError3 } from "payload";
205
-
206
- // src/translate/findEntityWithConfig.ts
207
- import { APIError } from "payload";
208
- var findConfigBySlug = (slug, enities) => enities.find((entity) => entity.slug === slug);
209
- var findEntityWithConfig = async (args) => {
210
- const { collectionSlug, globalSlug, id, locale, overrideAccess, req } = args;
211
- if (!collectionSlug && !globalSlug)
212
- throw new APIError("Bad Request", 400);
213
- const { payload } = req;
214
- const { config } = payload;
215
- const isGlobal = !!globalSlug;
216
- if (!isGlobal && !id)
217
- throw new APIError("Bad Request", 400);
218
- const entityConfig = isGlobal ? findConfigBySlug(globalSlug, config.globals) : findConfigBySlug(collectionSlug, config.collections);
219
- if (!entityConfig)
220
- throw new APIError("Bad Request", 400);
221
- const docPromise = isGlobal ? payload.findGlobal({
222
- depth: 0,
223
- fallbackLocale: undefined,
224
- locale,
225
- overrideAccess,
226
- req,
227
- slug: args.globalSlug
228
- }) : payload.findByID({
229
- collection: collectionSlug,
230
- depth: 0,
231
- fallbackLocale: undefined,
232
- id,
233
- locale,
234
- overrideAccess,
235
- req
236
- });
237
- return {
238
- config: entityConfig,
239
- doc: await docPromise
240
- };
241
- };
242
-
243
- // src/translate/traverseFields.ts
244
- import ObjectID from "bson-objectid";
245
- import { tabHasName } from "payload/shared";
246
-
247
- // src/utils/isEmpty.ts
248
- var isEmpty = (value) => {
249
- if (Array.isArray(value))
250
- return value.length === 0;
251
- if (value === null || typeof value === "undefined")
252
- return true;
253
- if (typeof value === "object" && Object.keys(value).length === 0)
254
- return true;
255
- return false;
256
- };
257
-
258
- // src/translate/traverseRichText.ts
259
- var traverseRichText = ({
260
- onText,
261
- root,
262
- siblingData,
263
- additionalTraverseRichText
264
- }) => {
265
- siblingData = siblingData ?? root;
266
- if (siblingData.text) {
267
- onText(siblingData);
268
- }
269
- if (Array.isArray(siblingData?.children)) {
270
- for (const child of siblingData.children) {
271
- traverseRichText({
272
- onText,
273
- root,
274
- siblingData: child,
275
- additionalTraverseRichText
276
- });
277
- }
278
- } else {
279
- additionalTraverseRichText?.({ onText, root, siblingData });
280
- }
281
- };
282
-
283
- // src/translate/traverseFields.ts
284
- var traverseFields = ({
285
- dataFrom,
286
- emptyOnly,
287
- fields,
288
- localizedParent,
289
- siblingDataFrom,
290
- siblingDataTranslated,
291
- translatedData,
292
- valuesToTranslate,
293
- _options
294
- }) => {
295
- const { additionalTraverseRichText } = _options ?? {};
296
- siblingDataFrom = siblingDataFrom ?? dataFrom;
297
- siblingDataTranslated = siblingDataTranslated ?? translatedData;
298
- for (const field of fields) {
299
- switch (field.type) {
300
- case "tabs":
301
- for (const tab of field.tabs) {
302
- const hasName = tabHasName(tab);
303
- const tabDataFrom = hasName ? siblingDataFrom[tab.name] : siblingDataFrom;
304
- if (!tabDataFrom)
305
- return;
306
- let tabDataTranslated;
307
- if (hasName) {
308
- if (!siblingDataTranslated[tab.name])
309
- siblingDataTranslated[tab.name] = {};
310
- tabDataTranslated = siblingDataTranslated[tab.name];
311
- } else {
312
- tabDataTranslated = siblingDataTranslated;
313
- }
314
- traverseFields({
315
- dataFrom,
316
- emptyOnly,
317
- fields: tab.fields,
318
- localizedParent: localizedParent ?? tab.localized,
319
- siblingDataFrom: tabDataFrom,
320
- siblingDataTranslated: tabDataTranslated,
321
- translatedData,
322
- valuesToTranslate,
323
- _options
324
- });
325
- }
326
- break;
327
- case "group": {
328
- if (!("name" in field))
329
- break;
330
- const groupDataFrom = siblingDataFrom[field.name];
331
- if (!groupDataFrom)
332
- break;
333
- if (!siblingDataTranslated[field.name])
334
- siblingDataTranslated[field.name] = {};
335
- const groupDataTranslated = siblingDataTranslated[field.name];
336
- traverseFields({
337
- dataFrom,
338
- emptyOnly,
339
- fields: field.fields,
340
- localizedParent: localizedParent ?? field.localized,
341
- siblingDataFrom: groupDataFrom,
342
- siblingDataTranslated: groupDataTranslated,
343
- translatedData,
344
- valuesToTranslate,
345
- _options
346
- });
347
- break;
348
- }
349
- case "array": {
350
- const arrayDataFrom = siblingDataFrom[field.name];
351
- if (isEmpty(arrayDataFrom))
352
- break;
353
- if (!siblingDataTranslated[field.name])
354
- siblingDataTranslated[field.name] = [];
355
- let arrayDataTranslated = siblingDataTranslated[field.name];
356
- if (field.localized || localizedParent) {
357
- if (arrayDataTranslated.length > 0 && emptyOnly)
358
- break;
359
- arrayDataTranslated = arrayDataFrom.map(() => ({
360
- id: ObjectID().toHexString()
361
- }));
362
- }
363
- arrayDataTranslated.forEach((item, index) => {
364
- traverseFields({
365
- dataFrom,
366
- emptyOnly,
367
- fields: field.fields,
368
- localizedParent: localizedParent ?? field.localized,
369
- siblingDataFrom: arrayDataFrom[index],
370
- siblingDataTranslated: item,
371
- translatedData,
372
- valuesToTranslate,
373
- _options
374
- });
375
- });
376
- siblingDataTranslated[field.name] = arrayDataTranslated;
377
- break;
378
- }
379
- case "blocks": {
380
- const blocksDataFrom = siblingDataFrom[field.name];
381
- if (isEmpty(blocksDataFrom))
382
- break;
383
- if (!siblingDataTranslated[field.name])
384
- siblingDataTranslated[field.name] = [];
385
- let blocksDataTranslated = siblingDataTranslated[field.name];
386
- if (field.localized || localizedParent) {
387
- if (blocksDataTranslated.length > 0 && emptyOnly)
388
- break;
389
- blocksDataTranslated = blocksDataFrom.map(({ blockType }) => ({
390
- blockType,
391
- id: ObjectID().toHexString()
392
- }));
393
- }
394
- blocksDataTranslated.forEach((item, index) => {
395
- const block = field.blocks.find((each) => each.slug === item.blockType);
396
- if (!block)
397
- return;
398
- traverseFields({
399
- dataFrom,
400
- emptyOnly,
401
- fields: block.fields,
402
- localizedParent: localizedParent ?? field.localized,
403
- siblingDataFrom: blocksDataFrom[index],
404
- siblingDataTranslated: item,
405
- translatedData,
406
- valuesToTranslate,
407
- _options
408
- });
409
- });
410
- siblingDataTranslated[field.name] = blocksDataTranslated;
411
- break;
412
- }
413
- case "collapsible":
414
- case "row":
415
- traverseFields({
416
- dataFrom,
417
- emptyOnly,
418
- fields: field.fields,
419
- localizedParent,
420
- siblingDataFrom,
421
- siblingDataTranslated,
422
- translatedData,
423
- valuesToTranslate,
424
- _options
425
- });
426
- break;
427
- case "date":
428
- case "checkbox":
429
- case "code":
430
- case "email":
431
- case "number":
432
- case "point":
433
- case "radio":
434
- case "relationship":
435
- case "select":
436
- case "upload":
437
- siblingDataTranslated[field.name] = siblingDataFrom[field.name];
438
- break;
439
- case "json":
440
- let traverseObject = function(obj) {
441
- if (!obj || typeof obj !== "object")
442
- return;
443
- for (const key in obj) {
444
- const value = obj[key];
445
- if (typeof value === "string" && value.trim()) {
446
- ((parentObj, parentKey, parentValue) => {
447
- valuesToTranslate.push({
448
- onTranslate: (translated) => {
449
- parentObj[parentKey] = translated;
450
- },
451
- value: parentValue
452
- });
453
- })(obj, key, value);
454
- } else if (typeof value === "object" && value !== null) {
455
- traverseObject(value);
456
- }
457
- }
458
- };
459
- if (!(field.localized || localizedParent))
460
- break;
461
- if (isEmpty(siblingDataFrom[field.name]))
462
- break;
463
- if (emptyOnly && siblingDataTranslated[field.name])
464
- break;
465
- const jsonDataFrom = siblingDataFrom[field.name];
466
- const jsonDataTranslated = JSON.parse(JSON.stringify(jsonDataFrom));
467
- siblingDataTranslated[field.name] = jsonDataTranslated;
468
- traverseObject(jsonDataTranslated);
469
- break;
470
- case "text":
471
- case "textarea":
472
- if (field.custom && typeof field.custom === "object" && field.custom.translatorSkip)
473
- break;
474
- if (!(field.localized || localizedParent) || isEmpty(siblingDataFrom[field.name]))
475
- break;
476
- if (emptyOnly && siblingDataTranslated[field.name])
477
- break;
478
- if (field.name === "blockName" || field.name === "id") {
479
- break;
480
- }
481
- valuesToTranslate.push({
482
- onTranslate: (translated) => {
483
- siblingDataTranslated[field.name] = translated;
484
- },
485
- value: siblingDataFrom[field.name]
486
- });
487
- break;
488
- case "richText": {
489
- if (!(field.localized || localizedParent) || isEmpty(siblingDataFrom[field.name]))
490
- break;
491
- if (emptyOnly && siblingDataTranslated[field.name])
492
- break;
493
- const richTextDataFrom = siblingDataFrom[field.name];
494
- siblingDataTranslated[field.name] = richTextDataFrom;
495
- if (!richTextDataFrom)
496
- break;
497
- const isSlate = Array.isArray(richTextDataFrom);
498
- const isLexical = "root" in richTextDataFrom;
499
- if (!isSlate && !isLexical)
500
- break;
501
- if (isLexical) {
502
- const root = siblingDataTranslated[field.name]?.root;
503
- if (root)
504
- traverseRichText({
505
- onText: (siblingData, attribute = "text") => {
506
- valuesToTranslate.push({
507
- onTranslate: (translated) => {
508
- siblingData[attribute] = translated;
509
- },
510
- value: siblingData[attribute]
511
- });
512
- },
513
- root,
514
- additionalTraverseRichText
515
- });
516
- } else {
517
- for (const root of siblingDataTranslated[field.name]) {
518
- traverseRichText({
519
- onText: (siblingData, attribute = "text") => {
520
- valuesToTranslate.push({
521
- onTranslate: (translated) => {
522
- siblingData[attribute] = translated;
523
- },
524
- value: siblingData[attribute]
525
- });
526
- },
527
- root,
528
- additionalTraverseRichText
529
- });
530
- }
531
- }
532
- break;
533
- }
534
- default:
535
- break;
536
- }
537
- }
538
- };
539
-
540
- // src/translate/updateEntity.ts
541
- import { APIError as APIError2 } from "payload";
542
- var updateEntity = ({
543
- collectionSlug,
544
- data,
545
- depth: incomingDepth,
546
- globalSlug,
547
- id,
548
- locale,
549
- overrideAccess,
550
- req
551
- }) => {
552
- if (!collectionSlug && !globalSlug)
553
- throw new APIError2("Bad Request", 400);
554
- const isGlobal = !!globalSlug;
555
- if (!isGlobal && !id)
556
- throw new APIError2("Bad Request", 400);
557
- const depth = incomingDepth ?? req.payload.config.defaultDepth;
558
- const promise = isGlobal ? req.payload.updateGlobal({
559
- data,
560
- depth,
561
- locale,
562
- overrideAccess,
563
- req,
564
- slug: globalSlug
565
- }) : req.payload.update({
566
- collection: collectionSlug,
567
- data,
568
- depth,
569
- id,
570
- locale,
571
- overrideAccess,
572
- req
573
- });
574
- return promise;
575
- };
576
-
577
- // src/translate/operation.ts
578
- var translateOperation = async (args) => {
579
- const req = "req" in args ? args.req : {
580
- payload: args.payload
581
- };
582
- const { collectionSlug, globalSlug, id, locale, localeFrom, overrideAccess } = args;
583
- const { config, doc: dataFrom } = await findEntityWithConfig({
584
- collectionSlug,
585
- globalSlug,
586
- id,
587
- locale: localeFrom,
588
- req
589
- });
590
- const resolver = (req.payload.config.custom?.translator?.resolvers ?? []).find((each) => each.key === args.resolver);
591
- if (!resolver)
592
- throw new APIError3(`Resolver with the key ${args.resolver} was not found`);
593
- const valuesToTranslate = [];
594
- let translatedData = args.data;
595
- if (!translatedData) {
596
- const { doc } = await findEntityWithConfig({
597
- collectionSlug,
598
- globalSlug,
599
- id,
600
- locale,
601
- overrideAccess,
602
- req
603
- });
604
- translatedData = doc;
605
- }
606
- traverseFields({
607
- dataFrom,
608
- emptyOnly: args.emptyOnly,
609
- fields: config.fields,
610
- translatedData,
611
- valuesToTranslate,
612
- _options: req.payload.config.custom?.translator?._options
613
- });
614
- const resolveResult = await resolver.resolve({
615
- localeFrom: args.localeFrom,
616
- localeTo: args.locale,
617
- req,
618
- texts: valuesToTranslate.map((each) => each.value)
619
- });
620
- let result;
621
- if (!resolveResult.success) {
622
- result = {
623
- success: false
624
- };
625
- } else {
626
- resolveResult.translatedTexts.forEach((translated, index) => {
627
- const formattedValue = he.decode(translated);
628
- valuesToTranslate[index].onTranslate(formattedValue);
629
- });
630
- if (args.update) {
631
- await updateEntity({
632
- collectionSlug,
633
- data: translatedData,
634
- depth: 0,
635
- globalSlug,
636
- id,
637
- locale,
638
- overrideAccess,
639
- req
640
- });
641
- }
642
- result = {
643
- success: true,
644
- translatedData,
645
- dataFrom
646
- };
647
- }
648
- return result;
649
- };
650
-
651
- // src/jobs/createTranslateTask.ts
652
- function createTranslateTask(options = {}) {
653
- const { slug = "translateEntityToLocale" } = options;
654
- return {
655
- slug,
656
- inputSchema: [
657
- { name: "id", type: "number", required: false },
658
- { name: "collection", type: "text", required: false },
659
- { name: "global", type: "text", required: false },
660
- { name: "fromLocale", type: "text", required: true },
661
- { name: "toLocale", type: "text", required: true },
662
- { name: "resolver", type: "text", required: false }
663
- ],
664
- outputSchema: [],
665
- retries: 3,
666
- handler: async (args) => {
667
- const { input, job, req } = args;
668
- const { id, collection, global, fromLocale, toLocale, resolver: inputResolver } = input;
669
- if (!collection && !global) {
670
- throw new Error("translateTask: either `collection` or `global` must be provided");
671
- }
672
- const resolverKey = options.resolverKey ?? inputResolver ?? readFirstResolverKey3(req);
673
- if (!resolverKey) {
674
- throw new Error(`translateTask: no resolver key available — pass \`resolverKey\` to \`createTranslateTask\` or queue the workflow with a \`resolver\` input. Did you forget to pass \`translator.resolvers\` to \`translator({...})\`?`);
675
- }
676
- const entityLabel = collection || global;
677
- req.payload.logger.info({
678
- jobId: job.id,
679
- msg: `translating ${entityLabel} to locale ${toLocale}`
680
- });
681
- let result;
682
- try {
683
- result = await translateOperation({
684
- req,
685
- collectionSlug: collection,
686
- globalSlug: global,
687
- emptyOnly: false,
688
- id,
689
- locale: toLocale,
690
- localeFrom: fromLocale,
691
- overrideAccess: true,
692
- resolver: resolverKey,
693
- update: false
694
- });
695
- } catch (error) {
696
- req.payload.logger.error({
697
- jobId: job.id,
698
- msg: `translateOperation threw for ${entityLabel} → ${toLocale}: ${String(error)}`
699
- });
700
- throw error;
701
- }
702
- if (!result.success) {
703
- req.payload.logger.error({
704
- jobId: job.id,
705
- msg: `translation for ${entityLabel} to ${toLocale} failed (resolver returned success=false)`
706
- });
707
- throw new Error(`translateTask: resolver returned success=false for ${entityLabel} → ${toLocale}`);
708
- }
709
- const translated = result.translatedData ?? {};
710
- const { _locale: _dropLocale, _parent_id: _dropParent, ...data } = translated;
711
- req.payload.logger.info({
712
- jobId: job.id,
713
- msg: `persisting ${entityLabel} (id=${id ?? global}) at locale ${toLocale}`
714
- });
715
- try {
716
- if (collection) {
717
- await req.payload.update({
718
- collection,
719
- data,
720
- id,
721
- locale: toLocale,
722
- overrideAccess: true,
723
- req
724
- });
725
- } else {
726
- await req.payload.updateGlobal({
727
- slug: global,
728
- data,
729
- locale: toLocale,
730
- overrideAccess: true,
731
- req
732
- });
733
- }
734
- } catch (error) {
735
- req.payload.logger.error({
736
- jobId: job.id,
737
- msg: `persist failed for ${entityLabel} (id=${id ?? global}) at locale ${toLocale}: ${String(error)}`
738
- });
739
- throw error;
740
- }
741
- req.payload.logger.info({
742
- jobId: job.id,
743
- msg: `translation complete for ${entityLabel} → ${toLocale}`
744
- });
745
- return { output: { success: true } };
746
- }
747
- };
748
- }
749
- function readFirstResolverKey3(req) {
750
- if (!req || typeof req !== "object")
751
- return "";
752
- const custom = req.payload?.config?.custom?.translator?.resolvers;
753
- if (!Array.isArray(custom) || custom.length === 0)
754
- return "";
755
- const first = custom[0];
756
- return typeof first?.key === "string" ? first.key : "";
757
- }
758
-
759
- // src/jobs/createTranslateWorkflow.ts
760
- function createTranslateWorkflow(options = {}) {
761
- const { slug = "translateEntityToLocales", taskSlug = "translateEntityToLocale" } = options;
762
- return {
763
- slug,
764
- inputSchema: [
765
- { name: "id", type: "number", required: false },
766
- { name: "updatedAt", type: "date", required: true },
767
- { name: "collection", type: "text", required: false },
768
- { name: "global", type: "text", required: false },
769
- { name: "fromLocale", type: "text", required: true },
770
- { name: "toLocale", type: "text", required: true },
771
- { name: "resolver", type: "text", required: false }
772
- ],
773
- handler: async (args) => {
774
- const { job, req, tasks } = args;
775
- const { id, collection, global, fromLocale, toLocale, updatedAt, resolver } = job.input;
776
- if (!collection && !global) {
777
- throw new Error("translateWorkflow: either `collection` or `global` must be provided");
778
- }
779
- if (typeof req.payload.config.localization !== "object") {
780
- req.payload.logger.error({
781
- jobId: job.id,
782
- msg: "localization is not enabled — skipping auto-translation workflow"
783
- });
784
- return;
785
- }
786
- const entityKey = collection ? `${collection}-${id}` : global;
787
- const updatedAtIso = updatedAt instanceof Date ? updatedAt.toISOString() : String(updatedAt);
788
- req.payload.logger.info({
789
- jobId: job.id,
790
- msg: `scheduling translation of ${entityKey} → ${toLocale}`
791
- });
792
- await tasks[taskSlug](`${entityKey}-${fromLocale}-${toLocale}-${updatedAtIso}`, {
793
- input: {
794
- id,
795
- collection,
796
- global,
797
- fromLocale,
798
- toLocale,
799
- resolver
800
- }
801
- });
802
- }
803
- };
804
- }
1
+ import {
2
+ TRANSLATE_TASK_SLUG,
3
+ TRANSLATE_WORKFLOW_SLUG,
4
+ createAutoTranslateCollectionHook,
5
+ createAutoTranslateGlobalHook,
6
+ createTranslateTask,
7
+ createTranslateWorkflow
8
+ } from "./shared/chunk-q51q8rd2.js";
805
9
 
806
10
  // src/exports/jobs.ts
807
11
  var jobs = {