@justanarthur/payload-plugin-translator 1.3.21 → 3.1.0
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/README.md +150 -0
- package/dist/client.css +225 -0
- package/dist/client.d.ts +14 -2
- package/dist/client.js +149 -63
- package/dist/index.d.ts +11 -38
- package/dist/index.js +179 -1071
- package/dist/jobs.d.ts +1 -146
- package/dist/jobs.js +22 -817
- package/dist/resolvers/copy.d.ts +1 -3
- package/dist/resolvers/copy.js +6 -16
- package/dist/resolvers/google.d.ts +1 -7
- package/dist/resolvers/google.js +6 -57
- package/dist/resolvers/libreTranslate.d.ts +1 -11
- package/dist/resolvers/libreTranslate.js +6 -59
- package/dist/resolvers/openAI.d.ts +5 -13
- package/dist/resolvers/openAI.js +6 -128
- package/dist/resolvers/types.d.ts +1 -3
- package/dist/rsc.d.ts +5 -0
- package/dist/rsc.js +483 -0
- package/dist/shared/chunk-15n5q7wd.js +253 -0
- package/dist/shared/chunk-31qgv1zk.js +5 -0
- package/dist/shared/chunk-9d433kmv.js +359 -0
- package/dist/shared/chunk-e6c0qzkt.js +6 -0
- package/dist/shared/chunk-mvqymw6n.js +57 -0
- package/dist/shared/chunk-nxddacte.js +243 -0
- package/dist/shared/chunk-p03jz82h.js +623 -0
- package/dist/shared/chunk-zenq7s47.js +15 -0
- package/dist/shared/chunk-zs19h344.js +55 -0
- package/dist/types.d.ts +6 -36
- package/package.json +23 -11
- package/dist/styles-jt3fm5fh.scss +0 -5
- package/dist/styles-vs6m7h55.scss +0 -76
package/dist/index.js
CHANGED
|
@@ -1,451 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
throw new APIError("Bad Request", 400);
|
|
20
|
-
const docPromise = isGlobal ? payload.findGlobal({
|
|
21
|
-
depth: 0,
|
|
22
|
-
fallbackLocale: undefined,
|
|
23
|
-
locale,
|
|
24
|
-
overrideAccess,
|
|
25
|
-
req,
|
|
26
|
-
slug: args.globalSlug
|
|
27
|
-
}) : payload.findByID({
|
|
28
|
-
collection: collectionSlug,
|
|
29
|
-
depth: 0,
|
|
30
|
-
fallbackLocale: undefined,
|
|
31
|
-
id,
|
|
32
|
-
locale,
|
|
33
|
-
overrideAccess,
|
|
34
|
-
req
|
|
35
|
-
});
|
|
36
|
-
return {
|
|
37
|
-
config: entityConfig,
|
|
38
|
-
doc: await docPromise
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// src/translate/traverseFields.ts
|
|
43
|
-
import ObjectID from "bson-objectid";
|
|
44
|
-
import { tabHasName } from "payload/shared";
|
|
45
|
-
|
|
46
|
-
// src/utils/isEmpty.ts
|
|
47
|
-
var isEmpty = (value) => {
|
|
48
|
-
if (Array.isArray(value))
|
|
49
|
-
return value.length === 0;
|
|
50
|
-
if (value === null || typeof value === "undefined")
|
|
51
|
-
return true;
|
|
52
|
-
if (typeof value === "object" && Object.keys(value).length === 0)
|
|
53
|
-
return true;
|
|
54
|
-
return false;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// src/translate/traverseRichText.ts
|
|
58
|
-
var traverseRichText = ({
|
|
59
|
-
onText,
|
|
60
|
-
root,
|
|
61
|
-
siblingData,
|
|
62
|
-
additionalTraverseRichText
|
|
63
|
-
}) => {
|
|
64
|
-
siblingData = siblingData ?? root;
|
|
65
|
-
if (siblingData.text) {
|
|
66
|
-
onText(siblingData);
|
|
67
|
-
}
|
|
68
|
-
if (Array.isArray(siblingData?.children)) {
|
|
69
|
-
for (const child of siblingData.children) {
|
|
70
|
-
traverseRichText({
|
|
71
|
-
onText,
|
|
72
|
-
root,
|
|
73
|
-
siblingData: child,
|
|
74
|
-
additionalTraverseRichText
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
additionalTraverseRichText?.({ onText, root, siblingData });
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// src/translate/traverseFields.ts
|
|
83
|
-
var traverseFields = ({
|
|
84
|
-
dataFrom,
|
|
85
|
-
emptyOnly,
|
|
86
|
-
fields,
|
|
87
|
-
localizedParent,
|
|
88
|
-
siblingDataFrom,
|
|
89
|
-
siblingDataTranslated,
|
|
90
|
-
translatedData,
|
|
91
|
-
valuesToTranslate,
|
|
92
|
-
_options
|
|
93
|
-
}) => {
|
|
94
|
-
const { additionalTraverseRichText } = _options ?? {};
|
|
95
|
-
siblingDataFrom = siblingDataFrom ?? dataFrom;
|
|
96
|
-
siblingDataTranslated = siblingDataTranslated ?? translatedData;
|
|
97
|
-
for (const field of fields) {
|
|
98
|
-
switch (field.type) {
|
|
99
|
-
case "tabs":
|
|
100
|
-
for (const tab of field.tabs) {
|
|
101
|
-
const hasName = tabHasName(tab);
|
|
102
|
-
const tabDataFrom = hasName ? siblingDataFrom[tab.name] : siblingDataFrom;
|
|
103
|
-
if (!tabDataFrom)
|
|
104
|
-
return;
|
|
105
|
-
let tabDataTranslated;
|
|
106
|
-
if (hasName) {
|
|
107
|
-
if (!siblingDataTranslated[tab.name])
|
|
108
|
-
siblingDataTranslated[tab.name] = {};
|
|
109
|
-
tabDataTranslated = siblingDataTranslated[tab.name];
|
|
110
|
-
} else {
|
|
111
|
-
tabDataTranslated = siblingDataTranslated;
|
|
112
|
-
}
|
|
113
|
-
traverseFields({
|
|
114
|
-
dataFrom,
|
|
115
|
-
emptyOnly,
|
|
116
|
-
fields: tab.fields,
|
|
117
|
-
localizedParent: localizedParent ?? tab.localized,
|
|
118
|
-
siblingDataFrom: tabDataFrom,
|
|
119
|
-
siblingDataTranslated: tabDataTranslated,
|
|
120
|
-
translatedData,
|
|
121
|
-
valuesToTranslate,
|
|
122
|
-
_options
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
break;
|
|
126
|
-
case "group": {
|
|
127
|
-
if (!("name" in field))
|
|
128
|
-
break;
|
|
129
|
-
const groupDataFrom = siblingDataFrom[field.name];
|
|
130
|
-
if (!groupDataFrom)
|
|
131
|
-
break;
|
|
132
|
-
if (!siblingDataTranslated[field.name])
|
|
133
|
-
siblingDataTranslated[field.name] = {};
|
|
134
|
-
const groupDataTranslated = siblingDataTranslated[field.name];
|
|
135
|
-
traverseFields({
|
|
136
|
-
dataFrom,
|
|
137
|
-
emptyOnly,
|
|
138
|
-
fields: field.fields,
|
|
139
|
-
localizedParent: localizedParent ?? field.localized,
|
|
140
|
-
siblingDataFrom: groupDataFrom,
|
|
141
|
-
siblingDataTranslated: groupDataTranslated,
|
|
142
|
-
translatedData,
|
|
143
|
-
valuesToTranslate,
|
|
144
|
-
_options
|
|
145
|
-
});
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
case "array": {
|
|
149
|
-
const arrayDataFrom = siblingDataFrom[field.name];
|
|
150
|
-
if (isEmpty(arrayDataFrom))
|
|
151
|
-
break;
|
|
152
|
-
if (!siblingDataTranslated[field.name])
|
|
153
|
-
siblingDataTranslated[field.name] = [];
|
|
154
|
-
let arrayDataTranslated = siblingDataTranslated[field.name];
|
|
155
|
-
if (field.localized || localizedParent) {
|
|
156
|
-
if (arrayDataTranslated.length > 0 && emptyOnly)
|
|
157
|
-
break;
|
|
158
|
-
arrayDataTranslated = arrayDataFrom.map(() => ({
|
|
159
|
-
id: ObjectID().toHexString()
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
arrayDataTranslated.forEach((item, index) => {
|
|
163
|
-
traverseFields({
|
|
164
|
-
dataFrom,
|
|
165
|
-
emptyOnly,
|
|
166
|
-
fields: field.fields,
|
|
167
|
-
localizedParent: localizedParent ?? field.localized,
|
|
168
|
-
siblingDataFrom: arrayDataFrom[index],
|
|
169
|
-
siblingDataTranslated: item,
|
|
170
|
-
translatedData,
|
|
171
|
-
valuesToTranslate,
|
|
172
|
-
_options
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
siblingDataTranslated[field.name] = arrayDataTranslated;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
case "blocks": {
|
|
179
|
-
const blocksDataFrom = siblingDataFrom[field.name];
|
|
180
|
-
if (isEmpty(blocksDataFrom))
|
|
181
|
-
break;
|
|
182
|
-
if (!siblingDataTranslated[field.name])
|
|
183
|
-
siblingDataTranslated[field.name] = [];
|
|
184
|
-
let blocksDataTranslated = siblingDataTranslated[field.name];
|
|
185
|
-
if (field.localized || localizedParent) {
|
|
186
|
-
if (blocksDataTranslated.length > 0 && emptyOnly)
|
|
187
|
-
break;
|
|
188
|
-
blocksDataTranslated = blocksDataFrom.map(({ blockType }) => ({
|
|
189
|
-
blockType,
|
|
190
|
-
id: ObjectID().toHexString()
|
|
191
|
-
}));
|
|
192
|
-
}
|
|
193
|
-
blocksDataTranslated.forEach((item, index) => {
|
|
194
|
-
const block = field.blocks.find((each) => each.slug === item.blockType);
|
|
195
|
-
if (!block)
|
|
196
|
-
return;
|
|
197
|
-
traverseFields({
|
|
198
|
-
dataFrom,
|
|
199
|
-
emptyOnly,
|
|
200
|
-
fields: block.fields,
|
|
201
|
-
localizedParent: localizedParent ?? field.localized,
|
|
202
|
-
siblingDataFrom: blocksDataFrom[index],
|
|
203
|
-
siblingDataTranslated: item,
|
|
204
|
-
translatedData,
|
|
205
|
-
valuesToTranslate,
|
|
206
|
-
_options
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
siblingDataTranslated[field.name] = blocksDataTranslated;
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
case "collapsible":
|
|
213
|
-
case "row":
|
|
214
|
-
traverseFields({
|
|
215
|
-
dataFrom,
|
|
216
|
-
emptyOnly,
|
|
217
|
-
fields: field.fields,
|
|
218
|
-
localizedParent,
|
|
219
|
-
siblingDataFrom,
|
|
220
|
-
siblingDataTranslated,
|
|
221
|
-
translatedData,
|
|
222
|
-
valuesToTranslate,
|
|
223
|
-
_options
|
|
224
|
-
});
|
|
225
|
-
break;
|
|
226
|
-
case "date":
|
|
227
|
-
case "checkbox":
|
|
228
|
-
case "code":
|
|
229
|
-
case "email":
|
|
230
|
-
case "number":
|
|
231
|
-
case "point":
|
|
232
|
-
case "radio":
|
|
233
|
-
case "relationship":
|
|
234
|
-
case "select":
|
|
235
|
-
case "upload":
|
|
236
|
-
siblingDataTranslated[field.name] = siblingDataFrom[field.name];
|
|
237
|
-
break;
|
|
238
|
-
case "json":
|
|
239
|
-
let traverseObject = function(obj) {
|
|
240
|
-
if (!obj || typeof obj !== "object")
|
|
241
|
-
return;
|
|
242
|
-
for (const key in obj) {
|
|
243
|
-
const value = obj[key];
|
|
244
|
-
if (typeof value === "string" && value.trim()) {
|
|
245
|
-
((parentObj, parentKey, parentValue) => {
|
|
246
|
-
valuesToTranslate.push({
|
|
247
|
-
onTranslate: (translated) => {
|
|
248
|
-
parentObj[parentKey] = translated;
|
|
249
|
-
},
|
|
250
|
-
value: parentValue
|
|
251
|
-
});
|
|
252
|
-
})(obj, key, value);
|
|
253
|
-
} else if (typeof value === "object" && value !== null) {
|
|
254
|
-
traverseObject(value);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
if (!(field.localized || localizedParent))
|
|
259
|
-
break;
|
|
260
|
-
if (isEmpty(siblingDataFrom[field.name]))
|
|
261
|
-
break;
|
|
262
|
-
if (emptyOnly && siblingDataTranslated[field.name])
|
|
263
|
-
break;
|
|
264
|
-
const jsonDataFrom = siblingDataFrom[field.name];
|
|
265
|
-
const jsonDataTranslated = JSON.parse(JSON.stringify(jsonDataFrom));
|
|
266
|
-
siblingDataTranslated[field.name] = jsonDataTranslated;
|
|
267
|
-
traverseObject(jsonDataTranslated);
|
|
268
|
-
break;
|
|
269
|
-
case "text":
|
|
270
|
-
case "textarea":
|
|
271
|
-
if (field.custom && typeof field.custom === "object" && field.custom.translatorSkip)
|
|
272
|
-
break;
|
|
273
|
-
if (!(field.localized || localizedParent) || isEmpty(siblingDataFrom[field.name]))
|
|
274
|
-
break;
|
|
275
|
-
if (emptyOnly && siblingDataTranslated[field.name])
|
|
276
|
-
break;
|
|
277
|
-
if (field.name === "blockName" || field.name === "id") {
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
valuesToTranslate.push({
|
|
281
|
-
onTranslate: (translated) => {
|
|
282
|
-
siblingDataTranslated[field.name] = translated;
|
|
283
|
-
},
|
|
284
|
-
value: siblingDataFrom[field.name]
|
|
285
|
-
});
|
|
286
|
-
break;
|
|
287
|
-
case "richText": {
|
|
288
|
-
if (!(field.localized || localizedParent) || isEmpty(siblingDataFrom[field.name]))
|
|
289
|
-
break;
|
|
290
|
-
if (emptyOnly && siblingDataTranslated[field.name])
|
|
291
|
-
break;
|
|
292
|
-
const richTextDataFrom = siblingDataFrom[field.name];
|
|
293
|
-
siblingDataTranslated[field.name] = richTextDataFrom;
|
|
294
|
-
if (!richTextDataFrom)
|
|
295
|
-
break;
|
|
296
|
-
const isSlate = Array.isArray(richTextDataFrom);
|
|
297
|
-
const isLexical = "root" in richTextDataFrom;
|
|
298
|
-
if (!isSlate && !isLexical)
|
|
299
|
-
break;
|
|
300
|
-
if (isLexical) {
|
|
301
|
-
const root = siblingDataTranslated[field.name]?.root;
|
|
302
|
-
if (root)
|
|
303
|
-
traverseRichText({
|
|
304
|
-
onText: (siblingData, attribute = "text") => {
|
|
305
|
-
valuesToTranslate.push({
|
|
306
|
-
onTranslate: (translated) => {
|
|
307
|
-
siblingData[attribute] = translated;
|
|
308
|
-
},
|
|
309
|
-
value: siblingData[attribute]
|
|
310
|
-
});
|
|
311
|
-
},
|
|
312
|
-
root,
|
|
313
|
-
additionalTraverseRichText
|
|
314
|
-
});
|
|
315
|
-
} else {
|
|
316
|
-
for (const root of siblingDataTranslated[field.name]) {
|
|
317
|
-
traverseRichText({
|
|
318
|
-
onText: (siblingData, attribute = "text") => {
|
|
319
|
-
valuesToTranslate.push({
|
|
320
|
-
onTranslate: (translated) => {
|
|
321
|
-
siblingData[attribute] = translated;
|
|
322
|
-
},
|
|
323
|
-
value: siblingData[attribute]
|
|
324
|
-
});
|
|
325
|
-
},
|
|
326
|
-
root,
|
|
327
|
-
additionalTraverseRichText
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
break;
|
|
332
|
-
}
|
|
333
|
-
default:
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
// src/translate/updateEntity.ts
|
|
340
|
-
import { APIError as APIError2 } from "payload";
|
|
341
|
-
var updateEntity = ({
|
|
342
|
-
collectionSlug,
|
|
343
|
-
data,
|
|
344
|
-
depth: incomingDepth,
|
|
345
|
-
globalSlug,
|
|
346
|
-
id,
|
|
347
|
-
locale,
|
|
348
|
-
overrideAccess,
|
|
349
|
-
req
|
|
350
|
-
}) => {
|
|
351
|
-
if (!collectionSlug && !globalSlug)
|
|
352
|
-
throw new APIError2("Bad Request", 400);
|
|
353
|
-
const isGlobal = !!globalSlug;
|
|
354
|
-
if (!isGlobal && !id)
|
|
355
|
-
throw new APIError2("Bad Request", 400);
|
|
356
|
-
const depth = incomingDepth ?? req.payload.config.defaultDepth;
|
|
357
|
-
const promise = isGlobal ? req.payload.updateGlobal({
|
|
358
|
-
data,
|
|
359
|
-
depth,
|
|
360
|
-
locale,
|
|
361
|
-
overrideAccess,
|
|
362
|
-
req,
|
|
363
|
-
slug: globalSlug
|
|
364
|
-
}) : req.payload.update({
|
|
365
|
-
collection: collectionSlug,
|
|
366
|
-
data,
|
|
367
|
-
depth,
|
|
368
|
-
id,
|
|
369
|
-
locale,
|
|
370
|
-
overrideAccess,
|
|
371
|
-
req
|
|
372
|
-
});
|
|
373
|
-
return promise;
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
// src/translate/operation.ts
|
|
377
|
-
var translateOperation = async (args) => {
|
|
378
|
-
const req = "req" in args ? args.req : {
|
|
379
|
-
payload: args.payload
|
|
380
|
-
};
|
|
381
|
-
const { collectionSlug, globalSlug, id, locale, localeFrom, overrideAccess } = args;
|
|
382
|
-
const { config, doc: dataFrom } = await findEntityWithConfig({
|
|
383
|
-
collectionSlug,
|
|
384
|
-
globalSlug,
|
|
385
|
-
id,
|
|
386
|
-
locale: localeFrom,
|
|
387
|
-
req
|
|
388
|
-
});
|
|
389
|
-
const resolver = (req.payload.config.custom?.translator?.resolvers ?? []).find((each) => each.key === args.resolver);
|
|
390
|
-
if (!resolver)
|
|
391
|
-
throw new APIError3(`Resolver with the key ${args.resolver} was not found`);
|
|
392
|
-
const valuesToTranslate = [];
|
|
393
|
-
let translatedData = args.data;
|
|
394
|
-
if (!translatedData) {
|
|
395
|
-
const { doc } = await findEntityWithConfig({
|
|
396
|
-
collectionSlug,
|
|
397
|
-
globalSlug,
|
|
398
|
-
id,
|
|
399
|
-
locale,
|
|
400
|
-
overrideAccess,
|
|
401
|
-
req
|
|
402
|
-
});
|
|
403
|
-
translatedData = doc;
|
|
404
|
-
}
|
|
405
|
-
traverseFields({
|
|
406
|
-
dataFrom,
|
|
407
|
-
emptyOnly: args.emptyOnly,
|
|
408
|
-
fields: config.fields,
|
|
409
|
-
translatedData,
|
|
410
|
-
valuesToTranslate,
|
|
411
|
-
_options: req.payload.config.custom?.translator?._options
|
|
412
|
-
});
|
|
413
|
-
const resolveResult = await resolver.resolve({
|
|
414
|
-
localeFrom: args.localeFrom,
|
|
415
|
-
localeTo: args.locale,
|
|
416
|
-
req,
|
|
417
|
-
texts: valuesToTranslate.map((each) => each.value)
|
|
418
|
-
});
|
|
419
|
-
let result;
|
|
420
|
-
if (!resolveResult.success) {
|
|
421
|
-
result = {
|
|
422
|
-
success: false
|
|
423
|
-
};
|
|
424
|
-
} else {
|
|
425
|
-
resolveResult.translatedTexts.forEach((translated, index) => {
|
|
426
|
-
const formattedValue = he.decode(translated);
|
|
427
|
-
valuesToTranslate[index].onTranslate(formattedValue);
|
|
428
|
-
});
|
|
429
|
-
if (args.update) {
|
|
430
|
-
await updateEntity({
|
|
431
|
-
collectionSlug,
|
|
432
|
-
data: translatedData,
|
|
433
|
-
depth: 0,
|
|
434
|
-
globalSlug,
|
|
435
|
-
id,
|
|
436
|
-
locale,
|
|
437
|
-
overrideAccess,
|
|
438
|
-
req
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
result = {
|
|
442
|
-
success: true,
|
|
443
|
-
translatedData,
|
|
444
|
-
dataFrom
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
return result;
|
|
448
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
findEntityWithConfig,
|
|
3
|
+
translateOperation2,
|
|
4
|
+
createAutoTranslateCollectionHook2,
|
|
5
|
+
createAutoTranslateGlobalHook2,
|
|
6
|
+
recordTranslationStatus,
|
|
7
|
+
createTranslateTask2,
|
|
8
|
+
createTranslateWorkflow2
|
|
9
|
+
} from "./shared/chunk-p03jz82h.js";
|
|
10
|
+
import"./shared/chunk-9d433kmv.js";
|
|
11
|
+
import {
|
|
12
|
+
readLocales,
|
|
13
|
+
parseEntityKey
|
|
14
|
+
} from "./shared/chunk-15n5q7wd.js";
|
|
15
|
+
import {
|
|
16
|
+
TRANSLATION_STATUS_SLUG2,
|
|
17
|
+
REVIEW_VIEW_PATH
|
|
18
|
+
} from "./shared/chunk-31qgv1zk.js";
|
|
449
19
|
|
|
450
20
|
// src/index.ts
|
|
451
21
|
import { deepMerge } from "payload/shared";
|
|
@@ -548,370 +118,115 @@ var translations = {
|
|
|
548
118
|
}
|
|
549
119
|
};
|
|
550
120
|
|
|
551
|
-
// src/
|
|
552
|
-
var
|
|
553
|
-
var
|
|
121
|
+
// src/review/collection.ts
|
|
122
|
+
var signedIn = ({ req }) => Boolean(req.user);
|
|
123
|
+
var createTranslationStatusCollection = () => ({
|
|
124
|
+
slug: TRANSLATION_STATUS_SLUG2,
|
|
125
|
+
admin: {
|
|
126
|
+
hidden: true
|
|
127
|
+
},
|
|
128
|
+
access: {
|
|
129
|
+
create: signedIn,
|
|
130
|
+
delete: signedIn,
|
|
131
|
+
read: signedIn,
|
|
132
|
+
update: signedIn
|
|
133
|
+
},
|
|
134
|
+
fields: [
|
|
135
|
+
{ name: "entity", type: "text", required: true, index: true },
|
|
136
|
+
{ name: "locale", type: "text", required: true, index: true },
|
|
137
|
+
{ name: "sourceHash", type: "text" },
|
|
138
|
+
{ name: "translatedAt", type: "date" },
|
|
139
|
+
{ name: "reviewedHash", type: "text" },
|
|
140
|
+
{ name: "reviewedAt", type: "date" },
|
|
141
|
+
{ name: "reviewedBy", type: "text" }
|
|
142
|
+
]
|
|
143
|
+
});
|
|
554
144
|
|
|
555
|
-
// src/
|
|
556
|
-
|
|
557
|
-
|
|
145
|
+
// src/review/endpoints.ts
|
|
146
|
+
import { APIError } from "payload";
|
|
147
|
+
var readReviewRequest = async (req) => {
|
|
148
|
+
if (!req.user)
|
|
149
|
+
throw new APIError("Unauthorized", 401);
|
|
150
|
+
if (!req.json)
|
|
151
|
+
throw new APIError("Content-Type should be json");
|
|
152
|
+
const body = await req.json();
|
|
153
|
+
const { targetLocales } = readLocales(req);
|
|
154
|
+
if (!body.entity || !body.locale || !targetLocales.includes(body.locale))
|
|
155
|
+
throw new APIError("Bad Request", 400);
|
|
156
|
+
const target = parseEntityKey(body.entity);
|
|
157
|
+
const pluginConfig = req.payload.config.custom?.translator;
|
|
158
|
+
const allowed = target.globalSlug ? pluginConfig?.globals.includes(target.globalSlug) : pluginConfig?.collections.includes(target.collectionSlug);
|
|
159
|
+
if (!allowed || !pluginConfig)
|
|
160
|
+
throw new APIError("Forbidden", 403);
|
|
161
|
+
return { ...body, ...target, locale: body.locale, pluginConfig };
|
|
162
|
+
};
|
|
163
|
+
var translateHandler = async (req) => {
|
|
164
|
+
const { collectionSlug, globalSlug, id, locale, mode, pluginConfig } = await readReviewRequest(req);
|
|
165
|
+
const { defaultLocale } = readLocales(req);
|
|
166
|
+
const resolver = pluginConfig.resolvers[0]?.key;
|
|
167
|
+
if (!resolver)
|
|
168
|
+
throw new APIError("No resolver configured", 400);
|
|
169
|
+
const result = await translateOperation2({
|
|
170
|
+
req,
|
|
558
171
|
collectionSlug,
|
|
559
|
-
defaultLocale: defaultLocaleOption,
|
|
560
|
-
targetLocales: targetLocalesOption,
|
|
561
|
-
onlyOnPublished = true,
|
|
562
|
-
workflowSlug = TRANSLATE_WORKFLOW_SLUG
|
|
563
|
-
} = options;
|
|
564
|
-
const afterChange = async ({ doc, req }) => {
|
|
565
|
-
if (shouldSkipAutoTranslate(req.context))
|
|
566
|
-
return doc;
|
|
567
|
-
const typed = doc;
|
|
568
|
-
const requestLocale = readRequestLocale(req);
|
|
569
|
-
const defaultLocale = defaultLocaleOption ?? readConfigDefaultLocale(req) ?? "";
|
|
570
|
-
if (!requestLocale || !defaultLocale || requestLocale !== defaultLocale)
|
|
571
|
-
return doc;
|
|
572
|
-
if (!req.user)
|
|
573
|
-
return doc;
|
|
574
|
-
if (onlyOnPublished && typed._status && typed._status !== "published")
|
|
575
|
-
return doc;
|
|
576
|
-
const targetLocales = targetLocalesOption ?? readTargetLocales(req, defaultLocale);
|
|
577
|
-
if (targetLocales.length === 0)
|
|
578
|
-
return doc;
|
|
579
|
-
const resolverKey = options.resolverKey ?? readFirstResolverKey(req);
|
|
580
|
-
if (!resolverKey) {
|
|
581
|
-
req.payload.logger.error({
|
|
582
|
-
msg: `auto-translate: no resolver key available for collection ${collectionSlug} — pass \`resolverKey\` to \`createAutoTranslateCollectionHook\` or configure \`translator.resolvers\` in \`translator({...})\``
|
|
583
|
-
});
|
|
584
|
-
return doc;
|
|
585
|
-
}
|
|
586
|
-
const updatedAt = typed.updatedAt ?? new Date().toISOString();
|
|
587
|
-
for (const toLocale of targetLocales) {
|
|
588
|
-
if (toLocale === defaultLocale)
|
|
589
|
-
continue;
|
|
590
|
-
try {
|
|
591
|
-
const job = await req.payload.jobs.queue({
|
|
592
|
-
workflow: workflowSlug,
|
|
593
|
-
input: {
|
|
594
|
-
id: typed.id,
|
|
595
|
-
updatedAt,
|
|
596
|
-
collection: collectionSlug,
|
|
597
|
-
fromLocale: defaultLocale,
|
|
598
|
-
resolver: resolverKey || undefined,
|
|
599
|
-
toLocale
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
req.payload.logger.info({
|
|
603
|
-
msg: `auto-translate: queued translation of ${collectionSlug}#${typed.id} → ${toLocale} (job ${job.id})`
|
|
604
|
-
});
|
|
605
|
-
} catch (error) {
|
|
606
|
-
req.payload.logger.error({
|
|
607
|
-
msg: `auto-translate: failed to queue ${collectionSlug}#${typed.id} → ${toLocale}: ${String(error)}`
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return doc;
|
|
612
|
-
};
|
|
613
|
-
return { afterChange };
|
|
614
|
-
}
|
|
615
|
-
function shouldSkipAutoTranslate(context) {
|
|
616
|
-
return Boolean(context?.disableAutoTranslate);
|
|
617
|
-
}
|
|
618
|
-
function readRequestLocale(req) {
|
|
619
|
-
if (!req || typeof req !== "object")
|
|
620
|
-
return "";
|
|
621
|
-
const locale = req.locale;
|
|
622
|
-
return typeof locale === "string" && locale.length > 0 ? locale : "";
|
|
623
|
-
}
|
|
624
|
-
function readConfigDefaultLocale(req) {
|
|
625
|
-
if (!req || typeof req !== "object")
|
|
626
|
-
return "";
|
|
627
|
-
const config = req.payload?.config;
|
|
628
|
-
const def = config?.localization?.defaultLocale;
|
|
629
|
-
return typeof def === "string" && def.length > 0 ? def : "";
|
|
630
|
-
}
|
|
631
|
-
function readTargetLocales(req, defaultLocale) {
|
|
632
|
-
if (!req || typeof req !== "object")
|
|
633
|
-
return [];
|
|
634
|
-
const list = req.payload?.config?.localization?.locales;
|
|
635
|
-
if (!Array.isArray(list) || list.length === 0)
|
|
636
|
-
return [];
|
|
637
|
-
const out = [];
|
|
638
|
-
for (const entry of list) {
|
|
639
|
-
const code = typeof entry === "string" ? entry : entry?.code;
|
|
640
|
-
if (typeof code === "string" && code.length > 0 && code !== defaultLocale) {
|
|
641
|
-
out.push(code);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
return out;
|
|
645
|
-
}
|
|
646
|
-
function readFirstResolverKey(req) {
|
|
647
|
-
if (!req || typeof req !== "object")
|
|
648
|
-
return "";
|
|
649
|
-
const custom = req.payload?.config?.custom?.translator?.resolvers;
|
|
650
|
-
if (!Array.isArray(custom) || custom.length === 0)
|
|
651
|
-
return "";
|
|
652
|
-
const first = custom[0];
|
|
653
|
-
return typeof first?.key === "string" ? first.key : "";
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// src/jobs/createAutoTranslateGlobalHook.ts
|
|
657
|
-
function createAutoTranslateGlobalHook(options) {
|
|
658
|
-
const {
|
|
659
172
|
globalSlug,
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
return doc;
|
|
672
|
-
if (!req.user)
|
|
673
|
-
return doc;
|
|
674
|
-
const targetLocales = targetLocalesOption ?? readTargetLocales2(req, defaultLocale);
|
|
675
|
-
if (targetLocales.length === 0)
|
|
676
|
-
return doc;
|
|
677
|
-
const resolverKey = options.resolverKey ?? readFirstResolverKey2(req);
|
|
678
|
-
if (!resolverKey) {
|
|
679
|
-
req.payload.logger.error({
|
|
680
|
-
msg: `auto-translate: no resolver key available for global ${globalSlug} — pass \`resolverKey\` to \`createAutoTranslateGlobalHook\` or configure \`translator.resolvers\` in \`translator({...})\``
|
|
681
|
-
});
|
|
682
|
-
return doc;
|
|
683
|
-
}
|
|
684
|
-
const updatedAt = typed.updatedAt ?? new Date().toISOString();
|
|
685
|
-
for (const toLocale of targetLocales) {
|
|
686
|
-
if (toLocale === defaultLocale)
|
|
687
|
-
continue;
|
|
688
|
-
try {
|
|
689
|
-
const job = await req.payload.jobs.queue({
|
|
690
|
-
workflow: workflowSlug,
|
|
691
|
-
input: {
|
|
692
|
-
updatedAt,
|
|
693
|
-
global: globalSlug,
|
|
694
|
-
fromLocale: defaultLocale,
|
|
695
|
-
resolver: resolverKey || undefined,
|
|
696
|
-
toLocale
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
req.payload.logger.info({
|
|
700
|
-
msg: `auto-translate: queued translation of ${globalSlug} → ${toLocale} (job ${job.id})`
|
|
701
|
-
});
|
|
702
|
-
} catch (error) {
|
|
703
|
-
req.payload.logger.error({
|
|
704
|
-
msg: `auto-translate: failed to queue ${globalSlug} → ${toLocale}: ${String(error)}`
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return doc;
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
function shouldSkipAutoTranslate2(context) {
|
|
712
|
-
return Boolean(context?.disableAutoTranslate);
|
|
713
|
-
}
|
|
714
|
-
function readRequestLocale2(req) {
|
|
715
|
-
if (!req || typeof req !== "object")
|
|
716
|
-
return "";
|
|
717
|
-
const locale = req.locale;
|
|
718
|
-
return typeof locale === "string" && locale.length > 0 ? locale : "";
|
|
719
|
-
}
|
|
720
|
-
function readConfigDefaultLocale2(req) {
|
|
721
|
-
if (!req || typeof req !== "object")
|
|
722
|
-
return "";
|
|
723
|
-
const config = req.payload?.config;
|
|
724
|
-
const def = config?.localization?.defaultLocale;
|
|
725
|
-
return typeof def === "string" && def.length > 0 ? def : "";
|
|
726
|
-
}
|
|
727
|
-
function readTargetLocales2(req, defaultLocale) {
|
|
728
|
-
if (!req || typeof req !== "object")
|
|
729
|
-
return [];
|
|
730
|
-
const list = req.payload?.config?.localization?.locales;
|
|
731
|
-
if (!Array.isArray(list) || list.length === 0)
|
|
732
|
-
return [];
|
|
733
|
-
const out = [];
|
|
734
|
-
for (const entry of list) {
|
|
735
|
-
const code = typeof entry === "string" ? entry : entry?.code;
|
|
736
|
-
if (typeof code === "string" && code.length > 0 && code !== defaultLocale) {
|
|
737
|
-
out.push(code);
|
|
738
|
-
}
|
|
173
|
+
id,
|
|
174
|
+
emptyOnly: mode !== "all",
|
|
175
|
+
locale,
|
|
176
|
+
localeFrom: defaultLocale,
|
|
177
|
+
overrideAccess: false,
|
|
178
|
+
resolver,
|
|
179
|
+
update: true
|
|
180
|
+
});
|
|
181
|
+
if (result.success) {
|
|
182
|
+
const { config } = await findEntityWithConfig({ collectionSlug, globalSlug, id, locale: defaultLocale, req });
|
|
183
|
+
await recordTranslationStatus({ req, collectionSlug, globalSlug, id, locale, config, dataFrom: result.dataFrom });
|
|
739
184
|
}
|
|
740
|
-
return
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if (!collection && !global) {
|
|
771
|
-
throw new Error("translateTask: either `collection` or `global` must be provided");
|
|
772
|
-
}
|
|
773
|
-
const resolverKey = options.resolverKey ?? inputResolver ?? readFirstResolverKey3(req);
|
|
774
|
-
if (!resolverKey) {
|
|
775
|
-
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({...})\`?`);
|
|
776
|
-
}
|
|
777
|
-
const entityLabel = collection || global;
|
|
778
|
-
req.payload.logger.info({
|
|
779
|
-
jobId: job.id,
|
|
780
|
-
msg: `translating ${entityLabel} to locale ${toLocale}`
|
|
781
|
-
});
|
|
782
|
-
let result;
|
|
783
|
-
try {
|
|
784
|
-
result = await translateOperation({
|
|
785
|
-
req,
|
|
786
|
-
collectionSlug: collection,
|
|
787
|
-
globalSlug: global,
|
|
788
|
-
emptyOnly: false,
|
|
789
|
-
id,
|
|
790
|
-
locale: toLocale,
|
|
791
|
-
localeFrom: fromLocale,
|
|
792
|
-
overrideAccess: true,
|
|
793
|
-
resolver: resolverKey,
|
|
794
|
-
update: false
|
|
795
|
-
});
|
|
796
|
-
} catch (error) {
|
|
797
|
-
req.payload.logger.error({
|
|
798
|
-
jobId: job.id,
|
|
799
|
-
msg: `translateOperation threw for ${entityLabel} → ${toLocale}: ${String(error)}`
|
|
800
|
-
});
|
|
801
|
-
throw error;
|
|
802
|
-
}
|
|
803
|
-
if (!result.success) {
|
|
804
|
-
req.payload.logger.error({
|
|
805
|
-
jobId: job.id,
|
|
806
|
-
msg: `translation for ${entityLabel} to ${toLocale} failed (resolver returned success=false)`
|
|
807
|
-
});
|
|
808
|
-
throw new Error(`translateTask: resolver returned success=false for ${entityLabel} → ${toLocale}`);
|
|
809
|
-
}
|
|
810
|
-
const translated = result.translatedData ?? {};
|
|
811
|
-
const { _locale: _dropLocale, _parent_id: _dropParent, ...data } = translated;
|
|
812
|
-
req.payload.logger.info({
|
|
813
|
-
jobId: job.id,
|
|
814
|
-
msg: `persisting ${entityLabel} (id=${id ?? global}) at locale ${toLocale}`
|
|
815
|
-
});
|
|
816
|
-
try {
|
|
817
|
-
if (collection) {
|
|
818
|
-
await req.payload.update({
|
|
819
|
-
collection,
|
|
820
|
-
data,
|
|
821
|
-
id,
|
|
822
|
-
locale: toLocale,
|
|
823
|
-
overrideAccess: true,
|
|
824
|
-
req
|
|
825
|
-
});
|
|
826
|
-
} else {
|
|
827
|
-
await req.payload.updateGlobal({
|
|
828
|
-
slug: global,
|
|
829
|
-
data,
|
|
830
|
-
locale: toLocale,
|
|
831
|
-
overrideAccess: true,
|
|
832
|
-
req
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
} catch (error) {
|
|
836
|
-
req.payload.logger.error({
|
|
837
|
-
jobId: job.id,
|
|
838
|
-
msg: `persist failed for ${entityLabel} (id=${id ?? global}) at locale ${toLocale}: ${String(error)}`
|
|
839
|
-
});
|
|
840
|
-
throw error;
|
|
841
|
-
}
|
|
842
|
-
req.payload.logger.info({
|
|
843
|
-
jobId: job.id,
|
|
844
|
-
msg: `translation complete for ${entityLabel} → ${toLocale}`
|
|
845
|
-
});
|
|
846
|
-
return { output: { success: true } };
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
function readFirstResolverKey3(req) {
|
|
851
|
-
if (!req || typeof req !== "object")
|
|
852
|
-
return "";
|
|
853
|
-
const custom = req.payload?.config?.custom?.translator?.resolvers;
|
|
854
|
-
if (!Array.isArray(custom) || custom.length === 0)
|
|
855
|
-
return "";
|
|
856
|
-
const first = custom[0];
|
|
857
|
-
return typeof first?.key === "string" ? first.key : "";
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// src/jobs/createTranslateWorkflow.ts
|
|
861
|
-
function createTranslateWorkflow(options = {}) {
|
|
862
|
-
const { slug = "translateEntityToLocales", taskSlug = "translateEntityToLocale" } = options;
|
|
863
|
-
return {
|
|
864
|
-
slug,
|
|
865
|
-
inputSchema: [
|
|
866
|
-
{ name: "id", type: "number", required: false },
|
|
867
|
-
{ name: "updatedAt", type: "date", required: true },
|
|
868
|
-
{ name: "collection", type: "text", required: false },
|
|
869
|
-
{ name: "global", type: "text", required: false },
|
|
870
|
-
{ name: "fromLocale", type: "text", required: true },
|
|
871
|
-
{ name: "toLocale", type: "text", required: true },
|
|
872
|
-
{ name: "resolver", type: "text", required: false }
|
|
873
|
-
],
|
|
874
|
-
handler: async (args) => {
|
|
875
|
-
const { job, req, tasks } = args;
|
|
876
|
-
const { id, collection, global, fromLocale, toLocale, updatedAt, resolver } = job.input;
|
|
877
|
-
if (!collection && !global) {
|
|
878
|
-
throw new Error("translateWorkflow: either `collection` or `global` must be provided");
|
|
879
|
-
}
|
|
880
|
-
if (typeof req.payload.config.localization !== "object") {
|
|
881
|
-
req.payload.logger.error({
|
|
882
|
-
jobId: job.id,
|
|
883
|
-
msg: "localization is not enabled — skipping auto-translation workflow"
|
|
884
|
-
});
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
const entityKey = collection ? `${collection}-${id}` : global;
|
|
888
|
-
const updatedAtIso = updatedAt instanceof Date ? updatedAt.toISOString() : String(updatedAt);
|
|
889
|
-
req.payload.logger.info({
|
|
890
|
-
jobId: job.id,
|
|
891
|
-
msg: `scheduling translation of ${entityKey} → ${toLocale}`
|
|
892
|
-
});
|
|
893
|
-
await tasks[taskSlug](`${entityKey}-${fromLocale}-${toLocale}-${updatedAtIso}`, {
|
|
894
|
-
input: {
|
|
895
|
-
id,
|
|
896
|
-
collection,
|
|
897
|
-
global,
|
|
898
|
-
fromLocale,
|
|
899
|
-
toLocale,
|
|
900
|
-
resolver
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
};
|
|
905
|
-
}
|
|
185
|
+
return Response.json({ success: result.success });
|
|
186
|
+
};
|
|
187
|
+
var markReviewedHandler = async (req) => {
|
|
188
|
+
const { collectionSlug, globalSlug, id, locale } = await readReviewRequest(req);
|
|
189
|
+
const { defaultLocale } = readLocales(req);
|
|
190
|
+
const { config, doc } = await findEntityWithConfig({
|
|
191
|
+
collectionSlug,
|
|
192
|
+
globalSlug,
|
|
193
|
+
id,
|
|
194
|
+
locale: defaultLocale,
|
|
195
|
+
overrideAccess: false,
|
|
196
|
+
req
|
|
197
|
+
});
|
|
198
|
+
const user = req.user;
|
|
199
|
+
await recordTranslationStatus({
|
|
200
|
+
req,
|
|
201
|
+
collectionSlug,
|
|
202
|
+
globalSlug,
|
|
203
|
+
id,
|
|
204
|
+
locale,
|
|
205
|
+
config,
|
|
206
|
+
dataFrom: doc,
|
|
207
|
+
reviewedBy: user.email ?? String(user.id)
|
|
208
|
+
});
|
|
209
|
+
return Response.json({ success: true });
|
|
210
|
+
};
|
|
211
|
+
var reviewEndpoints = [
|
|
212
|
+
{ path: "/translator/review/translate", method: "post", handler: translateHandler },
|
|
213
|
+
{ path: "/translator/review/mark-reviewed", method: "post", handler: markReviewedHandler }
|
|
214
|
+
];
|
|
906
215
|
|
|
907
216
|
// src/translate/endpoint.ts
|
|
908
|
-
import { APIError as
|
|
217
|
+
import { APIError as APIError2 } from "payload";
|
|
909
218
|
var translateEndpoint = async (req) => {
|
|
219
|
+
if (!req.user)
|
|
220
|
+
throw new APIError2("Unauthorized", 401);
|
|
910
221
|
if (!req.json)
|
|
911
|
-
throw new
|
|
222
|
+
throw new APIError2("Content-Type should be json");
|
|
912
223
|
const args = await req.json();
|
|
913
224
|
const { collectionSlug, data, emptyOnly, globalSlug, id, locale, localeFrom, resolver } = args;
|
|
914
|
-
const
|
|
225
|
+
const pluginConfig = req.payload.config.custom?.translator;
|
|
226
|
+
const allowed = globalSlug ? pluginConfig?.globals.includes(globalSlug) : pluginConfig?.collections.includes(collectionSlug);
|
|
227
|
+
if (!allowed)
|
|
228
|
+
throw new APIError2("Forbidden", 403);
|
|
229
|
+
const result = await translateOperation2({
|
|
915
230
|
collectionSlug,
|
|
916
231
|
data,
|
|
917
232
|
emptyOnly,
|
|
@@ -926,255 +241,34 @@ var translateEndpoint = async (req) => {
|
|
|
926
241
|
});
|
|
927
242
|
return Response.json(result);
|
|
928
243
|
};
|
|
929
|
-
|
|
930
|
-
// src/resolvers/copy.ts
|
|
931
|
-
var copyResolver = () => {
|
|
932
|
-
return {
|
|
933
|
-
key: "copy",
|
|
934
|
-
resolve: (args) => {
|
|
935
|
-
const { texts } = args;
|
|
936
|
-
return {
|
|
937
|
-
success: true,
|
|
938
|
-
translatedTexts: texts
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
};
|
|
942
|
-
};
|
|
943
|
-
// src/utils/chunkArray.ts
|
|
944
|
-
var chunkArray = (array, length) => {
|
|
945
|
-
return Array.from({ length: Math.ceil(array.length / length) }, (_, i) => array.slice(i * length, i * length + length));
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
// src/resolvers/google.ts
|
|
949
|
-
var localeToCountryCodeMapper = {
|
|
950
|
-
ua: "uk"
|
|
951
|
-
};
|
|
952
|
-
var mapLocale = (incoming) => (incoming in localeToCountryCodeMapper) ? localeToCountryCodeMapper[incoming] : incoming;
|
|
953
|
-
var googleResolver = ({
|
|
954
|
-
apiKey,
|
|
955
|
-
chunkLength = 100
|
|
956
|
-
}) => {
|
|
957
|
-
return {
|
|
958
|
-
key: "google",
|
|
959
|
-
resolve: async (args) => {
|
|
960
|
-
const { localeFrom, localeTo, req, texts } = args;
|
|
961
|
-
const apiUrl = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
|
|
962
|
-
const responses = await Promise.all(chunkArray(texts, chunkLength).map((q) => fetch(apiUrl, {
|
|
963
|
-
body: JSON.stringify({
|
|
964
|
-
q,
|
|
965
|
-
source: mapLocale(localeFrom),
|
|
966
|
-
target: mapLocale(localeTo)
|
|
967
|
-
}),
|
|
968
|
-
headers: {
|
|
969
|
-
"Content-Type": "application/json"
|
|
970
|
-
},
|
|
971
|
-
method: "POST"
|
|
972
|
-
}).then(async (res) => {
|
|
973
|
-
const data = await res.json();
|
|
974
|
-
if (!res.ok)
|
|
975
|
-
req.payload.logger.info({
|
|
976
|
-
googleResponse: data,
|
|
977
|
-
message: "An error occurred when trying to translate the data using Google API"
|
|
978
|
-
});
|
|
979
|
-
return {
|
|
980
|
-
data,
|
|
981
|
-
success: res.ok
|
|
982
|
-
};
|
|
983
|
-
})));
|
|
984
|
-
if (responses.some((res) => !res.success)) {
|
|
985
|
-
return {
|
|
986
|
-
success: false
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
const translatedTexts = responses.flatMap((chunk) => chunk.data.data.translations).map((translation) => translation.translatedText);
|
|
990
|
-
return {
|
|
991
|
-
success: true,
|
|
992
|
-
translatedTexts
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
};
|
|
997
|
-
// src/resolvers/libreTranslate.ts
|
|
998
|
-
var localeToCountryCodeMapper2 = {
|
|
999
|
-
ua: "uk"
|
|
1000
|
-
};
|
|
1001
|
-
var mapLocale2 = (incoming) => (incoming in localeToCountryCodeMapper2) ? localeToCountryCodeMapper2[incoming] : incoming;
|
|
1002
|
-
var libreResolver = ({
|
|
1003
|
-
apiKey,
|
|
1004
|
-
chunkLength = 100,
|
|
1005
|
-
url = "https://libretranslate.com/translate"
|
|
1006
|
-
}) => {
|
|
1007
|
-
return {
|
|
1008
|
-
key: "libre",
|
|
1009
|
-
resolve: async (args) => {
|
|
1010
|
-
const { localeFrom, localeTo, req, texts } = args;
|
|
1011
|
-
const apiUrl = url;
|
|
1012
|
-
const responses = await Promise.all(chunkArray(texts, chunkLength).map((q) => fetch(apiUrl, {
|
|
1013
|
-
body: JSON.stringify({
|
|
1014
|
-
api_key: apiKey,
|
|
1015
|
-
q,
|
|
1016
|
-
source: mapLocale2(localeFrom),
|
|
1017
|
-
target: mapLocale2(localeTo)
|
|
1018
|
-
}),
|
|
1019
|
-
headers: {
|
|
1020
|
-
"Content-Type": "application/json"
|
|
1021
|
-
},
|
|
1022
|
-
method: "POST"
|
|
1023
|
-
}).then(async (res) => {
|
|
1024
|
-
const data = await res.json();
|
|
1025
|
-
if (!res.ok)
|
|
1026
|
-
req.payload.logger.info({
|
|
1027
|
-
libreResponse: data,
|
|
1028
|
-
message: "An error occurred when trying to translate the data using LibreTranslate API"
|
|
1029
|
-
});
|
|
1030
|
-
return {
|
|
1031
|
-
data,
|
|
1032
|
-
success: res.ok
|
|
1033
|
-
};
|
|
1034
|
-
})));
|
|
1035
|
-
if (responses.some((res) => !res.success)) {
|
|
1036
|
-
return {
|
|
1037
|
-
success: false
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
const translatedTexts = responses.flatMap((chunk) => chunk.data.translatedText);
|
|
1041
|
-
return {
|
|
1042
|
-
success: true,
|
|
1043
|
-
translatedTexts
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
};
|
|
1048
|
-
// src/resolvers/openAI.ts
|
|
1049
|
-
var defaultPrompt = ({ localeFrom, localeTo, texts }) => {
|
|
1050
|
-
return `You are a machine translation service. Your task is to translate values in a strict JSON of key value pairs from ${localeFrom} to ${localeTo}.
|
|
1051
|
-
|
|
1052
|
-
**INSTRUCTIONS:**
|
|
1053
|
-
1. **Translate each value**.
|
|
1054
|
-
2. The output **must valid JSON**.
|
|
1055
|
-
3. The output array **must have the exact same number of elements** as the input.
|
|
1056
|
-
4. Preserve the structure of the JSON array.
|
|
1057
|
-
5. Preserve JSON keys without translation.
|
|
1058
|
-
6. The **order of the elements must not change**.
|
|
1059
|
-
7. **Do not include any text, explanations, or remarks outside of the JSON array.**
|
|
1060
|
-
8. **Preserve any special characters, HTML tags, or formatting** present in the original strings.
|
|
1061
|
-
9. **Preserve urls, hrefs, and email addresses** without translation.
|
|
1062
|
-
10. RETURN ONLY THE RAW JSON, DO NOT RESPOND WITH ANYTHING ELSE, AND NO FORMATTING.
|
|
1063
|
-
|
|
1064
|
-
**INPUT JSON TO TRANSLATE:**
|
|
1065
|
-
${JSON.stringify(texts)}`;
|
|
1066
|
-
};
|
|
1067
|
-
var openAIResolver = ({
|
|
1068
|
-
apiKey,
|
|
1069
|
-
baseUrl,
|
|
1070
|
-
chunkLength = 100,
|
|
1071
|
-
model = "gpt-3.5-turbo",
|
|
1072
|
-
prompt = defaultPrompt
|
|
1073
|
-
}) => {
|
|
1074
|
-
return {
|
|
1075
|
-
key: "openai",
|
|
1076
|
-
resolve: async ({ localeFrom, localeTo, req, texts }) => {
|
|
1077
|
-
const apiUrl = `${baseUrl || "https://api.openai.com"}/v1/chat/completions`;
|
|
1078
|
-
try {
|
|
1079
|
-
const response = await Promise.all(chunkArray(texts, chunkLength).map((texts2) => {
|
|
1080
|
-
const structuredTexts = texts2.reduce((acc, curr, index) => {
|
|
1081
|
-
acc[index + 1] = curr;
|
|
1082
|
-
return acc;
|
|
1083
|
-
}, {});
|
|
1084
|
-
return fetch(apiUrl, {
|
|
1085
|
-
body: JSON.stringify({
|
|
1086
|
-
messages: [
|
|
1087
|
-
{
|
|
1088
|
-
content: prompt({ localeFrom, localeTo, texts: structuredTexts }),
|
|
1089
|
-
role: "user"
|
|
1090
|
-
}
|
|
1091
|
-
],
|
|
1092
|
-
model
|
|
1093
|
-
}),
|
|
1094
|
-
headers: {
|
|
1095
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1096
|
-
"Content-Type": "application/json"
|
|
1097
|
-
},
|
|
1098
|
-
method: "post"
|
|
1099
|
-
}).then(async (res) => {
|
|
1100
|
-
const data = await res.json();
|
|
1101
|
-
if (!res.ok)
|
|
1102
|
-
req.payload.logger.info({
|
|
1103
|
-
message: "An error occurred when trying to translate the data using OpenAI API",
|
|
1104
|
-
openAIresponse: data
|
|
1105
|
-
});
|
|
1106
|
-
return {
|
|
1107
|
-
data,
|
|
1108
|
-
success: res.ok
|
|
1109
|
-
};
|
|
1110
|
-
});
|
|
1111
|
-
}));
|
|
1112
|
-
const translated = [];
|
|
1113
|
-
for (const { data, success } of response) {
|
|
1114
|
-
if (!success)
|
|
1115
|
-
return {
|
|
1116
|
-
success: false
|
|
1117
|
-
};
|
|
1118
|
-
const content = data?.choices?.[0]?.message?.content;
|
|
1119
|
-
if (!content) {
|
|
1120
|
-
req.payload.logger.error("An error occurred when trying to translate the data using OpenAI API - missing content in the response");
|
|
1121
|
-
return {
|
|
1122
|
-
success: false
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
const translatedStructuredTexts = JSON.parse(content);
|
|
1126
|
-
const translatedChunk = Object.values(translatedStructuredTexts);
|
|
1127
|
-
if (!Array.isArray(translatedChunk)) {
|
|
1128
|
-
req.payload.logger.error({
|
|
1129
|
-
data: translatedChunk,
|
|
1130
|
-
fullContent: content,
|
|
1131
|
-
message: "An error occurred when trying to translate the data using OpenAI API - parsed content is not an array"
|
|
1132
|
-
});
|
|
1133
|
-
return {
|
|
1134
|
-
success: false
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
for (const text of translatedChunk) {
|
|
1138
|
-
if (text && typeof text !== "string") {
|
|
1139
|
-
req.payload.logger.error({
|
|
1140
|
-
chunkData: translatedChunk,
|
|
1141
|
-
data: text,
|
|
1142
|
-
fullContent: content,
|
|
1143
|
-
message: "An error occurred when trying to translate the data using OpenAI API - parsed content is not a string"
|
|
1144
|
-
});
|
|
1145
|
-
return {
|
|
1146
|
-
success: false
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
translated.push(text);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
return {
|
|
1153
|
-
success: true,
|
|
1154
|
-
translatedTexts: translated
|
|
1155
|
-
};
|
|
1156
|
-
} catch (e) {
|
|
1157
|
-
if (e instanceof Error) {
|
|
1158
|
-
req.payload.logger.info({
|
|
1159
|
-
message: "An error occurred when trying to translate the data using OpenAI API",
|
|
1160
|
-
originalErr: e.message
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
return { success: false };
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
};
|
|
1167
|
-
};
|
|
1168
244
|
// src/index.ts
|
|
1169
245
|
var AUTO_TRANSLATE_MARKER = Symbol.for("@justanarthur/payload-plugin-translator/auto-translate");
|
|
1170
246
|
var translator = (pluginConfig) => {
|
|
1171
247
|
return (config) => {
|
|
1172
248
|
if (pluginConfig.disabled || !config.localization || config.localization.locales.length < 2)
|
|
1173
249
|
return config;
|
|
250
|
+
const autoTranslate = pluginConfig.autoTranslate ?? true;
|
|
251
|
+
const review = pluginConfig.review ?? true;
|
|
1174
252
|
const updatedConfig = {
|
|
1175
253
|
...config,
|
|
1176
254
|
admin: {
|
|
1177
255
|
...config.admin ?? {},
|
|
256
|
+
...review ? {
|
|
257
|
+
components: {
|
|
258
|
+
...config.admin?.components ?? {},
|
|
259
|
+
afterNavLinks: [
|
|
260
|
+
...config.admin?.components?.afterNavLinks ?? [],
|
|
261
|
+
"@justanarthur/payload-plugin-translator/client#TranslationsNavLink"
|
|
262
|
+
],
|
|
263
|
+
views: {
|
|
264
|
+
...config.admin?.components?.views ?? {},
|
|
265
|
+
translations: {
|
|
266
|
+
Component: "@justanarthur/payload-plugin-translator/rsc#TranslationsView",
|
|
267
|
+
path: REVIEW_VIEW_PATH
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} : {},
|
|
1178
272
|
custom: {
|
|
1179
273
|
...config.admin?.custom ?? {},
|
|
1180
274
|
translator: {
|
|
@@ -1182,27 +276,30 @@ var translator = (pluginConfig) => {
|
|
|
1182
276
|
}
|
|
1183
277
|
}
|
|
1184
278
|
},
|
|
1185
|
-
collections:
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
279
|
+
collections: [
|
|
280
|
+
...config.collections?.map((collection) => {
|
|
281
|
+
if (!pluginConfig.collections.includes(collection.slug))
|
|
282
|
+
return collection;
|
|
283
|
+
return {
|
|
284
|
+
...collection,
|
|
285
|
+
admin: {
|
|
286
|
+
...collection.admin ?? {},
|
|
287
|
+
components: {
|
|
288
|
+
...collection.admin?.components ?? {},
|
|
289
|
+
edit: {
|
|
290
|
+
...collection.admin?.components?.edit ?? {},
|
|
291
|
+
PublishButton: CustomButton("publish"),
|
|
292
|
+
SaveButton: CustomButton("save")
|
|
293
|
+
}
|
|
1198
294
|
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
|
|
295
|
+
},
|
|
296
|
+
...autoTranslate ? {
|
|
297
|
+
hooks: attachCollectionHook(collection.hooks, createAutoTranslateCollectionHook2({ collectionSlug: String(collection.slug) }).afterChange)
|
|
298
|
+
} : {}
|
|
299
|
+
};
|
|
300
|
+
}) ?? [],
|
|
301
|
+
...review ? [createTranslationStatusCollection()] : []
|
|
302
|
+
],
|
|
1206
303
|
custom: {
|
|
1207
304
|
...config.custom ?? {},
|
|
1208
305
|
translator: {
|
|
@@ -1215,7 +312,8 @@ var translator = (pluginConfig) => {
|
|
|
1215
312
|
handler: translateEndpoint,
|
|
1216
313
|
method: "post",
|
|
1217
314
|
path: "/translator/translate"
|
|
1218
|
-
}
|
|
315
|
+
},
|
|
316
|
+
...review ? reviewEndpoints : []
|
|
1219
317
|
],
|
|
1220
318
|
globals: config.globals?.map((global) => {
|
|
1221
319
|
if (!pluginConfig.globals.includes(global.slug))
|
|
@@ -1233,8 +331,8 @@ var translator = (pluginConfig) => {
|
|
|
1233
331
|
}
|
|
1234
332
|
}
|
|
1235
333
|
},
|
|
1236
|
-
...
|
|
1237
|
-
hooks: attachGlobalHook(global.hooks,
|
|
334
|
+
...autoTranslate ? {
|
|
335
|
+
hooks: attachGlobalHook(global.hooks, createAutoTranslateGlobalHook2({ globalSlug: String(global.slug) }))
|
|
1238
336
|
} : {}
|
|
1239
337
|
};
|
|
1240
338
|
}) ?? [],
|
|
@@ -1244,11 +342,20 @@ var translator = (pluginConfig) => {
|
|
|
1244
342
|
...deepMerge(config.i18n?.translations ?? {}, translations)
|
|
1245
343
|
}
|
|
1246
344
|
},
|
|
1247
|
-
...
|
|
345
|
+
...autoTranslate ? {
|
|
1248
346
|
jobs: {
|
|
1249
347
|
...config.jobs ?? {},
|
|
1250
|
-
tasks: ensureJobBySlug(config.jobs?.tasks,
|
|
1251
|
-
workflows: ensureJobBySlug(config.jobs?.workflows,
|
|
348
|
+
tasks: ensureJobBySlug(config.jobs?.tasks, createTranslateTask2()),
|
|
349
|
+
workflows: ensureJobBySlug(config.jobs?.workflows, createTranslateWorkflow2()),
|
|
350
|
+
deleteJobOnComplete: false,
|
|
351
|
+
jobsCollectionOverrides: ({ defaultJobsCollection }) => {
|
|
352
|
+
defaultJobsCollection.admin = {
|
|
353
|
+
...defaultJobsCollection.admin,
|
|
354
|
+
hidden: false,
|
|
355
|
+
group: "System"
|
|
356
|
+
};
|
|
357
|
+
return defaultJobsCollection;
|
|
358
|
+
}
|
|
1252
359
|
}
|
|
1253
360
|
} : {}
|
|
1254
361
|
};
|
|
@@ -1291,11 +398,12 @@ function markAutoTranslate(hook) {
|
|
|
1291
398
|
function isMarkedAutoTranslate(hook) {
|
|
1292
399
|
return Boolean(hook?.[AUTO_TRANSLATE_MARKER]);
|
|
1293
400
|
}
|
|
1294
|
-
|
|
1295
401
|
// src/exports/index.ts
|
|
1296
402
|
var exports_default = translator;
|
|
1297
403
|
export {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
exports_default as default
|
|
404
|
+
TRANSLATION_STATUS_SLUG2 as TRANSLATION_STATUS_SLUG,
|
|
405
|
+
createTranslationStatusCollection,
|
|
406
|
+
exports_default as default,
|
|
407
|
+
translateOperation2 as translateOperation,
|
|
408
|
+
translator
|
|
1301
409
|
};
|