@justanarthur/payload-plugin-translator 1.3.20 → 1.3.21

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.
Files changed (128) hide show
  1. package/dist/client.d.ts +6 -0
  2. package/dist/client.js +320 -0
  3. package/dist/index.d.ts +102 -11
  4. package/dist/index.js +1294 -80
  5. package/dist/jobs.d.ts +195 -0
  6. package/dist/jobs.js +824 -0
  7. package/dist/resolvers/copy.d.ts +21 -3
  8. package/dist/resolvers/copy.js +18 -12
  9. package/dist/resolvers/google.d.ts +29 -11
  10. package/dist/resolvers/google.js +58 -44
  11. package/dist/resolvers/libreTranslate.d.ts +33 -15
  12. package/dist/resolvers/libreTranslate.js +60 -45
  13. package/dist/resolvers/openAI.d.ts +39 -21
  14. package/dist/resolvers/openAI.js +110 -114
  15. package/dist/resolvers/types.d.ts +16 -16
  16. package/dist/resolvers/types.js +0 -3
  17. package/dist/{client/components/CustomButton/styles.scss → styles-jt3fm5fh.scss} +5 -5
  18. package/dist/{client/components/TranslatorModal/styles.scss → styles-vs6m7h55.scss} +76 -76
  19. package/dist/types.d.ts +70 -22
  20. package/dist/types.js +0 -3
  21. package/package.json +87 -64
  22. package/README.md +0 -215
  23. package/dist/client/api/index.d.ts +0 -8
  24. package/dist/client/api/index.d.ts.map +0 -1
  25. package/dist/client/api/index.js +0 -28
  26. package/dist/client/api/index.js.map +0 -1
  27. package/dist/client/components/CustomButton/CustomButtonWithTranslator.d.ts +0 -5
  28. package/dist/client/components/CustomButton/CustomButtonWithTranslator.d.ts.map +0 -1
  29. package/dist/client/components/CustomButton/CustomButtonWithTranslator.js +0 -28
  30. package/dist/client/components/CustomButton/CustomButtonWithTranslator.js.map +0 -1
  31. package/dist/client/components/CustomButton/index.d.ts +0 -3
  32. package/dist/client/components/CustomButton/index.d.ts.map +0 -1
  33. package/dist/client/components/CustomButton/index.js +0 -10
  34. package/dist/client/components/CustomButton/index.js.map +0 -1
  35. package/dist/client/components/LocaleLabel/LocaleLabel.d.ts +0 -5
  36. package/dist/client/components/LocaleLabel/LocaleLabel.d.ts.map +0 -1
  37. package/dist/client/components/LocaleLabel/LocaleLabel.js +0 -28
  38. package/dist/client/components/LocaleLabel/LocaleLabel.js.map +0 -1
  39. package/dist/client/components/LocaleLabel/index.d.ts +0 -2
  40. package/dist/client/components/LocaleLabel/index.d.ts.map +0 -1
  41. package/dist/client/components/LocaleLabel/index.js +0 -3
  42. package/dist/client/components/LocaleLabel/index.js.map +0 -1
  43. package/dist/client/components/ResolverButton/ResolverButton.d.ts +0 -5
  44. package/dist/client/components/ResolverButton/ResolverButton.d.ts.map +0 -1
  45. package/dist/client/components/ResolverButton/ResolverButton.js +0 -16
  46. package/dist/client/components/ResolverButton/ResolverButton.js.map +0 -1
  47. package/dist/client/components/ResolverButton/index.d.ts +0 -2
  48. package/dist/client/components/ResolverButton/index.d.ts.map +0 -1
  49. package/dist/client/components/ResolverButton/index.js +0 -3
  50. package/dist/client/components/ResolverButton/index.js.map +0 -1
  51. package/dist/client/components/TranslatorModal/Content.d.ts +0 -2
  52. package/dist/client/components/TranslatorModal/Content.d.ts.map +0 -1
  53. package/dist/client/components/TranslatorModal/Content.js +0 -60
  54. package/dist/client/components/TranslatorModal/Content.js.map +0 -1
  55. package/dist/client/components/TranslatorModal/TranslatorModal.d.ts +0 -3
  56. package/dist/client/components/TranslatorModal/TranslatorModal.d.ts.map +0 -1
  57. package/dist/client/components/TranslatorModal/TranslatorModal.js +0 -26
  58. package/dist/client/components/TranslatorModal/TranslatorModal.js.map +0 -1
  59. package/dist/client/components/TranslatorModal/index.d.ts +0 -2
  60. package/dist/client/components/TranslatorModal/index.d.ts.map +0 -1
  61. package/dist/client/components/TranslatorModal/index.js +0 -3
  62. package/dist/client/components/TranslatorModal/index.js.map +0 -1
  63. package/dist/client/providers/Translator/TranslatorProvider.d.ts +0 -5
  64. package/dist/client/providers/Translator/TranslatorProvider.d.ts.map +0 -1
  65. package/dist/client/providers/Translator/TranslatorProvider.js +0 -109
  66. package/dist/client/providers/Translator/TranslatorProvider.js.map +0 -1
  67. package/dist/client/providers/Translator/context.d.ts +0 -21
  68. package/dist/client/providers/Translator/context.d.ts.map +0 -1
  69. package/dist/client/providers/Translator/context.js +0 -9
  70. package/dist/client/providers/Translator/context.js.map +0 -1
  71. package/dist/exports/client.d.ts +0 -2
  72. package/dist/exports/client.d.ts.map +0 -1
  73. package/dist/exports/client.js +0 -3
  74. package/dist/exports/client.js.map +0 -1
  75. package/dist/i18n-translations.d.ts +0 -87
  76. package/dist/i18n-translations.d.ts.map +0 -1
  77. package/dist/i18n-translations.js +0 -88
  78. package/dist/i18n-translations.js.map +0 -1
  79. package/dist/index.d.ts.map +0 -1
  80. package/dist/index.js.map +0 -1
  81. package/dist/resolvers/copy.d.ts.map +0 -1
  82. package/dist/resolvers/copy.js.map +0 -1
  83. package/dist/resolvers/google.d.ts.map +0 -1
  84. package/dist/resolvers/google.js.map +0 -1
  85. package/dist/resolvers/libreTranslate.d.ts.map +0 -1
  86. package/dist/resolvers/libreTranslate.js.map +0 -1
  87. package/dist/resolvers/openAI.d.ts.map +0 -1
  88. package/dist/resolvers/openAI.js.map +0 -1
  89. package/dist/resolvers/types.d.ts.map +0 -1
  90. package/dist/resolvers/types.js.map +0 -1
  91. package/dist/translate/endpoint.d.ts +0 -3
  92. package/dist/translate/endpoint.d.ts.map +0 -1
  93. package/dist/translate/endpoint.js +0 -23
  94. package/dist/translate/endpoint.js.map +0 -1
  95. package/dist/translate/findEntityWithConfig.d.ts +0 -16
  96. package/dist/translate/findEntityWithConfig.d.ts.map +0 -1
  97. package/dist/translate/findEntityWithConfig.js +0 -34
  98. package/dist/translate/findEntityWithConfig.js.map +0 -1
  99. package/dist/translate/operation.d.ts +0 -9
  100. package/dist/translate/operation.d.ts.map +0 -1
  101. package/dist/translate/operation.js +0 -78
  102. package/dist/translate/operation.js.map +0 -1
  103. package/dist/translate/traverseFields.d.ts +0 -17
  104. package/dist/translate/traverseFields.d.ts.map +0 -1
  105. package/dist/translate/traverseFields.js +0 -233
  106. package/dist/translate/traverseFields.js.map +0 -1
  107. package/dist/translate/traverseRichText.d.ts +0 -10
  108. package/dist/translate/traverseRichText.d.ts.map +0 -1
  109. package/dist/translate/traverseRichText.js +0 -24
  110. package/dist/translate/traverseRichText.js.map +0 -1
  111. package/dist/translate/types.d.ts +0 -27
  112. package/dist/translate/types.d.ts.map +0 -1
  113. package/dist/translate/types.js +0 -3
  114. package/dist/translate/types.js.map +0 -1
  115. package/dist/translate/updateEntity.d.ts +0 -14
  116. package/dist/translate/updateEntity.d.ts.map +0 -1
  117. package/dist/translate/updateEntity.js +0 -26
  118. package/dist/translate/updateEntity.js.map +0 -1
  119. package/dist/types.d.ts.map +0 -1
  120. package/dist/types.js.map +0 -1
  121. package/dist/utils/chunkArray.d.ts +0 -2
  122. package/dist/utils/chunkArray.d.ts.map +0 -1
  123. package/dist/utils/chunkArray.js +0 -7
  124. package/dist/utils/chunkArray.js.map +0 -1
  125. package/dist/utils/isEmpty.d.ts +0 -2
  126. package/dist/utils/isEmpty.d.ts.map +0 -1
  127. package/dist/utils/isEmpty.js +0 -8
  128. package/dist/utils/isEmpty.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,87 +1,1301 @@
1
- import { deepMerge } from 'payload/shared';
2
- import { CustomButton } from './client/components/CustomButton';
3
- import { translations } from './i18n-translations';
4
- import { translateEndpoint } from './translate/endpoint';
5
- import { translateOperation } from './translate/operation';
6
- export { copyResolver } from './resolvers/copy';
7
- export { googleResolver } from './resolvers/google';
8
- export { libreResolver } from './resolvers/libreTranslate';
9
- export { openAIResolver } from './resolvers/openAI';
10
- export * from './resolvers/types';
11
- export { translateOperation };
12
- export const translator = (pluginConfig)=>{
13
- return (config)=>{
14
- if (pluginConfig.disabled || !config.localization || config.localization.locales.length < 2) return config;
15
- const updatedConfig = {
16
- ...config,
17
- admin: {
18
- ...config.admin ?? {},
19
- custom: {
20
- ...config.admin?.custom ?? {},
21
- translator: {
22
- resolvers: pluginConfig.resolvers.map(({ key })=>({
23
- key
24
- }))
25
- }
26
- }
27
- },
28
- collections: config.collections?.map((collection)=>{
29
- if (!pluginConfig.collections.includes(collection.slug)) return collection;
30
- return {
31
- ...collection,
32
- admin: {
33
- ...collection.admin ?? {},
34
- components: {
35
- ...collection.admin?.components ?? {},
36
- edit: {
37
- ...collection.admin?.components?.edit ?? {},
38
- PublishButton: CustomButton('publish'),
39
- SaveButton: CustomButton('save')
40
- }
41
- }
42
- }
43
- };
44
- }) ?? [],
45
- custom: {
46
- ...config.custom ?? {},
47
- translator: {
48
- ...pluginConfig
49
- }
50
- },
51
- endpoints: [
52
- ...config.endpoints ?? [],
1
+ // src/translate/operation.ts
2
+ import he from "he";
3
+ import { APIError as APIError3 } from "payload";
4
+
5
+ // src/translate/findEntityWithConfig.ts
6
+ import { APIError } from "payload";
7
+ var findConfigBySlug = (slug, enities) => enities.find((entity) => entity.slug === slug);
8
+ var findEntityWithConfig = async (args) => {
9
+ const { collectionSlug, globalSlug, id, locale, overrideAccess, req } = args;
10
+ if (!collectionSlug && !globalSlug)
11
+ throw new APIError("Bad Request", 400);
12
+ const { payload } = req;
13
+ const { config } = payload;
14
+ const isGlobal = !!globalSlug;
15
+ if (!isGlobal && !id)
16
+ throw new APIError("Bad Request", 400);
17
+ const entityConfig = isGlobal ? findConfigBySlug(globalSlug, config.globals) : findConfigBySlug(collectionSlug, config.collections);
18
+ if (!entityConfig)
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
+ };
449
+
450
+ // src/index.ts
451
+ import { deepMerge } from "payload/shared";
452
+
453
+ // src/client/components/CustomButton/index.tsx
454
+ var CustomButton = (type) => {
455
+ return {
456
+ clientProps: {
457
+ type
458
+ },
459
+ path: "@justanarthur/payload-plugin-translator/client#CustomButtonWithTranslator"
460
+ };
461
+ };
462
+
463
+ // src/i18n-translations.ts
464
+ var translations = {
465
+ en: {
466
+ "plugin-translator": {
467
+ resolver_copy_buttonLabel: "Copy from other locale",
468
+ resolver_copy_errorMessage: "An error occurred when trying to translate the data",
469
+ resolver_copy_modalTitle: "Choose the locale to copy from",
470
+ resolver_copy_submitButtonLabelEmpty: "Copy only empty fields",
471
+ resolver_copy_submitButtonLabelFull: "Copy all",
472
+ resolver_copy_successMessage: 'Successfully copied. Press "Save" to apply the changes.',
473
+ resolver_google_buttonLabel: "Google Translate",
474
+ resolver_google_errorMessage: "An error occurred when trying to translate the data",
475
+ resolver_google_modalTitle: "Choose the locale to translate from",
476
+ resolver_google_submitButtonLabelEmpty: "Translate only empty fields",
477
+ resolver_google_submitButtonLabelFull: "Translate all",
478
+ resolver_google_successMessage: 'Successfully translated. Press "Save" to apply the changes.',
479
+ resolver_libre_buttonLabel: "Libre Translate",
480
+ resolver_libre_errorMessage: "An error occurred when trying to translate the data",
481
+ resolver_libre_modalTitle: "Choose the locale to translate from",
482
+ resolver_libre_submitButtonLabelEmpty: "Translate only empty fields",
483
+ resolver_libre_submitButtonLabelFull: "Translate all",
484
+ resolver_libre_successMessage: 'Successfully translated. Press "Save" to apply the changes.',
485
+ resolver_openai_buttonLabel: "AI Translate",
486
+ resolver_openai_errorMessage: "An error occurred when trying to translate the data",
487
+ resolver_openai_modalTitle: "Choose the locale to translate from",
488
+ resolver_openai_submitButtonLabelEmpty: "Translate only empty fields",
489
+ resolver_openai_submitButtonLabelFull: "Translate all",
490
+ resolver_openai_successMessage: 'Successfully translated. Press "Save" to apply the changes.'
491
+ }
492
+ },
493
+ uk: {
494
+ "plugin-translator": {
495
+ resolver_copy_buttonLabel: "Копіювати з іншої локалі",
496
+ resolver_copy_errorMessage: "Сталася помилка під час спроби перекладу даних",
497
+ resolver_copy_modalTitle: "Виберіть локаль для копіювання",
498
+ resolver_copy_submitButtonLabelEmpty: "Копіювати лише порожні поля",
499
+ resolver_copy_submitButtonLabelFull: "Копіювати все",
500
+ resolver_copy_successMessage: 'Успішно скопійовано. Натисніть "Зберегти", щоб застосувати зміни.',
501
+ resolver_google_buttonLabel: "Переклад Google",
502
+ resolver_google_errorMessage: "Сталася помилка під час спроби перекладу даних",
503
+ resolver_google_modalTitle: "Виберіть локаль для перекладу",
504
+ resolver_google_submitButtonLabelEmpty: "Перекласти лише порожні поля",
505
+ resolver_google_submitButtonLabelFull: "Перекласти все",
506
+ resolver_google_successMessage: 'Успішно перекладено. Натисніть "Зберегти", щоб застосувати зміни.',
507
+ resolver_libre_buttonLabel: "Переклад Libre",
508
+ resolver_libre_errorMessage: "Сталася помилка під час спроби перекладу даних",
509
+ resolver_libre_modalTitle: "Виберіть локаль для перекладу",
510
+ resolver_libre_submitButtonLabelEmpty: "Перекласти лише порожні поля",
511
+ resolver_libre_submitButtonLabelFull: "Перекласти все",
512
+ resolver_libre_successMessage: 'Успішно перекладено. Натисніть "Зберегти", щоб застосувати зміни.',
513
+ resolver_openai_buttonLabel: "Переклад AI",
514
+ resolver_openai_errorMessage: "Сталася помилка під час спроби перекладу даних",
515
+ resolver_openai_modalTitle: "Виберіть локаль для перекладу",
516
+ resolver_openai_submitButtonLabelEmpty: "Перекласти лише порожні поля",
517
+ resolver_openai_submitButtonLabelFull: "Перекласти все",
518
+ resolver_openai_successMessage: 'Успішно перекладено. Натисніть "Зберегти", щоб застосувати зміни.'
519
+ }
520
+ },
521
+ zh: {
522
+ "plugin-translator": {
523
+ resolver_copy_buttonLabel: "从其他语言复制",
524
+ resolver_copy_errorMessage: "尝试翻译数据时发生错误",
525
+ resolver_copy_modalTitle: "选择要复制的语言",
526
+ resolver_copy_submitButtonLabelEmpty: "仅复制空字段",
527
+ resolver_copy_submitButtonLabelFull: "复制全部",
528
+ resolver_copy_successMessage: "复制成功。按下“保存”以应用更改。",
529
+ resolver_google_buttonLabel: "谷歌翻译",
530
+ resolver_google_errorMessage: "尝试翻译数据时发生错误",
531
+ resolver_google_modalTitle: "选择要翻译的语言",
532
+ resolver_google_submitButtonLabelEmpty: "仅翻译空字段",
533
+ resolver_google_submitButtonLabelFull: "翻译全部",
534
+ resolver_google_successMessage: "翻译成功。按下“保存”以应用更改。",
535
+ resolver_libre_buttonLabel: "自由翻译",
536
+ resolver_libre_errorMessage: "尝试翻译数据时发生错误",
537
+ resolver_libre_modalTitle: "选择要翻译的语言",
538
+ resolver_libre_submitButtonLabelEmpty: "仅翻译空字段",
539
+ resolver_libre_submitButtonLabelFull: "翻译全部",
540
+ resolver_libre_successMessage: "翻译成功。按下“保存”以应用更改。",
541
+ resolver_openai_buttonLabel: "AI翻译",
542
+ resolver_openai_errorMessage: "尝试翻译数据时发生错误",
543
+ resolver_openai_modalTitle: "选择要翻译的语言",
544
+ resolver_openai_submitButtonLabelEmpty: "仅翻译空字段",
545
+ resolver_openai_submitButtonLabelFull: "翻译全部",
546
+ resolver_openai_successMessage: "翻译成功。按下“保存”以应用更改。"
547
+ }
548
+ }
549
+ };
550
+
551
+ // src/jobs/constants.ts
552
+ var TRANSLATE_TASK_SLUG = "translateEntityToLocale";
553
+ var TRANSLATE_WORKFLOW_SLUG = "translateEntityToLocales";
554
+
555
+ // src/jobs/createAutoTranslateCollectionHook.ts
556
+ function createAutoTranslateCollectionHook(options) {
557
+ const {
558
+ 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
+ globalSlug,
660
+ defaultLocale: defaultLocaleOption,
661
+ targetLocales: targetLocalesOption,
662
+ workflowSlug = TRANSLATE_WORKFLOW_SLUG
663
+ } = options;
664
+ return async ({ doc, req }) => {
665
+ if (shouldSkipAutoTranslate2(req.context))
666
+ return doc;
667
+ const typed = doc;
668
+ const requestLocale = readRequestLocale2(req);
669
+ const defaultLocale = defaultLocaleOption ?? readConfigDefaultLocale2(req) ?? "";
670
+ if (!requestLocale || !defaultLocale || requestLocale !== defaultLocale)
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
+ }
739
+ }
740
+ return out;
741
+ }
742
+ function readFirstResolverKey2(req) {
743
+ if (!req || typeof req !== "object")
744
+ return "";
745
+ const custom = req.payload?.config?.custom?.translator?.resolvers;
746
+ if (!Array.isArray(custom) || custom.length === 0)
747
+ return "";
748
+ const first = custom[0];
749
+ return typeof first?.key === "string" ? first.key : "";
750
+ }
751
+
752
+ // src/jobs/createTranslateTask.ts
753
+ function createTranslateTask(options = {}) {
754
+ const { slug = "translateEntityToLocale" } = options;
755
+ return {
756
+ slug,
757
+ inputSchema: [
758
+ { name: "id", type: "number", required: false },
759
+ { name: "collection", type: "text", required: false },
760
+ { name: "global", type: "text", required: false },
761
+ { name: "fromLocale", type: "text", required: true },
762
+ { name: "toLocale", type: "text", required: true },
763
+ { name: "resolver", type: "text", required: false }
764
+ ],
765
+ outputSchema: [],
766
+ retries: 3,
767
+ handler: async (args) => {
768
+ const { input, job, req } = args;
769
+ const { id, collection, global, fromLocale, toLocale, resolver: inputResolver } = input;
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
+ }
906
+
907
+ // src/translate/endpoint.ts
908
+ import { APIError as APIError4 } from "payload";
909
+ var translateEndpoint = async (req) => {
910
+ if (!req.json)
911
+ throw new APIError4("Content-Type should be json");
912
+ const args = await req.json();
913
+ const { collectionSlug, data, emptyOnly, globalSlug, id, locale, localeFrom, resolver } = args;
914
+ const result = await translateOperation({
915
+ collectionSlug,
916
+ data,
917
+ emptyOnly,
918
+ globalSlug,
919
+ id,
920
+ locale,
921
+ localeFrom,
922
+ overrideAccess: false,
923
+ req,
924
+ resolver,
925
+ update: false
926
+ });
927
+ return Response.json(result);
928
+ };
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: [
53
1087
  {
54
- handler: translateEndpoint,
55
- method: 'post',
56
- path: '/translator/translate'
57
- }
58
- ],
59
- globals: config.globals?.map((global)=>{
60
- if (!pluginConfig.globals.includes(global.slug)) return global;
61
- return {
62
- ...global,
63
- admin: {
64
- ...global.admin ?? {},
65
- components: {
66
- ...global.admin?.components ?? {},
67
- elements: {
68
- ...global.admin?.components?.elements ?? {},
69
- PublishButton: CustomButton('publish'),
70
- SaveButton: CustomButton('save')
71
- }
72
- }
73
- }
74
- };
75
- }) ?? [],
76
- i18n: {
77
- ...config.i18n,
78
- translations: {
79
- ...deepMerge(config.i18n?.translations ?? {}, translations)
1088
+ content: prompt({ localeFrom, localeTo, texts: structuredTexts }),
1089
+ role: "user"
80
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
+ // src/index.ts
1169
+ var AUTO_TRANSLATE_MARKER = Symbol.for("@justanarthur/payload-plugin-translator/auto-translate");
1170
+ var translator = (pluginConfig) => {
1171
+ return (config) => {
1172
+ if (pluginConfig.disabled || !config.localization || config.localization.locales.length < 2)
1173
+ return config;
1174
+ const updatedConfig = {
1175
+ ...config,
1176
+ admin: {
1177
+ ...config.admin ?? {},
1178
+ custom: {
1179
+ ...config.admin?.custom ?? {},
1180
+ translator: {
1181
+ resolvers: pluginConfig.resolvers.map(({ key }) => ({ key }))
1182
+ }
1183
+ }
1184
+ },
1185
+ collections: config.collections?.map((collection) => {
1186
+ if (!pluginConfig.collections.includes(collection.slug))
1187
+ return collection;
1188
+ return {
1189
+ ...collection,
1190
+ admin: {
1191
+ ...collection.admin ?? {},
1192
+ components: {
1193
+ ...collection.admin?.components ?? {},
1194
+ edit: {
1195
+ ...collection.admin?.components?.edit ?? {},
1196
+ PublishButton: CustomButton("publish"),
1197
+ SaveButton: CustomButton("save")
1198
+ }
81
1199
  }
1200
+ },
1201
+ ...pluginConfig.autoTranslate ? {
1202
+ hooks: attachCollectionHook(collection.hooks, createAutoTranslateCollectionHook({ collectionSlug: String(collection.slug) }).afterChange)
1203
+ } : {}
82
1204
  };
83
- return updatedConfig;
1205
+ }) ?? [],
1206
+ custom: {
1207
+ ...config.custom ?? {},
1208
+ translator: {
1209
+ ...pluginConfig
1210
+ }
1211
+ },
1212
+ endpoints: [
1213
+ ...config.endpoints ?? [],
1214
+ {
1215
+ handler: translateEndpoint,
1216
+ method: "post",
1217
+ path: "/translator/translate"
1218
+ }
1219
+ ],
1220
+ globals: config.globals?.map((global) => {
1221
+ if (!pluginConfig.globals.includes(global.slug))
1222
+ return global;
1223
+ return {
1224
+ ...global,
1225
+ admin: {
1226
+ ...global.admin ?? {},
1227
+ components: {
1228
+ ...global.admin?.components ?? {},
1229
+ elements: {
1230
+ ...global.admin?.components?.elements ?? {},
1231
+ PublishButton: CustomButton("publish"),
1232
+ SaveButton: CustomButton("save")
1233
+ }
1234
+ }
1235
+ },
1236
+ ...pluginConfig.autoTranslate ? {
1237
+ hooks: attachGlobalHook(global.hooks, createAutoTranslateGlobalHook({ globalSlug: String(global.slug) }))
1238
+ } : {}
1239
+ };
1240
+ }) ?? [],
1241
+ i18n: {
1242
+ ...config.i18n,
1243
+ translations: {
1244
+ ...deepMerge(config.i18n?.translations ?? {}, translations)
1245
+ }
1246
+ },
1247
+ ...pluginConfig.autoTranslate ? {
1248
+ jobs: {
1249
+ ...config.jobs ?? {},
1250
+ tasks: ensureJobBySlug(config.jobs?.tasks, createTranslateTask()),
1251
+ workflows: ensureJobBySlug(config.jobs?.workflows, createTranslateWorkflow())
1252
+ }
1253
+ } : {}
84
1254
  };
1255
+ return updatedConfig;
1256
+ };
85
1257
  };
1258
+ function ensureJobBySlug(existing, job) {
1259
+ const list = existing ?? [];
1260
+ if (list.some((entry) => entry.slug === job.slug))
1261
+ return list;
1262
+ return [...list, job];
1263
+ }
1264
+ function attachCollectionHook(existing, hookToAttach) {
1265
+ const list = toHookArray(existing?.afterChange);
1266
+ if (list.some(isMarkedAutoTranslate))
1267
+ return existing ?? {};
1268
+ return {
1269
+ ...existing ?? {},
1270
+ afterChange: [markAutoTranslate(hookToAttach), ...list]
1271
+ };
1272
+ }
1273
+ function attachGlobalHook(existing, hookToAttach) {
1274
+ const list = toHookArray(existing?.afterChange);
1275
+ if (list.some(isMarkedAutoTranslate))
1276
+ return existing ?? {};
1277
+ return {
1278
+ ...existing ?? {},
1279
+ afterChange: [markAutoTranslate(hookToAttach), ...list]
1280
+ };
1281
+ }
1282
+ function toHookArray(input) {
1283
+ if (input === undefined)
1284
+ return [];
1285
+ return Array.isArray(input) ? input : [input];
1286
+ }
1287
+ function markAutoTranslate(hook) {
1288
+ hook[AUTO_TRANSLATE_MARKER] = true;
1289
+ return hook;
1290
+ }
1291
+ function isMarkedAutoTranslate(hook) {
1292
+ return Boolean(hook?.[AUTO_TRANSLATE_MARKER]);
1293
+ }
86
1294
 
87
- //# sourceMappingURL=index.js.map
1295
+ // src/exports/index.ts
1296
+ var exports_default = translator;
1297
+ export {
1298
+ translator,
1299
+ translateOperation,
1300
+ exports_default as default
1301
+ };