@justanarthur/payload-plugin-translator 1.3.21 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -0
- package/dist/client.css +41 -0
- package/dist/client.js +52 -52
- package/dist/index.d.ts +0 -39
- package/dist/index.js +28 -1049
- package/dist/jobs.d.ts +0 -145
- package/dist/jobs.js +8 -804
- package/dist/resolvers/copy.d.ts +0 -2
- package/dist/resolvers/copy.js +3 -13
- package/dist/resolvers/google.d.ts +0 -6
- package/dist/resolvers/google.js +4 -54
- package/dist/resolvers/libreTranslate.d.ts +0 -10
- package/dist/resolvers/libreTranslate.js +4 -56
- package/dist/resolvers/openAI.d.ts +2 -12
- package/dist/resolvers/openAI.js +4 -125
- package/dist/resolvers/types.d.ts +0 -2
- package/dist/shared/chunk-97ktc51q.js +15 -0
- package/dist/shared/chunk-ecnph6hw.js +205 -0
- package/dist/shared/chunk-frnemj69.js +6 -0
- package/dist/shared/chunk-fsveh29m.js +55 -0
- package/dist/shared/chunk-q51q8rd2.js +855 -0
- package/dist/shared/chunk-zhrw0jnz.js +57 -0
- package/dist/types.d.ts +0 -38
- package/package.json +6 -3
- package/dist/styles-jt3fm5fh.scss +0 -5
- package/dist/styles-vs6m7h55.scss +0 -76
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @justanarthur/payload-plugin-translator
|
|
2
|
+
|
|
3
|
+
[Payload CMS](https://payloadcms.com) plugin that keeps localized docs in sync. On every save of a
|
|
4
|
+
listed collection or global, it schedules background jobs that translate each field from the source
|
|
5
|
+
locale into every other declared locale, using a configurable resolver (Google Translate, OpenAI,
|
|
6
|
+
LibreTranslate, or your own).
|
|
7
|
+
|
|
8
|
+
Editor UX: the publish and save buttons in the affected collections/globals are swapped for a
|
|
9
|
+
custom variant that surfaces the job status. Locales without translations stay empty until the job
|
|
10
|
+
finishes — the plugin never blocks the editor's save.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @justanarthur/payload-plugin-translator
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires `payload@^3.85.0` and `react@^19`. The plugin reads your `config.localization.locales` —
|
|
19
|
+
it short-circuits (returns the config unchanged) when there's only one locale or no localization.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// payload.config.ts
|
|
25
|
+
import { buildConfig } from 'payload'
|
|
26
|
+
import { translator, openAIResolver } from '@justanarthur/payload-plugin-translator'
|
|
27
|
+
|
|
28
|
+
export default buildConfig({
|
|
29
|
+
plugins: [
|
|
30
|
+
translator({
|
|
31
|
+
collections: ['pages', 'posts'],
|
|
32
|
+
globals: ['header', 'footer'],
|
|
33
|
+
resolvers: [
|
|
34
|
+
openAIResolver({ apiKey: process.env.OPENAI_API_KEY! })
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
]
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's the whole integration. The first save of a Page doc schedules one translate job per non-source
|
|
42
|
+
locale; the job runs against your resolver and writes each translated field back into the
|
|
43
|
+
collection's `_locales` row.
|
|
44
|
+
|
|
45
|
+
## Built-in resolvers
|
|
46
|
+
|
|
47
|
+
| Resolver | Import | Env / config | Notes |
|
|
48
|
+
|--------------------|-------------------------------------------------|---------------------------------|------------------------------------------------------------------|
|
|
49
|
+
| `googleResolver` | `@justanarthur/payload-plugin-translator/resolvers/google` | `apiKey` (Google Cloud Translation API) | Cheap, good for bulk text. Locale codes are remapped where useful (e.g. `ua` → `uk`). |
|
|
50
|
+
| `openAIResolver` | `@justanarthur/payload-plugin-translator/resolvers/openAI` | `apiKey`, optional `model`/`baseUrl`/`prompt`/`chunkLength` | Best fidelity. Default model `gpt-4o-mini`. gpt-5.x uses `max_completion_tokens` + `reasoning_effort: 'low'` automatically. Slugs get a transliteration rule baked into the prompt. |
|
|
51
|
+
| `libreResolver` | `@justanarthur/payload-plugin-translator/resolvers/libreTranslate` | `apiKey`, optional `url`/`chunkLength` | Self-host friendly. Same locale remap as Google. |
|
|
52
|
+
| `copyResolver` | `@justanarthur/payload-plugin-translator/resolvers/copy` | none | Returns the source text verbatim. Useful as a fallback or for testing. |
|
|
53
|
+
|
|
54
|
+
You can supply multiple resolvers — they run in the order declared, and the first one to return
|
|
55
|
+
`success: true` wins for the chunk.
|
|
56
|
+
|
|
57
|
+
## Writing your own resolver
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import type { TranslateResolver } from '@justanarthur/payload-plugin-translator/resolvers/types'
|
|
61
|
+
|
|
62
|
+
export const myResolver: TranslateResolver = {
|
|
63
|
+
key: 'my-translator',
|
|
64
|
+
resolve: async ({ localeFrom, localeTo, texts, req }) => {
|
|
65
|
+
// call your translator of choice
|
|
66
|
+
return { success: true, translatedTexts: ['...'] }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`texts` is the flat string array extracted from the doc; you return the same-length array in
|
|
72
|
+
`translatedTexts`. Returning `{ success: false }` makes the job retry.
|
|
73
|
+
|
|
74
|
+
## Options reference
|
|
75
|
+
|
|
76
|
+
| Option | Type | Required | Notes |
|
|
77
|
+
|---------------|-----------------------|----------|------------------------------------------------------------------------|
|
|
78
|
+
| `collections` | `CollectionSlug[]` | yes | Collections to auto-translate. |
|
|
79
|
+
| `globals` | `GlobalSlug[]` | yes | Globals to auto-translate. |
|
|
80
|
+
| `resolvers` | `TranslateResolver[]` | yes | Tried in order; first to succeed wins per chunk. |
|
|
81
|
+
| `autoTranslate` | `boolean` | no | Default `true`. Set `false` to opt out of the auto-translate afterChange hook — useful when you drive translation manually via the `translateOperation` export. |
|
|
82
|
+
| `disabled` | `boolean` | no | Skip the plugin entirely (no overrides, no jobs). Useful in tests. |
|
|
83
|
+
| `_options.additionalTraverseRichText` | function | no | Hook to extend rich-text traversal — see below. |
|
|
84
|
+
|
|
85
|
+
### Rich-text traversal hook
|
|
86
|
+
|
|
87
|
+
The plugin walks Lexical richText fields itself, but custom Lexical nodes (e.g. blocks the host
|
|
88
|
+
defines for landing pages) won't be visited. `_options.additionalTraverseRichText` lets you register
|
|
89
|
+
a custom walker:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
translator({
|
|
93
|
+
// ...
|
|
94
|
+
_options: {
|
|
95
|
+
additionalTraverseRichText: ({ root, onText }) => {
|
|
96
|
+
// call onText(siblingData, attribute?) for every leaf string you find
|
|
97
|
+
// siblingData mutates the doc tree in-place
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The hook receives `onText` which mutates the data tree, plus the root node. It's called for every
|
|
104
|
+
richText field before translation begins.
|
|
105
|
+
|
|
106
|
+
## How it works under the hood
|
|
107
|
+
|
|
108
|
+
- On plugin init: registers a `translate` Payload **task** and a `translate` **workflow**, swapped
|
|
109
|
+
Publish/Save buttons per collection/global, and an admin-only endpoint at `/api/translator/translate`.
|
|
110
|
+
- On `afterChange` (when `autoTranslate: true`): the plugin enqueues one workflow run per non-source
|
|
111
|
+
locale. The job extracts translatable fields, asks each resolver in turn, writes the result back
|
|
112
|
+
into the `_locales` row.
|
|
113
|
+
- The plugin de-duplicates via `AUTO_TRANSLATE_MARKER` — re-running it on an already-attached hook
|
|
114
|
+
is a no-op, so it's safe to wrap multiple plugins around the same collection.
|
|
115
|
+
|
|
116
|
+
## Manual translation
|
|
117
|
+
|
|
118
|
+
Two escape hatches when you don't want the auto-hook:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// the operation (Payload `operation` you can call from custom endpoints)
|
|
122
|
+
import { translateOperation } from '@justanarthur/payload-plugin-translator'
|
|
123
|
+
|
|
124
|
+
// the job factories (advanced — you usually don't need these directly)
|
|
125
|
+
import { createTranslateTask, createTranslateWorkflow } from '@justanarthur/payload-plugin-translator/jobs'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For most hosts the auto-hook + resolver list is enough.
|
|
129
|
+
|
|
130
|
+
## Licence
|
|
131
|
+
|
|
132
|
+
MIT
|
package/dist/client.css
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* src/client/components/CustomButton/styles.css */
|
|
2
|
+
.translator__custom-save-button {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: .5rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* src/client/components/TranslatorModal/styles.css */
|
|
9
|
+
.translator__modal {
|
|
10
|
+
position: relative;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.translator__wrapper {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 1rem;
|
|
17
|
+
padding: 1rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.translator__close {
|
|
21
|
+
position: absolute;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
background: none;
|
|
24
|
+
border: 0;
|
|
25
|
+
font-size: 1.25rem;
|
|
26
|
+
line-height: 1;
|
|
27
|
+
top: .5rem;
|
|
28
|
+
right: .5rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.translator__content {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: .75rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.translator__buttons {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-wrap: wrap;
|
|
40
|
+
gap: .5rem;
|
|
41
|
+
}
|
package/dist/client.js
CHANGED
|
@@ -54,7 +54,7 @@ var useTranslator = () => {
|
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
// src/client/providers/Translator/TranslatorProvider.tsx
|
|
57
|
-
import {
|
|
57
|
+
import { jsx } from "react/jsx-runtime";
|
|
58
58
|
var modalSlug = "translator-modal";
|
|
59
59
|
var TranslatorProvider = ({ children }) => {
|
|
60
60
|
const [resolver, setResolver] = useState(null);
|
|
@@ -141,7 +141,7 @@ var TranslatorProvider = ({ children }) => {
|
|
|
141
141
|
}
|
|
142
142
|
closeTranslator();
|
|
143
143
|
};
|
|
144
|
-
return /* @__PURE__ */
|
|
144
|
+
return /* @__PURE__ */ jsx(TranslatorContext.Provider, {
|
|
145
145
|
value: {
|
|
146
146
|
closeTranslator,
|
|
147
147
|
localeToTranslateFrom,
|
|
@@ -157,22 +157,22 @@ var TranslatorProvider = ({ children }) => {
|
|
|
157
157
|
submit
|
|
158
158
|
},
|
|
159
159
|
children
|
|
160
|
-
}
|
|
160
|
+
});
|
|
161
161
|
};
|
|
162
162
|
|
|
163
163
|
// src/client/components/ResolverButton/ResolverButton.tsx
|
|
164
164
|
import { Button, useTranslation as useTranslation2 } from "@payloadcms/ui";
|
|
165
|
-
import {
|
|
165
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
166
166
|
var ResolverButton = ({
|
|
167
167
|
resolver: { key: resolverKey }
|
|
168
168
|
}) => {
|
|
169
169
|
const { openTranslator } = useTranslator();
|
|
170
170
|
const { t } = useTranslation2();
|
|
171
171
|
const handleClick = () => openTranslator({ resolverKey });
|
|
172
|
-
return /* @__PURE__ */
|
|
172
|
+
return /* @__PURE__ */ jsx2(Button, {
|
|
173
173
|
onClick: handleClick,
|
|
174
174
|
children: t(`plugin-translator:resolver_${resolverKey}_buttonLabel`)
|
|
175
|
-
}
|
|
175
|
+
});
|
|
176
176
|
};
|
|
177
177
|
// src/client/components/TranslatorModal/TranslatorModal.tsx
|
|
178
178
|
import { Modal } from "@payloadcms/ui";
|
|
@@ -184,32 +184,32 @@ import { Button as Button2, Popup, PopupList, useTranslation as useTranslation4
|
|
|
184
184
|
// src/client/components/LocaleLabel/LocaleLabel.tsx
|
|
185
185
|
import { getTranslation } from "@payloadcms/translations";
|
|
186
186
|
import { ChevronIcon, useTranslation as useTranslation3 } from "@payloadcms/ui";
|
|
187
|
-
import {
|
|
187
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
188
188
|
var baseClass = "localizer-button";
|
|
189
189
|
var LocaleLabel = ({ locale }) => {
|
|
190
190
|
const { i18n, t } = useTranslation3();
|
|
191
|
-
return /* @__PURE__ */
|
|
191
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
192
192
|
"aria-label": t("general:locale"),
|
|
193
193
|
className: baseClass,
|
|
194
194
|
children: [
|
|
195
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ jsx3("div", {
|
|
196
196
|
className: `${baseClass}__label`,
|
|
197
197
|
children: `${t("general:locale")}:`
|
|
198
|
-
}
|
|
198
|
+
}),
|
|
199
199
|
" ",
|
|
200
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ jsx3("span", {
|
|
201
201
|
className: `${baseClass}__current-label`,
|
|
202
202
|
children: `${getTranslation(locale.label, i18n)}`
|
|
203
|
-
}
|
|
203
|
+
}),
|
|
204
204
|
" ",
|
|
205
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ jsx3(ChevronIcon, {
|
|
206
206
|
className: `${baseClass}__chevron`
|
|
207
|
-
}
|
|
207
|
+
})
|
|
208
208
|
]
|
|
209
|
-
}
|
|
209
|
+
});
|
|
210
210
|
};
|
|
211
211
|
// src/client/components/TranslatorModal/Content.tsx
|
|
212
|
-
import {
|
|
212
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
213
213
|
var Content = () => {
|
|
214
214
|
const {
|
|
215
215
|
localeToTranslateFrom: localeCodeToTranslateFrom,
|
|
@@ -220,21 +220,21 @@ var Content = () => {
|
|
|
220
220
|
} = useTranslator();
|
|
221
221
|
const { i18n } = useTranslation4();
|
|
222
222
|
const localeToTranslateFrom = localesOptions.find((each) => each.code === localeCodeToTranslateFrom);
|
|
223
|
-
return /* @__PURE__ */
|
|
223
|
+
return /* @__PURE__ */ jsxs2("div", {
|
|
224
224
|
className: "translator__content",
|
|
225
225
|
children: [
|
|
226
|
-
/* @__PURE__ */
|
|
226
|
+
/* @__PURE__ */ jsx4("h2", {
|
|
227
227
|
children: resolverT("modalTitle")
|
|
228
|
-
}
|
|
229
|
-
localeToTranslateFrom && /* @__PURE__ */
|
|
230
|
-
button: /* @__PURE__ */
|
|
228
|
+
}),
|
|
229
|
+
localeToTranslateFrom && /* @__PURE__ */ jsx4(Popup, {
|
|
230
|
+
button: /* @__PURE__ */ jsx4(LocaleLabel, {
|
|
231
231
|
locale: localeToTranslateFrom
|
|
232
|
-
}
|
|
232
|
+
}),
|
|
233
233
|
horizontalAlign: "center",
|
|
234
|
-
render: ({ close }) => /* @__PURE__ */
|
|
234
|
+
render: ({ close }) => /* @__PURE__ */ jsx4(PopupList.ButtonGroup, {
|
|
235
235
|
children: localesOptions.map((option) => {
|
|
236
236
|
const label = getTranslation2(option.label, i18n);
|
|
237
|
-
return /* @__PURE__ */
|
|
237
|
+
return /* @__PURE__ */ jsxs2(PopupList.Button, {
|
|
238
238
|
active: option.code === localeCodeToTranslateFrom,
|
|
239
239
|
onClick: () => {
|
|
240
240
|
setLocaleToTranslateFrom(option.code);
|
|
@@ -244,52 +244,52 @@ var Content = () => {
|
|
|
244
244
|
label,
|
|
245
245
|
label !== option.code && ` (${option.code})`
|
|
246
246
|
]
|
|
247
|
-
}, option.code
|
|
247
|
+
}, option.code);
|
|
248
248
|
})
|
|
249
|
-
}
|
|
249
|
+
}),
|
|
250
250
|
verticalAlign: "bottom"
|
|
251
|
-
}
|
|
252
|
-
/* @__PURE__ */
|
|
251
|
+
}),
|
|
252
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
253
253
|
className: "translator__buttons",
|
|
254
254
|
children: [
|
|
255
|
-
/* @__PURE__ */
|
|
255
|
+
/* @__PURE__ */ jsx4(Button2, {
|
|
256
256
|
onClick: () => submit({ emptyOnly: false }),
|
|
257
257
|
children: resolverT("submitButtonLabelFull")
|
|
258
|
-
}
|
|
259
|
-
/* @__PURE__ */
|
|
258
|
+
}),
|
|
259
|
+
/* @__PURE__ */ jsx4(Button2, {
|
|
260
260
|
onClick: () => submit({ emptyOnly: true }),
|
|
261
261
|
children: resolverT("submitButtonLabelEmpty")
|
|
262
|
-
}
|
|
262
|
+
})
|
|
263
263
|
]
|
|
264
|
-
}
|
|
264
|
+
})
|
|
265
265
|
]
|
|
266
|
-
}
|
|
266
|
+
});
|
|
267
267
|
};
|
|
268
268
|
|
|
269
269
|
// src/client/components/TranslatorModal/TranslatorModal.tsx
|
|
270
|
-
import {
|
|
270
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
271
271
|
var TranslatorModal = () => {
|
|
272
272
|
const { closeTranslator, modalSlug: modalSlug2, resolver } = useTranslator();
|
|
273
273
|
if (!resolver)
|
|
274
274
|
return;
|
|
275
|
-
return /* @__PURE__ */
|
|
275
|
+
return /* @__PURE__ */ jsx5(Modal, {
|
|
276
276
|
className: "translator__modal",
|
|
277
277
|
slug: modalSlug2,
|
|
278
|
-
children: /* @__PURE__ */
|
|
278
|
+
children: /* @__PURE__ */ jsxs3("div", {
|
|
279
279
|
className: "translator__wrapper",
|
|
280
280
|
children: [
|
|
281
|
-
/* @__PURE__ */
|
|
281
|
+
/* @__PURE__ */ jsx5("button", {
|
|
282
282
|
"aria-label": "Close",
|
|
283
283
|
className: "translator__close",
|
|
284
284
|
onClick: closeTranslator
|
|
285
|
-
}
|
|
286
|
-
/* @__PURE__ */
|
|
285
|
+
}),
|
|
286
|
+
/* @__PURE__ */ jsx5(Content, {})
|
|
287
287
|
]
|
|
288
|
-
}
|
|
289
|
-
}
|
|
288
|
+
})
|
|
289
|
+
});
|
|
290
290
|
};
|
|
291
291
|
// src/client/components/CustomButton/CustomButtonWithTranslator.tsx
|
|
292
|
-
import {
|
|
292
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
293
293
|
|
|
294
294
|
var CustomButtonWithTranslator = ({ type }) => {
|
|
295
295
|
const { config } = useConfig2();
|
|
@@ -297,19 +297,19 @@ var CustomButtonWithTranslator = ({ type }) => {
|
|
|
297
297
|
const { globalSlug, id } = useDocumentInfo2();
|
|
298
298
|
const resolvers = config.admin?.custom?.translator?.resolvers ?? [];
|
|
299
299
|
if (!id && !globalSlug)
|
|
300
|
-
return /* @__PURE__ */
|
|
301
|
-
return /* @__PURE__ */
|
|
302
|
-
children: /* @__PURE__ */
|
|
300
|
+
return /* @__PURE__ */ jsx6(DefaultButton, {});
|
|
301
|
+
return /* @__PURE__ */ jsx6(TranslatorProvider, {
|
|
302
|
+
children: /* @__PURE__ */ jsxs4("div", {
|
|
303
303
|
className: "translator__custom-save-button",
|
|
304
304
|
children: [
|
|
305
|
-
/* @__PURE__ */
|
|
306
|
-
resolvers.map((resolver) => /* @__PURE__ */
|
|
305
|
+
/* @__PURE__ */ jsx6(TranslatorModal, {}),
|
|
306
|
+
resolvers.map((resolver) => /* @__PURE__ */ jsx6(ResolverButton, {
|
|
307
307
|
resolver
|
|
308
|
-
}, resolver.key
|
|
309
|
-
/* @__PURE__ */
|
|
308
|
+
}, resolver.key)),
|
|
309
|
+
/* @__PURE__ */ jsx6(DefaultButton, {})
|
|
310
310
|
]
|
|
311
|
-
}
|
|
312
|
-
}
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
313
|
};
|
|
314
314
|
|
|
315
315
|
// src/exports/client.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ type TranslateArgs = {
|
|
|
7
7
|
emptyOnly?: boolean;
|
|
8
8
|
globalSlug?: string;
|
|
9
9
|
id?: number | string;
|
|
10
|
-
/** active locale */
|
|
11
10
|
locale: string;
|
|
12
11
|
localeFrom: string;
|
|
13
12
|
overrideAccess?: boolean;
|
|
@@ -31,9 +30,7 @@ import { Plugin } from "payload";
|
|
|
31
30
|
import { CollectionSlug, GlobalSlug } from "payload";
|
|
32
31
|
import { PayloadRequest as PayloadRequest3 } from "payload";
|
|
33
32
|
type TranslateResolverArgs = {
|
|
34
|
-
/** Locale to translate from */
|
|
35
33
|
localeFrom: string;
|
|
36
|
-
/** Locale to translate to */
|
|
37
34
|
localeTo: string;
|
|
38
35
|
req: PayloadRequest3;
|
|
39
36
|
texts: string[];
|
|
@@ -49,48 +46,12 @@ type TranslateResolver = {
|
|
|
49
46
|
resolve: (args: TranslateResolverArgs) => Promise<TranslateResolverResponse> | TranslateResolverResponse;
|
|
50
47
|
};
|
|
51
48
|
type TranslatorConfig = {
|
|
52
|
-
/**
|
|
53
|
-
* Collections with the enabled translator in the admin UI
|
|
54
|
-
*/
|
|
55
49
|
collections: CollectionSlug[];
|
|
56
|
-
/**
|
|
57
|
-
* Disable the plugin
|
|
58
|
-
*/
|
|
59
50
|
disabled?: boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Globals with the enabled translator in the admin UI
|
|
62
|
-
*/
|
|
63
51
|
globals: GlobalSlug[];
|
|
64
|
-
/**
|
|
65
|
-
* Add resolvers that you want to include, examples on how to write your own in ./plugin/src/resolvers
|
|
66
|
-
*/
|
|
67
52
|
resolvers: TranslateResolver[];
|
|
68
|
-
/**
|
|
69
|
-
* Auto-translate every save in the default locale to the other
|
|
70
|
-
* configured locales via Payload's job queue.
|
|
71
|
-
*
|
|
72
|
-
* When `true`, the plugin:
|
|
73
|
-
* - registers `createTranslateTask()` and `createTranslateWorkflow()`
|
|
74
|
-
* in `config.jobs` (idempotent — skips if a task/workflow with
|
|
75
|
-
* the same slug is already registered, so host-customized
|
|
76
|
-
* factories survive),
|
|
77
|
-
* - prepends `createAutoTranslateCollectionHook` /
|
|
78
|
-
* `createAutoTranslateGlobalHook` to each collection/global
|
|
79
|
-
* listed in `collections` / `globals`.
|
|
80
|
-
*
|
|
81
|
-
* Default: `false`. Public factories stay exported for hosts that
|
|
82
|
-
* need per-entity control.
|
|
83
|
-
*/
|
|
84
53
|
autoTranslate?: boolean;
|
|
85
|
-
/**
|
|
86
|
-
* Advanced traversal options. Mirrors the options accepted by the
|
|
87
|
-
* internal field walker — keep in sync if you need the latest shape.
|
|
88
|
-
*/
|
|
89
54
|
_options?: {
|
|
90
|
-
/**
|
|
91
|
-
* Hook invoked while walking each rich-text node. Lets you append
|
|
92
|
-
* extra text segments to be translated (e.g. custom lexical nodes).
|
|
93
|
-
*/
|
|
94
55
|
additionalTraverseRichText?: (args: {
|
|
95
56
|
onText: (siblingData: Record<string, unknown>, attribute?: string) => void;
|
|
96
57
|
root: Record<string, unknown>;
|