@justanarthur/payload-plugin-translator 3.3.3 → 3.4.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/dist/index.d.ts +2 -0
- package/dist/index.js +3 -3
- package/dist/jobs.js +2 -2
- package/dist/rsc.js +2 -2
- package/dist/shared/{chunk-xgsxe28w.js → chunk-9ky9w8k3.js} +40 -28
- package/dist/shared/{chunk-12hf0kdf.js → chunk-k3q5eq41.js} +45 -9
- package/dist/shared/{chunk-pmxe5c20.js → chunk-m5jdmn2w.js} +49 -15
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ type TranslateResult = {
|
|
|
22
22
|
translatedData: Record<string, any>;
|
|
23
23
|
/** values sent to the resolver; 0 means the target needed nothing */
|
|
24
24
|
translatedCount: number;
|
|
25
|
+
/** non-translatable values (relationships, selects, …) copied from the source because they differed; a sync-only run still persists */
|
|
26
|
+
syncedCount: number;
|
|
25
27
|
dataFrom: Doc;
|
|
26
28
|
};
|
|
27
29
|
type TranslateOperationArgs = ({
|
package/dist/index.js
CHANGED
|
@@ -8,15 +8,15 @@ import {
|
|
|
8
8
|
isTranslateMode,
|
|
9
9
|
createTranslateTask2,
|
|
10
10
|
createTranslateWorkflow2
|
|
11
|
-
} from "./shared/chunk-
|
|
12
|
-
import"./shared/chunk-
|
|
11
|
+
} from "./shared/chunk-k3q5eq41.js";
|
|
12
|
+
import"./shared/chunk-m5jdmn2w.js";
|
|
13
13
|
import {
|
|
14
14
|
readLocales,
|
|
15
15
|
parseEntityKey,
|
|
16
16
|
hasTranslateWorkflow,
|
|
17
17
|
queueBulkTranslation,
|
|
18
18
|
runQueuedTranslations
|
|
19
|
-
} from "./shared/chunk-
|
|
19
|
+
} from "./shared/chunk-9ky9w8k3.js";
|
|
20
20
|
import {
|
|
21
21
|
TRANSLATION_STATUS_SLUG2,
|
|
22
22
|
REVIEW_VIEW_PATH
|
package/dist/jobs.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
createAutoTranslateGlobalHook2,
|
|
4
4
|
createTranslateTask2,
|
|
5
5
|
createTranslateWorkflow2
|
|
6
|
-
} from "./shared/chunk-
|
|
6
|
+
} from "./shared/chunk-k3q5eq41.js";
|
|
7
7
|
import {
|
|
8
8
|
TRANSLATE_TASK_SLUG2,
|
|
9
9
|
TRANSLATE_WORKFLOW_SLUG2
|
|
10
|
-
} from "./shared/chunk-
|
|
10
|
+
} from "./shared/chunk-m5jdmn2w.js";
|
|
11
11
|
|
|
12
12
|
// src/exports/jobs.ts
|
|
13
13
|
var jobs = {
|
package/dist/rsc.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import"./shared/chunk-
|
|
1
|
+
import"./shared/chunk-m5jdmn2w.js";
|
|
2
2
|
import {
|
|
3
3
|
readLocales,
|
|
4
4
|
loadCollectionReview,
|
|
5
5
|
loadGlobalsReview,
|
|
6
6
|
loadEntityLocaleReview,
|
|
7
7
|
loadJobsProgress
|
|
8
|
-
} from "./shared/chunk-
|
|
8
|
+
} from "./shared/chunk-9ky9w8k3.js";
|
|
9
9
|
import {
|
|
10
10
|
REVIEW_VIEW_PATH
|
|
11
11
|
} from "./shared/chunk-31qgv1zk.js";
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadWrongLanguageCheck,
|
|
3
|
-
samePlaceholders,
|
|
4
3
|
plainText,
|
|
4
|
+
buildCrossLocaleTexts,
|
|
5
|
+
crossLocaleMatch,
|
|
6
|
+
isOpaqueText,
|
|
5
7
|
looksUntranslated,
|
|
6
|
-
TRANSLATE_WORKFLOW_SLUG2,
|
|
7
8
|
collectTranslatableFields,
|
|
9
|
+
samePlaceholders,
|
|
10
|
+
TRANSLATE_WORKFLOW_SLUG2,
|
|
8
11
|
entityKey,
|
|
9
12
|
sourceHash
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-m5jdmn2w.js";
|
|
11
14
|
import {
|
|
12
15
|
TRANSLATION_STATUS_SLUG2
|
|
13
16
|
} from "./chunk-31qgv1zk.js";
|
|
@@ -23,15 +26,15 @@ var classify = (field, sourceText, targetText) => {
|
|
|
23
26
|
return "identical";
|
|
24
27
|
return "ok";
|
|
25
28
|
};
|
|
26
|
-
var computeStatus = (fields,
|
|
29
|
+
var computeStatus = (fields, options) => {
|
|
27
30
|
const statuses = [];
|
|
28
31
|
for (const field of fields) {
|
|
29
32
|
const sourceText = plainText(field.source);
|
|
30
33
|
if (!normalize(sourceText))
|
|
31
34
|
continue;
|
|
32
|
-
const targetText = plainText(field.target);
|
|
35
|
+
const targetText = normalize(plainText(field.target));
|
|
33
36
|
const state = classify(field, sourceText, targetText);
|
|
34
|
-
const detectedLanguage = state === "ok" && field.type !== "slug" &&
|
|
37
|
+
const detectedLanguage = state === "ok" && field.type !== "slug" && !isOpaqueText(targetText) ? options?.check?.(targetText, options.locale) ?? (options?.crossLocale ? crossLocaleMatch(options.crossLocale, field.path, options.locale, targetText) : undefined) : undefined;
|
|
35
38
|
statuses.push({
|
|
36
39
|
...field,
|
|
37
40
|
sourceText,
|
|
@@ -80,19 +83,27 @@ var loadStatusRows = async (req, keys) => {
|
|
|
80
83
|
return docs;
|
|
81
84
|
};
|
|
82
85
|
var loadLanguageCheck = (req) => pluginOptions(req)?.languageDetection === false ? Promise.resolve(null) : loadWrongLanguageCheck(readLocales(req).targetLocales.concat(readLocales(req).defaultLocale));
|
|
83
|
-
var
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
86
|
+
var reviewEntity = (config, source, targetByLocale, targetLocales, hash, rowByLocale, req, check) => {
|
|
87
|
+
const options = pluginOptions(req)?._options;
|
|
88
|
+
const fieldsPerLocale = targetLocales.map((locale) => ({
|
|
89
|
+
locale,
|
|
90
|
+
fields: collectTranslatableFields({
|
|
91
|
+
config,
|
|
92
|
+
dataFrom: source,
|
|
93
|
+
dataTarget: targetByLocale[locale] ?? {},
|
|
94
|
+
options
|
|
95
|
+
})
|
|
96
|
+
}));
|
|
97
|
+
const crossLocale = buildCrossLocaleTexts(fieldsPerLocale);
|
|
98
|
+
return Object.fromEntries(fieldsPerLocale.map(({ locale, fields }) => {
|
|
99
|
+
const row = rowByLocale(locale);
|
|
100
|
+
const { fields: _fields, ...review } = {
|
|
101
|
+
...computeStatus(fields, { locale, check, crossLocale }),
|
|
102
|
+
stale: Boolean(row?.sourceHash) && row?.sourceHash !== hash,
|
|
103
|
+
reviewed: Boolean(row?.reviewedHash) && row?.reviewedHash === hash
|
|
104
|
+
};
|
|
105
|
+
return [locale, review];
|
|
106
|
+
}));
|
|
96
107
|
};
|
|
97
108
|
var labelOf = (config, doc) => {
|
|
98
109
|
const title = doc[config.admin?.useAsTitle ?? "id"];
|
|
@@ -143,10 +154,7 @@ var loadCollectionReview = async ({ req, collectionSlug, page = 1, limit = 25 })
|
|
|
143
154
|
label: labelOf(config, source),
|
|
144
155
|
collectionSlug,
|
|
145
156
|
id: source.id,
|
|
146
|
-
locales: Object.fromEntries(targetLocales.map((locale) =>
|
|
147
|
-
const { fields: _fields, ...review } = reviewLocale(config, source, targets[locale].get(String(source.id)), hash, rows.find((row) => row.entity === key && row.locale === locale), req, locale, check);
|
|
148
|
-
return [locale, review];
|
|
149
|
-
}))
|
|
157
|
+
locales: reviewEntity(config, source, Object.fromEntries(targetLocales.map((locale) => [locale, targets[locale].get(String(source.id))])), targetLocales, hash, (locale) => rows.find((row) => row.entity === key && row.locale === locale), req, check)
|
|
150
158
|
};
|
|
151
159
|
});
|
|
152
160
|
return {
|
|
@@ -177,11 +185,11 @@ var loadGlobalsReview = async ({ req, globalSlugs }) => {
|
|
|
177
185
|
const source = await read(defaultLocale);
|
|
178
186
|
const hash = hashOf(config, source, req);
|
|
179
187
|
const key = entityKey({ globalSlug });
|
|
180
|
-
const
|
|
188
|
+
const targetByLocale = {};
|
|
181
189
|
for (const locale of targetLocales) {
|
|
182
|
-
|
|
183
|
-
locales[locale] = review;
|
|
190
|
+
targetByLocale[locale] = await read(locale);
|
|
184
191
|
}
|
|
192
|
+
const locales = reviewEntity(config, source, targetByLocale, targetLocales, hash, (locale) => rows.find((row) => row.entity === key && row.locale === locale), req, check);
|
|
185
193
|
entities.push({ key, label: globalLabel(config), globalSlug, locales });
|
|
186
194
|
}
|
|
187
195
|
return { entities, locales: targetLocales };
|
|
@@ -199,10 +207,14 @@ var loadEntityLocaleReview = async ({ req, entity, locale }) => {
|
|
|
199
207
|
throw new Error(`unknown entity ${entity}`);
|
|
200
208
|
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 });
|
|
201
209
|
const source = await read(defaultLocale);
|
|
202
|
-
const target = await read(locale);
|
|
203
210
|
const rows = await loadStatusRows(req, [entity]);
|
|
204
211
|
const row = rows.find((each) => each.locale === locale);
|
|
205
|
-
const
|
|
212
|
+
const { targetLocales } = readLocales(req);
|
|
213
|
+
const targetByLocale = {};
|
|
214
|
+
for (const each of targetLocales) {
|
|
215
|
+
targetByLocale[each] = await read(each);
|
|
216
|
+
}
|
|
217
|
+
const review = reviewEntity(config, source, targetByLocale, targetLocales, hashOf(config, source, req), (each) => rows.find((row2) => row2.locale === each), req, await loadLanguageCheck(req))[locale];
|
|
206
218
|
return {
|
|
207
219
|
...review,
|
|
208
220
|
entity,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadWrongLanguageCheck,
|
|
3
|
-
samePlaceholders,
|
|
4
3
|
plainText,
|
|
4
|
+
buildCrossLocaleTexts,
|
|
5
|
+
crossLocaleMatch,
|
|
5
6
|
traverseFields,
|
|
6
|
-
TRANSLATE_WORKFLOW_SLUG2,
|
|
7
7
|
collectTranslatableFields,
|
|
8
|
+
samePlaceholders,
|
|
9
|
+
TRANSLATE_WORKFLOW_SLUG2,
|
|
8
10
|
entityKey,
|
|
9
11
|
sourceHash
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-m5jdmn2w.js";
|
|
11
13
|
import {
|
|
12
14
|
TRANSLATION_STATUS_SLUG2
|
|
13
15
|
} from "./chunk-31qgv1zk.js";
|
|
@@ -102,6 +104,35 @@ var preview = (value) => {
|
|
|
102
104
|
return JSON.stringify(flat.length > 60 ? `${flat.slice(0, 57)}…` : flat);
|
|
103
105
|
};
|
|
104
106
|
var localeCodes = (req) => (req.payload.config.localization ? req.payload.config.localization.locales : []).map((each) => typeof each === "string" ? each : each.code);
|
|
107
|
+
var buildCrossLocaleTextsFor = async ({
|
|
108
|
+
config,
|
|
109
|
+
dataFrom,
|
|
110
|
+
req,
|
|
111
|
+
args
|
|
112
|
+
}) => {
|
|
113
|
+
const others = localeCodes(req).filter((each) => each !== args.locale && each !== args.localeFrom);
|
|
114
|
+
const fieldsPerLocale = [];
|
|
115
|
+
for (const locale of others) {
|
|
116
|
+
const { doc } = await findEntityWithConfig({
|
|
117
|
+
collectionSlug: args.collectionSlug,
|
|
118
|
+
globalSlug: args.globalSlug,
|
|
119
|
+
id: args.id,
|
|
120
|
+
locale,
|
|
121
|
+
overrideAccess: args.overrideAccess,
|
|
122
|
+
req
|
|
123
|
+
});
|
|
124
|
+
fieldsPerLocale.push({
|
|
125
|
+
locale,
|
|
126
|
+
fields: collectTranslatableFields({
|
|
127
|
+
config,
|
|
128
|
+
dataFrom,
|
|
129
|
+
dataTarget: doc,
|
|
130
|
+
options: req.payload.config.custom?.translator?._options
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return buildCrossLocaleTexts(fieldsPerLocale);
|
|
135
|
+
};
|
|
105
136
|
var translateOperation2 = async (args) => {
|
|
106
137
|
const req = "req" in args ? args.req : {
|
|
107
138
|
payload: args.payload
|
|
@@ -123,6 +154,7 @@ var translateOperation2 = async (args) => {
|
|
|
123
154
|
if (!resolver)
|
|
124
155
|
throw new APIError3(`Resolver with the key ${args.resolver} was not found`);
|
|
125
156
|
const valuesToTranslate = [];
|
|
157
|
+
const syncedValues = { count: 0 };
|
|
126
158
|
let translatedData = args.data;
|
|
127
159
|
if (!translatedData) {
|
|
128
160
|
const { doc } = await findEntityWithConfig({
|
|
@@ -135,8 +167,10 @@ var translateOperation2 = async (args) => {
|
|
|
135
167
|
});
|
|
136
168
|
translatedData = doc;
|
|
137
169
|
}
|
|
138
|
-
const
|
|
139
|
-
const
|
|
170
|
+
const detectionOn = args.retranslateIdentical && req.payload.config.custom?.translator?.languageDetection !== false;
|
|
171
|
+
const wrongLanguageCheck = detectionOn ? await loadWrongLanguageCheck(localeCodes(req)) : null;
|
|
172
|
+
const crossLocale = detectionOn ? await buildCrossLocaleTextsFor({ config, dataFrom, req, args }) : undefined;
|
|
173
|
+
const isWrongLanguage = wrongLanguageCheck || crossLocale ? (target, path) => Boolean(wrongLanguageCheck?.(plainText(target), args.locale)) || (path !== undefined && crossLocale !== undefined ? crossLocaleMatch(crossLocale, path, args.locale, plainText(target)) !== undefined : false) : undefined;
|
|
140
174
|
traverseFields({
|
|
141
175
|
dataFrom,
|
|
142
176
|
emptyOnly: args.emptyOnly,
|
|
@@ -145,12 +179,13 @@ var translateOperation2 = async (args) => {
|
|
|
145
179
|
fields: config.fields,
|
|
146
180
|
translatedData,
|
|
147
181
|
valuesToTranslate,
|
|
182
|
+
syncedValues,
|
|
148
183
|
_options: req.payload.config.custom?.translator?._options
|
|
149
184
|
});
|
|
150
185
|
const entityLabel = `${collectionSlug || globalSlug}#${id ?? ""}`;
|
|
151
186
|
const direction = `${args.localeFrom}→${args.locale}`;
|
|
152
187
|
req.payload.logger.info({
|
|
153
|
-
msg: `[translate] ${entityLabel} ${direction}: traversed ${valuesToTranslate.length} translatable value(s)`
|
|
188
|
+
msg: `[translate] ${entityLabel} ${direction}: traversed ${valuesToTranslate.length} translatable value(s)` + (syncedValues.count ? `, synced ${syncedValues.count} field value(s)` : "")
|
|
154
189
|
});
|
|
155
190
|
const resolveResult = valuesToTranslate.length === 0 ? { success: true, translatedTexts: [] } : await resolver.resolve({
|
|
156
191
|
localeFrom: args.localeFrom,
|
|
@@ -193,7 +228,7 @@ ${valuesToTranslate.map((v) => ` ${v.path ?? "(unknown)"}: ${preview(v.value)}`
|
|
|
193
228
|
${summary.join(`
|
|
194
229
|
`)}` : "")
|
|
195
230
|
});
|
|
196
|
-
if (args.update && valuesToTranslate.length > 0) {
|
|
231
|
+
if (args.update && (valuesToTranslate.length > 0 || syncedValues.count > 0)) {
|
|
197
232
|
const { _locale, _parent_id, createdAt, updatedAt, ...data } = translatedData;
|
|
198
233
|
await updateEntity({
|
|
199
234
|
collectionSlug,
|
|
@@ -210,6 +245,7 @@ ${summary.join(`
|
|
|
210
245
|
success: true,
|
|
211
246
|
translatedData,
|
|
212
247
|
translatedCount: valuesToTranslate.length,
|
|
248
|
+
syncedCount: syncedValues.count,
|
|
213
249
|
dataFrom
|
|
214
250
|
};
|
|
215
251
|
}
|
|
@@ -526,7 +562,7 @@ function createTranslateTask2(options = {}) {
|
|
|
526
562
|
});
|
|
527
563
|
return { output: { success: true } };
|
|
528
564
|
}
|
|
529
|
-
if (result.translatedCount === 0) {
|
|
565
|
+
if (result.translatedCount === 0 && result.syncedCount === 0) {
|
|
530
566
|
req.payload.logger.info({ jobId: job.id, msg: `[translate] ${entityLabel}#${id ?? global} → ${toLocale}: nothing to translate` });
|
|
531
567
|
return { output: { success: true } };
|
|
532
568
|
}
|
|
@@ -534,7 +570,7 @@ function createTranslateTask2(options = {}) {
|
|
|
534
570
|
const { _locale: _dropLocale, _parent_id: _dropParent, updatedAt: _dropUpdatedAt, createdAt: _dropCreatedAt, ...data } = translated;
|
|
535
571
|
req.payload.logger.info({
|
|
536
572
|
jobId: job.id,
|
|
537
|
-
msg: `[translate] persisting ${entityLabel}#${id ?? global} → ${toLocale}`
|
|
573
|
+
msg: `[translate] persisting ${entityLabel}#${id ?? global} → ${toLocale}` + (result.translatedCount === 0 ? " (field sync only)" : "")
|
|
538
574
|
});
|
|
539
575
|
try {
|
|
540
576
|
await updateEntity({
|
|
@@ -20,9 +20,10 @@ var looksLikeList = (text) => {
|
|
|
20
20
|
const lengths = lines.map((line) => line.length).sort((a, b) => a - b);
|
|
21
21
|
return lengths[Math.floor(lengths.length / 2)] < MIN_LENGTH;
|
|
22
22
|
};
|
|
23
|
-
var
|
|
23
|
+
var eldModule;
|
|
24
24
|
var loadWrongLanguageCheck = (locales) => {
|
|
25
|
-
|
|
25
|
+
eldModule ??= import("eld/small");
|
|
26
|
+
return eldModule.then(({ eld }) => {
|
|
26
27
|
const languages = [...new Set(locales.map(base))];
|
|
27
28
|
eld.setLanguageSubset(languages);
|
|
28
29
|
return (text, expectedLocale) => {
|
|
@@ -41,13 +42,8 @@ var loadWrongLanguageCheck = (locales) => {
|
|
|
41
42
|
return lead >= margin ? result.language : null;
|
|
42
43
|
};
|
|
43
44
|
}).catch(() => null);
|
|
44
|
-
return detector;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
// src/utils/placeholders.ts
|
|
48
|
-
var placeholdersOf = (value) => (value.match(/\{\s*[\w.]+\s*(?:,[^{}]*)?\}/g) ?? []).map((each) => each.replace(/\s+/g, "")).sort();
|
|
49
|
-
var samePlaceholders = (source, translated) => placeholdersOf(source).join("|") === placeholdersOf(translated).join("|");
|
|
50
|
-
|
|
51
47
|
// src/utils/plainText.ts
|
|
52
48
|
var plainText = (value) => {
|
|
53
49
|
if (typeof value === "string")
|
|
@@ -67,6 +63,34 @@ var plainText = (value) => {
|
|
|
67
63
|
return "";
|
|
68
64
|
};
|
|
69
65
|
|
|
66
|
+
// src/utils/crossLocale.ts
|
|
67
|
+
var normalize = (text) => text.replace(/\s+/g, " ").trim();
|
|
68
|
+
var buildCrossLocaleTexts = (fieldsPerLocale) => {
|
|
69
|
+
const texts = new Map;
|
|
70
|
+
for (const { locale, fields } of fieldsPerLocale) {
|
|
71
|
+
for (const field of fields) {
|
|
72
|
+
const value = normalize(plainText(field.target));
|
|
73
|
+
if (!value || value === normalize(plainText(field.source)))
|
|
74
|
+
continue;
|
|
75
|
+
const byLocale = texts.get(field.path) ?? new Map;
|
|
76
|
+
byLocale.set(locale, value);
|
|
77
|
+
texts.set(field.path, byLocale);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return texts;
|
|
81
|
+
};
|
|
82
|
+
var crossLocaleMatch = (texts, path, locale, text) => {
|
|
83
|
+
const value = normalize(text);
|
|
84
|
+
if (!value)
|
|
85
|
+
return;
|
|
86
|
+
for (const [other, otherValue] of texts.get(path) ?? []) {
|
|
87
|
+
if (other === locale || otherValue !== value || CLOSE_PAIRS.has(`${locale}|${other}`))
|
|
88
|
+
continue;
|
|
89
|
+
return other;
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
};
|
|
93
|
+
|
|
70
94
|
// src/translate/traverseFields.ts
|
|
71
95
|
import ObjectID from "bson-objectid";
|
|
72
96
|
import { tabHasName } from "payload/shared";
|
|
@@ -112,10 +136,10 @@ var isOpaqueText = (value) => {
|
|
|
112
136
|
};
|
|
113
137
|
|
|
114
138
|
// src/utils/looksUntranslated.ts
|
|
115
|
-
var
|
|
139
|
+
var normalize2 = (text) => text.replace(/\s+/g, " ").trim();
|
|
116
140
|
var looksUntranslated = (source, target) => {
|
|
117
|
-
const sourceText =
|
|
118
|
-
return sourceText.length > 3 && /\s/.test(sourceText) && !isOpaqueText(sourceText) && sourceText ===
|
|
141
|
+
const sourceText = normalize2(plainText(source));
|
|
142
|
+
return sourceText.length > 3 && /\s/.test(sourceText) && !isOpaqueText(sourceText) && sourceText === normalize2(plainText(target));
|
|
119
143
|
};
|
|
120
144
|
|
|
121
145
|
// src/utils/sanitizeSlug.ts
|
|
@@ -175,13 +199,14 @@ var traverseFields = (args) => {
|
|
|
175
199
|
path,
|
|
176
200
|
translatedData,
|
|
177
201
|
valuesToTranslate,
|
|
202
|
+
syncedValues,
|
|
178
203
|
onField,
|
|
179
204
|
_options
|
|
180
205
|
} = args;
|
|
181
206
|
const { additionalTraverseRichText } = _options ?? {};
|
|
182
207
|
const siblingDataFrom = args.siblingDataFrom ?? dataFrom;
|
|
183
208
|
const siblingDataTranslated = args.siblingDataTranslated ?? translatedData;
|
|
184
|
-
const keepsTarget = (source, target) => Boolean(emptyOnly) && hasText(target) && !(retranslateIdentical && (looksUntranslated(source, target) || Boolean(isWrongLanguage?.(target))));
|
|
209
|
+
const keepsTarget = (source, target, path) => Boolean(emptyOnly) && hasText(target) && !(retranslateIdentical && (looksUntranslated(source, target) || Boolean(isWrongLanguage?.(target, path))));
|
|
185
210
|
const recurse = (overrides) => traverseFields({ ...args, siblingDataFrom, siblingDataTranslated, ...overrides });
|
|
186
211
|
for (const field of fields) {
|
|
187
212
|
switch (field.type) {
|
|
@@ -235,6 +260,8 @@ var traverseFields = (args) => {
|
|
|
235
260
|
break;
|
|
236
261
|
const existing = Array.isArray(siblingDataTranslated[field.name]) ? siblingDataTranslated[field.name] : [];
|
|
237
262
|
const localized = Boolean(field.localized || localizedParent);
|
|
263
|
+
if (localized && existing.length !== rowsFrom.length)
|
|
264
|
+
syncedValues.count++;
|
|
238
265
|
const rows = localized ? buildLocalizedRows(rowsFrom, existing, isBlocks, emptyOnly) : existing.length ? existing : structuredClone(rowsFrom);
|
|
239
266
|
rows.forEach((row, index) => {
|
|
240
267
|
const rowFrom = rowsFrom[index];
|
|
@@ -278,6 +305,8 @@ var traverseFields = (args) => {
|
|
|
278
305
|
case "relationship":
|
|
279
306
|
case "select":
|
|
280
307
|
case "upload":
|
|
308
|
+
if (JSON.stringify(siblingDataTranslated[field.name]) !== JSON.stringify(siblingDataFrom[field.name]))
|
|
309
|
+
syncedValues.count++;
|
|
281
310
|
siblingDataTranslated[field.name] = siblingDataFrom[field.name];
|
|
282
311
|
break;
|
|
283
312
|
case "json": {
|
|
@@ -302,7 +331,7 @@ var traverseFields = (args) => {
|
|
|
302
331
|
onField?.({ path: keyPath, type: "json", source: value, target: previous });
|
|
303
332
|
if (!value.trim())
|
|
304
333
|
continue;
|
|
305
|
-
if (keepsTarget(value, previous))
|
|
334
|
+
if (keepsTarget(value, previous, keyPath))
|
|
306
335
|
continue;
|
|
307
336
|
target[key] = value;
|
|
308
337
|
if (isOpaqueText(value))
|
|
@@ -342,7 +371,7 @@ var traverseFields = (args) => {
|
|
|
342
371
|
if (typeof value !== "string" || !value.trim())
|
|
343
372
|
break;
|
|
344
373
|
const hasCurrent = typeof current === "string" && current.trim().length > 0;
|
|
345
|
-
if (isSlug ? hasCurrent : keepsTarget(value, current))
|
|
374
|
+
if (isSlug ? hasCurrent : keepsTarget(value, current, fieldPath))
|
|
346
375
|
break;
|
|
347
376
|
if (isOpaqueText(value)) {
|
|
348
377
|
siblingDataTranslated[field.name] = value;
|
|
@@ -384,7 +413,7 @@ var traverseFields = (args) => {
|
|
|
384
413
|
}
|
|
385
414
|
break;
|
|
386
415
|
}
|
|
387
|
-
if (keepsTarget(richTextDataFrom, current))
|
|
416
|
+
if (keepsTarget(richTextDataFrom, current, richTextPath))
|
|
388
417
|
break;
|
|
389
418
|
const isSlate = Array.isArray(richTextDataFrom);
|
|
390
419
|
const isLexical = typeof richTextDataFrom === "object" && "root" in richTextDataFrom;
|
|
@@ -423,12 +452,17 @@ var collectTranslatableFields = ({ config, dataFrom, dataTarget, options }) => {
|
|
|
423
452
|
fields: config.fields,
|
|
424
453
|
translatedData: structuredClone(dataTarget ?? {}),
|
|
425
454
|
valuesToTranslate: [],
|
|
455
|
+
syncedValues: { count: 0 },
|
|
426
456
|
onField: (field) => fields.push(field),
|
|
427
457
|
_options: options
|
|
428
458
|
});
|
|
429
459
|
return fields;
|
|
430
460
|
};
|
|
431
461
|
|
|
462
|
+
// src/utils/placeholders.ts
|
|
463
|
+
var placeholdersOf = (value) => (value.match(/\{\s*[\w.]+\s*(?:,[^{}]*)?\}/g) ?? []).map((each) => each.replace(/\s+/g, "")).sort();
|
|
464
|
+
var samePlaceholders = (source, translated) => placeholdersOf(source).join("|") === placeholdersOf(translated).join("|");
|
|
465
|
+
|
|
432
466
|
// src/review/entityKey.ts
|
|
433
467
|
var entityKey = ({ collectionSlug, globalSlug, id }) => globalSlug ? `global:${globalSlug}` : `${collectionSlug}:${id}`;
|
|
434
468
|
|
|
@@ -436,4 +470,4 @@ var entityKey = ({ collectionSlug, globalSlug, id }) => globalSlug ? `global:${g
|
|
|
436
470
|
import { createHash } from "node:crypto";
|
|
437
471
|
var sourceHash = (fields) => createHash("sha1").update(JSON.stringify(fields.map(({ path, source }) => [path, source]))).digest("hex");
|
|
438
472
|
|
|
439
|
-
export { loadWrongLanguageCheck,
|
|
473
|
+
export { loadWrongLanguageCheck, plainText, buildCrossLocaleTexts, crossLocaleMatch, isOpaqueText, looksUntranslated, traverseFields, collectTranslatableFields, samePlaceholders, TRANSLATE_TASK_SLUG2, TRANSLATE_WORKFLOW_SLUG2, entityKey, sourceHash };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justanarthur/payload-plugin-translator",
|
|
3
3
|
"description": "Translator plugin for Payload CMS — automatic localization via Google, OpenAI, LibreTranslate, or custom resolvers.",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"files": [
|