@focus-reactive/payload-plugin-translator 0.10.3 → 0.11.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 (56) hide show
  1. package/README.md +152 -10
  2. package/dist/core/domain/locales/index.d.ts +6 -0
  3. package/dist/core/domain/locales/index.js +7 -0
  4. package/dist/core/domain/locales/resolveLocales.d.ts +59 -0
  5. package/dist/core/domain/locales/resolveLocales.js +68 -0
  6. package/dist/index.d.ts +8 -3
  7. package/dist/index.js +4 -3
  8. package/dist/server/features/enqueue-translation/handler.js +2 -2
  9. package/dist/server/modules/auto-translate/AutoTranslate.policy.js +7 -8
  10. package/dist/translation-providers/index.d.ts +1 -0
  11. package/dist/translation-providers/index.js +1 -7
  12. package/dist/translation-providers/openai/OpenAI.shapes.d.ts +52 -0
  13. package/dist/translation-providers/openai/OpenAI.shapes.js +13 -0
  14. package/dist/translation-providers/openai/OpenAITranslation.provider.d.ts +51 -112
  15. package/dist/translation-providers/openai/OpenAITranslation.provider.js +49 -137
  16. package/dist/translation-providers/openai/OpenAITranslationLegacy.provider.d.ts +11 -0
  17. package/dist/translation-providers/openai/OpenAITranslationLegacy.provider.js +15 -0
  18. package/dist/translation-providers/openai/index.d.ts +6 -1
  19. package/dist/translation-providers/openai/index.js +3 -3
  20. package/dist/translation-providers/openai/loadOpenAIClient.d.ts +24 -0
  21. package/dist/translation-providers/openai/loadOpenAIClient.js +79 -0
  22. package/dist/translation-providers/openai/openAIComplete.d.ts +39 -0
  23. package/dist/translation-providers/openai/openAIComplete.js +84 -0
  24. package/dist/translation-providers/shared/CompletionProvider.provider.d.ts +65 -0
  25. package/dist/translation-providers/shared/CompletionProvider.provider.js +93 -0
  26. package/dist/translation-providers/shared/buildResponseSchema.d.ts +13 -0
  27. package/dist/translation-providers/shared/buildResponseSchema.js +22 -0
  28. package/dist/translation-providers/shared/buildSystemPrompt.d.ts +28 -0
  29. package/dist/translation-providers/shared/buildSystemPrompt.js +25 -0
  30. package/dist/translation-providers/shared/errors/KeySetMismatchError.d.ts +15 -0
  31. package/dist/translation-providers/shared/errors/KeySetMismatchError.js +26 -0
  32. package/dist/translation-providers/shared/errors/NoContentError.d.ts +9 -0
  33. package/dist/translation-providers/shared/errors/NoContentError.js +10 -0
  34. package/dist/translation-providers/shared/errors/ProviderConfigurationError.d.ts +10 -0
  35. package/dist/translation-providers/shared/errors/ProviderConfigurationError.js +11 -0
  36. package/dist/translation-providers/shared/errors/TranslationProviderError.d.ts +18 -0
  37. package/dist/translation-providers/shared/errors/TranslationProviderError.js +21 -0
  38. package/dist/translation-providers/shared/errors/TransportError.d.ts +10 -0
  39. package/dist/translation-providers/shared/errors/TransportError.js +11 -0
  40. package/dist/translation-providers/shared/errors/UnparseableReplyError.d.ts +9 -0
  41. package/dist/translation-providers/shared/errors/UnparseableReplyError.js +10 -0
  42. package/dist/translation-providers/shared/errors/errorMessageLower.d.ts +1 -0
  43. package/dist/translation-providers/shared/errors/errorMessageLower.js +8 -0
  44. package/dist/translation-providers/shared/errors/index.d.ts +9 -0
  45. package/dist/translation-providers/shared/errors/index.js +10 -0
  46. package/dist/translation-providers/shared/errors/wrapTransportError.d.ts +7 -0
  47. package/dist/translation-providers/shared/errors/wrapTransportError.js +14 -0
  48. package/dist/translation-providers/shared/index.d.ts +8 -0
  49. package/dist/translation-providers/shared/index.js +5 -0
  50. package/dist/translation-providers/shared/parseAndValidateReply.d.ts +13 -0
  51. package/dist/translation-providers/shared/parseAndValidateReply.js +46 -0
  52. package/dist/translation-providers/shared/runDryRun.d.ts +20 -0
  53. package/dist/translation-providers/shared/runDryRun.js +17 -0
  54. package/package.json +1 -1
  55. package/dist/server/features/enqueue-translation/resolveTargetLocales.d.ts +0 -30
  56. package/dist/server/features/enqueue-translation/resolveTargetLocales.js +0 -35
@@ -0,0 +1,65 @@
1
+ import type { TranslationProvider } from "../../core/domain/translation-providers";
2
+ import type { JsonSchemaObject } from "./buildResponseSchema";
3
+ import type { SystemPromptBuilder } from "./buildSystemPrompt";
4
+ import type { DryRunConfig } from "./runDryRun";
5
+ /**
6
+ * Everything a transport needs in order to make one translation request.
7
+ *
8
+ * @since 0.11.0
9
+ */
10
+ export type CompletionRequest = {
11
+ /** Already includes any `systemPrompt` override — send it verbatim, do not rebuild it. */
12
+ systemPrompt: string;
13
+ /** The user message: the input serialized as JSON. */
14
+ userContent: string;
15
+ /** A JSON Schema requiring exactly the input's keys. Feed it to the service's structured-output mechanism. */
16
+ responseSchema: JsonSchemaObject;
17
+ /** Reserved: the port carries no cancellation yet, so this is always `undefined` today. */
18
+ signal?: AbortSignal;
19
+ };
20
+ /**
21
+ * Sends one translation request and returns the service's reply as **raw text**.
22
+ *
23
+ * Returning text rather than a parsed object is deliberate: parsing and key-set validation stay on
24
+ * this side of the boundary, so a provider built on this factory cannot accidentally skip them.
25
+ *
26
+ * @since 0.11.0
27
+ */
28
+ export type CompletionFn = (request: CompletionRequest) => Promise<string>;
29
+ /**
30
+ * Configuration for {@link createTranslationProvider}.
31
+ *
32
+ * @since 0.11.0
33
+ */
34
+ export type TranslationProviderConfig = {
35
+ complete: CompletionFn;
36
+ systemPrompt?: SystemPromptBuilder;
37
+ /**
38
+ * Simulate translations without calling anything. `true` reverses the text; an object supplies a
39
+ * transformer and an optional delay.
40
+ *
41
+ * @deprecated Supply your own fake `complete` instead. Remove in next major.
42
+ * See docs/DEPRECATIONS.md#provider-dry-run
43
+ */
44
+ dryRun?: boolean | DryRunConfig;
45
+ };
46
+ /**
47
+ * Builds a complete {@link TranslationProvider} from a single request function.
48
+ *
49
+ * Use it when you need a service this package does not ship an adapter for, or when you need full
50
+ * control of the request body. For OpenAI, `openAIComplete({ client, model })` is a ready-made
51
+ * `complete` — construct the client yourself.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const provider = createTranslationProvider({
56
+ * complete: async ({ systemPrompt, userContent, responseSchema }) => {
57
+ * const reply = await myService.chat({ systemPrompt, userContent, schema: responseSchema })
58
+ * return reply.text
59
+ * },
60
+ * })
61
+ * ```
62
+ *
63
+ * @since 0.11.0
64
+ */
65
+ export declare function createTranslationProvider(config: TranslationProviderConfig): TranslationProvider;
@@ -0,0 +1,93 @@
1
+ import { buildResponseSchema } from "./buildResponseSchema";
2
+ import { buildSystemPrompt } from "./buildSystemPrompt";
3
+ import { NoContentError, ProviderConfigurationError, wrapTransportError } from "./errors";
4
+ import { parseAndValidateReply } from "./parseAndValidateReply";
5
+ import { runDryRun } from "./runDryRun";
6
+ const MAX_LOGGED_REPLY_KEYS = 10;
7
+ /** Keys are model output: quoting them stops a newline inside a key from forging log entries. */ function describeReplyKeys(keys) {
8
+ const shown = keys.slice(0, MAX_LOGGED_REPLY_KEYS).map((key)=>JSON.stringify(key));
9
+ const rest = keys.length - shown.length;
10
+ return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
11
+ }
12
+ function warnAboutPartialReply(missingInputKeys, unrequestedReplyKeys) {
13
+ const lead = missingInputKeys.length > 0 ? `The provider's reply did not cover every field. Untranslated indices: ${missingInputKeys.join(", ")}.` : "The provider's reply carried keys that were not requested.";
14
+ const extra = unrequestedReplyKeys.length > 0 ? ` Unexpected keys ignored: ${describeReplyKeys(unrequestedReplyKeys)}.` : "";
15
+ console.warn(`[payload-plugin-translator] ${lead}${extra}`);
16
+ }
17
+ async function asConfigurationFailure(what, run) {
18
+ try {
19
+ return await run();
20
+ } catch (cause) {
21
+ throw new ProviderConfigurationError(`The ${what} threw. See this error's \`cause\`.`, {
22
+ cause
23
+ });
24
+ }
25
+ }
26
+ /** Only the consumer's transform is wrapped — our own loop must not report its bugs as configuration failures. */ function guardTransformer(dryRun) {
27
+ if (typeof dryRun !== "object" || !dryRun.transform) return dryRun;
28
+ const { transform } = dryRun;
29
+ return {
30
+ ...dryRun,
31
+ transform: (text)=>asConfigurationFailure("dry-run transformer", ()=>transform(text))
32
+ };
33
+ }
34
+ /**
35
+ * Builds a complete {@link TranslationProvider} from a single request function.
36
+ *
37
+ * Use it when you need a service this package does not ship an adapter for, or when you need full
38
+ * control of the request body. For OpenAI, `openAIComplete({ client, model })` is a ready-made
39
+ * `complete` — construct the client yourself.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const provider = createTranslationProvider({
44
+ * complete: async ({ systemPrompt, userContent, responseSchema }) => {
45
+ * const reply = await myService.chat({ systemPrompt, userContent, schema: responseSchema })
46
+ * return reply.text
47
+ * },
48
+ * })
49
+ * ```
50
+ *
51
+ * @since 0.11.0
52
+ */ export function createTranslationProvider(config) {
53
+ const { complete, systemPrompt, dryRun } = config;
54
+ if (dryRun) {
55
+ const guarded = guardTransformer(dryRun);
56
+ return {
57
+ translate: (input)=>runDryRun(input, guarded)
58
+ };
59
+ }
60
+ return {
61
+ async translate (input, sourceLng, targetLng) {
62
+ // A strict response schema with no properties is rejected by the service, so an empty
63
+ // document must not reach the transport at all.
64
+ if (Object.keys(input).length === 0) return {};
65
+ const systemPromptText = await asConfigurationFailure("systemPrompt builder", ()=>buildSystemPrompt({
66
+ sourceLng,
67
+ targetLng,
68
+ override: systemPrompt
69
+ }));
70
+ const request = await asConfigurationFailure("serialization of the input", ()=>({
71
+ systemPrompt: systemPromptText,
72
+ userContent: JSON.stringify(input),
73
+ responseSchema: buildResponseSchema(input)
74
+ }));
75
+ let raw;
76
+ try {
77
+ raw = await complete(request);
78
+ } catch (cause) {
79
+ throw wrapTransportError(cause);
80
+ }
81
+ if (!raw) {
82
+ throw new NoContentError("The provider returned an empty reply.");
83
+ }
84
+ const { translations, missingInputKeys, unrequestedReplyKeys } = parseAndValidateReply(input, raw);
85
+ if (missingInputKeys.length > 0 || unrequestedReplyKeys.length > 0) {
86
+ warnAboutPartialReply(missingInputKeys, unrequestedReplyKeys);
87
+ }
88
+ return translations;
89
+ }
90
+ };
91
+ }
92
+
93
+ //# sourceMappingURL=CompletionProvider.provider.js.map
@@ -0,0 +1,13 @@
1
+ import type { TranslationInput } from "../../core/domain/translation-providers";
2
+ /**
3
+ * A JSON Schema document, as a plain object — deliberately untyped beyond that, so it can be dropped
4
+ * into any vendor's response-format envelope.
5
+ *
6
+ * @since 0.11.0
7
+ */
8
+ export type JsonSchemaObject = Record<string, unknown>;
9
+ /**
10
+ * Builds the response schema for a translation request: exactly the input's keys, all required, no
11
+ * extras.
12
+ */
13
+ export declare function buildResponseSchema(input: TranslationInput): JsonSchemaObject;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Builds the response schema for a translation request: exactly the input's keys, all required, no
3
+ * extras.
4
+ */ export function buildResponseSchema(input) {
5
+ const keys = Object.keys(input);
6
+ const properties = {};
7
+ for (const key of keys){
8
+ properties[key] = {
9
+ type: "string"
10
+ };
11
+ }
12
+ // Strict structured-output modes reject a schema unless every property is listed in `required`
13
+ // and extras are forbidden.
14
+ return {
15
+ type: "object",
16
+ properties,
17
+ required: keys,
18
+ additionalProperties: false
19
+ };
20
+ }
21
+
22
+ //# sourceMappingURL=buildResponseSchema.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Context handed to a {@link SystemPromptBuilder}.
3
+ *
4
+ * @since 0.11.0
5
+ */
6
+ export type SystemPromptContext = {
7
+ /** Source language code (e.g. 'en', 'de'). Empty when the provider should auto-detect. */
8
+ sourceLang: string;
9
+ targetLang: string;
10
+ defaultPrompt: string;
11
+ };
12
+ /**
13
+ * Builds a custom system prompt for translation.
14
+ *
15
+ * @since 0.11.0
16
+ */
17
+ export type SystemPromptBuilder = (context: SystemPromptContext) => string;
18
+ /**
19
+ * Produces the system prompt sent with a translation request.
20
+ *
21
+ * The default wording is handed to `systemPrompt` builders as `defaultPrompt`, so consumers extend
22
+ * this exact string — changing it changes every custom prompt built on it.
23
+ */
24
+ export declare function buildSystemPrompt(args: {
25
+ sourceLng: string;
26
+ targetLng: string;
27
+ override?: SystemPromptBuilder;
28
+ }): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Context handed to a {@link SystemPromptBuilder}.
3
+ *
4
+ * @since 0.11.0
5
+ */ /**
6
+ * Produces the system prompt sent with a translation request.
7
+ *
8
+ * The default wording is handed to `systemPrompt` builders as `defaultPrompt`, so consumers extend
9
+ * this exact string — changing it changes every custom prompt built on it.
10
+ */ export function buildSystemPrompt(args) {
11
+ const { sourceLng, targetLng, override } = args;
12
+ const defaultPrompt = `Translate the values from the JSON that the user will send you${sourceLng ? ` from ${sourceLng}` : ""} into ${targetLng}. Keep all JSON keys exactly as they are, only translate the values.
13
+ The response should be a valid JSON object with the same structure and keys as the input, but with translated values.
14
+ Maintain any special formatting, placeholders, or variables within the values if they exist.`;
15
+ if (override) {
16
+ return override({
17
+ sourceLang: sourceLng,
18
+ targetLang: targetLng,
19
+ defaultPrompt
20
+ });
21
+ }
22
+ return defaultPrompt;
23
+ }
24
+
25
+ //# sourceMappingURL=buildSystemPrompt.js.map
@@ -0,0 +1,15 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * Nothing in the reply could be applied — either its keys did not overlap the input's, or they did
4
+ * and none of the values was a string. A partial gap is not an error; see `parseAndValidateReply`.
5
+ *
6
+ * @since 0.11.0
7
+ */
8
+ export declare class KeySetMismatchError extends TranslationProviderError {
9
+ readonly code: "key-set-mismatch";
10
+ /** Input keys left without a usable translation. */
11
+ readonly missingInputKeys: number[];
12
+ /** Keys the reply carried but nobody asked for — model output, so untrusted text. */
13
+ readonly unrequestedReplyKeys: string[];
14
+ constructor(message: string, missingInputKeys: number[], unrequestedReplyKeys: string[], options?: ErrorOptions);
15
+ }
@@ -0,0 +1,26 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * Nothing in the reply could be applied — either its keys did not overlap the input's, or they did
4
+ * and none of the values was a string. A partial gap is not an error; see `parseAndValidateReply`.
5
+ *
6
+ * @since 0.11.0
7
+ */ export class KeySetMismatchError extends TranslationProviderError {
8
+ code = "key-set-mismatch";
9
+ /** Input keys left without a usable translation. */ missingInputKeys;
10
+ /** Keys the reply carried but nobody asked for — model output, so untrusted text. */ unrequestedReplyKeys;
11
+ constructor(message, missingInputKeys, unrequestedReplyKeys, options){
12
+ super(message, options);
13
+ this.missingInputKeys = missingInputKeys;
14
+ this.unrequestedReplyKeys = unrequestedReplyKeys;
15
+ // Model-invented strings: enumerable, they would land in `JSON.stringify(error)` and every
16
+ // structured logger.
17
+ Object.defineProperty(this, "missingInputKeys", {
18
+ enumerable: false
19
+ });
20
+ Object.defineProperty(this, "unrequestedReplyKeys", {
21
+ enumerable: false
22
+ });
23
+ }
24
+ }
25
+
26
+ //# sourceMappingURL=KeySetMismatchError.js.map
@@ -0,0 +1,9 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The provider produced no usable text — an empty reply, or a response the vendor filtered.
4
+ *
5
+ * @since 0.11.0
6
+ */
7
+ export declare class NoContentError extends TranslationProviderError {
8
+ readonly code: "no-content";
9
+ }
@@ -0,0 +1,10 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The provider produced no usable text — an empty reply, or a response the vendor filtered.
4
+ *
5
+ * @since 0.11.0
6
+ */ export class NoContentError extends TranslationProviderError {
7
+ code = "no-content";
8
+ }
9
+
10
+ //# sourceMappingURL=NoContentError.js.map
@@ -0,0 +1,10 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The provider is configured in a way that cannot work — most often an optional SDK that is not
4
+ * installed.
5
+ *
6
+ * @since 0.11.0
7
+ */
8
+ export declare class ProviderConfigurationError extends TranslationProviderError {
9
+ readonly code: "config";
10
+ }
@@ -0,0 +1,11 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The provider is configured in a way that cannot work — most often an optional SDK that is not
4
+ * installed.
5
+ *
6
+ * @since 0.11.0
7
+ */ export class ProviderConfigurationError extends TranslationProviderError {
8
+ code = "config";
9
+ }
10
+
11
+ //# sourceMappingURL=ProviderConfigurationError.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Failure causes a translation provider can report.
3
+ *
4
+ * @since 0.11.0
5
+ */
6
+ export type TranslationFailureCode = "no-content" | "unparseable-reply" | "key-set-mismatch" | "transport" | "config";
7
+ /**
8
+ * Base class for every failure a built-in translation provider reports.
9
+ *
10
+ * The original failure travels on `cause` and is never interpolated into `message`: a vendor SDK
11
+ * error can carry an API key, and `message` reaches an HTTP response body.
12
+ *
13
+ * @since 0.11.0
14
+ */
15
+ export declare abstract class TranslationProviderError extends Error {
16
+ abstract readonly code: TranslationFailureCode;
17
+ constructor(message: string, options?: ErrorOptions);
18
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Failure causes a translation provider can report.
3
+ *
4
+ * @since 0.11.0
5
+ */ /**
6
+ * Base class for every failure a built-in translation provider reports.
7
+ *
8
+ * The original failure travels on `cause` and is never interpolated into `message`: a vendor SDK
9
+ * error can carry an API key, and `message` reaches an HTTP response body.
10
+ *
11
+ * @since 0.11.0
12
+ */ export class TranslationProviderError extends Error {
13
+ constructor(message, options){
14
+ // Never `this.cause = cause`: an assigned property is enumerable, so JSON.stringify and pino
15
+ // serialize the vendor error whole — headers and API key with it. `super` installs it non-enumerably.
16
+ super(message, options);
17
+ this.name = new.target.name;
18
+ }
19
+ }
20
+
21
+ //# sourceMappingURL=TranslationProviderError.js.map
@@ -0,0 +1,10 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The call to the provider failed — network, auth, rate limit, timeout. The vendor's own error is on
4
+ * `cause`.
5
+ *
6
+ * @since 0.11.0
7
+ */
8
+ export declare class TransportError extends TranslationProviderError {
9
+ readonly code: "transport";
10
+ }
@@ -0,0 +1,11 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The call to the provider failed — network, auth, rate limit, timeout. The vendor's own error is on
4
+ * `cause`.
5
+ *
6
+ * @since 0.11.0
7
+ */ export class TransportError extends TranslationProviderError {
8
+ code = "transport";
9
+ }
10
+
11
+ //# sourceMappingURL=TransportError.js.map
@@ -0,0 +1,9 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The reply was not JSON, or parsed to something that is not an object — arrays included.
4
+ *
5
+ * @since 0.11.0
6
+ */
7
+ export declare class UnparseableReplyError extends TranslationProviderError {
8
+ readonly code: "unparseable-reply";
9
+ }
@@ -0,0 +1,10 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * The reply was not JSON, or parsed to something that is not an object — arrays included.
4
+ *
5
+ * @since 0.11.0
6
+ */ export class UnparseableReplyError extends TranslationProviderError {
7
+ code = "unparseable-reply";
8
+ }
9
+
10
+ //# sourceMappingURL=UnparseableReplyError.js.map
@@ -0,0 +1 @@
1
+ export declare function errorMessageLower(cause: unknown): string | null;
@@ -0,0 +1,8 @@
1
+ import { isObject } from "../../../core/kernel/utils/isObject";
2
+ export function errorMessageLower(cause) {
3
+ if (!isObject(cause)) return null;
4
+ const message = cause.message;
5
+ return typeof message === "string" ? message.toLowerCase() : null;
6
+ }
7
+
8
+ //# sourceMappingURL=errorMessageLower.js.map
@@ -0,0 +1,9 @@
1
+ export { TranslationProviderError } from "./TranslationProviderError";
2
+ export type { TranslationFailureCode } from "./TranslationProviderError";
3
+ export { NoContentError } from "./NoContentError";
4
+ export { UnparseableReplyError } from "./UnparseableReplyError";
5
+ export { KeySetMismatchError } from "./KeySetMismatchError";
6
+ export { TransportError } from "./TransportError";
7
+ export { ProviderConfigurationError } from "./ProviderConfigurationError";
8
+ export { wrapTransportError } from "./wrapTransportError";
9
+ export { errorMessageLower } from "./errorMessageLower";
@@ -0,0 +1,10 @@
1
+ export { TranslationProviderError } from "./TranslationProviderError";
2
+ export { NoContentError } from "./NoContentError";
3
+ export { UnparseableReplyError } from "./UnparseableReplyError";
4
+ export { KeySetMismatchError } from "./KeySetMismatchError";
5
+ export { TransportError } from "./TransportError";
6
+ export { ProviderConfigurationError } from "./ProviderConfigurationError";
7
+ export { wrapTransportError } from "./wrapTransportError";
8
+ export { errorMessageLower } from "./errorMessageLower";
9
+
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import { TranslationProviderError } from "./TranslationProviderError";
2
+ /**
3
+ * Turns anything a transport threw into a {@link TransportError}, keeping the original on `cause`.
4
+ * A value that is already one of ours passes through: re-wrapping would bury a specific cause under
5
+ * a generic transport failure.
6
+ */
7
+ export declare function wrapTransportError(cause: unknown): TranslationProviderError;
@@ -0,0 +1,14 @@
1
+ import { TransportError } from "./TransportError";
2
+ import { TranslationProviderError } from "./TranslationProviderError";
3
+ /**
4
+ * Turns anything a transport threw into a {@link TransportError}, keeping the original on `cause`.
5
+ * A value that is already one of ours passes through: re-wrapping would bury a specific cause under
6
+ * a generic transport failure.
7
+ */ export function wrapTransportError(cause) {
8
+ if (cause instanceof TranslationProviderError) return cause;
9
+ return new TransportError("The translation request failed before a reply could be read. The provider's own error is on this error's `cause` property.", {
10
+ cause
11
+ });
12
+ }
13
+
14
+ //# sourceMappingURL=wrapTransportError.js.map
@@ -0,0 +1,8 @@
1
+ export { createTranslationProvider } from "./CompletionProvider.provider";
2
+ export type { CompletionFn, CompletionRequest, TranslationProviderConfig, } from "./CompletionProvider.provider";
3
+ export type { JsonSchemaObject } from "./buildResponseSchema";
4
+ export type { SystemPromptBuilder, SystemPromptContext } from "./buildSystemPrompt";
5
+ export type { DryRunConfig, DryRunTransformer } from "./runDryRun";
6
+ export { errorMessageLower } from "./errors";
7
+ export { TranslationProviderError, NoContentError, UnparseableReplyError, KeySetMismatchError, TransportError, ProviderConfigurationError, } from "./errors";
8
+ export type { TranslationFailureCode } from "./errors";
@@ -0,0 +1,5 @@
1
+ export { createTranslationProvider } from "./CompletionProvider.provider";
2
+ export { errorMessageLower } from "./errors";
3
+ export { TranslationProviderError, NoContentError, UnparseableReplyError, KeySetMismatchError, TransportError, ProviderConfigurationError } from "./errors";
4
+
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { TranslationInput, TranslationOutput } from "../../core/domain/translation-providers";
2
+ export type ParsedReply = {
3
+ translations: TranslationOutput;
4
+ missingInputKeys: number[];
5
+ unrequestedReplyKeys: string[];
6
+ };
7
+ /**
8
+ * Parses an untrusted reply into a {@link TranslationOutput} and reports the key-set gap.
9
+ *
10
+ * A partial answer is applied, not rejected — the gap is returned so the caller can report it. Only
11
+ * a reply that answers nothing is a failure ({@link KeySetMismatchError}).
12
+ */
13
+ export declare function parseAndValidateReply(input: TranslationInput, raw: string): ParsedReply;
@@ -0,0 +1,46 @@
1
+ import { isObject } from "../../core/kernel/utils/isObject";
2
+ import { KeySetMismatchError, UnparseableReplyError } from "./errors";
3
+ /**
4
+ * Parses an untrusted reply into a {@link TranslationOutput} and reports the key-set gap.
5
+ *
6
+ * A partial answer is applied, not rejected — the gap is returned so the caller can report it. Only
7
+ * a reply that answers nothing is a failure ({@link KeySetMismatchError}).
8
+ */ export function parseAndValidateReply(input, raw) {
9
+ let parsed;
10
+ try {
11
+ parsed = JSON.parse(raw);
12
+ } catch (cause) {
13
+ throw new UnparseableReplyError("The provider's reply was not valid JSON.", {
14
+ cause
15
+ });
16
+ }
17
+ // `isObject` admits arrays: `["a"]` has key "0", so a 0-based input would accept it by coincidence.
18
+ if (!isObject(parsed) || Array.isArray(parsed)) {
19
+ throw new UnparseableReplyError("The provider's reply was JSON, but not an object.");
20
+ }
21
+ const expectedKeys = new Set(Object.keys(input));
22
+ const translations = {};
23
+ const missingInputKeys = [];
24
+ for (const key of expectedKeys){
25
+ const value = parsed[key];
26
+ if (typeof value === "string") {
27
+ translations[Number(key)] = value;
28
+ } else {
29
+ missingInputKeys.push(Number(key));
30
+ }
31
+ }
32
+ const unrequestedReplyKeys = Object.keys(parsed).filter((key)=>!expectedKeys.has(key));
33
+ if (expectedKeys.size > 0 && Object.keys(translations).length === 0) {
34
+ const answeredAnyKey = [
35
+ ...expectedKeys
36
+ ].some((key)=>Object.hasOwn(parsed, key));
37
+ throw new KeySetMismatchError(answeredAnyKey ? `The provider's reply carried the requested keys, but none of the ${expectedKeys.size} values was a string.` : `The provider's reply answered none of the ${expectedKeys.size} requested keys.`, missingInputKeys, unrequestedReplyKeys);
38
+ }
39
+ return {
40
+ translations,
41
+ missingInputKeys,
42
+ unrequestedReplyKeys
43
+ };
44
+ }
45
+
46
+ //# sourceMappingURL=parseAndValidateReply.js.map
@@ -0,0 +1,20 @@
1
+ import type { TranslationInput, TranslationOutput } from "../../core/domain/translation-providers";
2
+ export type DryRunTransformer = (text: string) => string | Promise<string>;
3
+ /**
4
+ * Dry-run configuration: a transformer, and optionally a delay that imitates network latency.
5
+ *
6
+ * @deprecated Supply your own fake `complete` instead. Remove in next major.
7
+ * See docs/DEPRECATIONS.md#provider-dry-run
8
+ */
9
+ export type DryRunConfig = {
10
+ /** Applied to every non-blank value. */
11
+ transform: DryRunTransformer;
12
+ /** Milliseconds to wait before returning. */
13
+ timeout?: number;
14
+ };
15
+ /**
16
+ * Simulates a translation without calling anything. Blank values pass through untouched.
17
+ *
18
+ * Logs the field count and never field content: the log reaches shared infrastructure.
19
+ */
20
+ export declare function runDryRun(input: TranslationInput, config: boolean | DryRunConfig): Promise<TranslationOutput>;
@@ -0,0 +1,17 @@
1
+ const reverse = (text)=>text.split("").reverse().join("");
2
+ /**
3
+ * Simulates a translation without calling anything. Blank values pass through untouched.
4
+ *
5
+ * Logs the field count and never field content: the log reaches shared infrastructure.
6
+ */ export async function runDryRun(input, config) {
7
+ console.info(`[payload-plugin-translator] Dry run: simulated ${Object.keys(input).length} field(s), no API call made.`);
8
+ const { transform = reverse, timeout = 0 } = typeof config === "object" ? config : {};
9
+ if (timeout > 0) await new Promise((resolve)=>setTimeout(resolve, timeout));
10
+ const output = {};
11
+ for (const [key, value] of Object.entries(input)){
12
+ output[Number(key)] = value.trim() ? await transform(value) : value;
13
+ }
14
+ return output;
15
+ }
16
+
17
+ //# sourceMappingURL=runDryRun.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@focus-reactive/payload-plugin-translator",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "description": "Translation plugin for Payload CMS 3.x. Automatically translate your localized content using any translation provider.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,30 +0,0 @@
1
- /**
2
- * Resolved target locales for a manual enqueue, plus what was dropped so the handler can log precisely.
3
- */
4
- export type ResolvedTargetLocales = {
5
- /** The concrete locales to fan out to — de-duplicated, source excluded, unknown removed. */
6
- targets: string[];
7
- /** Requested locales that are not configured (dropped). */
8
- droppedUnknown: string[];
9
- /** Whether the source locale was requested as a target and excluded. */
10
- droppedSource: boolean;
11
- };
12
- /**
13
- * Normalize the enqueue `target_lng` input (scalar or array) into the concrete list of target locales
14
- * to translate into. Applies, in order: array-coercion, de-duplication (first-seen order preserved),
15
- * source-locale exclusion, and dropping unknown (unconfigured) locales.
16
- *
17
- * De-dup and unknown-dropping are the manual-enqueue counterparts of the auto-translate policy filter:
18
- * the runner only supersedes against already-stored jobs, so duplicates within one enqueue must be
19
- * collapsed here; and an unknown locale must never reach the pipeline — it burns a provider call and
20
- * either errors on a Postgres locale enum or writes orphaned, invisible data on Mongo/SQLite.
21
- *
22
- * @param knownLocales - the configured locale codes. Never null: the caller must reject a
23
- * localization-less config before reaching here (translating without localization has no valid
24
- * target and would corrupt data), so "no localization" is not representable as an input.
25
- */
26
- export declare function resolveTargetLocales(args: {
27
- target_lng: string | string[];
28
- source_lng: string;
29
- knownLocales: Set<string>;
30
- }): ResolvedTargetLocales;