@justanarthur/payload-plugin-translator 1.3.21 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -1,4 +1,8 @@
1
1
  "use client";
2
+ import {
3
+ REVIEW_VIEW_PATH
4
+ } from "./shared/chunk-31qgv1zk.js";
5
+
2
6
  // src/client/components/CustomButton/CustomButtonWithTranslator.tsx
3
7
  import { PublishButton, SaveButton, useConfig as useConfig2, useDocumentInfo as useDocumentInfo2 } from "@payloadcms/ui";
4
8
 
@@ -54,7 +58,7 @@ var useTranslator = () => {
54
58
  };
55
59
 
56
60
  // src/client/providers/Translator/TranslatorProvider.tsx
57
- import { jsxDEV } from "react/jsx-dev-runtime";
61
+ import { jsx } from "react/jsx-runtime";
58
62
  var modalSlug = "translator-modal";
59
63
  var TranslatorProvider = ({ children }) => {
60
64
  const [resolver, setResolver] = useState(null);
@@ -85,19 +89,18 @@ var TranslatorProvider = ({ children }) => {
85
89
  const resolvers = custom?.translator?.resolvers || undefined;
86
90
  if (!resolvers)
87
91
  return null;
88
- const resolverConfig2 = resolvers.find((each) => each.key === resolver);
89
- return resolverConfig2 ?? null;
92
+ const resolverConfig = resolvers.find((each) => each.key === resolver);
93
+ return resolverConfig ?? null;
90
94
  }, [custom, resolver]);
91
95
  if (!localization)
92
96
  throw new Error("Localization config is not provided and PluginTranslator is used");
93
97
  const localesOptions = localization.locales.filter((each) => each.code !== locale.code);
94
98
  const [localeToTranslateFrom, setLocaleToTranslateFrom] = useState("");
95
99
  useEffect(() => {
96
- const defaultFromOptions = localesOptions.find((each) => localization.defaultLocale === each.code);
97
- if (defaultFromOptions)
98
- setLocaleToTranslateFrom(defaultFromOptions.code);
99
- setLocaleToTranslateFrom(localesOptions[0].code);
100
- }, [locale, localesOptions, localization.defaultLocale]);
100
+ const options = localization.locales.filter((each) => each.code !== locale.code);
101
+ const defaultFromOptions = options.find((each) => localization.defaultLocale === each.code);
102
+ setLocaleToTranslateFrom((defaultFromOptions ?? options[0])?.code ?? "");
103
+ }, [locale.code, localization.defaultLocale, localization.locales]);
101
104
  const closeTranslator = () => modal.closeModal(modalSlug);
102
105
  const submit = async ({ emptyOnly }) => {
103
106
  if (!resolver)
@@ -141,7 +144,7 @@ var TranslatorProvider = ({ children }) => {
141
144
  }
142
145
  closeTranslator();
143
146
  };
144
- return /* @__PURE__ */ jsxDEV(TranslatorContext.Provider, {
147
+ return /* @__PURE__ */ jsx(TranslatorContext.Provider, {
145
148
  value: {
146
149
  closeTranslator,
147
150
  localeToTranslateFrom,
@@ -157,22 +160,22 @@ var TranslatorProvider = ({ children }) => {
157
160
  submit
158
161
  },
159
162
  children
160
- }, undefined, false, undefined, this);
163
+ });
161
164
  };
162
165
 
163
166
  // src/client/components/ResolverButton/ResolverButton.tsx
164
167
  import { Button, useTranslation as useTranslation2 } from "@payloadcms/ui";
165
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
168
+ import { jsx as jsx2 } from "react/jsx-runtime";
166
169
  var ResolverButton = ({
167
170
  resolver: { key: resolverKey }
168
171
  }) => {
169
172
  const { openTranslator } = useTranslator();
170
173
  const { t } = useTranslation2();
171
174
  const handleClick = () => openTranslator({ resolverKey });
172
- return /* @__PURE__ */ jsxDEV2(Button, {
175
+ return /* @__PURE__ */ jsx2(Button, {
173
176
  onClick: handleClick,
174
177
  children: t(`plugin-translator:resolver_${resolverKey}_buttonLabel`)
175
- }, undefined, false, undefined, this);
178
+ });
176
179
  };
177
180
  // src/client/components/TranslatorModal/TranslatorModal.tsx
178
181
  import { Modal } from "@payloadcms/ui";
@@ -184,32 +187,32 @@ import { Button as Button2, Popup, PopupList, useTranslation as useTranslation4
184
187
  // src/client/components/LocaleLabel/LocaleLabel.tsx
185
188
  import { getTranslation } from "@payloadcms/translations";
186
189
  import { ChevronIcon, useTranslation as useTranslation3 } from "@payloadcms/ui";
187
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
190
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
188
191
  var baseClass = "localizer-button";
189
192
  var LocaleLabel = ({ locale }) => {
190
193
  const { i18n, t } = useTranslation3();
191
- return /* @__PURE__ */ jsxDEV3("div", {
194
+ return /* @__PURE__ */ jsxs("div", {
192
195
  "aria-label": t("general:locale"),
193
196
  className: baseClass,
194
197
  children: [
195
- /* @__PURE__ */ jsxDEV3("div", {
198
+ /* @__PURE__ */ jsx3("div", {
196
199
  className: `${baseClass}__label`,
197
200
  children: `${t("general:locale")}:`
198
- }, undefined, false, undefined, this),
201
+ }),
199
202
  "  ",
200
- /* @__PURE__ */ jsxDEV3("span", {
203
+ /* @__PURE__ */ jsx3("span", {
201
204
  className: `${baseClass}__current-label`,
202
205
  children: `${getTranslation(locale.label, i18n)}`
203
- }, undefined, false, undefined, this),
206
+ }),
204
207
  " ",
205
- /* @__PURE__ */ jsxDEV3(ChevronIcon, {
208
+ /* @__PURE__ */ jsx3(ChevronIcon, {
206
209
  className: `${baseClass}__chevron`
207
- }, undefined, false, undefined, this)
210
+ })
208
211
  ]
209
- }, undefined, true, undefined, this);
212
+ });
210
213
  };
211
214
  // src/client/components/TranslatorModal/Content.tsx
212
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
215
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
213
216
  var Content = () => {
214
217
  const {
215
218
  localeToTranslateFrom: localeCodeToTranslateFrom,
@@ -220,21 +223,21 @@ var Content = () => {
220
223
  } = useTranslator();
221
224
  const { i18n } = useTranslation4();
222
225
  const localeToTranslateFrom = localesOptions.find((each) => each.code === localeCodeToTranslateFrom);
223
- return /* @__PURE__ */ jsxDEV4("div", {
226
+ return /* @__PURE__ */ jsxs2("div", {
224
227
  className: "translator__content",
225
228
  children: [
226
- /* @__PURE__ */ jsxDEV4("h2", {
229
+ /* @__PURE__ */ jsx4("h2", {
227
230
  children: resolverT("modalTitle")
228
- }, undefined, false, undefined, this),
229
- localeToTranslateFrom && /* @__PURE__ */ jsxDEV4(Popup, {
230
- button: /* @__PURE__ */ jsxDEV4(LocaleLabel, {
231
+ }),
232
+ localeToTranslateFrom && /* @__PURE__ */ jsx4(Popup, {
233
+ button: /* @__PURE__ */ jsx4(LocaleLabel, {
231
234
  locale: localeToTranslateFrom
232
- }, undefined, false, undefined, this),
235
+ }),
233
236
  horizontalAlign: "center",
234
- render: ({ close }) => /* @__PURE__ */ jsxDEV4(PopupList.ButtonGroup, {
237
+ render: ({ close }) => /* @__PURE__ */ jsx4(PopupList.ButtonGroup, {
235
238
  children: localesOptions.map((option) => {
236
239
  const label = getTranslation2(option.label, i18n);
237
- return /* @__PURE__ */ jsxDEV4(PopupList.Button, {
240
+ return /* @__PURE__ */ jsxs2(PopupList.Button, {
238
241
  active: option.code === localeCodeToTranslateFrom,
239
242
  onClick: () => {
240
243
  setLocaleToTranslateFrom(option.code);
@@ -244,52 +247,52 @@ var Content = () => {
244
247
  label,
245
248
  label !== option.code && ` (${option.code})`
246
249
  ]
247
- }, option.code, true, undefined, this);
250
+ }, option.code);
248
251
  })
249
- }, undefined, false, undefined, this),
252
+ }),
250
253
  verticalAlign: "bottom"
251
- }, undefined, false, undefined, this),
252
- /* @__PURE__ */ jsxDEV4("div", {
254
+ }),
255
+ /* @__PURE__ */ jsxs2("div", {
253
256
  className: "translator__buttons",
254
257
  children: [
255
- /* @__PURE__ */ jsxDEV4(Button2, {
258
+ /* @__PURE__ */ jsx4(Button2, {
256
259
  onClick: () => submit({ emptyOnly: false }),
257
260
  children: resolverT("submitButtonLabelFull")
258
- }, undefined, false, undefined, this),
259
- /* @__PURE__ */ jsxDEV4(Button2, {
261
+ }),
262
+ /* @__PURE__ */ jsx4(Button2, {
260
263
  onClick: () => submit({ emptyOnly: true }),
261
264
  children: resolverT("submitButtonLabelEmpty")
262
- }, undefined, false, undefined, this)
265
+ })
263
266
  ]
264
- }, undefined, true, undefined, this)
267
+ })
265
268
  ]
266
- }, undefined, true, undefined, this);
269
+ });
267
270
  };
268
271
 
269
272
  // src/client/components/TranslatorModal/TranslatorModal.tsx
270
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
273
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
271
274
  var TranslatorModal = () => {
272
- const { closeTranslator, modalSlug: modalSlug2, resolver } = useTranslator();
275
+ const { closeTranslator, modalSlug, resolver } = useTranslator();
273
276
  if (!resolver)
274
277
  return;
275
- return /* @__PURE__ */ jsxDEV5(Modal, {
278
+ return /* @__PURE__ */ jsx5(Modal, {
276
279
  className: "translator__modal",
277
- slug: modalSlug2,
278
- children: /* @__PURE__ */ jsxDEV5("div", {
280
+ slug: modalSlug,
281
+ children: /* @__PURE__ */ jsxs3("div", {
279
282
  className: "translator__wrapper",
280
283
  children: [
281
- /* @__PURE__ */ jsxDEV5("button", {
284
+ /* @__PURE__ */ jsx5("button", {
282
285
  "aria-label": "Close",
283
286
  className: "translator__close",
284
287
  onClick: closeTranslator
285
- }, undefined, false, undefined, this),
286
- /* @__PURE__ */ jsxDEV5(Content, {}, undefined, false, undefined, this)
288
+ }),
289
+ /* @__PURE__ */ jsx5(Content, {})
287
290
  ]
288
- }, undefined, true, undefined, this)
289
- }, undefined, false, undefined, this);
291
+ })
292
+ });
290
293
  };
291
294
  // src/client/components/CustomButton/CustomButtonWithTranslator.tsx
292
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
295
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
293
296
 
294
297
  var CustomButtonWithTranslator = ({ type }) => {
295
298
  const { config } = useConfig2();
@@ -297,24 +300,107 @@ var CustomButtonWithTranslator = ({ type }) => {
297
300
  const { globalSlug, id } = useDocumentInfo2();
298
301
  const resolvers = config.admin?.custom?.translator?.resolvers ?? [];
299
302
  if (!id && !globalSlug)
300
- return /* @__PURE__ */ jsxDEV6(DefaultButton, {}, undefined, false, undefined, this);
301
- return /* @__PURE__ */ jsxDEV6(TranslatorProvider, {
302
- children: /* @__PURE__ */ jsxDEV6("div", {
303
+ return /* @__PURE__ */ jsx6(DefaultButton, {});
304
+ return /* @__PURE__ */ jsx6(TranslatorProvider, {
305
+ children: /* @__PURE__ */ jsxs4("div", {
303
306
  className: "translator__custom-save-button",
304
307
  children: [
305
- /* @__PURE__ */ jsxDEV6(TranslatorModal, {}, undefined, false, undefined, this),
306
- resolvers.map((resolver) => /* @__PURE__ */ jsxDEV6(ResolverButton, {
308
+ /* @__PURE__ */ jsx6(TranslatorModal, {}),
309
+ resolvers.map((resolver) => /* @__PURE__ */ jsx6(ResolverButton, {
307
310
  resolver
308
- }, resolver.key, false, undefined, this)),
309
- /* @__PURE__ */ jsxDEV6(DefaultButton, {}, undefined, false, undefined, this)
311
+ }, resolver.key)),
312
+ /* @__PURE__ */ jsx6(DefaultButton, {})
310
313
  ]
311
- }, undefined, true, undefined, this)
312
- }, undefined, false, undefined, this);
314
+ })
315
+ });
316
+ };
317
+
318
+ // src/client/components/Review/ReviewActions.tsx
319
+ import { Button as Button3, toast as toast2, useConfig as useConfig3 } from "@payloadcms/ui";
320
+ import { useState as useState2 } from "react";
321
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
322
+
323
+ var ReviewActions = ({ entity, locale, reviewed }) => {
324
+ const { config: { routes: { api }, serverURL } } = useConfig3();
325
+ const [pending, setPending] = useState2(null);
326
+ const run = async (action) => {
327
+ if (action === "translate-all" && !window.confirm(`Re-translate every field in ${locale}? Existing ${locale} copy is replaced.`))
328
+ return;
329
+ setPending(action);
330
+ try {
331
+ const path = action === "mark-reviewed" ? "mark-reviewed" : "translate";
332
+ const res = await fetch(`${serverURL ?? ""}${api}/translator/review/${path}`, {
333
+ method: "POST",
334
+ credentials: "include",
335
+ headers: { "Content-Type": "application/json" },
336
+ body: JSON.stringify({ entity, locale, mode: action === "translate-all" ? "all" : "missing" })
337
+ });
338
+ const result = await res.json().catch(() => ({}));
339
+ if (!res.ok || !result.success)
340
+ throw new Error(result?.errors?.[0]?.message ?? "request failed");
341
+ toast2.success(action === "mark-reviewed" ? `${locale} marked as reviewed` : `${locale} translated`);
342
+ window.location.reload();
343
+ } catch (error) {
344
+ toast2.error(`Could not ${action.replace("-", " ")}: ${error instanceof Error ? error.message : String(error)}`);
345
+ } finally {
346
+ setPending(null);
347
+ }
348
+ };
349
+ return /* @__PURE__ */ jsxs5("div", {
350
+ className: "translator-review__actions",
351
+ children: [
352
+ /* @__PURE__ */ jsx7(Button3, {
353
+ buttonStyle: "primary",
354
+ disabled: Boolean(pending),
355
+ onClick: () => run("translate-missing"),
356
+ size: "small",
357
+ children: pending === "translate-missing" ? "Translating…" : "Translate missing fields"
358
+ }),
359
+ /* @__PURE__ */ jsx7(Button3, {
360
+ buttonStyle: "secondary",
361
+ disabled: Boolean(pending),
362
+ onClick: () => run("translate-all"),
363
+ size: "small",
364
+ children: pending === "translate-all" ? "Translating…" : "Re-translate all"
365
+ }),
366
+ /* @__PURE__ */ jsx7(Button3, {
367
+ buttonStyle: "secondary",
368
+ disabled: Boolean(pending) || reviewed,
369
+ onClick: () => run("mark-reviewed"),
370
+ size: "small",
371
+ children: reviewed ? "Reviewed" : "Mark reviewed"
372
+ })
373
+ ]
374
+ });
375
+ };
376
+ // src/client/components/Review/ReviewStyles.tsx
377
+
378
+ var ReviewStyles = () => null;
379
+ // src/client/components/Review/TranslationsNavLink.tsx
380
+ import { useConfig as useConfig4 } from "@payloadcms/ui";
381
+ import { formatAdminURL } from "payload/shared";
382
+ import { jsx as jsx8 } from "react/jsx-runtime";
383
+
384
+ var TranslationsNavLink = () => {
385
+ const { config: { routes: { admin } } } = useConfig4();
386
+ const href = formatAdminURL({ adminRoute: admin, path: REVIEW_VIEW_PATH });
387
+ return /* @__PURE__ */ jsx8("a", {
388
+ className: "nav__link",
389
+ href,
390
+ id: "nav-translations",
391
+ children: /* @__PURE__ */ jsx8("span", {
392
+ className: "nav__link-label",
393
+ children: "Translations"
394
+ })
395
+ });
313
396
  };
314
397
 
315
398
  // src/exports/client.ts
316
399
  var client_default = CustomButtonWithTranslator;
317
400
  export {
318
- client_default as default,
319
- CustomButtonWithTranslator
401
+ CustomButtonWithTranslator,
402
+ ReviewActions,
403
+ ReviewStyles,
404
+ TranslationsNavLink,
405
+ client_default as default
320
406
  };
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,20 @@ 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
54
  /**
86
- * Advanced traversal options. Mirrors the options accepted by the
87
- * internal field walker keep in sync if you need the latest shape.
55
+ * what a publish in the default locale does to the other locales.
56
+ * `missing` (default) fills empty fields and keeps existing translations and slugs;
57
+ * `all` re-translates every field.
88
58
  */
59
+ autoTranslateMode?: "all" | "missing";
60
+ /** adds the /admin/translations review view, its endpoints and the status collection (default true) */
61
+ review?: boolean;
89
62
  _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
63
  additionalTraverseRichText?: (args: {
95
64
  onText: (siblingData: Record<string, unknown>, attribute?: string) => void;
96
65
  root: Record<string, unknown>;
@@ -99,4 +68,8 @@ type TranslatorConfig = {
99
68
  };
100
69
  };
101
70
  declare const translator: (pluginConfig: TranslatorConfig) => Plugin;
102
- export { translator, translateOperation, translator as default, TranslateResolverResponse, TranslateResolverArgs, TranslateResolver };
71
+ import { CollectionConfig } from "payload";
72
+ /** one row per entity and target locale: what source it was translated from, and who reviewed it */
73
+ declare const createTranslationStatusCollection: () => CollectionConfig;
74
+ declare const TRANSLATION_STATUS_SLUG = "translation-status";
75
+ export { TRANSLATION_STATUS_SLUG, TranslateResolver, TranslateResolverArgs, TranslateResolverResponse, createTranslationStatusCollection, translator as default, translateOperation, translator };