@focus-reactive/payload-plugin-translator 0.4.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.
- package/README.md +44 -29
- package/dist/index.d.ts +16 -14
- package/dist/index.js +9 -7
- package/dist/plugin.d.ts +15 -4
- package/dist/plugin.js +34 -57
- package/dist/server/features/cancel/handler.d.ts +2 -2
- package/dist/server/features/cancel/handler.js +3 -1
- package/dist/server/features/cancel/route.d.ts +4 -4
- package/dist/server/features/cancel/route.js +4 -4
- package/dist/server/features/cancel-by-collection/handler.d.ts +3 -3
- package/dist/server/features/cancel-by-collection/handler.js +1 -1
- package/dist/server/features/cancel-by-collection/route.d.ts +5 -5
- package/dist/server/features/cancel-by-collection/route.js +4 -4
- package/dist/server/features/createTranslationRoutes.d.ts +23 -0
- package/dist/server/features/createTranslationRoutes.js +27 -0
- package/dist/server/features/enqueue-translation/handler.d.ts +3 -3
- package/dist/server/features/enqueue-translation/handler.js +1 -1
- package/dist/server/features/enqueue-translation/route.d.ts +5 -5
- package/dist/server/features/enqueue-translation/route.js +4 -4
- package/dist/server/features/get-collection-status/handler.d.ts +3 -3
- package/dist/server/features/get-collection-status/handler.js +1 -1
- package/dist/server/features/get-collection-status/route.d.ts +5 -5
- package/dist/server/features/get-collection-status/route.js +4 -4
- package/dist/server/features/get-document-status/handler.d.ts +3 -3
- package/dist/server/features/get-document-status/handler.js +1 -1
- package/dist/server/features/get-document-status/route.d.ts +5 -5
- package/dist/server/features/get-document-status/route.js +4 -4
- package/dist/server/features/index.d.ts +7 -6
- package/dist/server/features/index.js +7 -6
- package/dist/server/features/run-translation/handler.d.ts +2 -2
- package/dist/server/features/run-translation/route.d.ts +4 -4
- package/dist/server/features/run-translation/route.js +4 -4
- package/dist/server/features/translate-document/handler.js +7 -11
- package/dist/server/modules/task-runner/TaskRunnerProvider.interface.d.ts +18 -1
- package/dist/server/modules/task-runner/index.d.ts +6 -6
- package/dist/server/modules/task-runner/index.js +2 -2
- package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.d.ts +16 -1
- package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.js +16 -1
- package/dist/server/modules/task-runner/sync-runner/SyncRunnerProvider.d.ts +18 -10
- package/dist/server/modules/task-runner/sync-runner/SyncRunnerProvider.js +24 -14
- package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +46 -0
- package/dist/server/modules/translation-levels/PluginConfigBuilder.js +108 -0
- package/dist/server/modules/translation-levels/collectionLevel.d.ts +15 -0
- package/dist/server/modules/translation-levels/collectionLevel.js +24 -0
- package/dist/server/modules/translation-levels/documentLevel.d.ts +19 -0
- package/dist/server/modules/translation-levels/documentLevel.js +28 -0
- package/dist/server/modules/translation-levels/index.d.ts +3 -0
- package/dist/server/modules/translation-levels/index.js +4 -0
- package/dist/server/modules/translation-levels/types.d.ts +42 -0
- package/dist/server/modules/translation-levels/types.js +10 -0
- package/dist/server/modules/translation-levels/useDocTranslationApi.d.ts +8 -0
- package/dist/server/modules/translation-levels/useDocTranslationApi.js +18 -0
- package/dist/server/modules/translation-pipeline/index.d.ts +5 -3
- package/dist/server/modules/translation-pipeline/index.js +3 -2
- package/dist/server/modules/translation-pipeline/translateContent.d.ts +35 -0
- package/dist/server/modules/translation-pipeline/translateContent.js +30 -0
- package/dist/server/modules/translation-providers/OpenAITranslation.provider.d.ts +4 -3
- package/dist/server/modules/translation-providers/OpenAITranslation.provider.js +15 -15
- package/dist/types/AccessGuard.d.ts +24 -1
- package/dist/types/AccessGuard.js +12 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { withErrorHandler, withAccessCheck } from
|
|
2
|
-
import { GetCollectionStatusHandler } from
|
|
1
|
+
import { withErrorHandler, withAccessCheck } from "../../shared";
|
|
2
|
+
import { GetCollectionStatusHandler } from "./handler";
|
|
3
3
|
/**
|
|
4
4
|
* Creates the get collection status endpoint
|
|
5
|
-
*/ export function createGetCollectionStatusRoute(config, taskRunnerFactory, access, basePath =
|
|
5
|
+
*/ export function createGetCollectionStatusRoute(config, taskRunnerFactory, access, basePath = "/translate") {
|
|
6
6
|
const handler = new GetCollectionStatusHandler(config, taskRunnerFactory);
|
|
7
7
|
return {
|
|
8
8
|
path: `${basePath}/collection/:collection_slug`,
|
|
9
|
-
method:
|
|
9
|
+
method: "get",
|
|
10
10
|
handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
|
|
11
11
|
};
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { PayloadRequest } from "payload";
|
|
2
|
-
import type {
|
|
3
|
-
import type { GetDocumentStatusConfig } from
|
|
2
|
+
import type { TaskRunnerFactory } from "../../modules/task-runner";
|
|
3
|
+
import type { GetDocumentStatusConfig } from "./model";
|
|
4
4
|
/**
|
|
5
5
|
* Gets the translation status for a specific document
|
|
6
6
|
*/
|
|
7
7
|
export declare class GetDocumentStatusHandler {
|
|
8
8
|
private readonly config;
|
|
9
9
|
private readonly taskRunnerFactory;
|
|
10
|
-
constructor(config: GetDocumentStatusConfig, taskRunnerFactory:
|
|
10
|
+
constructor(config: GetDocumentStatusConfig, taskRunnerFactory: TaskRunnerFactory);
|
|
11
11
|
handle(req: PayloadRequest): Promise<Response>;
|
|
12
12
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ServerResponse } from "../../shared";
|
|
2
2
|
import { isCollectionAvailable } from "../_lib/collection-utils";
|
|
3
|
-
import { GetDocumentStatusInputSchema, taskToJobStatusOutput } from
|
|
3
|
+
import { GetDocumentStatusInputSchema, taskToJobStatusOutput } from "./model";
|
|
4
4
|
/**
|
|
5
5
|
* Gets the translation status for a specific document
|
|
6
6
|
*/ export class GetDocumentStatusHandler {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Endpoint } from
|
|
2
|
-
import type { AccessGuard } from
|
|
3
|
-
import type {
|
|
4
|
-
import type { GetDocumentStatusConfig } from
|
|
1
|
+
import type { Endpoint } from "payload";
|
|
2
|
+
import type { AccessGuard } from "../../shared";
|
|
3
|
+
import type { TaskRunnerFactory } from "../../modules/task-runner";
|
|
4
|
+
import type { GetDocumentStatusConfig } from "./model";
|
|
5
5
|
/**
|
|
6
6
|
* Creates the get document status endpoint
|
|
7
7
|
*/
|
|
8
|
-
export declare function createGetDocumentStatusRoute(config: GetDocumentStatusConfig, taskRunnerFactory:
|
|
8
|
+
export declare function createGetDocumentStatusRoute(config: GetDocumentStatusConfig, taskRunnerFactory: TaskRunnerFactory, access?: AccessGuard, basePath?: string): Endpoint;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { withErrorHandler, withAccessCheck } from
|
|
2
|
-
import { GetDocumentStatusHandler } from
|
|
1
|
+
import { withErrorHandler, withAccessCheck } from "../../shared";
|
|
2
|
+
import { GetDocumentStatusHandler } from "./handler";
|
|
3
3
|
/**
|
|
4
4
|
* Creates the get document status endpoint
|
|
5
|
-
*/ export function createGetDocumentStatusRoute(config, taskRunnerFactory, access, basePath =
|
|
5
|
+
*/ export function createGetDocumentStatusRoute(config, taskRunnerFactory, access, basePath = "/translate") {
|
|
6
6
|
const handler = new GetDocumentStatusHandler(config, taskRunnerFactory);
|
|
7
7
|
return {
|
|
8
8
|
path: `${basePath}/document/:collection_slug/:collection_id`,
|
|
9
|
-
method:
|
|
9
|
+
method: "get",
|
|
10
10
|
handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
|
|
11
11
|
};
|
|
12
12
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
1
|
+
export * from "./createTranslationRoutes";
|
|
2
|
+
export * from "./translate-document";
|
|
3
|
+
export * from "./enqueue-translation";
|
|
4
|
+
export * from "./run-translation";
|
|
5
|
+
export * from "./cancel";
|
|
6
|
+
export * from "./get-document-status";
|
|
7
|
+
export * from "./get-collection-status";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
1
|
+
export * from "./createTranslationRoutes";
|
|
2
|
+
export * from "./translate-document";
|
|
3
|
+
export * from "./enqueue-translation";
|
|
4
|
+
export * from "./run-translation";
|
|
5
|
+
export * from "./cancel";
|
|
6
|
+
export * from "./get-document-status";
|
|
7
|
+
export * from "./get-collection-status";
|
|
7
8
|
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { PayloadRequest } from "payload";
|
|
2
|
-
import type {
|
|
2
|
+
import type { TaskRunnerFactory } from "../../modules/task-runner";
|
|
3
3
|
/**
|
|
4
4
|
* Runs a translation task by ID
|
|
5
5
|
*/
|
|
6
6
|
export declare class RunTranslationHandler {
|
|
7
7
|
private readonly taskRunnerFactory;
|
|
8
|
-
constructor(taskRunnerFactory:
|
|
8
|
+
constructor(taskRunnerFactory: TaskRunnerFactory);
|
|
9
9
|
handle(req: PayloadRequest): Promise<Response>;
|
|
10
10
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Endpoint } from
|
|
2
|
-
import type { AccessGuard } from
|
|
3
|
-
import type {
|
|
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 run translation endpoint
|
|
6
6
|
*/
|
|
7
|
-
export declare function createRunRoute(taskRunnerFactory:
|
|
7
|
+
export declare function createRunRoute(taskRunnerFactory: TaskRunnerFactory, access?: AccessGuard, basePath?: string): Endpoint;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { withErrorHandler, withAccessCheck } from
|
|
2
|
-
import { RunTranslationHandler } from
|
|
1
|
+
import { withErrorHandler, withAccessCheck } from "../../shared";
|
|
2
|
+
import { RunTranslationHandler } from "./handler";
|
|
3
3
|
/**
|
|
4
4
|
* Creates the run translation endpoint
|
|
5
|
-
*/ export function createRunRoute(taskRunnerFactory, access, basePath =
|
|
5
|
+
*/ export function createRunRoute(taskRunnerFactory, access, basePath = "/translate") {
|
|
6
6
|
const handler = new RunTranslationHandler(taskRunnerFactory);
|
|
7
7
|
return {
|
|
8
8
|
path: `${basePath}/run/:id`,
|
|
9
|
-
method:
|
|
9
|
+
method: "post",
|
|
10
10
|
handler: withAccessCheck(withErrorHandler(handler.handle.bind(handler)), access)
|
|
11
11
|
};
|
|
12
12
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { APIError } from "payload";
|
|
2
|
-
import {
|
|
3
|
-
import { createTranslationStrategy } from "../../modules/translation-pipeline/strategies";
|
|
2
|
+
import { translateContent } from "../../modules/translation-pipeline";
|
|
4
3
|
/**
|
|
5
4
|
* Translates a single document from source language to target language
|
|
6
5
|
*/ export class TranslateDocumentHandler {
|
|
@@ -28,23 +27,20 @@ import { createTranslationStrategy } from "../../modules/translation-pipeline/st
|
|
|
28
27
|
fallbackLocale: false,
|
|
29
28
|
depth: 0
|
|
30
29
|
});
|
|
31
|
-
const
|
|
32
|
-
const pipeline = new TranslationPipeline({
|
|
33
|
-
translationProvider: this.translationProvider,
|
|
34
|
-
translationStrategy
|
|
35
|
-
});
|
|
36
|
-
const result = await pipeline.execute({
|
|
30
|
+
const translatedData = await translateContent({
|
|
37
31
|
schema,
|
|
38
32
|
sourceData,
|
|
39
33
|
targetData,
|
|
40
34
|
sourceLng,
|
|
41
|
-
targetLng
|
|
35
|
+
targetLng,
|
|
36
|
+
translationProvider: this.translationProvider,
|
|
37
|
+
strategy
|
|
42
38
|
});
|
|
43
|
-
if (!
|
|
39
|
+
if (!translatedData) return {
|
|
44
40
|
success: true
|
|
45
41
|
};
|
|
46
42
|
const collectionConfig = payload.collections[collection].config;
|
|
47
|
-
await this.saveTranslatedDocument(payload, collection, collectionId,
|
|
43
|
+
await this.saveTranslatedDocument(payload, collection, collectionId, translatedData, targetLng, sourceLng, collectionConfig, publishOnTranslation);
|
|
48
44
|
return {
|
|
49
45
|
success: true
|
|
50
46
|
};
|
|
@@ -24,6 +24,16 @@ export type TaskRunnerContext = {
|
|
|
24
24
|
handler: TaskHandler;
|
|
25
25
|
collections: CollectionSlug[];
|
|
26
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* The runtime half of a runner: produces a `TaskRunner` for one request.
|
|
29
|
+
*
|
|
30
|
+
* Routes depend on this narrow surface — they only ever `create()`, never
|
|
31
|
+
* `configure()`. The plugin binds the {@link TaskRunnerContext} once at build
|
|
32
|
+
* time and hands routes a factory, so a `create()` call needs no ambient state.
|
|
33
|
+
*/
|
|
34
|
+
export type TaskRunnerFactory = {
|
|
35
|
+
create(payload: Payload): TaskRunner;
|
|
36
|
+
};
|
|
27
37
|
/**
|
|
28
38
|
* Main interface for pluggable task runner providers.
|
|
29
39
|
*
|
|
@@ -36,8 +46,15 @@ export type TaskRunnerContext = {
|
|
|
36
46
|
export interface TaskRunnerProvider {
|
|
37
47
|
/**
|
|
38
48
|
* Creates a TaskRunner instance for runtime operations (enqueue, cancel, find).
|
|
49
|
+
*
|
|
50
|
+
* `handler` is supplied by the caller at create time (the plugin binds it
|
|
51
|
+
* into a {@link TaskRunnerFactory}) — so providers hold no mutable per-instance
|
|
52
|
+
* state and `create()` does not depend on `configure()` having run. It is the
|
|
53
|
+
* only runtime dependency (collection metadata is a `configure()`-time concern).
|
|
54
|
+
* `PayloadJobsRunner` ignores it (the handler is baked into the registered
|
|
55
|
+
* task at configure time); `SyncRunner` uses it to run translations inline.
|
|
39
56
|
*/
|
|
40
|
-
create(payload: Payload): TaskRunner;
|
|
57
|
+
create(payload: Payload, handler: TaskHandler): TaskRunner;
|
|
41
58
|
/**
|
|
42
59
|
* Configures the runner and returns a Payload config modifier.
|
|
43
60
|
* The modifier adds necessary tasks, jobs, queues to Payload config.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export type { TaskRunnerProvider } from
|
|
2
|
-
export type { Task, TaskStatus } from
|
|
3
|
-
export { createPayloadJobsRunner } from
|
|
4
|
-
export type { PayloadJobsRunnerOptions } from
|
|
5
|
-
export { createSyncRunner } from
|
|
6
|
-
export type { TaskRunner } from
|
|
1
|
+
export type { TaskRunnerProvider, TaskRunnerFactory, TaskRunnerContext } from "./TaskRunnerProvider.interface";
|
|
2
|
+
export type { Task, TaskStatus } from "./types";
|
|
3
|
+
export { createPayloadJobsRunner } from "./payload-jobs-runner";
|
|
4
|
+
export type { PayloadJobsRunnerOptions } from "./payload-jobs-runner";
|
|
5
|
+
export { createSyncRunner } from "./sync-runner";
|
|
6
|
+
export type { TaskRunner } from "./TaskRunner.interface";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createPayloadJobsRunner } from
|
|
2
|
-
export { createSyncRunner } from
|
|
1
|
+
export { createPayloadJobsRunner } from "./payload-jobs-runner";
|
|
2
|
+
export { createSyncRunner } from "./sync-runner";
|
|
3
3
|
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -14,6 +14,21 @@ export declare class PayloadJobsRunnerProvider implements TaskRunnerProvider {
|
|
|
14
14
|
configure(context: TaskRunnerContext): (config: Config) => Config;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Creates
|
|
17
|
+
* Creates the **recommended** task runner: translations run as Payload Jobs
|
|
18
|
+
* (queued, executed by autoRun cron or a manual run, with stale-lock recovery).
|
|
19
|
+
* Durable across restarts and suited to production/serverless. Pass the result
|
|
20
|
+
* as `translatorPlugin({ runner })`.
|
|
21
|
+
*
|
|
22
|
+
* @param options - Queue/task names, `autoRun` cron (or `false` to disable),
|
|
23
|
+
* `staleJobTimeoutMs`, and retry policy. See {@link PayloadJobsRunnerOptions}.
|
|
24
|
+
* @returns A {@link TaskRunnerProvider} for the plugin's `runner` option.
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* translatorPlugin({
|
|
28
|
+
* collections: [Posts],
|
|
29
|
+
* translationProvider: createOpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
30
|
+
* runner: createPayloadJobsRunner({ autoRun: { cron: '* * * * *' } }),
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
18
33
|
*/
|
|
19
34
|
export declare function createPayloadJobsRunner(options?: PayloadJobsRunnerOptions): TaskRunnerProvider;
|
|
@@ -172,7 +172,22 @@ const defaultValues = {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
|
-
* Creates
|
|
175
|
+
* Creates the **recommended** task runner: translations run as Payload Jobs
|
|
176
|
+
* (queued, executed by autoRun cron or a manual run, with stale-lock recovery).
|
|
177
|
+
* Durable across restarts and suited to production/serverless. Pass the result
|
|
178
|
+
* as `translatorPlugin({ runner })`.
|
|
179
|
+
*
|
|
180
|
+
* @param options - Queue/task names, `autoRun` cron (or `false` to disable),
|
|
181
|
+
* `staleJobTimeoutMs`, and retry policy. See {@link PayloadJobsRunnerOptions}.
|
|
182
|
+
* @returns A {@link TaskRunnerProvider} for the plugin's `runner` option.
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* translatorPlugin({
|
|
186
|
+
* collections: [Posts],
|
|
187
|
+
* translationProvider: createOpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
188
|
+
* runner: createPayloadJobsRunner({ autoRun: { cron: '* * * * *' } }),
|
|
189
|
+
* })
|
|
190
|
+
* ```
|
|
176
191
|
*/ export function createPayloadJobsRunner(options) {
|
|
177
192
|
return new PayloadJobsRunnerProvider(options);
|
|
178
193
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Config, Payload } from
|
|
2
|
-
import type { TaskRunner } from
|
|
3
|
-
import type { TaskRunnerProvider,
|
|
4
|
-
import type { SyncRunnerOptions } from
|
|
1
|
+
import type { Config, Payload } from "payload";
|
|
2
|
+
import type { TaskRunner } from "../TaskRunner.interface";
|
|
3
|
+
import type { TaskRunnerProvider, TaskHandler } from "../TaskRunnerProvider.interface";
|
|
4
|
+
import type { SyncRunnerOptions } from "./types";
|
|
5
5
|
/**
|
|
6
6
|
* Synchronous TaskRunnerProvider implementation.
|
|
7
7
|
*
|
|
@@ -9,16 +9,24 @@ import type { SyncRunnerOptions } from './types';
|
|
|
9
9
|
* Useful for development, testing, or simple use cases.
|
|
10
10
|
*/
|
|
11
11
|
export declare class SyncRunnerProvider implements TaskRunnerProvider {
|
|
12
|
-
private handler?;
|
|
13
12
|
private readonly tasks;
|
|
14
13
|
constructor(options?: SyncRunnerOptions);
|
|
15
|
-
create(payload: Payload): TaskRunner;
|
|
16
|
-
configure(
|
|
14
|
+
create(payload: Payload, handler: TaskHandler): TaskRunner;
|
|
15
|
+
configure(): (config: Config) => Config;
|
|
17
16
|
}
|
|
18
17
|
/**
|
|
19
|
-
* Creates a synchronous
|
|
18
|
+
* Creates a synchronous task runner: translations execute inline on enqueue,
|
|
19
|
+
* with no Payload Jobs queue. Status is kept in an in-memory map (bounded by
|
|
20
|
+
* `maxSize`/`ttlMs`) and is **lost on server restart**, so this is best for
|
|
21
|
+
* development, tests, or simple single-process setups — not serverless. Pass the
|
|
22
|
+
* result as `translatorPlugin({ runner })`.
|
|
20
23
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* @param options - In-memory store bounds: `maxSize` (default 100) and `ttlMs`
|
|
25
|
+
* (default 1h) for completed/failed task records.
|
|
26
|
+
* @returns A {@link TaskRunnerProvider} for the plugin's `runner` option.
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* translatorPlugin({ collections, translationProvider, runner: createSyncRunner() })
|
|
30
|
+
* ```
|
|
23
31
|
*/
|
|
24
32
|
export declare function createSyncRunner(options?: SyncRunnerOptions): TaskRunnerProvider;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { SyncTaskRunner } from
|
|
2
|
-
import { LazyMap } from
|
|
1
|
+
import { SyncTaskRunner } from "./SyncTaskRunner";
|
|
2
|
+
import { LazyMap } from "../../../shared/utils";
|
|
3
3
|
const DEFAULT_MAX_SIZE = 100;
|
|
4
|
-
const DEFAULT_TTL_MS = 60 * 60 * 1000 // 1 hour
|
|
5
|
-
;
|
|
4
|
+
const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
6
5
|
/**
|
|
7
6
|
* Synchronous TaskRunnerProvider implementation.
|
|
8
7
|
*
|
|
9
8
|
* Executes translations immediately without Payload Jobs.
|
|
10
9
|
* Useful for development, testing, or simple use cases.
|
|
11
10
|
*/ export class SyncRunnerProvider {
|
|
12
|
-
handler;
|
|
13
11
|
tasks;
|
|
14
12
|
constructor(options){
|
|
15
13
|
const maxSize = options?.maxSize ?? DEFAULT_MAX_SIZE;
|
|
@@ -17,24 +15,36 @@ const DEFAULT_TTL_MS = 60 * 60 * 1000 // 1 hour
|
|
|
17
15
|
this.tasks = new LazyMap({
|
|
18
16
|
maxSize,
|
|
19
17
|
ttlMs,
|
|
20
|
-
isRemovable: (task)=>task.status ===
|
|
18
|
+
isRemovable: (task)=>task.status === "completed" || task.status === "failed",
|
|
21
19
|
getTimestamp: (task)=>new Date(task.updatedAt).getTime()
|
|
22
20
|
});
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// SyncRunner executes the translation inline on enqueue, so it needs the
|
|
23
|
+
// handler at create time — taken from the caller-supplied argument rather than
|
|
24
|
+
// stashed on the instance during configure(). No ambient state, no ordering
|
|
25
|
+
// coupling: create() is a pure function of its arguments.
|
|
26
|
+
create(payload, handler) {
|
|
27
|
+
return new SyncTaskRunner(payload, handler, this.tasks);
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
// No config changes needed — translations run synchronously, no Payload jobs.
|
|
30
|
+
configure() {
|
|
30
31
|
return (config)=>config;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
|
-
* Creates a synchronous
|
|
35
|
+
* Creates a synchronous task runner: translations execute inline on enqueue,
|
|
36
|
+
* with no Payload Jobs queue. Status is kept in an in-memory map (bounded by
|
|
37
|
+
* `maxSize`/`ttlMs`) and is **lost on server restart**, so this is best for
|
|
38
|
+
* development, tests, or simple single-process setups — not serverless. Pass the
|
|
39
|
+
* result as `translatorPlugin({ runner })`.
|
|
35
40
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
41
|
+
* @param options - In-memory store bounds: `maxSize` (default 100) and `ttlMs`
|
|
42
|
+
* (default 1h) for completed/failed task records.
|
|
43
|
+
* @returns A {@link TaskRunnerProvider} for the plugin's `runner` option.
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* translatorPlugin({ collections, translationProvider, runner: createSyncRunner() })
|
|
47
|
+
* ```
|
|
38
48
|
*/ export function createSyncRunner(options) {
|
|
39
49
|
return new SyncRunnerProvider(options);
|
|
40
50
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CollectionConfig, Config, Endpoint } from "payload";
|
|
2
|
+
import type { AccessGuard } from "../../../types/AccessGuard";
|
|
3
|
+
import type { RawPayloadComponentExport } from "../../../client/shared/types/PayloadComponentExport";
|
|
4
|
+
import type { TaskRunnerFactory } from "../task-runner";
|
|
5
|
+
import type { CollectionAdminSlot, LevelContext } from "./types";
|
|
6
|
+
type ConfigModifier = (config: Config) => Config;
|
|
7
|
+
export type PluginConfigBuilderDeps = {
|
|
8
|
+
collections: CollectionConfig[];
|
|
9
|
+
basePath: string;
|
|
10
|
+
access?: AccessGuard;
|
|
11
|
+
taskRunnerFactory: TaskRunnerFactory;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The single place that mutates the Payload `config`. Levels (through the narrow
|
|
15
|
+
* {@link LevelContext}) and the plugin (through `addAdminProvider` /
|
|
16
|
+
* `addConfigModifier`) only *describe* their contributions; `applyTo(config)` is
|
|
17
|
+
* the one sink that writes them in. This keeps Payload's in-place-mutation style
|
|
18
|
+
* — including the lazy nested-config init — confined here, so `plugin.ts` and the
|
|
19
|
+
* levels stay free of direct config mutation.
|
|
20
|
+
*
|
|
21
|
+
* It implements {@link LevelContext}, so the same instance is what levels receive
|
|
22
|
+
* as their context (they see only `addEndpoints` / `addCollectionComponent`).
|
|
23
|
+
*/
|
|
24
|
+
export declare class PluginConfigBuilder implements LevelContext {
|
|
25
|
+
readonly collections: CollectionConfig[];
|
|
26
|
+
readonly basePath: string;
|
|
27
|
+
readonly access?: AccessGuard;
|
|
28
|
+
readonly taskRunnerFactory: TaskRunnerFactory;
|
|
29
|
+
private readonly endpoints;
|
|
30
|
+
private readonly collectionComponents;
|
|
31
|
+
private readonly adminProviders;
|
|
32
|
+
private readonly configModifiers;
|
|
33
|
+
constructor(deps: PluginConfigBuilderDeps);
|
|
34
|
+
addEndpoints(endpoints: Endpoint[]): void;
|
|
35
|
+
addCollectionComponent(slot: CollectionAdminSlot, make: (collection: CollectionConfig) => RawPayloadComponentExport): void;
|
|
36
|
+
/** Register a global admin provider (e.g. the client cache provider). */
|
|
37
|
+
addAdminProvider(provider: RawPayloadComponentExport): void;
|
|
38
|
+
/** Register a config modifier (e.g. the runner's jobs/autorun/onInit setup). */
|
|
39
|
+
addConfigModifier(modifier: ConfigModifier): void;
|
|
40
|
+
/** The one sink that writes every accumulated contribution into `config`. */
|
|
41
|
+
applyTo(config: Config): Config;
|
|
42
|
+
private attachAdminProviders;
|
|
43
|
+
private attachCollectionComponents;
|
|
44
|
+
private registerEndpoints;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const endpointKey = (endpoint)=>`${endpoint.method} ${endpoint.path}`;
|
|
2
|
+
function attachToSlot(collection, slot, component) {
|
|
3
|
+
if (!collection.admin) collection.admin = {};
|
|
4
|
+
if (!collection.admin.components) collection.admin.components = {};
|
|
5
|
+
const components = collection.admin.components;
|
|
6
|
+
if (slot === "beforeDocumentControls") {
|
|
7
|
+
if (!components.edit) components.edit = {};
|
|
8
|
+
if (!components.edit.beforeDocumentControls) components.edit.beforeDocumentControls = [];
|
|
9
|
+
components.edit.beforeDocumentControls.push(component);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (slot === "beforeListTable") {
|
|
13
|
+
if (!components.beforeListTable) components.beforeListTable = [];
|
|
14
|
+
components.beforeListTable.push(component);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Exhaustiveness: a new CollectionAdminSlot must be handled above, not fall through.
|
|
18
|
+
slot;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The single place that mutates the Payload `config`. Levels (through the narrow
|
|
22
|
+
* {@link LevelContext}) and the plugin (through `addAdminProvider` /
|
|
23
|
+
* `addConfigModifier`) only *describe* their contributions; `applyTo(config)` is
|
|
24
|
+
* the one sink that writes them in. This keeps Payload's in-place-mutation style
|
|
25
|
+
* — including the lazy nested-config init — confined here, so `plugin.ts` and the
|
|
26
|
+
* levels stay free of direct config mutation.
|
|
27
|
+
*
|
|
28
|
+
* It implements {@link LevelContext}, so the same instance is what levels receive
|
|
29
|
+
* as their context (they see only `addEndpoints` / `addCollectionComponent`).
|
|
30
|
+
*/ export class PluginConfigBuilder {
|
|
31
|
+
collections;
|
|
32
|
+
basePath;
|
|
33
|
+
access;
|
|
34
|
+
taskRunnerFactory;
|
|
35
|
+
endpoints = [];
|
|
36
|
+
collectionComponents = [];
|
|
37
|
+
adminProviders = [];
|
|
38
|
+
configModifiers = [];
|
|
39
|
+
constructor(deps){
|
|
40
|
+
this.collections = deps.collections;
|
|
41
|
+
this.basePath = deps.basePath;
|
|
42
|
+
this.access = deps.access;
|
|
43
|
+
this.taskRunnerFactory = deps.taskRunnerFactory;
|
|
44
|
+
}
|
|
45
|
+
addEndpoints(endpoints) {
|
|
46
|
+
this.endpoints.push(...endpoints);
|
|
47
|
+
}
|
|
48
|
+
addCollectionComponent(slot, make) {
|
|
49
|
+
this.collectionComponents.push({
|
|
50
|
+
slot,
|
|
51
|
+
make
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Register a global admin provider (e.g. the client cache provider). */ addAdminProvider(provider) {
|
|
55
|
+
this.adminProviders.push(provider);
|
|
56
|
+
}
|
|
57
|
+
/** Register a config modifier (e.g. the runner's jobs/autorun/onInit setup). */ addConfigModifier(modifier) {
|
|
58
|
+
this.configModifiers.push(modifier);
|
|
59
|
+
}
|
|
60
|
+
/** The one sink that writes every accumulated contribution into `config`. */ applyTo(config) {
|
|
61
|
+
// Config modifiers first — a runner's modifier may return a fresh config
|
|
62
|
+
// object, so everything else must be applied to its result.
|
|
63
|
+
let result = config;
|
|
64
|
+
for (const modify of this.configModifiers)result = modify(result);
|
|
65
|
+
this.attachAdminProviders(result);
|
|
66
|
+
this.attachCollectionComponents(result);
|
|
67
|
+
this.registerEndpoints(result);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
attachAdminProviders(config) {
|
|
71
|
+
if (this.adminProviders.length === 0) return;
|
|
72
|
+
if (!config.admin) config.admin = {};
|
|
73
|
+
if (!config.admin.components) config.admin.components = {};
|
|
74
|
+
if (!config.admin.components.providers) config.admin.components.providers = [];
|
|
75
|
+
// Not deduplicated. Unlike a duplicate endpoint (a route conflict), a
|
|
76
|
+
// duplicate admin provider is harmless, and providers have no reliable
|
|
77
|
+
// identity key across their shapes (`string | { path } | false`, with
|
|
78
|
+
// distinguishing `serverProps`). The single CacheProvider is added once per
|
|
79
|
+
// plugin instance.
|
|
80
|
+
config.admin.components.providers.push(...this.adminProviders);
|
|
81
|
+
}
|
|
82
|
+
attachCollectionComponents(config) {
|
|
83
|
+
if (this.collectionComponents.length === 0) return;
|
|
84
|
+
const managed = new Set(this.collections.map((collection)=>collection.slug));
|
|
85
|
+
config.collections?.forEach((collection)=>{
|
|
86
|
+
if (!managed.has(collection.slug)) return;
|
|
87
|
+
for (const { slot, make } of this.collectionComponents){
|
|
88
|
+
attachToSlot(collection, slot, make(collection));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
registerEndpoints(config) {
|
|
93
|
+
if (this.endpoints.length === 0) return;
|
|
94
|
+
if (!config.endpoints) config.endpoints = [];
|
|
95
|
+
// Seed from endpoints already on the config so each (method, path) registers
|
|
96
|
+
// once — across the levels' contributions AND anything already present (e.g.
|
|
97
|
+
// the plugin registered twice, or a host route at the same path).
|
|
98
|
+
const seen = new Set(config.endpoints.map(endpointKey));
|
|
99
|
+
for (const endpoint of this.endpoints){
|
|
100
|
+
const key = endpointKey(endpoint);
|
|
101
|
+
if (seen.has(key)) continue;
|
|
102
|
+
seen.add(key);
|
|
103
|
+
config.endpoints.push(endpoint);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=PluginConfigBuilder.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TranslationLevel } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Bulk collection translation: a dashboard on the list view (translate many
|
|
4
|
+
* documents at once), backed by the same shared document-translation API as
|
|
5
|
+
* {@link documentLevel}.
|
|
6
|
+
*
|
|
7
|
+
* @since 0.5.0
|
|
8
|
+
* @returns An opaque {@link TranslationLevel} to list in `translatorPlugin({ levels })`.
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Enable only bulk collection translation (no per-document popup):
|
|
12
|
+
* translatorPlugin({ collections: [Posts], translationProvider, runner, levels: [collectionLevel()] })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectionLevel(): TranslationLevel;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BulkDocumentTranslationDashboard } from "../../../client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export";
|
|
2
|
+
import { useDocTranslationApi } from "./useDocTranslationApi";
|
|
3
|
+
/**
|
|
4
|
+
* Bulk collection translation: a dashboard on the list view (translate many
|
|
5
|
+
* documents at once), backed by the same shared document-translation API as
|
|
6
|
+
* {@link documentLevel}.
|
|
7
|
+
*
|
|
8
|
+
* @since 0.5.0
|
|
9
|
+
* @returns An opaque {@link TranslationLevel} to list in `translatorPlugin({ levels })`.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Enable only bulk collection translation (no per-document popup):
|
|
13
|
+
* translatorPlugin({ collections: [Posts], translationProvider, runner, levels: [collectionLevel()] })
|
|
14
|
+
* ```
|
|
15
|
+
*/ export function collectionLevel() {
|
|
16
|
+
return {
|
|
17
|
+
extend (ctx) {
|
|
18
|
+
useDocTranslationApi(ctx);
|
|
19
|
+
ctx.addCollectionComponent("beforeListTable", ()=>new BulkDocumentTranslationDashboard(ctx.access));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=collectionLevel.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TranslationLevel } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Per-document translation: a popup control on the document edit view, backed by
|
|
4
|
+
* the shared document-translation API (async/job by default; synchronous if the
|
|
5
|
+
* configured `runner` is a sync runner).
|
|
6
|
+
*
|
|
7
|
+
* @since 0.5.0
|
|
8
|
+
* @returns An opaque {@link TranslationLevel} to list in `translatorPlugin({ levels })`.
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* translatorPlugin({
|
|
12
|
+
* collections: [Posts],
|
|
13
|
+
* translationProvider,
|
|
14
|
+
* runner: createPayloadJobsRunner(),
|
|
15
|
+
* levels: [documentLevel(), collectionLevel()], // this is the default — omit for the same result
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function documentLevel(): TranslationLevel;
|