@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
@@ -40,6 +40,22 @@ export type PayloadJobsRunnerOptions = {
40
40
  * @default { cron: '* * * * *', limit: 50 }
41
41
  */
42
42
  autoRun?: false | AutoRunConfig;
43
+ /**
44
+ * How long (ms) a job may stay `processing: true` before its lock is
45
+ * considered stale and the job becomes eligible to be re-run.
46
+ *
47
+ * A process killed mid-run (deploy, crash, request timeout) leaves a job
48
+ * stuck at `processing: true`; the autorun picker only takes
49
+ * `processing: false`, so without recovery such a job would hang forever.
50
+ * On boot the runner resets stale locks, and manual `run()` will re-claim a
51
+ * stale-locked job instead of refusing it as already-running.
52
+ *
53
+ * MUST be larger than the longest a single document translation can
54
+ * legitimately take, otherwise a genuinely in-flight job could be reclaimed
55
+ * and run twice (safe under the idempotent `overwrite` strategy, but wasteful).
56
+ * @default 300000 (5 minutes)
57
+ */
58
+ staleJobTimeoutMs?: number;
43
59
  /**
44
60
  * Retry configuration for failed jobs.
45
61
  */
@@ -59,6 +75,7 @@ export type PayloadJobsRunnerConfig = {
59
75
  queueName: string;
60
76
  jobsCollection: CollectionSlug;
61
77
  autoRun: false | Required<AutoRunConfig>;
78
+ staleJobTimeoutMs: number;
62
79
  retries?: PayloadJobsRunnerOptions["retries"];
63
80
  };
64
81
  /**
@@ -1,7 +1,7 @@
1
- import type { Config, Payload } from 'payload';
2
- import type { TaskRunner } from '../TaskRunner.interface';
3
- import type { TaskRunnerProvider, TaskRunnerContext } from '../TaskRunnerProvider.interface';
4
- import type { SyncRunnerOptions } from './types';
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(context: TaskRunnerContext): (config: Config) => Config;
14
+ create(payload: Payload, handler: TaskHandler): TaskRunner;
15
+ configure(): (config: Config) => Config;
17
16
  }
18
17
  /**
19
- * Creates a synchronous TaskRunnerProvider.
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
- * Executes translations immediately without queuing.
22
- * Results are stored in memory and lost on server restart.
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 './SyncTaskRunner';
2
- import { LazyMap } from '../../../shared/utils';
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 === 'completed' || task.status === 'failed',
18
+ isRemovable: (task)=>task.status === "completed" || task.status === "failed",
21
19
  getTimestamp: (task)=>new Date(task.updatedAt).getTime()
22
20
  });
23
21
  }
24
- create(payload) {
25
- if (!this.handler) throw new Error('SyncRunnerProvider not configured. Call configure() first.');
26
- return new SyncTaskRunner(payload, this.handler, this.tasks);
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
- configure(context) {
29
- this.handler = context.handler;
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 TaskRunnerProvider.
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
- * Executes translations immediately without queuing.
37
- * Results are stored in memory and lost on server restart.
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;
@@ -0,0 +1,28 @@
1
+ import { TranslateDocumentExport } from "../../../client/widgets/translate-document";
2
+ import { useDocTranslationApi } from "./useDocTranslationApi";
3
+ /**
4
+ * Per-document translation: a popup control on the document edit view, backed by
5
+ * the shared document-translation API (async/job by default; synchronous if the
6
+ * configured `runner` is a sync runner).
7
+ *
8
+ * @since 0.5.0
9
+ * @returns An opaque {@link TranslationLevel} to list in `translatorPlugin({ levels })`.
10
+ * @example
11
+ * ```ts
12
+ * translatorPlugin({
13
+ * collections: [Posts],
14
+ * translationProvider,
15
+ * runner: createPayloadJobsRunner(),
16
+ * levels: [documentLevel(), collectionLevel()], // this is the default — omit for the same result
17
+ * })
18
+ * ```
19
+ */ export function documentLevel() {
20
+ return {
21
+ extend (ctx) {
22
+ useDocTranslationApi(ctx);
23
+ ctx.addCollectionComponent("beforeDocumentControls", (collection)=>new TranslateDocumentExport(collection, ctx.access));
24
+ }
25
+ };
26
+ }
27
+
28
+ //# sourceMappingURL=documentLevel.js.map
@@ -0,0 +1,3 @@
1
+ export type { TranslationLevel } from "./types";
2
+ export { documentLevel } from "./documentLevel";
3
+ export { collectionLevel } from "./collectionLevel";
@@ -0,0 +1,4 @@
1
+ export { documentLevel } from "./documentLevel";
2
+ export { collectionLevel } from "./collectionLevel";
3
+
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,42 @@
1
+ import type { CollectionConfig, 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
+ export type CollectionAdminSlot = "beforeDocumentControls" | "beforeListTable";
6
+ /**
7
+ * A composable translation surface (document / collection / field).
8
+ *
9
+ * Treat it as an **opaque value**: create one with `documentLevel()` /
10
+ * `collectionLevel()` (and, later, `fieldLevel()`) and list it in
11
+ * `translatorPlugin({ levels })`. It is produced only by those built-in
12
+ * factories — not externally implementable yet, since {@link LevelContext} is
13
+ * intentionally not exported. Opening it later (export `LevelContext`, drop the
14
+ * `@internal` tag) is additive and non-breaking.
15
+ *
16
+ * @since 0.5.0
17
+ */
18
+ export interface TranslationLevel {
19
+ /**
20
+ * Not a stable contract yet — produce via the level factories.
21
+ * @internal
22
+ */
23
+ extend(ctx: LevelContext): void;
24
+ }
25
+ /**
26
+ * Public-ready shape, kept internal until external levels are needed.
27
+ *
28
+ * A level contributes via these generic primitives; it never mutates the raw
29
+ * Payload config. The plugin deduplicates endpoints by method + path on apply.
30
+ * (fieldLevel — Phase 2 — will also read `schemaMap` + `translationProvider`.)
31
+ * @internal
32
+ */
33
+ export interface LevelContext {
34
+ readonly collections: CollectionConfig[];
35
+ readonly basePath: string;
36
+ readonly access?: AccessGuard;
37
+ readonly taskRunnerFactory: TaskRunnerFactory;
38
+ /** Register endpoints. Deduplicated by method + path when applied. */
39
+ addEndpoints(endpoints: Endpoint[]): void;
40
+ /** Attach an admin component to a slot on every managed collection. */
41
+ addCollectionComponent(slot: CollectionAdminSlot, make: (collection: CollectionConfig) => RawPayloadComponentExport): void;
42
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public-ready shape, kept internal until external levels are needed.
3
+ *
4
+ * A level contributes via these generic primitives; it never mutates the raw
5
+ * Payload config. The plugin deduplicates endpoints by method + path on apply.
6
+ * (fieldLevel — Phase 2 — will also read `schemaMap` + `translationProvider`.)
7
+ * @internal
8
+ */ export { };
9
+
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,8 @@
1
+ import type { LevelContext } from "./types";
2
+ /**
3
+ * Contribute the runner-agnostic document-translation API — the shared 6-route
4
+ * bundle, bound to the level context's runner. Both `documentLevel` and
5
+ * `collectionLevel` call this; the plugin deduplicates the endpoints by method +
6
+ * path, so the bundle registers exactly once.
7
+ */
8
+ export declare function useDocTranslationApi(ctx: LevelContext): void;
@@ -0,0 +1,18 @@
1
+ import { createTranslationRoutes } from "../../features/createTranslationRoutes";
2
+ /**
3
+ * Contribute the runner-agnostic document-translation API — the shared 6-route
4
+ * bundle, bound to the level context's runner. Both `documentLevel` and
5
+ * `collectionLevel` call this; the plugin deduplicates the endpoints by method +
6
+ * path, so the bundle registers exactly once.
7
+ */ export function useDocTranslationApi(ctx) {
8
+ ctx.addEndpoints(createTranslationRoutes({
9
+ taskRunnerFactory: ctx.taskRunnerFactory,
10
+ collectionConfig: {
11
+ availableCollections: new Set(ctx.collections.map((collection)=>collection.slug))
12
+ },
13
+ access: ctx.access,
14
+ basePath: ctx.basePath
15
+ }));
16
+ }
17
+
18
+ //# sourceMappingURL=useDocTranslationApi.js.map
@@ -1,3 +1,5 @@
1
- export { TranslationPipeline } from './TranslationPipeline';
2
- export type { TranslationStrategy } from './strategies';
3
- export { OverwriteStrategy, SkipExistingStrategy } from './strategies';
1
+ export { TranslationPipeline } from "./TranslationPipeline";
2
+ export { translateContent } from "./translateContent";
3
+ export type { TranslateContentArgs } from "./translateContent";
4
+ export type { TranslationStrategy } from "./strategies";
5
+ export { OverwriteStrategy, SkipExistingStrategy } from "./strategies";
@@ -1,4 +1,5 @@
1
- export { TranslationPipeline } from './TranslationPipeline';
2
- export { OverwriteStrategy, SkipExistingStrategy } from './strategies';
1
+ export { TranslationPipeline } from "./TranslationPipeline";
2
+ export { translateContent } from "./translateContent";
3
+ export { OverwriteStrategy, SkipExistingStrategy } from "./strategies";
3
4
 
4
5
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ import type { Field } from "payload";
2
+ import type { TranslationProvider } from "../translation-providers";
3
+ import type { TranslationStrategyName } from "./strategies";
4
+ export type TranslateContentArgs = {
5
+ /** Schema subtree to translate (e.g. `[declaredFieldConfig]`). */
6
+ schema: Field[];
7
+ /** Source values, rooted to match `schema` (e.g. `{ [fieldName]: value }`). */
8
+ sourceData: Record<string, unknown>;
9
+ /**
10
+ * Existing target-locale values to reconcile against (target wins when
11
+ * non-empty under `skip_existing`). Defaults to `{}` — i.e. translate the
12
+ * source in place (`overwrite`).
13
+ */
14
+ targetData?: Record<string, unknown>;
15
+ /** Source language code, or `''` for provider auto-detect. */
16
+ sourceLng: string;
17
+ targetLng: string;
18
+ translationProvider: TranslationProvider;
19
+ /** @default 'overwrite' */
20
+ strategy?: TranslationStrategyName;
21
+ };
22
+ /**
23
+ * Translate a content object over a schema subtree — no DB, no document.
24
+ *
25
+ * A thin reusable entry over {@link TranslationPipeline}, which is already pure
26
+ * and walks any `Field[]` + matching data (a subtree + partial data works
27
+ * unchanged). The document level routes through this wrapper today (instead of
28
+ * constructing the pipeline inline); the upcoming field level will too —
29
+ * passing a single declared field's subtree + its current unsaved form value.
30
+ *
31
+ * Only `localized` text/richText leaves are translated; non-localized values
32
+ * are reconciled through unchanged. Returns the translated data (same shape as
33
+ * `sourceData`) or `null` when nothing was translatable.
34
+ */
35
+ export declare function translateContent({ schema, sourceData, targetData, sourceLng, targetLng, translationProvider, strategy, }: TranslateContentArgs): Promise<Record<string, unknown> | null>;
@@ -0,0 +1,30 @@
1
+ import { TranslationPipeline } from "./TranslationPipeline";
2
+ import { createTranslationStrategy } from "./strategies";
3
+ /**
4
+ * Translate a content object over a schema subtree — no DB, no document.
5
+ *
6
+ * A thin reusable entry over {@link TranslationPipeline}, which is already pure
7
+ * and walks any `Field[]` + matching data (a subtree + partial data works
8
+ * unchanged). The document level routes through this wrapper today (instead of
9
+ * constructing the pipeline inline); the upcoming field level will too —
10
+ * passing a single declared field's subtree + its current unsaved form value.
11
+ *
12
+ * Only `localized` text/richText leaves are translated; non-localized values
13
+ * are reconciled through unchanged. Returns the translated data (same shape as
14
+ * `sourceData`) or `null` when nothing was translatable.
15
+ */ export async function translateContent({ schema, sourceData, targetData = {}, sourceLng, targetLng, translationProvider, strategy = "overwrite" }) {
16
+ const pipeline = new TranslationPipeline({
17
+ translationProvider,
18
+ translationStrategy: createTranslationStrategy(strategy)
19
+ });
20
+ const result = await pipeline.execute({
21
+ schema,
22
+ sourceData,
23
+ targetData,
24
+ sourceLng,
25
+ targetLng
26
+ });
27
+ return result ? result.translatedData : null;
28
+ }
29
+
30
+ //# sourceMappingURL=translateContent.js.map
@@ -1,5 +1,5 @@
1
- import type { TranslationProvider, TranslationInput, TranslationOutput } from './TranslationProvider.interface';
2
- import type { ChatModel } from 'openai/resources/index.mjs';
1
+ import type { TranslationProvider, TranslationInput, TranslationOutput } from "./TranslationProvider.interface";
2
+ import type { ChatModel } from "openai/resources/index.mjs";
3
3
  /**
4
4
  * Function to transform text in dry run mode.
5
5
  * Receives the original text and returns the transformed text.
@@ -30,6 +30,7 @@ export type SystemPromptContext = {
30
30
  */
31
31
  type SystemPromptBuilder = (context: SystemPromptContext) => string;
32
32
  export type OpenAIProviderConfig = {
33
+ /** OpenAI API key (required). Read it from an env var — never hard-code it. */
33
34
  apiKey: string;
34
35
  /**
35
36
  * OpenAI model to use for translation.
@@ -78,8 +79,8 @@ export type OpenAIProviderConfig = {
78
79
  };
79
80
  /** @deprecated Use `createOpenAIProvider` function instead */
80
81
  export declare class OpenAITranslationProvider implements TranslationProvider {
81
- private readonly config;
82
82
  private openAiClient;
83
+ private readonly config;
83
84
  constructor(config: OpenAIProviderConfig);
84
85
  translate(content: TranslationInput, souceLng: string, targetLng: string): Promise<TranslationOutput | null>;
85
86
  /**