@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.
@@ -0,0 +1,253 @@
1
+ import {
2
+ samePlaceholders,
3
+ isOpaqueText,
4
+ collectTranslatableFields,
5
+ entityKey,
6
+ sourceHash
7
+ } from "./chunk-9d433kmv.js";
8
+ import {
9
+ TRANSLATION_STATUS_SLUG2
10
+ } from "./chunk-31qgv1zk.js";
11
+
12
+ // src/review/plainText.ts
13
+ var plainText = (value) => {
14
+ if (typeof value === "string")
15
+ return value;
16
+ if (!value || typeof value !== "object")
17
+ return "";
18
+ if (Array.isArray(value))
19
+ return value.map(plainText).filter(Boolean).join(" ");
20
+ const node = value;
21
+ if (typeof node.text === "string")
22
+ return node.text;
23
+ if ("root" in node)
24
+ return plainText(node.root);
25
+ if (Array.isArray(node.children))
26
+ return node.children.map(plainText).filter(Boolean).join(node.type === "root" ? `
27
+ ` : "");
28
+ return "";
29
+ };
30
+
31
+ // src/review/computeStatus.ts
32
+ var normalize = (text) => text.replace(/\s+/g, " ").trim();
33
+ var classify = (field, sourceText, targetText) => {
34
+ if (!normalize(targetText))
35
+ return "missing";
36
+ if (field.type === "json" && !samePlaceholders(sourceText, targetText))
37
+ return "placeholders";
38
+ const same = normalize(sourceText) === normalize(targetText);
39
+ if (same && field.type !== "slug" && normalize(sourceText).length > 3 && !isOpaqueText(sourceText) && /\s/.test(normalize(sourceText)))
40
+ return "identical";
41
+ return "ok";
42
+ };
43
+ var computeStatus = (fields) => {
44
+ const statuses = [];
45
+ for (const field of fields) {
46
+ const sourceText = plainText(field.source);
47
+ if (!normalize(sourceText))
48
+ continue;
49
+ const targetText = plainText(field.target);
50
+ statuses.push({ ...field, sourceText, targetText, state: classify(field, sourceText, targetText) });
51
+ }
52
+ const count = (state) => statuses.filter((each) => each.state === state).length;
53
+ const ok = count("ok");
54
+ return {
55
+ fields: statuses,
56
+ summary: {
57
+ total: statuses.length,
58
+ ok,
59
+ missing: count("missing"),
60
+ identical: count("identical"),
61
+ placeholders: count("placeholders"),
62
+ coverage: statuses.length ? Math.floor(ok / statuses.length * 100) : 100,
63
+ slugMissing: statuses.some((each) => each.type === "slug" && each.state === "missing")
64
+ }
65
+ };
66
+ };
67
+
68
+ // src/review/loadReview.ts
69
+ var readLocales = (req) => {
70
+ const localization = req.payload.config.localization;
71
+ if (!localization)
72
+ return { defaultLocale: "", targetLocales: [] };
73
+ const codes = localization.locales.map((each) => typeof each === "string" ? each : each.code);
74
+ return { defaultLocale: localization.defaultLocale, targetLocales: codes.filter((code) => code !== localization.defaultLocale) };
75
+ };
76
+ var pluginOptions = (req) => req.payload.config.custom?.translator;
77
+ var loadStatusRows = async (req, keys) => {
78
+ if (!keys.length || !req.payload.collections[TRANSLATION_STATUS_SLUG2])
79
+ return [];
80
+ const { docs } = await req.payload.find({
81
+ collection: TRANSLATION_STATUS_SLUG2,
82
+ where: { entity: { in: keys } },
83
+ pagination: false,
84
+ depth: 0,
85
+ overrideAccess: false,
86
+ req
87
+ });
88
+ return docs;
89
+ };
90
+ var reviewLocale = (config, source, target, hash, row, req) => {
91
+ const { fields, summary } = computeStatus(collectTranslatableFields({
92
+ config,
93
+ dataFrom: source,
94
+ dataTarget: target ?? {},
95
+ options: pluginOptions(req)?._options
96
+ }));
97
+ return {
98
+ fields,
99
+ summary,
100
+ stale: Boolean(row?.sourceHash) && row?.sourceHash !== hash,
101
+ reviewed: Boolean(row?.reviewedHash) && row?.reviewedHash === hash
102
+ };
103
+ };
104
+ var labelOf = (config, doc) => {
105
+ const title = doc[config.admin?.useAsTitle ?? "id"];
106
+ return typeof title === "string" && title.trim() ? title : `#${doc.id}`;
107
+ };
108
+ var globalLabel = (config) => typeof config.label === "string" ? config.label : config.slug;
109
+ var hashOf = (config, source, req) => sourceHash(collectTranslatableFields({ config, dataFrom: source, dataTarget: {}, options: pluginOptions(req)?._options }));
110
+ var loadCollectionReview = async ({ req, collectionSlug, page = 1, limit = 25 }) => {
111
+ const config = req.payload.collections[collectionSlug]?.config;
112
+ if (!config)
113
+ throw new Error(`unknown collection ${collectionSlug}`);
114
+ const { defaultLocale, targetLocales } = readLocales(req);
115
+ const sources = await req.payload.find({
116
+ collection: collectionSlug,
117
+ locale: defaultLocale,
118
+ fallbackLocale: false,
119
+ depth: 0,
120
+ limit,
121
+ page,
122
+ sort: "-updatedAt",
123
+ overrideAccess: false,
124
+ req
125
+ });
126
+ const ids = sources.docs.map((doc) => doc.id);
127
+ const targets = {};
128
+ for (const locale of targetLocales) {
129
+ const { docs } = ids.length ? await req.payload.find({
130
+ collection: collectionSlug,
131
+ locale,
132
+ fallbackLocale: false,
133
+ depth: 0,
134
+ where: { id: { in: ids } },
135
+ pagination: false,
136
+ overrideAccess: false,
137
+ req
138
+ }) : { docs: [] };
139
+ targets[locale] = new Map(docs.map((doc) => [String(doc.id), doc]));
140
+ }
141
+ const keys = ids.map((id) => entityKey({ collectionSlug, id }));
142
+ const rows = await loadStatusRows(req, keys);
143
+ const entities = sources.docs.map((doc) => {
144
+ const source = doc;
145
+ const key = entityKey({ collectionSlug, id: source.id });
146
+ const hash = hashOf(config, source, req);
147
+ return {
148
+ key,
149
+ label: labelOf(config, source),
150
+ collectionSlug,
151
+ id: source.id,
152
+ locales: Object.fromEntries(targetLocales.map((locale) => {
153
+ const { fields: _fields, ...review } = reviewLocale(config, source, targets[locale].get(String(source.id)), hash, rows.find((row) => row.entity === key && row.locale === locale), req);
154
+ return [locale, review];
155
+ }))
156
+ };
157
+ });
158
+ return {
159
+ entities,
160
+ locales: targetLocales,
161
+ page: sources.page ?? page,
162
+ totalPages: sources.totalPages,
163
+ totalDocs: sources.totalDocs
164
+ };
165
+ };
166
+ var loadGlobalsReview = async ({ req, globalSlugs }) => {
167
+ const { defaultLocale, targetLocales } = readLocales(req);
168
+ const rows = await loadStatusRows(req, globalSlugs.map((globalSlug) => entityKey({ globalSlug })));
169
+ const entities = [];
170
+ for (const globalSlug of globalSlugs) {
171
+ const config = req.payload.config.globals.find((each) => each.slug === globalSlug);
172
+ if (!config)
173
+ continue;
174
+ const read = (locale) => req.payload.findGlobal({
175
+ slug: globalSlug,
176
+ locale,
177
+ fallbackLocale: false,
178
+ depth: 0,
179
+ overrideAccess: false,
180
+ req
181
+ });
182
+ const source = await read(defaultLocale);
183
+ const hash = hashOf(config, source, req);
184
+ const key = entityKey({ globalSlug });
185
+ const locales = {};
186
+ for (const locale of targetLocales) {
187
+ const { fields: _fields, ...review } = reviewLocale(config, source, await read(locale), hash, rows.find((row) => row.entity === key && row.locale === locale), req);
188
+ locales[locale] = review;
189
+ }
190
+ entities.push({ key, label: globalLabel(config), globalSlug, locales });
191
+ }
192
+ return { entities, locales: targetLocales };
193
+ };
194
+ var parseEntityKey = (key) => {
195
+ const [scope, ...rest] = key.split(":");
196
+ const value = rest.join(":");
197
+ return scope === "global" ? { globalSlug: value } : { collectionSlug: scope, id: value };
198
+ };
199
+ var loadEntityLocaleReview = async ({ req, entity, locale }) => {
200
+ const { defaultLocale } = readLocales(req);
201
+ const { collectionSlug, globalSlug, id } = parseEntityKey(entity);
202
+ const config = globalSlug ? req.payload.config.globals.find((each) => each.slug === globalSlug) : req.payload.collections[collectionSlug]?.config;
203
+ if (!config)
204
+ throw new Error(`unknown entity ${entity}`);
205
+ const read = (readLocale) => globalSlug ? req.payload.findGlobal({ slug: globalSlug, locale: readLocale, fallbackLocale: false, depth: 0, overrideAccess: false, req }) : req.payload.findByID({ collection: collectionSlug, id, locale: readLocale, fallbackLocale: false, depth: 0, overrideAccess: false, req });
206
+ const source = await read(defaultLocale);
207
+ const target = await read(locale);
208
+ const rows = await loadStatusRows(req, [entity]);
209
+ const row = rows.find((each) => each.locale === locale);
210
+ const review = reviewLocale(config, source, target, hashOf(config, source, req), row, req);
211
+ return {
212
+ ...review,
213
+ entity,
214
+ locale,
215
+ defaultLocale,
216
+ collectionSlug,
217
+ globalSlug,
218
+ id,
219
+ label: globalSlug ? globalLabel(config) : labelOf(config, source),
220
+ translatedAt: row?.translatedAt,
221
+ reviewedAt: row?.reviewedAt,
222
+ reviewedBy: row?.reviewedBy,
223
+ lastJob: await loadLastJob(req, { collectionSlug, globalSlug, id }, locale)
224
+ };
225
+ };
226
+ var loadLastJob = async (req, { collectionSlug, globalSlug, id }, locale) => {
227
+ if (!req.payload.collections["payload-jobs"])
228
+ return null;
229
+ try {
230
+ const { docs } = await req.payload.find({
231
+ collection: "payload-jobs",
232
+ sort: "-createdAt",
233
+ limit: 100,
234
+ depth: 0,
235
+ overrideAccess: true,
236
+ req
237
+ });
238
+ const job = docs.find(({ input }) => input && (globalSlug ? input.global === globalSlug : input.collection === collectionSlug && String(input.id) === String(id)) && (!Array.isArray(input.toLocales) || input.toLocales.includes(locale)));
239
+ if (!job)
240
+ return null;
241
+ return {
242
+ id: job.id,
243
+ createdAt: job.createdAt,
244
+ completedAt: job.completedAt,
245
+ processing: Boolean(job.processing),
246
+ error: job.hasError ? typeof job.error === "string" ? job.error : JSON.stringify(job.error) : undefined
247
+ };
248
+ } catch {
249
+ return null;
250
+ }
251
+ };
252
+
253
+ export { readLocales, loadCollectionReview, loadGlobalsReview, parseEntityKey, loadEntityLocaleReview };
@@ -0,0 +1,5 @@
1
+ // src/review/constants.ts
2
+ var TRANSLATION_STATUS_SLUG2 = "translation-status";
3
+ var REVIEW_VIEW_PATH = "/translations";
4
+
5
+ export { TRANSLATION_STATUS_SLUG2, REVIEW_VIEW_PATH };
@@ -0,0 +1,359 @@
1
+ // src/utils/placeholders.ts
2
+ var placeholdersOf = (value) => (value.match(/\{\s*[\w.]+\s*(?:,[^{}]*)?\}/g) ?? []).map((each) => each.replace(/\s+/g, "")).sort();
3
+ var samePlaceholders = (source, translated) => placeholdersOf(source).join("|") === placeholdersOf(translated).join("|");
4
+
5
+ // src/translate/traverseFields.ts
6
+ import ObjectID from "bson-objectid";
7
+ import { tabHasName } from "payload/shared";
8
+
9
+ // src/utils/isEmpty.ts
10
+ var isEmpty = (value) => {
11
+ if (Array.isArray(value))
12
+ return value.length === 0;
13
+ if (value === null || typeof value === "undefined")
14
+ return true;
15
+ if (typeof value === "object" && Object.keys(value).length === 0)
16
+ return true;
17
+ return false;
18
+ };
19
+
20
+ // src/utils/hasText.ts
21
+ var hasText = (value) => {
22
+ if (typeof value === "string")
23
+ return value.trim().length > 0;
24
+ if (isEmpty(value))
25
+ return false;
26
+ if (Array.isArray(value))
27
+ return value.some(hasText);
28
+ if (typeof value === "object") {
29
+ const node = value;
30
+ if (typeof node.text === "string" && node.text.trim())
31
+ return true;
32
+ if ("root" in node)
33
+ return hasText(node.root);
34
+ if (Array.isArray(node.children))
35
+ return node.children.some(hasText);
36
+ if (node.type === "block" || node.type === "inlineBlock" || node.type === "upload")
37
+ return true;
38
+ return !("children" in node) && Object.values(node).some(hasText);
39
+ }
40
+ return value !== null && value !== undefined;
41
+ };
42
+
43
+ // src/utils/isOpaqueText.ts
44
+ var isOpaqueText = (value) => {
45
+ const text = value.trim();
46
+ return /^([a-z][a-z0-9+.-]*:|\/\/|\/|#|www\.)/i.test(text) || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text) || !/\p{L}/u.test(text);
47
+ };
48
+
49
+ // src/utils/sanitizeSlug.ts
50
+ var sanitizeSlug = (text) => text.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
51
+
52
+ // src/translate/traverseRichText.ts
53
+ var traverseRichText = ({
54
+ onText,
55
+ root,
56
+ siblingData,
57
+ additionalTraverseRichText
58
+ }) => {
59
+ siblingData = siblingData ?? root;
60
+ if (typeof siblingData.text === "string" && siblingData.text.trim()) {
61
+ onText(siblingData);
62
+ }
63
+ if (!("text" in siblingData)) {
64
+ additionalTraverseRichText?.({ onText, root, siblingData });
65
+ }
66
+ if (Array.isArray(siblingData.children)) {
67
+ for (const child of siblingData.children) {
68
+ if (child && typeof child === "object")
69
+ traverseRichText({
70
+ onText,
71
+ root,
72
+ siblingData: child,
73
+ additionalTraverseRichText
74
+ });
75
+ }
76
+ }
77
+ };
78
+
79
+ // src/translate/traverseFields.ts
80
+ var joinPath = (base, segment) => base ? `${base}.${segment}` : segment;
81
+ var buildLocalizedRows = (from, existing, isBlocks, emptyOnly) => {
82
+ const sameShape = existing.length === from.length && (!isBlocks || existing.every((row, index) => row.blockType === from[index].blockType));
83
+ if (emptyOnly && sameShape)
84
+ return existing;
85
+ return from.map((row, index) => {
86
+ const previous = existing[index] && (!isBlocks || existing[index].blockType === row.blockType) ? existing[index] : undefined;
87
+ return {
88
+ ...previous ?? {},
89
+ id: previous?.id ?? ObjectID().toHexString(),
90
+ ...isBlocks ? { blockType: row.blockType, blockName: row.blockName ?? previous?.blockName } : {}
91
+ };
92
+ });
93
+ };
94
+ var traverseFields = (args) => {
95
+ const {
96
+ dataFrom,
97
+ emptyOnly,
98
+ fields,
99
+ localizedParent,
100
+ path,
101
+ translatedData,
102
+ valuesToTranslate,
103
+ onField,
104
+ _options
105
+ } = args;
106
+ const { additionalTraverseRichText } = _options ?? {};
107
+ const siblingDataFrom = args.siblingDataFrom ?? dataFrom;
108
+ const siblingDataTranslated = args.siblingDataTranslated ?? translatedData;
109
+ const recurse = (overrides) => traverseFields({ ...args, siblingDataFrom, siblingDataTranslated, ...overrides });
110
+ for (const field of fields) {
111
+ switch (field.type) {
112
+ case "tabs":
113
+ for (const tab of field.tabs) {
114
+ const hasName = tabHasName(tab);
115
+ const tabDataFrom = hasName ? siblingDataFrom[tab.name] : siblingDataFrom;
116
+ if (!tabDataFrom)
117
+ continue;
118
+ let tabDataTranslated;
119
+ if (hasName) {
120
+ if (!siblingDataTranslated[tab.name])
121
+ siblingDataTranslated[tab.name] = {};
122
+ tabDataTranslated = siblingDataTranslated[tab.name];
123
+ } else {
124
+ tabDataTranslated = siblingDataTranslated;
125
+ }
126
+ recurse({
127
+ fields: tab.fields,
128
+ localizedParent: localizedParent || "localized" in tab && Boolean(tab.localized),
129
+ path: hasName ? joinPath(path, tab.name) : path,
130
+ siblingDataFrom: tabDataFrom,
131
+ siblingDataTranslated: tabDataTranslated
132
+ });
133
+ }
134
+ break;
135
+ case "group": {
136
+ if (!("name" in field) || !field.name) {
137
+ recurse({ fields: field.fields });
138
+ break;
139
+ }
140
+ const groupDataFrom = siblingDataFrom[field.name];
141
+ if (!groupDataFrom)
142
+ break;
143
+ if (!siblingDataTranslated[field.name])
144
+ siblingDataTranslated[field.name] = {};
145
+ recurse({
146
+ fields: field.fields,
147
+ localizedParent: localizedParent || Boolean(field.localized),
148
+ path: joinPath(path, field.name),
149
+ siblingDataFrom: groupDataFrom,
150
+ siblingDataTranslated: siblingDataTranslated[field.name]
151
+ });
152
+ break;
153
+ }
154
+ case "array":
155
+ case "blocks": {
156
+ const isBlocks = field.type === "blocks";
157
+ const rowsFrom = siblingDataFrom[field.name];
158
+ if (isEmpty(rowsFrom) || !Array.isArray(rowsFrom))
159
+ break;
160
+ const existing = Array.isArray(siblingDataTranslated[field.name]) ? siblingDataTranslated[field.name] : [];
161
+ const localized = Boolean(field.localized || localizedParent);
162
+ const rows = localized ? buildLocalizedRows(rowsFrom, existing, isBlocks, emptyOnly) : existing.length ? existing : structuredClone(rowsFrom);
163
+ rows.forEach((row, index) => {
164
+ const rowFrom = rowsFrom[index];
165
+ if (!rowFrom)
166
+ return;
167
+ if (!isBlocks) {
168
+ recurse({
169
+ fields: field.fields,
170
+ localizedParent: localized,
171
+ path: `${joinPath(path, field.name)}[${index}]`,
172
+ siblingDataFrom: rowFrom,
173
+ siblingDataTranslated: row
174
+ });
175
+ return;
176
+ }
177
+ const block = field.blocks.find((each) => each.slug === row.blockType);
178
+ if (!block)
179
+ return;
180
+ recurse({
181
+ fields: block.fields,
182
+ localizedParent: localized,
183
+ path: `${joinPath(path, field.name)}[${index}](${row.blockType})`,
184
+ siblingDataFrom: rowFrom,
185
+ siblingDataTranslated: row
186
+ });
187
+ });
188
+ siblingDataTranslated[field.name] = rows;
189
+ break;
190
+ }
191
+ case "collapsible":
192
+ case "row":
193
+ recurse({ fields: field.fields });
194
+ break;
195
+ case "date":
196
+ case "checkbox":
197
+ case "code":
198
+ case "email":
199
+ case "number":
200
+ case "point":
201
+ case "radio":
202
+ case "relationship":
203
+ case "select":
204
+ case "upload":
205
+ siblingDataTranslated[field.name] = siblingDataFrom[field.name];
206
+ break;
207
+ case "json": {
208
+ if (!(field.localized || localizedParent))
209
+ break;
210
+ const jsonDataFrom = siblingDataFrom[field.name];
211
+ if (isEmpty(jsonDataFrom))
212
+ break;
213
+ const jsonPath = joinPath(path, field.name);
214
+ const current = siblingDataTranslated[field.name];
215
+ const currentIsObject = Boolean(current) && typeof current === "object";
216
+ const jsonDataTranslated = emptyOnly && currentIsObject ? structuredClone(current) : structuredClone(jsonDataFrom);
217
+ siblingDataTranslated[field.name] = jsonDataTranslated;
218
+ const traverseObject = (source, target, target0, objPath) => {
219
+ if (!source || typeof source !== "object")
220
+ return;
221
+ for (const key of Object.keys(source)) {
222
+ const value = source[key];
223
+ const previous = target0 && typeof target0 === "object" ? target0[key] : undefined;
224
+ const keyPath = joinPath(objPath, key);
225
+ if (typeof value === "string") {
226
+ onField?.({ path: keyPath, type: "json", source: value, target: previous });
227
+ if (!value.trim())
228
+ continue;
229
+ if (emptyOnly && typeof previous === "string" && previous.trim())
230
+ continue;
231
+ target[key] = value;
232
+ if (isOpaqueText(value))
233
+ continue;
234
+ valuesToTranslate.push({
235
+ onTranslate: (translated) => {
236
+ target[key] = translated;
237
+ },
238
+ value,
239
+ path: keyPath
240
+ });
241
+ } else if (value && typeof value === "object") {
242
+ if (!target[key] || typeof target[key] !== "object")
243
+ target[key] = Array.isArray(value) ? [] : {};
244
+ traverseObject(value, target[key], previous, keyPath);
245
+ } else if (!(key in target)) {
246
+ target[key] = value;
247
+ }
248
+ }
249
+ };
250
+ traverseObject(jsonDataFrom, jsonDataTranslated, currentIsObject ? current : undefined, jsonPath);
251
+ break;
252
+ }
253
+ case "text":
254
+ case "textarea": {
255
+ if (field.custom && typeof field.custom === "object" && field.custom.translatorSkip)
256
+ break;
257
+ if (!(field.localized || localizedParent))
258
+ break;
259
+ if (field.name === "blockName" || field.name === "id")
260
+ break;
261
+ const value = siblingDataFrom[field.name];
262
+ const current = siblingDataTranslated[field.name];
263
+ const fieldPath = joinPath(path, field.name);
264
+ const isSlug = field.name === "slug";
265
+ onField?.({ path: fieldPath, type: isSlug ? "slug" : "text", source: value, target: current });
266
+ if (typeof value !== "string" || !value.trim())
267
+ break;
268
+ const hasCurrent = typeof current === "string" && current.trim().length > 0;
269
+ if (hasCurrent && (emptyOnly || isSlug))
270
+ break;
271
+ if (isOpaqueText(value)) {
272
+ siblingDataTranslated[field.name] = value;
273
+ break;
274
+ }
275
+ if (isSlug) {
276
+ const segments = value.split("_");
277
+ segments.forEach((segment, index) => {
278
+ valuesToTranslate.push({
279
+ onTranslate: (translated) => {
280
+ segments[index] = sanitizeSlug(translated) || segment;
281
+ siblingDataTranslated[field.name] = segments.join("_");
282
+ },
283
+ value: segment.replace(/-/g, " "),
284
+ path: `${fieldPath}#${index}`
285
+ });
286
+ });
287
+ break;
288
+ }
289
+ valuesToTranslate.push({
290
+ onTranslate: (translated) => {
291
+ siblingDataTranslated[field.name] = translated;
292
+ },
293
+ value,
294
+ path: fieldPath
295
+ });
296
+ break;
297
+ }
298
+ case "richText": {
299
+ if (!(field.localized || localizedParent))
300
+ break;
301
+ const richTextDataFrom = siblingDataFrom[field.name];
302
+ const current = siblingDataTranslated[field.name];
303
+ const richTextPath = joinPath(path, field.name);
304
+ onField?.({ path: richTextPath, type: "richText", source: richTextDataFrom, target: current });
305
+ if (!richTextDataFrom || !hasText(richTextDataFrom))
306
+ break;
307
+ if (emptyOnly && hasText(current))
308
+ break;
309
+ const isSlate = Array.isArray(richTextDataFrom);
310
+ const isLexical = typeof richTextDataFrom === "object" && "root" in richTextDataFrom;
311
+ if (!isSlate && !isLexical)
312
+ break;
313
+ const richTextTranslated = structuredClone(richTextDataFrom);
314
+ siblingDataTranslated[field.name] = richTextTranslated;
315
+ let richTextNodeIndex = 0;
316
+ const onText = (siblingData, attribute = "text") => {
317
+ valuesToTranslate.push({
318
+ onTranslate: (translated) => {
319
+ siblingData[attribute] = translated;
320
+ },
321
+ value: siblingData[attribute],
322
+ path: `${richTextPath}#${richTextNodeIndex++}`
323
+ });
324
+ };
325
+ const roots = isLexical ? [richTextTranslated.root] : richTextTranslated;
326
+ for (const root of roots) {
327
+ if (root && typeof root === "object")
328
+ traverseRichText({ onText, root, additionalTraverseRichText });
329
+ }
330
+ break;
331
+ }
332
+ default:
333
+ break;
334
+ }
335
+ }
336
+ };
337
+
338
+ // src/review/collectTranslatableFields.ts
339
+ var collectTranslatableFields = ({ config, dataFrom, dataTarget, options }) => {
340
+ const fields = [];
341
+ traverseFields({
342
+ dataFrom,
343
+ fields: config.fields,
344
+ translatedData: structuredClone(dataTarget ?? {}),
345
+ valuesToTranslate: [],
346
+ onField: (field) => fields.push(field),
347
+ _options: options
348
+ });
349
+ return fields;
350
+ };
351
+
352
+ // src/review/entityKey.ts
353
+ var entityKey = ({ collectionSlug, globalSlug, id }) => globalSlug ? `global:${globalSlug}` : `${collectionSlug}:${id}`;
354
+
355
+ // src/review/sourceHash.ts
356
+ import { createHash } from "node:crypto";
357
+ var sourceHash = (fields) => createHash("sha1").update(JSON.stringify(fields.map(({ path, source }) => [path, source]))).digest("hex");
358
+
359
+ export { samePlaceholders, isOpaqueText, traverseFields, collectTranslatableFields, entityKey, sourceHash };
@@ -0,0 +1,6 @@
1
+ // src/utils/chunkArray.ts
2
+ var chunkArray = (array, length) => {
3
+ return Array.from({ length: Math.ceil(array.length / length) }, (_, i) => array.slice(i * length, i * length + length));
4
+ };
5
+
6
+ export { chunkArray };
@@ -0,0 +1,57 @@
1
+ import {
2
+ chunkArray
3
+ } from "./chunk-e6c0qzkt.js";
4
+
5
+ // src/resolvers/libreTranslate.ts
6
+ var localeToCountryCodeMapper = {
7
+ ua: "uk"
8
+ };
9
+ var mapLocale = (incoming) => (incoming in localeToCountryCodeMapper) ? localeToCountryCodeMapper[incoming] : incoming;
10
+ var libreResolver2 = ({
11
+ apiKey,
12
+ chunkLength = 100,
13
+ url = "https://libretranslate.com/translate"
14
+ }) => {
15
+ return {
16
+ key: "libre",
17
+ resolve: async (args) => {
18
+ const { localeFrom, localeTo, req, texts } = args;
19
+ const apiUrl = url;
20
+ const responses = await Promise.all(chunkArray(texts, chunkLength).map((q) => fetch(apiUrl, {
21
+ body: JSON.stringify({
22
+ api_key: apiKey,
23
+ q,
24
+ source: mapLocale(localeFrom),
25
+ target: mapLocale(localeTo)
26
+ }),
27
+ headers: {
28
+ "Content-Type": "application/json"
29
+ },
30
+ method: "POST"
31
+ }).then(async (res) => {
32
+ const data = await res.json();
33
+ if (!res.ok)
34
+ req.payload.logger.info({
35
+ libreResponse: data,
36
+ message: "An error occurred when trying to translate the data using LibreTranslate API"
37
+ });
38
+ return {
39
+ data,
40
+ success: res.ok
41
+ };
42
+ })));
43
+ if (responses.some((res) => !res.success)) {
44
+ return {
45
+ success: false
46
+ };
47
+ }
48
+ const translatedTexts = responses.flatMap((chunk) => chunk.data.translatedText);
49
+ return {
50
+ success: true,
51
+ translatedTexts
52
+ };
53
+ }
54
+ };
55
+ };
56
+
57
+ export { libreResolver2 };