@justanarthur/payload-plugin-translator 1.3.20 → 3.0.3
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 +132 -215
- package/dist/client.css +41 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +320 -0
- package/dist/index.d.ts +63 -11
- package/dist/index.js +275 -82
- package/dist/jobs.d.ts +50 -0
- package/dist/jobs.js +28 -0
- package/dist/resolvers/copy.d.ts +19 -3
- package/dist/resolvers/copy.js +9 -13
- package/dist/resolvers/google.d.ts +23 -11
- package/dist/resolvers/google.js +10 -46
- package/dist/resolvers/libreTranslate.d.ts +23 -15
- package/dist/resolvers/libreTranslate.js +10 -47
- package/dist/resolvers/openAI.d.ts +29 -21
- package/dist/resolvers/openAI.js +9 -134
- package/dist/resolvers/types.d.ts +14 -16
- package/dist/resolvers/types.js +0 -3
- package/dist/shared/chunk-97ktc51q.js +15 -0
- package/dist/shared/chunk-ecnph6hw.js +205 -0
- package/dist/shared/chunk-frnemj69.js +6 -0
- package/dist/shared/chunk-fsveh29m.js +55 -0
- package/dist/shared/chunk-q51q8rd2.js +855 -0
- package/dist/shared/chunk-zhrw0jnz.js +57 -0
- package/dist/types.d.ts +32 -22
- package/dist/types.js +0 -3
- package/package.json +89 -63
- package/dist/client/api/index.d.ts +0 -8
- package/dist/client/api/index.d.ts.map +0 -1
- package/dist/client/api/index.js +0 -28
- package/dist/client/api/index.js.map +0 -1
- package/dist/client/components/CustomButton/CustomButtonWithTranslator.d.ts +0 -5
- package/dist/client/components/CustomButton/CustomButtonWithTranslator.d.ts.map +0 -1
- package/dist/client/components/CustomButton/CustomButtonWithTranslator.js +0 -28
- package/dist/client/components/CustomButton/CustomButtonWithTranslator.js.map +0 -1
- package/dist/client/components/CustomButton/index.d.ts +0 -3
- package/dist/client/components/CustomButton/index.d.ts.map +0 -1
- package/dist/client/components/CustomButton/index.js +0 -10
- package/dist/client/components/CustomButton/index.js.map +0 -1
- package/dist/client/components/CustomButton/styles.scss +0 -5
- package/dist/client/components/LocaleLabel/LocaleLabel.d.ts +0 -5
- package/dist/client/components/LocaleLabel/LocaleLabel.d.ts.map +0 -1
- package/dist/client/components/LocaleLabel/LocaleLabel.js +0 -28
- package/dist/client/components/LocaleLabel/LocaleLabel.js.map +0 -1
- package/dist/client/components/LocaleLabel/index.d.ts +0 -2
- package/dist/client/components/LocaleLabel/index.d.ts.map +0 -1
- package/dist/client/components/LocaleLabel/index.js +0 -3
- package/dist/client/components/LocaleLabel/index.js.map +0 -1
- package/dist/client/components/ResolverButton/ResolverButton.d.ts +0 -5
- package/dist/client/components/ResolverButton/ResolverButton.d.ts.map +0 -1
- package/dist/client/components/ResolverButton/ResolverButton.js +0 -16
- package/dist/client/components/ResolverButton/ResolverButton.js.map +0 -1
- package/dist/client/components/ResolverButton/index.d.ts +0 -2
- package/dist/client/components/ResolverButton/index.d.ts.map +0 -1
- package/dist/client/components/ResolverButton/index.js +0 -3
- package/dist/client/components/ResolverButton/index.js.map +0 -1
- package/dist/client/components/TranslatorModal/Content.d.ts +0 -2
- package/dist/client/components/TranslatorModal/Content.d.ts.map +0 -1
- package/dist/client/components/TranslatorModal/Content.js +0 -60
- package/dist/client/components/TranslatorModal/Content.js.map +0 -1
- package/dist/client/components/TranslatorModal/TranslatorModal.d.ts +0 -3
- package/dist/client/components/TranslatorModal/TranslatorModal.d.ts.map +0 -1
- package/dist/client/components/TranslatorModal/TranslatorModal.js +0 -26
- package/dist/client/components/TranslatorModal/TranslatorModal.js.map +0 -1
- package/dist/client/components/TranslatorModal/index.d.ts +0 -2
- package/dist/client/components/TranslatorModal/index.d.ts.map +0 -1
- package/dist/client/components/TranslatorModal/index.js +0 -3
- package/dist/client/components/TranslatorModal/index.js.map +0 -1
- package/dist/client/components/TranslatorModal/styles.scss +0 -76
- package/dist/client/providers/Translator/TranslatorProvider.d.ts +0 -5
- package/dist/client/providers/Translator/TranslatorProvider.d.ts.map +0 -1
- package/dist/client/providers/Translator/TranslatorProvider.js +0 -109
- package/dist/client/providers/Translator/TranslatorProvider.js.map +0 -1
- package/dist/client/providers/Translator/context.d.ts +0 -21
- package/dist/client/providers/Translator/context.d.ts.map +0 -1
- package/dist/client/providers/Translator/context.js +0 -9
- package/dist/client/providers/Translator/context.js.map +0 -1
- package/dist/exports/client.d.ts +0 -2
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/client.js +0 -3
- package/dist/exports/client.js.map +0 -1
- package/dist/i18n-translations.d.ts +0 -87
- package/dist/i18n-translations.d.ts.map +0 -1
- package/dist/i18n-translations.js +0 -88
- package/dist/i18n-translations.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/resolvers/copy.d.ts.map +0 -1
- package/dist/resolvers/copy.js.map +0 -1
- package/dist/resolvers/google.d.ts.map +0 -1
- package/dist/resolvers/google.js.map +0 -1
- package/dist/resolvers/libreTranslate.d.ts.map +0 -1
- package/dist/resolvers/libreTranslate.js.map +0 -1
- package/dist/resolvers/openAI.d.ts.map +0 -1
- package/dist/resolvers/openAI.js.map +0 -1
- package/dist/resolvers/types.d.ts.map +0 -1
- package/dist/resolvers/types.js.map +0 -1
- package/dist/translate/endpoint.d.ts +0 -3
- package/dist/translate/endpoint.d.ts.map +0 -1
- package/dist/translate/endpoint.js +0 -23
- package/dist/translate/endpoint.js.map +0 -1
- package/dist/translate/findEntityWithConfig.d.ts +0 -16
- package/dist/translate/findEntityWithConfig.d.ts.map +0 -1
- package/dist/translate/findEntityWithConfig.js +0 -34
- package/dist/translate/findEntityWithConfig.js.map +0 -1
- package/dist/translate/operation.d.ts +0 -9
- package/dist/translate/operation.d.ts.map +0 -1
- package/dist/translate/operation.js +0 -78
- package/dist/translate/operation.js.map +0 -1
- package/dist/translate/traverseFields.d.ts +0 -17
- package/dist/translate/traverseFields.d.ts.map +0 -1
- package/dist/translate/traverseFields.js +0 -233
- package/dist/translate/traverseFields.js.map +0 -1
- package/dist/translate/traverseRichText.d.ts +0 -10
- package/dist/translate/traverseRichText.d.ts.map +0 -1
- package/dist/translate/traverseRichText.js +0 -24
- package/dist/translate/traverseRichText.js.map +0 -1
- package/dist/translate/types.d.ts +0 -27
- package/dist/translate/types.d.ts.map +0 -1
- package/dist/translate/types.js +0 -3
- package/dist/translate/types.js.map +0 -1
- package/dist/translate/updateEntity.d.ts +0 -14
- package/dist/translate/updateEntity.d.ts.map +0 -1
- package/dist/translate/updateEntity.js +0 -26
- package/dist/translate/updateEntity.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/chunkArray.d.ts +0 -2
- package/dist/utils/chunkArray.d.ts.map +0 -1
- package/dist/utils/chunkArray.js +0 -7
- package/dist/utils/chunkArray.js.map +0 -1
- package/dist/utils/isEmpty.d.ts +0 -2
- package/dist/utils/isEmpty.d.ts.map +0 -1
- package/dist/utils/isEmpty.js +0 -8
- package/dist/utils/isEmpty.js.map +0 -1
|
@@ -1,48 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const mapLocale = (incoming)=>incoming in localeToCountryCodeMapper ? localeToCountryCodeMapper[incoming] : incoming;
|
|
6
|
-
export const libreResolver = ({ apiKey, chunkLength = 100, url = 'https://libretranslate.com/translate' })=>{
|
|
7
|
-
return {
|
|
8
|
-
key: 'libre',
|
|
9
|
-
resolve: async (args)=>{
|
|
10
|
-
const { localeFrom, localeTo, req, texts } = args;
|
|
11
|
-
const apiUrl = url;
|
|
12
|
-
const responses = await Promise.all(chunkArray(texts, chunkLength).map((q)=>fetch(apiUrl, {
|
|
13
|
-
body: JSON.stringify({
|
|
14
|
-
api_key: apiKey,
|
|
15
|
-
q,
|
|
16
|
-
source: mapLocale(localeFrom),
|
|
17
|
-
target: mapLocale(localeTo)
|
|
18
|
-
}),
|
|
19
|
-
headers: {
|
|
20
|
-
'Content-Type': 'application/json'
|
|
21
|
-
},
|
|
22
|
-
method: 'POST'
|
|
23
|
-
}).then(async (res)=>{
|
|
24
|
-
const data = await res.json();
|
|
25
|
-
if (!res.ok) req.payload.logger.info({
|
|
26
|
-
libreResponse: data,
|
|
27
|
-
message: 'An error occurred when trying to translate the data using LibreTranslate API'
|
|
28
|
-
});
|
|
29
|
-
return {
|
|
30
|
-
data,
|
|
31
|
-
success: res.ok
|
|
32
|
-
};
|
|
33
|
-
})));
|
|
34
|
-
if (responses.some((res)=>!res.success)) {
|
|
35
|
-
return {
|
|
36
|
-
success: false
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const translatedTexts = responses.flatMap((chunk)=>chunk.data.translatedText);
|
|
40
|
-
return {
|
|
41
|
-
success: true,
|
|
42
|
-
translatedTexts
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
libreResolver
|
|
3
|
+
} from "../shared/chunk-zhrw0jnz.js";
|
|
4
|
+
import"../shared/chunk-frnemj69.js";
|
|
47
5
|
|
|
48
|
-
|
|
6
|
+
// src/exports/resolvers/libreTranslate.ts
|
|
7
|
+
var libreTranslate_default = libreResolver;
|
|
8
|
+
export {
|
|
9
|
+
libreResolver,
|
|
10
|
+
libreTranslate_default as default
|
|
11
|
+
};
|
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { PayloadRequest } from "payload";
|
|
2
|
+
type TranslateResolverArgs = {
|
|
3
|
+
localeFrom: string;
|
|
4
|
+
localeTo: string;
|
|
5
|
+
req: PayloadRequest;
|
|
6
|
+
texts: string[];
|
|
7
|
+
};
|
|
8
|
+
type TranslateResolverResponse = {
|
|
9
|
+
success: false;
|
|
10
|
+
} | {
|
|
11
|
+
success: true;
|
|
12
|
+
translatedTexts: string[];
|
|
13
|
+
};
|
|
14
|
+
type TranslateResolver = {
|
|
15
|
+
key: string;
|
|
16
|
+
resolve: (args: TranslateResolverArgs) => Promise<TranslateResolverResponse> | TranslateResolverResponse;
|
|
17
|
+
};
|
|
18
|
+
type OpenAIPrompt = (args: {
|
|
19
|
+
localeFrom: string;
|
|
20
|
+
localeTo: string;
|
|
21
|
+
texts: string[];
|
|
7
22
|
}) => string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
chunkLength?: number;
|
|
16
|
-
/**
|
|
17
|
-
* @default "gpt-3.5-turbo"
|
|
18
|
-
*/
|
|
19
|
-
model?: string;
|
|
20
|
-
prompt?: OpenAIPrompt;
|
|
23
|
+
type OpenAIResolverConfig = {
|
|
24
|
+
apiKey: string;
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
chunkLength?: number;
|
|
27
|
+
model?: string;
|
|
28
|
+
prompt?: OpenAIPrompt;
|
|
21
29
|
};
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
declare const openAIResolver: ({ apiKey, baseUrl, chunkLength, model, prompt }: OpenAIResolverConfig) => TranslateResolver;
|
|
31
|
+
export { openAIResolver, openAIResolver as default, OpenAIResolverConfig, OpenAIPrompt };
|
package/dist/resolvers/openAI.js
CHANGED
|
@@ -1,136 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
openAIResolver
|
|
3
|
+
} from "../shared/chunk-ecnph6hw.js";
|
|
4
|
+
import"../shared/chunk-frnemj69.js";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
5. Preserve JSON keys without translation.
|
|
11
|
-
6. The **order of the elements must not change**.
|
|
12
|
-
7. **Do not include any text, explanations, or remarks outside of the JSON array.**
|
|
13
|
-
8. **Preserve any special characters, HTML tags, or formatting** present in the original strings.
|
|
14
|
-
9. **Preserve urls, hrefs, and email addresses** without translation.
|
|
15
|
-
10. RETURN ONLY THE RAW JSON, DO NOT RESPOND WITH ANYTHING ELSE, AND NO FORMATTING.
|
|
16
|
-
|
|
17
|
-
**INPUT JSON TO TRANSLATE:**
|
|
18
|
-
${JSON.stringify(texts)}`;
|
|
19
|
-
};
|
|
20
|
-
export const openAIResolver = ({ apiKey, baseUrl, chunkLength = 100, model = 'gpt-3.5-turbo', prompt = defaultPrompt })=>{
|
|
21
|
-
return {
|
|
22
|
-
key: 'openai',
|
|
23
|
-
resolve: async ({ localeFrom, localeTo, req, texts })=>{
|
|
24
|
-
const apiUrl = `${baseUrl || 'https://api.openai.com'}/v1/chat/completions`;
|
|
25
|
-
const localizationConfig = req.payload.config.localization;
|
|
26
|
-
const resolveLocaleName = (code)=>{
|
|
27
|
-
if (localizationConfig && localizationConfig.locales) {
|
|
28
|
-
const found = localizationConfig.locales.find((l)=>(typeof l === 'string' ? l : l.code) === code);
|
|
29
|
-
if (found && typeof found !== 'string' && found.label) {
|
|
30
|
-
return typeof found.label === 'string' ? found.label : found.label['en'] ?? code;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return new Intl.DisplayNames([
|
|
34
|
-
'en'
|
|
35
|
-
], {
|
|
36
|
-
type: 'language'
|
|
37
|
-
}).of(code) ?? code;
|
|
38
|
-
};
|
|
39
|
-
const localeFromName = resolveLocaleName(localeFrom);
|
|
40
|
-
const localeToName = resolveLocaleName(localeTo);
|
|
41
|
-
try {
|
|
42
|
-
const response = await Promise.all(chunkArray(texts, chunkLength).map((texts)=>{
|
|
43
|
-
const structuredTexts = texts.reduce((acc, curr, index)=>{
|
|
44
|
-
acc[index + 1] = curr;
|
|
45
|
-
return acc;
|
|
46
|
-
}, {});
|
|
47
|
-
return fetch(apiUrl, {
|
|
48
|
-
body: JSON.stringify({
|
|
49
|
-
messages: [
|
|
50
|
-
{
|
|
51
|
-
content: prompt({
|
|
52
|
-
localeFrom: localeFromName,
|
|
53
|
-
localeTo: localeToName,
|
|
54
|
-
texts: structuredTexts
|
|
55
|
-
}),
|
|
56
|
-
role: 'user'
|
|
57
|
-
}
|
|
58
|
-
],
|
|
59
|
-
model
|
|
60
|
-
}),
|
|
61
|
-
headers: {
|
|
62
|
-
Authorization: `Bearer ${apiKey}`,
|
|
63
|
-
'Content-Type': 'application/json'
|
|
64
|
-
},
|
|
65
|
-
method: 'post'
|
|
66
|
-
}).then(async (res)=>{
|
|
67
|
-
const data = await res.json();
|
|
68
|
-
if (!res.ok) req.payload.logger.info({
|
|
69
|
-
message: 'An error occurred when trying to translate the data using OpenAI API',
|
|
70
|
-
openAIresponse: data
|
|
71
|
-
});
|
|
72
|
-
return {
|
|
73
|
-
data,
|
|
74
|
-
success: res.ok
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
}));
|
|
78
|
-
const translated = [];
|
|
79
|
-
for (const { data, success } of response){
|
|
80
|
-
if (!success) return {
|
|
81
|
-
success: false
|
|
82
|
-
};
|
|
83
|
-
const content = data?.choices?.[0]?.message?.content;
|
|
84
|
-
if (!content) {
|
|
85
|
-
req.payload.logger.error('An error occurred when trying to translate the data using OpenAI API - missing content in the response');
|
|
86
|
-
return {
|
|
87
|
-
success: false
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
const translatedStructuredTexts = JSON.parse(content);
|
|
91
|
-
const translatedChunk = Object.values(translatedStructuredTexts);
|
|
92
|
-
if (!Array.isArray(translatedChunk)) {
|
|
93
|
-
req.payload.logger.error({
|
|
94
|
-
data: translatedChunk,
|
|
95
|
-
fullContent: content,
|
|
96
|
-
message: 'An error occurred when trying to translate the data using OpenAI API - parsed content is not an array'
|
|
97
|
-
});
|
|
98
|
-
return {
|
|
99
|
-
success: false
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
for (const text of translatedChunk){
|
|
103
|
-
if (text && typeof text !== 'string') {
|
|
104
|
-
req.payload.logger.error({
|
|
105
|
-
chunkData: translatedChunk,
|
|
106
|
-
data: text,
|
|
107
|
-
fullContent: content,
|
|
108
|
-
message: 'An error occurred when trying to translate the data using OpenAI API - parsed content is not a string'
|
|
109
|
-
});
|
|
110
|
-
return {
|
|
111
|
-
success: false
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
translated.push(text);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
success: true,
|
|
119
|
-
translatedTexts: translated
|
|
120
|
-
};
|
|
121
|
-
} catch (e) {
|
|
122
|
-
if (e instanceof Error) {
|
|
123
|
-
req.payload.logger.info({
|
|
124
|
-
message: 'An error occurred when trying to translate the data using OpenAI API',
|
|
125
|
-
originalErr: e.message
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
success: false
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
6
|
+
// src/exports/resolvers/openAI.ts
|
|
7
|
+
var openAI_default = openAIResolver;
|
|
8
|
+
export {
|
|
9
|
+
openAIResolver,
|
|
10
|
+
openAI_default as default
|
|
134
11
|
};
|
|
135
|
-
|
|
136
|
-
//# sourceMappingURL=openAI.js.map
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
req: PayloadRequest;
|
|
8
|
-
texts: string[];
|
|
1
|
+
import { PayloadRequest } from "payload";
|
|
2
|
+
type TranslateResolverArgs = {
|
|
3
|
+
localeFrom: string;
|
|
4
|
+
localeTo: string;
|
|
5
|
+
req: PayloadRequest;
|
|
6
|
+
texts: string[];
|
|
9
7
|
};
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
type TranslateResolverResponse = {
|
|
9
|
+
success: false;
|
|
12
10
|
} | {
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
success: true;
|
|
12
|
+
translatedTexts: string[];
|
|
15
13
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
type TranslateResolver = {
|
|
15
|
+
key: string;
|
|
16
|
+
resolve: (args: TranslateResolverArgs) => Promise<TranslateResolverResponse> | TranslateResolverResponse;
|
|
19
17
|
};
|
|
20
|
-
|
|
18
|
+
export { TranslateResolverResponse, TranslateResolverArgs, TranslateResolver };
|
package/dist/resolvers/types.js
CHANGED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chunkArray
|
|
3
|
+
} from "./chunk-frnemj69.js";
|
|
4
|
+
|
|
5
|
+
// src/resolvers/openAI.ts
|
|
6
|
+
var LOCALE_DISPLAY_NAME = {
|
|
7
|
+
en: "English",
|
|
8
|
+
sk: "Slovak",
|
|
9
|
+
cs: "Czech",
|
|
10
|
+
de: "German",
|
|
11
|
+
uk: "Ukrainian",
|
|
12
|
+
ua: "Ukrainian",
|
|
13
|
+
pl: "Polish",
|
|
14
|
+
hu: "Hungarian",
|
|
15
|
+
fr: "French",
|
|
16
|
+
es: "Spanish",
|
|
17
|
+
it: "Italian",
|
|
18
|
+
pt: "Portuguese",
|
|
19
|
+
nl: "Dutch",
|
|
20
|
+
ro: "Romanian"
|
|
21
|
+
};
|
|
22
|
+
var RETRY_DELAYS_MS = [500, 1000, 2000];
|
|
23
|
+
var defaultPrompt = ({ localeFrom, localeTo, texts }) => {
|
|
24
|
+
const from = LOCALE_DISPLAY_NAME[localeFrom] ?? localeFrom;
|
|
25
|
+
const to = LOCALE_DISPLAY_NAME[localeTo] ?? localeTo;
|
|
26
|
+
return `You are a machine-translation engine. Translate each string in the input JSON array from ${from} (${localeFrom}) to ${to} (${localeTo}).
|
|
27
|
+
|
|
28
|
+
Rules:
|
|
29
|
+
1. The output must be a valid JSON array of strings, with the same length and order as the input.
|
|
30
|
+
2. SLUGS: any input that looks like a URL slug (lowercase, contains only ASCII letters/digits/hyphens/underscores, no spaces, no punctuation) MUST be transliterated into the target language. Output the slug in the same format: lowercase ASCII only, using only letters a-z, digits 0-9, hyphens (-), and underscores (_). Never leave a slug unchanged. Never output accented characters, uppercase letters, spaces, or other symbols in a slug. Examples:
|
|
31
|
+
- "about-us-copy" (en) → "o-nas-kopia" (sk) → "uber-uns-kopie" (de) → "sobre-nos-copia" (pt) → "a-propos-copie" (fr)
|
|
32
|
+
- For Cyrillic source scripts, transliterate to Latin first, then translate.
|
|
33
|
+
3. URLs (containing "://" or "www."), email addresses, hex strings, and other opaque identifiers: keep as-is.
|
|
34
|
+
4. Apply locale-specific formatting for dates, currency, decimal separators in human-readable text.
|
|
35
|
+
5. Return only the JSON array. No markdown fences, no prose, no trailing commentary.
|
|
36
|
+
|
|
37
|
+
INPUT:
|
|
38
|
+
${JSON.stringify(texts)}`;
|
|
39
|
+
};
|
|
40
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
41
|
+
var isRetryableStatus = (status) => status === 429 || status >= 500;
|
|
42
|
+
var isGpt5Family = (model) => /^gpt-5/.test(model);
|
|
43
|
+
var isGpt54Plus = (model) => /^gpt-5\.[1-9]/.test(model);
|
|
44
|
+
var usesMaxCompletionTokens = (model) => isGpt5Family(model) || /^o[1-9]/.test(model);
|
|
45
|
+
var deriveMaxTokens = (chunkLength, model) => {
|
|
46
|
+
const base = Math.max(chunkLength * 100, 4000);
|
|
47
|
+
if (model && isGpt54Plus(model))
|
|
48
|
+
return Math.max(base * 4, 16000);
|
|
49
|
+
return base;
|
|
50
|
+
};
|
|
51
|
+
var parseContent = (raw) => {
|
|
52
|
+
const trimmed = raw.trim();
|
|
53
|
+
const m = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/);
|
|
54
|
+
const candidate = m ? m[1].trim() : trimmed;
|
|
55
|
+
const fenceStripped = m !== null;
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(candidate);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return {
|
|
61
|
+
error: e instanceof Error ? e.message : String(e),
|
|
62
|
+
fenceStripped,
|
|
63
|
+
ok: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(parsed)) {
|
|
67
|
+
return { error: "parsed value is not an array", fenceStripped, ok: false };
|
|
68
|
+
}
|
|
69
|
+
if (!parsed.every((v) => typeof v === "string")) {
|
|
70
|
+
return { error: "array contains non-string element", fenceStripped, ok: false };
|
|
71
|
+
}
|
|
72
|
+
return { fenceStripped, ok: true, translated: parsed };
|
|
73
|
+
};
|
|
74
|
+
var openAIResolver = ({
|
|
75
|
+
apiKey,
|
|
76
|
+
baseUrl,
|
|
77
|
+
chunkLength = 100,
|
|
78
|
+
model = "gpt-4o-mini",
|
|
79
|
+
prompt = defaultPrompt
|
|
80
|
+
}) => {
|
|
81
|
+
return {
|
|
82
|
+
key: "openai",
|
|
83
|
+
resolve: async ({ localeFrom, localeTo, req, texts }) => {
|
|
84
|
+
const apiUrl = `${baseUrl || "https://api.openai.com"}/v1/chat/completions`;
|
|
85
|
+
const maxTokens = deriveMaxTokens(chunkLength, model);
|
|
86
|
+
const maxTokensKey = usesMaxCompletionTokens(model) ? "max_completion_tokens" : "max_tokens";
|
|
87
|
+
const supportsCustomTemperature = !isGpt5Family(model);
|
|
88
|
+
const reasoningEffort = isGpt54Plus(model) ? "low" : undefined;
|
|
89
|
+
const logger = req.payload.logger;
|
|
90
|
+
try {
|
|
91
|
+
const response = await Promise.all(chunkArray(texts, chunkLength).map(async (chunk) => {
|
|
92
|
+
for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
93
|
+
let shouldRetry = false;
|
|
94
|
+
let httpStatus = 0;
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(apiUrl, {
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
messages: [
|
|
99
|
+
{
|
|
100
|
+
content: prompt({ localeFrom, localeTo, texts: chunk }),
|
|
101
|
+
role: "user"
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
model,
|
|
105
|
+
...supportsCustomTemperature ? { temperature: 0 } : {},
|
|
106
|
+
...reasoningEffort ? { reasoning_effort: reasoningEffort } : {},
|
|
107
|
+
[maxTokensKey]: maxTokens
|
|
108
|
+
}),
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${apiKey}`,
|
|
111
|
+
"Content-Type": "application/json"
|
|
112
|
+
},
|
|
113
|
+
method: "post"
|
|
114
|
+
});
|
|
115
|
+
httpStatus = res.status;
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
if (res.ok) {
|
|
118
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
119
|
+
if (!content) {
|
|
120
|
+
logger.error({
|
|
121
|
+
code: "OPENAI_BAD_JSON",
|
|
122
|
+
message: "OpenAI response missing content",
|
|
123
|
+
openAIResponse: data
|
|
124
|
+
});
|
|
125
|
+
shouldRetry = true;
|
|
126
|
+
} else {
|
|
127
|
+
const result = parseContent(content);
|
|
128
|
+
if (result.ok) {
|
|
129
|
+
if (result.fenceStripped) {
|
|
130
|
+
logger.info({
|
|
131
|
+
code: "OPENAI_FENCE_STRIPPED",
|
|
132
|
+
message: "OpenAI returned fenced JSON despite json_object mode"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return { success: true, translated: result.translated };
|
|
136
|
+
}
|
|
137
|
+
logger.error({
|
|
138
|
+
code: "OPENAI_BAD_JSON",
|
|
139
|
+
error: result.error,
|
|
140
|
+
fenceStripped: result.fenceStripped,
|
|
141
|
+
message: "Failed to parse OpenAI response"
|
|
142
|
+
});
|
|
143
|
+
shouldRetry = true;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
logger.error({
|
|
147
|
+
code: "OPENAI_HTTP_ERROR",
|
|
148
|
+
message: "OpenAI returned non-2xx status",
|
|
149
|
+
openAIResponse: data,
|
|
150
|
+
status: httpStatus
|
|
151
|
+
});
|
|
152
|
+
if (isRetryableStatus(httpStatus))
|
|
153
|
+
shouldRetry = true;
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
logger.error({
|
|
157
|
+
code: "OPENAI_NETWORK_ERROR",
|
|
158
|
+
message: "OpenAI request threw",
|
|
159
|
+
originalErr: e instanceof Error ? e.message : String(e)
|
|
160
|
+
});
|
|
161
|
+
shouldRetry = true;
|
|
162
|
+
}
|
|
163
|
+
if (attempt < RETRY_DELAYS_MS.length && shouldRetry) {
|
|
164
|
+
logger.info({
|
|
165
|
+
attempt: attempt + 1,
|
|
166
|
+
code: "OPENAI_RETRY",
|
|
167
|
+
message: "Retrying OpenAI request after backoff",
|
|
168
|
+
nextBackoffMs: RETRY_DELAYS_MS[attempt],
|
|
169
|
+
status: httpStatus
|
|
170
|
+
});
|
|
171
|
+
await sleep(RETRY_DELAYS_MS[attempt]);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
logger.error({
|
|
175
|
+
code: "OPENAI_GIVE_UP",
|
|
176
|
+
message: "OpenAI chunk failed after retries",
|
|
177
|
+
status: httpStatus
|
|
178
|
+
});
|
|
179
|
+
return { success: false };
|
|
180
|
+
}
|
|
181
|
+
return { success: false };
|
|
182
|
+
}));
|
|
183
|
+
const translated = [];
|
|
184
|
+
for (const result of response) {
|
|
185
|
+
if (!result.success)
|
|
186
|
+
return { success: false };
|
|
187
|
+
translated.push(...result.translated);
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
success: true,
|
|
191
|
+
translatedTexts: translated
|
|
192
|
+
};
|
|
193
|
+
} catch (e) {
|
|
194
|
+
logger.error({
|
|
195
|
+
code: "OPENAI_UNEXPECTED",
|
|
196
|
+
message: "OpenAI resolve threw an unexpected error",
|
|
197
|
+
originalErr: e instanceof Error ? e.message : String(e)
|
|
198
|
+
});
|
|
199
|
+
return { success: false };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export { openAIResolver };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chunkArray
|
|
3
|
+
} from "./chunk-frnemj69.js";
|
|
4
|
+
|
|
5
|
+
// src/resolvers/google.ts
|
|
6
|
+
var localeToCountryCodeMapper = {
|
|
7
|
+
ua: "uk"
|
|
8
|
+
};
|
|
9
|
+
var mapLocale = (incoming) => (incoming in localeToCountryCodeMapper) ? localeToCountryCodeMapper[incoming] : incoming;
|
|
10
|
+
var googleResolver = ({
|
|
11
|
+
apiKey,
|
|
12
|
+
chunkLength = 100
|
|
13
|
+
}) => {
|
|
14
|
+
return {
|
|
15
|
+
key: "google",
|
|
16
|
+
resolve: async (args) => {
|
|
17
|
+
const { localeFrom, localeTo, req, texts } = args;
|
|
18
|
+
const apiUrl = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
|
|
19
|
+
const responses = await Promise.all(chunkArray(texts, chunkLength).map((q) => fetch(apiUrl, {
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
q,
|
|
22
|
+
source: mapLocale(localeFrom),
|
|
23
|
+
target: mapLocale(localeTo)
|
|
24
|
+
}),
|
|
25
|
+
headers: {
|
|
26
|
+
"Content-Type": "application/json"
|
|
27
|
+
},
|
|
28
|
+
method: "POST"
|
|
29
|
+
}).then(async (res) => {
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
if (!res.ok)
|
|
32
|
+
req.payload.logger.info({
|
|
33
|
+
googleResponse: data,
|
|
34
|
+
message: "An error occurred when trying to translate the data using Google API"
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
data,
|
|
38
|
+
success: res.ok
|
|
39
|
+
};
|
|
40
|
+
})));
|
|
41
|
+
if (responses.some((res) => !res.success)) {
|
|
42
|
+
return {
|
|
43
|
+
success: false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const translatedTexts = responses.flatMap((chunk) => chunk.data.data.translations).map((translation) => translation.translatedText);
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
translatedTexts
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { googleResolver };
|