@focus-reactive/payload-plugin-translator 0.3.0 → 0.5.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.
Files changed (65) hide show
  1. package/README.md +44 -29
  2. package/dist/index.d.ts +16 -14
  3. package/dist/index.js +9 -7
  4. package/dist/plugin.d.ts +15 -4
  5. package/dist/plugin.js +34 -57
  6. package/dist/server/features/cancel/handler.d.ts +3 -3
  7. package/dist/server/features/cancel/handler.js +5 -3
  8. package/dist/server/features/cancel/route.d.ts +4 -4
  9. package/dist/server/features/cancel/route.js +4 -4
  10. package/dist/server/features/cancel-by-collection/handler.d.ts +4 -4
  11. package/dist/server/features/cancel-by-collection/handler.js +6 -6
  12. package/dist/server/features/cancel-by-collection/route.d.ts +5 -5
  13. package/dist/server/features/cancel-by-collection/route.js +4 -4
  14. package/dist/server/features/createTranslationRoutes.d.ts +23 -0
  15. package/dist/server/features/createTranslationRoutes.js +27 -0
  16. package/dist/server/features/enqueue-translation/handler.d.ts +4 -4
  17. package/dist/server/features/enqueue-translation/handler.js +5 -5
  18. package/dist/server/features/enqueue-translation/route.d.ts +5 -5
  19. package/dist/server/features/enqueue-translation/route.js +4 -4
  20. package/dist/server/features/get-collection-status/handler.d.ts +4 -4
  21. package/dist/server/features/get-collection-status/handler.js +5 -5
  22. package/dist/server/features/get-collection-status/route.d.ts +5 -5
  23. package/dist/server/features/get-collection-status/route.js +4 -4
  24. package/dist/server/features/get-document-status/handler.d.ts +4 -4
  25. package/dist/server/features/get-document-status/handler.js +5 -5
  26. package/dist/server/features/get-document-status/route.d.ts +5 -5
  27. package/dist/server/features/get-document-status/route.js +4 -4
  28. package/dist/server/features/index.d.ts +7 -6
  29. package/dist/server/features/index.js +7 -6
  30. package/dist/server/features/run-translation/handler.d.ts +3 -3
  31. package/dist/server/features/run-translation/handler.js +7 -7
  32. package/dist/server/features/run-translation/route.d.ts +4 -4
  33. package/dist/server/features/run-translation/route.js +4 -4
  34. package/dist/server/features/translate-document/handler.js +7 -11
  35. package/dist/server/modules/task-runner/TaskRunnerProvider.interface.d.ts +19 -1
  36. package/dist/server/modules/task-runner/index.d.ts +6 -6
  37. package/dist/server/modules/task-runner/index.js +2 -2
  38. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.d.ts +16 -1
  39. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.js +42 -1
  40. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.d.ts +29 -0
  41. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsTaskRunner.js +101 -6
  42. package/dist/server/modules/task-runner/payload-jobs-runner/types.d.ts +17 -0
  43. package/dist/server/modules/task-runner/sync-runner/SyncRunnerProvider.d.ts +18 -10
  44. package/dist/server/modules/task-runner/sync-runner/SyncRunnerProvider.js +24 -14
  45. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +46 -0
  46. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +108 -0
  47. package/dist/server/modules/translation-levels/collectionLevel.d.ts +15 -0
  48. package/dist/server/modules/translation-levels/collectionLevel.js +24 -0
  49. package/dist/server/modules/translation-levels/documentLevel.d.ts +19 -0
  50. package/dist/server/modules/translation-levels/documentLevel.js +28 -0
  51. package/dist/server/modules/translation-levels/index.d.ts +3 -0
  52. package/dist/server/modules/translation-levels/index.js +4 -0
  53. package/dist/server/modules/translation-levels/types.d.ts +42 -0
  54. package/dist/server/modules/translation-levels/types.js +10 -0
  55. package/dist/server/modules/translation-levels/useDocTranslationApi.d.ts +8 -0
  56. package/dist/server/modules/translation-levels/useDocTranslationApi.js +18 -0
  57. package/dist/server/modules/translation-pipeline/index.d.ts +5 -3
  58. package/dist/server/modules/translation-pipeline/index.js +3 -2
  59. package/dist/server/modules/translation-pipeline/translateContent.d.ts +35 -0
  60. package/dist/server/modules/translation-pipeline/translateContent.js +30 -0
  61. package/dist/server/modules/translation-providers/OpenAITranslation.provider.d.ts +4 -3
  62. package/dist/server/modules/translation-providers/OpenAITranslation.provider.js +15 -15
  63. package/dist/types/AccessGuard.d.ts +24 -1
  64. package/dist/types/AccessGuard.js +12 -1
  65. package/package.json +1 -1
package/README.md CHANGED
@@ -12,6 +12,7 @@ Translation plugin for Payload CMS 3.x. Automatically translate your localized c
12
12
  - **Pluggable providers** — use OpenAI or create custom translation providers
13
13
  - **Field exclusion** — exclude specific fields from translation via `withFieldTranslation`
14
14
  - **Translation strategies** — choose between overwrite all or skip existing translations
15
+ - **Configurable surfaces** — enable the per-document popup and/or the bulk-collection dashboard via the `levels` option _(since v0.5.0)_
15
16
 
16
17
  ## Installation
17
18
 
@@ -33,11 +34,7 @@ yarn add @focus-reactive/payload-plugin-translator
33
34
 
34
35
  ```typescript
35
36
  import { buildConfig } from "payload";
36
- import {
37
- translatorPlugin,
38
- createOpenAIProvider,
39
- createPayloadJobsRunner,
40
- } from "@focus-reactive/payload-plugin-translator";
37
+ import { translatorPlugin, createOpenAIProvider, createPayloadJobsRunner } from "@focus-reactive/payload-plugin-translator";
41
38
  import { Posts } from "./collections/Posts";
42
39
  import { Pages } from "./collections/Pages";
43
40
 
@@ -65,13 +62,14 @@ export default buildConfig({
65
62
 
66
63
  Configuration for `translatorPlugin()`.
67
64
 
68
- | Property | Type | Required | Default | Description |
69
- | --------------------- | --------------------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------- |
70
- | `collections` | `CollectionConfig[]` | Yes | — | Original collection configs to enable translation for. Must be the same objects passed to `buildConfig`, not slugs. |
71
- | `translationProvider` | `TranslationProvider` | Yes | — | Translation provider instance (e.g., `createOpenAIProvider(...)`) |
72
- | `runner` | `TaskRunnerProvider` | Yes | — | Task runner provider for background processing (e.g., `createPayloadJobsRunner()`) |
73
- | `access` | `AccessGuard` | No | `undefined` | Access control function for translation endpoints |
74
- | `basePath` | `string` | No | `'/translate'` | Base path for all API endpoints |
65
+ | Property | Type | Required | Default | Description |
66
+ | --------------------- | --------------------- | -------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
67
+ | `collections` | `CollectionConfig[]` | Yes | — | Original collection configs to enable translation for. Must be the same objects passed to `buildConfig`, not slugs. |
68
+ | `translationProvider` | `TranslationProvider` | Yes | — | Translation provider instance (e.g., `createOpenAIProvider(...)`) |
69
+ | `runner` | `TaskRunnerProvider` | Yes | — | Task runner provider for background processing (e.g., `createPayloadJobsRunner()`) |
70
+ | `access` | `AccessGuard` | No | `undefined` | Access guard (`{ check }`) for the translation endpoints; omit to leave them open |
71
+ | `basePath` | `string` | No | `'/translate'` | Base path for all API endpoints |
72
+ | `levels` | `TranslationLevel[]` | No | `[documentLevel(), collectionLevel()]` | Which translation surfaces to enable. See [Translation Levels](#translation-levels) |
75
73
 
76
74
  ```typescript
77
75
  translatorPlugin({
@@ -80,11 +78,37 @@ translatorPlugin({
80
78
  apiKey: process.env.OPENAI_API_KEY,
81
79
  }),
82
80
  runner: createPayloadJobsRunner(),
83
- access: async ({ req }) => req.user?.role === "admin",
81
+ access: { check: ({ req }) => req.user?.role === "admin" },
84
82
  basePath: "/translate",
85
83
  });
86
84
  ```
87
85
 
86
+ ### Translation Levels
87
+
88
+ _Since v0.5.0._
89
+
90
+ The `levels` option controls which translation surfaces the plugin exposes. Each entry is a factory you import and list:
91
+
92
+ - `documentLevel()` — a **Translate** popup on the document edit view (translate one document).
93
+ - `collectionLevel()` — a **bulk dashboard** on the collection list view (translate many at once).
94
+
95
+ Both run through the configured `runner` (async Payload Jobs by default, or synchronous via `createSyncRunner()`) and share the same translation REST API.
96
+
97
+ ```typescript
98
+ import { translatorPlugin, documentLevel, collectionLevel, createOpenAIProvider, createPayloadJobsRunner } from "@focus-reactive/payload-plugin-translator";
99
+
100
+ translatorPlugin({
101
+ collections: [Posts],
102
+ translationProvider: createOpenAIProvider({
103
+ apiKey: process.env.OPENAI_API_KEY,
104
+ }),
105
+ runner: createPayloadJobsRunner(),
106
+ levels: [collectionLevel()], // bulk dashboard only — no per-document popup
107
+ });
108
+ ```
109
+
110
+ Omit `levels` for the default `[documentLevel(), collectionLevel()]`, which is identical to the previous behaviour — so adopting this option is non-breaking. A synchronous `fieldLevel()` (in-place field translation) is planned for a later release.
111
+
88
112
  ### OpenAIProviderConfig
89
113
 
90
114
  Configuration for `createOpenAIProvider()`.
@@ -100,8 +124,7 @@ Configuration for `createOpenAIProvider()`.
100
124
  createOpenAIProvider({
101
125
  apiKey: process.env.OPENAI_API_KEY,
102
126
  model: "gpt-4o-mini",
103
- systemPrompt: ({ sourceLang, targetLang, defaultPrompt }) =>
104
- `${defaultPrompt}\nUse formal language. Keep brand names unchanged.`,
127
+ systemPrompt: ({ sourceLang, targetLang, defaultPrompt }) => `${defaultPrompt}\nUse formal language. Keep brand names unchanged.`,
105
128
  dryRun: false,
106
129
  });
107
130
  ```
@@ -165,10 +188,7 @@ Configuration for `withFieldTranslation()` helper or `field.custom.translateKit`
165
188
  ```typescript
166
189
  import { withFieldTranslation } from "@focus-reactive/payload-plugin-translator";
167
190
 
168
- withFieldTranslation(
169
- { name: "sku", type: "text", localized: true },
170
- { exclude: true },
171
- );
191
+ withFieldTranslation({ name: "sku", type: "text", localized: true }, { exclude: true });
172
192
  ```
173
193
 
174
194
  ## Translation Strategies
@@ -310,20 +330,12 @@ type TranslationOutput = Record<TranslationIndex, string>;
310
330
  #### Example Implementation
311
331
 
312
332
  ```typescript
313
- import type {
314
- TranslationProvider,
315
- TranslationInput,
316
- TranslationOutput,
317
- } from "@focus-reactive/payload-plugin-translator";
333
+ import type { TranslationProvider, TranslationInput, TranslationOutput } from "@focus-reactive/payload-plugin-translator";
318
334
 
319
335
  class DeepLProvider implements TranslationProvider {
320
336
  constructor(private apiKey: string) {}
321
337
 
322
- async translate(
323
- content: TranslationInput,
324
- sourceLng: string,
325
- targetLng: string,
326
- ): Promise<TranslationOutput | null> {
338
+ async translate(content: TranslationInput, sourceLng: string, targetLng: string): Promise<TranslationOutput | null> {
327
339
  try {
328
340
  // content example: { "0": "Hello", "1": "World" }
329
341
  const texts = Object.values(content);
@@ -401,6 +413,9 @@ import type {
401
413
 
402
414
  // Field config
403
415
  FieldTranslationConfig,
416
+
417
+ // Translation levels
418
+ TranslationLevel,
404
419
  } from "@focus-reactive/payload-plugin-translator";
405
420
  ```
406
421
 
package/dist/index.d.ts CHANGED
@@ -1,14 +1,16 @@
1
- export { translatorPlugin } from './plugin';
2
- export type { TranslatorPluginConfig } from './plugin';
3
- export type { AccessGuard, AccessGuardRequest } from './types/AccessGuard';
4
- export { createOpenAIProvider } from './server/modules/translation-providers';
5
- export type { TranslationProvider, TranslationInput, TranslationOutput, OpenAIProviderConfig, DryRunConfig, } from './server/modules/translation-providers';
6
- export { createPayloadJobsRunner, createSyncRunner } from './server/modules/task-runner';
7
- export type { TaskRunnerProvider, PayloadJobsRunnerOptions } from './server/modules/task-runner';
8
- export { withFieldTranslation } from './field-config';
9
- export type { FieldTranslationConfig } from './field-config';
10
- export { createTranslatePlugin, TranslateCollectionPlugin } from './plugin';
11
- export type { TranslateCollectionPluginConfig } from './plugin';
12
- export { OpenAITranslationProvider } from './server/modules/translation-providers';
13
- export { translateKitField } from './field-config';
14
- export type { TranslateKitFieldConfig } from './field-config';
1
+ export { translatorPlugin } from "./plugin";
2
+ export type { TranslatorPluginConfig } from "./plugin";
3
+ export type { AccessGuard, AccessGuardRequest } from "./types/AccessGuard";
4
+ export { createOpenAIProvider } from "./server/modules/translation-providers";
5
+ export type { TranslationProvider, TranslationInput, TranslationOutput, OpenAIProviderConfig, DryRunConfig } from "./server/modules/translation-providers";
6
+ export { createPayloadJobsRunner, createSyncRunner } from "./server/modules/task-runner";
7
+ export type { TaskRunnerProvider, PayloadJobsRunnerOptions } from "./server/modules/task-runner";
8
+ export { documentLevel, collectionLevel } from "./server/modules/translation-levels";
9
+ export type { TranslationLevel } from "./server/modules/translation-levels";
10
+ export { withFieldTranslation } from "./field-config";
11
+ export type { FieldTranslationConfig } from "./field-config";
12
+ export { createTranslatePlugin, TranslateCollectionPlugin } from "./plugin";
13
+ export type { TranslateCollectionPluginConfig } from "./plugin";
14
+ export { OpenAITranslationProvider } from "./server/modules/translation-providers";
15
+ export { translateKitField } from "./field-config";
16
+ export type { TranslateKitFieldConfig } from "./field-config";
package/dist/index.js CHANGED
@@ -1,14 +1,16 @@
1
1
  // Main plugin
2
- export { translatorPlugin } from './plugin';
2
+ export { translatorPlugin } from "./plugin";
3
3
  // Translation providers
4
- export { createOpenAIProvider } from './server/modules/translation-providers';
4
+ export { createOpenAIProvider } from "./server/modules/translation-providers";
5
5
  // Task runners
6
- export { createPayloadJobsRunner, createSyncRunner } from './server/modules/task-runner';
6
+ export { createPayloadJobsRunner, createSyncRunner } from "./server/modules/task-runner";
7
+ // Translation levels
8
+ export { documentLevel, collectionLevel } from "./server/modules/translation-levels";
7
9
  // Field config
8
- export { withFieldTranslation } from './field-config';
10
+ export { withFieldTranslation } from "./field-config";
9
11
  // Deprecated exports (for backwards compatibility)
10
- export { createTranslatePlugin, TranslateCollectionPlugin } from './plugin';
11
- export { OpenAITranslationProvider } from './server/modules/translation-providers';
12
- export { translateKitField } from './field-config';
12
+ export { createTranslatePlugin, TranslateCollectionPlugin } from "./plugin";
13
+ export { OpenAITranslationProvider } from "./server/modules/translation-providers";
14
+ export { translateKitField } from "./field-config";
13
15
 
14
16
  //# sourceMappingURL=index.js.map
package/dist/plugin.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import type { CollectionConfig, Config } from 'payload';
2
- import type { AccessGuard } from './types/AccessGuard';
3
- import type { TranslationProvider } from './server/modules/translation-providers';
4
- import type { TaskRunnerProvider } from './server/modules/task-runner';
1
+ import type { CollectionConfig, Config } from "payload";
2
+ import type { AccessGuard } from "./types/AccessGuard";
3
+ import type { TranslationProvider } from "./server/modules/translation-providers";
4
+ import type { TaskRunnerProvider } from "./server/modules/task-runner";
5
+ import type { TranslationLevel } from "./server/modules/translation-levels";
5
6
  export type TranslatorPluginConfig = {
6
7
  /**
7
8
  * Original collection configs (same objects passed to buildConfig).
@@ -32,6 +33,16 @@ export type TranslatorPluginConfig = {
32
33
  * @default '/translate'
33
34
  */
34
35
  basePath?: string;
36
+ /**
37
+ * Which translation surfaces to enable, as level factories — `documentLevel()`,
38
+ * `collectionLevel()` (and, from a later phase, `fieldLevel()`). Omit for the
39
+ * default `[documentLevel(), collectionLevel()]`, which is exactly today's behaviour.
40
+ * Each level should appear at most once: endpoints are deduplicated, but admin
41
+ * components are not, so a duplicated level renders a duplicated control.
42
+ * @default [documentLevel(), collectionLevel()]
43
+ * @since 0.5.0
44
+ */
45
+ levels?: TranslationLevel[];
35
46
  };
36
47
  /** @deprecated Use `TranslatorPluginConfig` instead */
37
48
  export type TranslateCollectionPluginConfig = TranslatorPluginConfig;
package/dist/plugin.js CHANGED
@@ -1,14 +1,8 @@
1
- import { CacheProviderExport } from './client/app/cache/CacheProvider.export';
2
- import { BulkDocumentTranslationDashboard } from './client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export';
3
- import { TranslateDocumentExport } from './client/widgets/translate-document';
4
- import { TranslateDocumentHandler } from './server/features/translate-document';
5
- import { createEnqueueRoute } from './server/features/enqueue-translation';
6
- import { createRunRoute } from './server/features/run-translation';
7
- import { createCancelRoute } from './server/features/cancel';
8
- import { createCancelByCollectionRoute } from './server/features/cancel-by-collection';
9
- import { createGetDocumentStatusRoute } from './server/features/get-document-status';
10
- import { createGetCollectionStatusRoute } from './server/features/get-collection-status';
11
- import { normalizePath, pipe } from './server/shared';
1
+ import { CacheProviderExport } from "./client/app/cache/CacheProvider.export";
2
+ import { TranslateDocumentHandler } from "./server/features/translate-document";
3
+ import { documentLevel, collectionLevel } from "./server/modules/translation-levels";
4
+ import { PluginConfigBuilder } from "./server/modules/translation-levels/PluginConfigBuilder";
5
+ import { normalizePath } from "./server/shared";
12
6
  /** @deprecated Use `translatorPlugin` function instead */ export class TranslateCollectionPlugin {
13
7
  pluginConfig;
14
8
  constructor(pluginConfig){
@@ -16,7 +10,7 @@ import { normalizePath, pipe } from './server/shared';
16
10
  }
17
11
  init() {
18
12
  return async (config)=>{
19
- const { access, translationProvider, basePath: rawBasePath = '/translate' } = this.pluginConfig;
13
+ const { access, translationProvider, runner, collections, levels, basePath: rawBasePath = "/translate" } = this.pluginConfig;
20
14
  // Build schema map from deep-cloned collections
21
15
  // Deep clone is required because Payload mutates the original collection objects,
22
16
  // removing `localized: true` from nested fields during sanitization.
@@ -25,14 +19,14 @@ import { normalizePath, pipe } from './server/shared';
25
19
  // TODO: Consider introducing a FieldLike interface with only the properties
26
20
  // used by the pipeline (name, type, localized, fields, blocks, tabs, custom)
27
21
  // to make the contract explicit and avoid reliance on JSON round-trip.
28
- const schemaMap = new Map(this.pluginConfig.collections.map((col)=>[
22
+ const schemaMap = new Map(collections.map((col)=>[
29
23
  col.slug,
30
24
  JSON.parse(JSON.stringify(col.fields))
31
25
  ]));
32
26
  const collectionSlugs = new Set(schemaMap.keys());
33
27
  const basePath = normalizePath(rawBasePath);
34
28
  const translateHandler = new TranslateDocumentHandler(translationProvider, schemaMap);
35
- const runnerConfigModifier = this.pluginConfig.runner.configure({
29
+ const runnerContext = {
36
30
  handler: async (payload, input)=>{
37
31
  await translateHandler.handle(payload, {
38
32
  collection: input.collection,
@@ -44,50 +38,33 @@ import { normalizePath, pipe } from './server/shared';
44
38
  });
45
39
  },
46
40
  collections: Array.from(collectionSlugs)
41
+ };
42
+ const runnerConfigModifier = runner.configure(runnerContext);
43
+ // Bind the context once so routes receive a self-sufficient factory: the
44
+ // runner needs no mutable per-instance handler state and create() has no
45
+ // "configure() must run first" ordering coupling (translator plan, 0c).
46
+ const taskRunnerFactory = {
47
+ create: (payload)=>runner.create(payload, runnerContext.handler)
48
+ };
49
+ const activeLevels = levels ?? [
50
+ documentLevel(),
51
+ collectionLevel()
52
+ ];
53
+ const builder = new PluginConfigBuilder({
54
+ collections,
55
+ basePath,
56
+ access,
57
+ taskRunnerFactory
47
58
  });
48
- return pipe(// 1. Set up UI component provider import
49
- (cfg)=>{
50
- if (!cfg.admin) cfg.admin = {};
51
- if (!cfg.admin.components) cfg.admin.components = {};
52
- if (!cfg.admin.components.providers) cfg.admin.components.providers = [];
53
- cfg.admin.components.providers.push(new CacheProviderExport(basePath));
54
- return cfg;
55
- }, // 2. Set up UI component import
56
- (cfg)=>{
57
- cfg.collections?.forEach((collection)=>{
58
- if (!collectionSlugs.has(collection.slug)) {
59
- return;
60
- }
61
- if (!collection.admin) collection.admin = {};
62
- if (!collection.admin.components) collection.admin.components = {};
63
- if (!collection.admin.components.edit) collection.admin.components.edit = {};
64
- if (!collection.admin.components.edit.beforeDocumentControls) {
65
- collection.admin.components.edit.beforeDocumentControls = [];
66
- }
67
- if (!collection.admin.components.beforeListTable) {
68
- collection.admin.components.beforeListTable = [];
69
- }
70
- collection.admin.components.edit.beforeDocumentControls.push(new TranslateDocumentExport(collection, access));
71
- collection.admin.components.beforeListTable.push(new BulkDocumentTranslationDashboard(access));
72
- });
73
- return cfg;
74
- }, // 3. Runner configures jobs/tasks/autorun
75
- runnerConfigModifier, // 4. Add global endpoints
76
- (cfg)=>{
77
- if (!cfg.endpoints) cfg.endpoints = [];
78
- const collectionConfig = {
79
- availableCollections: collectionSlugs
80
- };
81
- cfg.endpoints.push(...[
82
- createEnqueueRoute(this.pluginConfig.runner, collectionConfig, access, basePath),
83
- createRunRoute(this.pluginConfig.runner, access, basePath),
84
- createCancelRoute(this.pluginConfig.runner, access, basePath),
85
- createCancelByCollectionRoute(collectionConfig, this.pluginConfig.runner, access, basePath),
86
- createGetDocumentStatusRoute(collectionConfig, this.pluginConfig.runner, access, basePath),
87
- createGetCollectionStatusRoute(collectionConfig, this.pluginConfig.runner, access, basePath)
88
- ]);
89
- return cfg;
90
- })(config);
59
+ for (const level of activeLevels)level.extend(builder);
60
+ // Plugin-level contributions, routed through the same single config-writer:
61
+ // - the runner's jobs/autorun/onInit modifier (the builder applies it first,
62
+ // so a modifier returning a fresh config object doesn't drop later writes),
63
+ // - the always-on client cache provider.
64
+ builder.addConfigModifier(runnerConfigModifier);
65
+ builder.addAdminProvider(new CacheProviderExport(basePath));
66
+ // The single place the Payload config is mutated.
67
+ return builder.applyTo(config);
91
68
  };
92
69
  }
93
70
  }
@@ -1,10 +1,10 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerFactory } from "../../modules/task-runner";
3
3
  /**
4
4
  * Cancels and deletes translation tasks by IDs
5
5
  */
6
6
  export declare class CancelHandler {
7
7
  private readonly taskRunnerFactory;
8
- constructor(taskRunnerFactory: TaskRunnerProvider);
8
+ constructor(taskRunnerFactory: TaskRunnerFactory);
9
9
  handle(req: PayloadRequest): Promise<Response>;
10
10
  }
@@ -1,5 +1,5 @@
1
- import { ServerResponse } from '../../shared';
2
- import { CancelInputSchema } from './model';
1
+ import { ServerResponse } from "../../shared";
2
+ import { CancelInputSchema } from "./model";
3
3
  /**
4
4
  * Cancels and deletes translation tasks by IDs
5
5
  */ export class CancelHandler {
@@ -9,7 +9,9 @@ import { CancelInputSchema } from './model';
9
9
  }
10
10
  async handle(req) {
11
11
  const validationResult = CancelInputSchema.safeParse(await req.json?.());
12
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
12
+ if (validationResult.error) {
13
+ return ServerResponse.validationError(validationResult.error.issues);
14
+ }
13
15
  const { ids } = validationResult.data;
14
16
  const runner = this.taskRunnerFactory.create(req.payload);
15
17
  await runner.cancel(ids);
@@ -1,7 +1,7 @@
1
- import type { Endpoint } from 'payload';
2
- import type { AccessGuard } from '../../shared';
3
- import type { TaskRunnerProvider } from '../../modules/task-runner';
1
+ import type { Endpoint } from "payload";
2
+ import type { AccessGuard } from "../../shared";
3
+ import type { TaskRunnerFactory } from "../../modules/task-runner";
4
4
  /**
5
5
  * Creates the batch cancel endpoint
6
6
  */
7
- export declare function createCancelRoute(taskRunnerFactory: TaskRunnerProvider, access?: AccessGuard, basePath?: string): Endpoint;
7
+ export declare function createCancelRoute(taskRunnerFactory: TaskRunnerFactory, access?: AccessGuard, basePath?: string): Endpoint;
@@ -1,12 +1,12 @@
1
- import { withErrorHandler, withAccessCheck } from '../../shared';
2
- import { CancelHandler } from './handler';
1
+ import { withErrorHandler, withAccessCheck } from "../../shared";
2
+ import { CancelHandler } from "./handler";
3
3
  /**
4
4
  * Creates the batch cancel endpoint
5
- */ export function createCancelRoute(taskRunnerFactory, access, basePath = '/translate') {
5
+ */ export function createCancelRoute(taskRunnerFactory, access, basePath = "/translate") {
6
6
  const handler = new CancelHandler(taskRunnerFactory);
7
7
  return {
8
8
  path: `${basePath}/cancel`,
9
- method: 'delete',
9
+ method: "delete",
10
10
  handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
11
11
  };
12
12
  }
@@ -1,12 +1,12 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
3
- import { type CancelConfig } from './model';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerFactory } from "../../modules/task-runner";
3
+ import type { CancelConfig } from "./model";
4
4
  /**
5
5
  * Cancels all pending translation tasks for a collection
6
6
  */
7
7
  export declare class CancelByCollectionHandler {
8
8
  private readonly config;
9
9
  private readonly taskRunnerFactory;
10
- constructor(config: CancelConfig, taskRunnerFactory: TaskRunnerProvider);
10
+ constructor(config: CancelConfig, taskRunnerFactory: TaskRunnerFactory);
11
11
  handle(req: PayloadRequest): Promise<Response>;
12
12
  }
@@ -1,6 +1,6 @@
1
- import { ServerResponse } from '../../shared';
2
- import { isCollectionAvailable } from '../_lib/collection-utils';
3
- import { CancelByCollectionInputSchema } from './model';
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable } from "../_lib/collection-utils";
3
+ import { CancelByCollectionInputSchema } from "./model";
4
4
  /**
5
5
  * Cancels all pending translation tasks for a collection
6
6
  */ export class CancelByCollectionHandler {
@@ -12,13 +12,13 @@ import { CancelByCollectionInputSchema } from './model';
12
12
  }
13
13
  async handle(req) {
14
14
  const validationResult = CancelByCollectionInputSchema.safeParse(req.routeParams);
15
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
15
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
16
16
  const collectionSlug = isCollectionAvailable(validationResult.data.collection_slug, this.config.availableCollections);
17
- if (!collectionSlug) return ServerResponse.badRequest('Collection not available for translation');
17
+ if (!collectionSlug) return ServerResponse.badRequest("Collection not available for translation");
18
18
  const runner = this.taskRunnerFactory.create(req.payload);
19
19
  const tasks = await runner.findByCollection(collectionSlug);
20
20
  if (tasks.length === 0) return ServerResponse.noContent();
21
- const pendingTaskIds = tasks.filter((task)=>task.status === 'pending').map((task)=>task.id);
21
+ const pendingTaskIds = tasks.filter((task)=>task.status === "pending").map((task)=>task.id);
22
22
  if (pendingTaskIds.length === 0) return ServerResponse.noContent();
23
23
  await runner.cancel(pendingTaskIds);
24
24
  return ServerResponse.noContent();
@@ -1,8 +1,8 @@
1
- import type { Endpoint } from 'payload';
2
- import type { AccessGuard } from '../../shared';
3
- import type { TaskRunnerProvider } from '../../modules/task-runner';
4
- import type { CancelConfig } from './model';
1
+ import type { Endpoint } from "payload";
2
+ import type { AccessGuard } from "../../shared";
3
+ import type { TaskRunnerFactory } from "../../modules/task-runner";
4
+ import type { CancelConfig } from "./model";
5
5
  /**
6
6
  * Creates the cancel by collection endpoint
7
7
  */
8
- export declare function createCancelByCollectionRoute(config: CancelConfig, taskRunnerFactory: TaskRunnerProvider, access?: AccessGuard, basePath?: string): Endpoint;
8
+ export declare function createCancelByCollectionRoute(config: CancelConfig, taskRunnerFactory: TaskRunnerFactory, access?: AccessGuard, basePath?: string): Endpoint;
@@ -1,12 +1,12 @@
1
- import { withErrorHandler, withAccessCheck } from '../../shared';
2
- import { CancelByCollectionHandler } from './handler';
1
+ import { withErrorHandler, withAccessCheck } from "../../shared";
2
+ import { CancelByCollectionHandler } from "./handler";
3
3
  /**
4
4
  * Creates the cancel by collection endpoint
5
- */ export function createCancelByCollectionRoute(config, taskRunnerFactory, access, basePath = '/translate') {
5
+ */ export function createCancelByCollectionRoute(config, taskRunnerFactory, access, basePath = "/translate") {
6
6
  const handler = new CancelByCollectionHandler(config, taskRunnerFactory);
7
7
  return {
8
8
  path: `${basePath}/cancel-by-collection/:collection_slug`,
9
- method: 'delete',
9
+ method: "delete",
10
10
  handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
11
11
  };
12
12
  }
@@ -0,0 +1,23 @@
1
+ import type { CollectionSlug, Endpoint } from "payload";
2
+ import type { AccessGuard } from "../shared";
3
+ import type { TaskRunnerFactory } from "../modules/task-runner";
4
+ export type TranslationRoutesDeps = {
5
+ taskRunnerFactory: TaskRunnerFactory;
6
+ /** Collections the plugin manages — gates which slugs the routes accept. */
7
+ collectionConfig: {
8
+ availableCollections: Set<CollectionSlug>;
9
+ };
10
+ access?: AccessGuard;
11
+ basePath?: string;
12
+ };
13
+ /**
14
+ * The translator's HTTP surface as one shared bundle: the 6 job-API endpoints
15
+ * (enqueue / run / cancel / cancel-by-collection / document-status /
16
+ * collection-status), all wired to the same runner factory and access guard.
17
+ *
18
+ * Extracted from the plugin's `init()` so registration is a single unit. The
19
+ * document and collection levels each contribute this bundle via the level
20
+ * context; the plugin deduplicates by method + path, so it registers exactly
21
+ * once. Behaviour-preserving: same endpoints, same order, same wiring.
22
+ */
23
+ export declare function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath }: TranslationRoutesDeps): Endpoint[];
@@ -0,0 +1,27 @@
1
+ import { createEnqueueRoute } from "./enqueue-translation";
2
+ import { createRunRoute } from "./run-translation";
3
+ import { createCancelRoute } from "./cancel";
4
+ import { createCancelByCollectionRoute } from "./cancel-by-collection";
5
+ import { createGetDocumentStatusRoute } from "./get-document-status";
6
+ import { createGetCollectionStatusRoute } from "./get-collection-status";
7
+ /**
8
+ * The translator's HTTP surface as one shared bundle: the 6 job-API endpoints
9
+ * (enqueue / run / cancel / cancel-by-collection / document-status /
10
+ * collection-status), all wired to the same runner factory and access guard.
11
+ *
12
+ * Extracted from the plugin's `init()` so registration is a single unit. The
13
+ * document and collection levels each contribute this bundle via the level
14
+ * context; the plugin deduplicates by method + path, so it registers exactly
15
+ * once. Behaviour-preserving: same endpoints, same order, same wiring.
16
+ */ export function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath }) {
17
+ return [
18
+ createEnqueueRoute(taskRunnerFactory, collectionConfig, access, basePath),
19
+ createRunRoute(taskRunnerFactory, access, basePath),
20
+ createCancelRoute(taskRunnerFactory, access, basePath),
21
+ createCancelByCollectionRoute(collectionConfig, taskRunnerFactory, access, basePath),
22
+ createGetDocumentStatusRoute(collectionConfig, taskRunnerFactory, access, basePath),
23
+ createGetCollectionStatusRoute(collectionConfig, taskRunnerFactory, access, basePath)
24
+ ];
25
+ }
26
+
27
+ //# sourceMappingURL=createTranslationRoutes.js.map
@@ -1,12 +1,12 @@
1
- import type { PayloadRequest } from 'payload';
2
- import type { TaskRunnerProvider } from '../../modules/task-runner';
3
- import { type EnqueueConfig } from './model';
1
+ import type { PayloadRequest } from "payload";
2
+ import type { TaskRunnerFactory } from "../../modules/task-runner";
3
+ import type { EnqueueConfig } from "./model";
4
4
  /**
5
5
  * Enqueues translation tasks for documents
6
6
  */
7
7
  export declare class EnqueueTranslationHandler {
8
8
  private readonly config;
9
9
  private readonly taskRunnerFactory;
10
- constructor(config: EnqueueConfig, taskRunnerFactory: TaskRunnerProvider);
10
+ constructor(config: EnqueueConfig, taskRunnerFactory: TaskRunnerFactory);
11
11
  handle(req: PayloadRequest): Promise<Response>;
12
12
  }
@@ -1,6 +1,6 @@
1
- import { ServerResponse } from '../../shared';
2
- import { isCollectionAvailable, getAllCollectionIds } from '../_lib/collection-utils';
3
- import { EnqueueInputSchema } from './model';
1
+ import { ServerResponse } from "../../shared";
2
+ import { isCollectionAvailable, getAllCollectionIds } from "../_lib/collection-utils";
3
+ import { EnqueueInputSchema } from "./model";
4
4
  /**
5
5
  * Enqueues translation tasks for documents
6
6
  */ export class EnqueueTranslationHandler {
@@ -12,10 +12,10 @@ import { EnqueueInputSchema } from './model';
12
12
  }
13
13
  async handle(req) {
14
14
  const validationResult = EnqueueInputSchema.safeParse(await req.json?.());
15
- if (validationResult.error) return ServerResponse.validationError(validationResult.error.errors);
15
+ if (validationResult.error) return ServerResponse.validationError(validationResult.error.issues);
16
16
  const { source_lng, target_lng, collection_slug, collection_id, select_all, strategy, publish_on_translation } = validationResult.data;
17
17
  const collectionSlug = isCollectionAvailable(collection_slug, this.config.availableCollections);
18
- if (!collectionSlug) return ServerResponse.badRequest('Content of this collection is not available for translation');
18
+ if (!collectionSlug) return ServerResponse.badRequest("Content of this collection is not available for translation");
19
19
  const collectionIds = select_all ? await getAllCollectionIds(req.payload, collectionSlug) : collection_id;
20
20
  const runner = this.taskRunnerFactory.create(req.payload);
21
21
  const tasks = collectionIds.map((id)=>({
@@ -1,8 +1,8 @@
1
- import type { Endpoint } from 'payload';
2
- import type { AccessGuard } from '../../shared';
3
- import type { TaskRunnerProvider } from '../../modules/task-runner';
4
- import type { EnqueueConfig } from './model';
1
+ import type { Endpoint } from "payload";
2
+ import type { AccessGuard } from "../../shared";
3
+ import type { TaskRunnerFactory } from "../../modules/task-runner";
4
+ import type { EnqueueConfig } from "./model";
5
5
  /**
6
6
  * Creates the enqueue translation endpoint
7
7
  */
8
- export declare function createEnqueueRoute(taskRunnerFactory: TaskRunnerProvider, config: EnqueueConfig, access?: AccessGuard, basePath?: string): Endpoint;
8
+ export declare function createEnqueueRoute(taskRunnerFactory: TaskRunnerFactory, config: EnqueueConfig, access?: AccessGuard, basePath?: string): Endpoint;
@@ -1,12 +1,12 @@
1
- import { withErrorHandler, withAccessCheck } from '../../shared';
2
- import { EnqueueTranslationHandler } from './handler';
1
+ import { withErrorHandler, withAccessCheck } from "../../shared";
2
+ import { EnqueueTranslationHandler } from "./handler";
3
3
  /**
4
4
  * Creates the enqueue translation endpoint
5
- */ export function createEnqueueRoute(taskRunnerFactory, config, access, basePath = '/translate') {
5
+ */ export function createEnqueueRoute(taskRunnerFactory, config, access, basePath = "/translate") {
6
6
  const handler = new EnqueueTranslationHandler(config, taskRunnerFactory);
7
7
  return {
8
8
  path: `${basePath}/enqueue`,
9
- method: 'post',
9
+ method: "post",
10
10
  handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
11
11
  };
12
12
  }