strapi-llm-translator 0.9.1 → 0.9.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/dist/server/index.js
CHANGED
|
@@ -128,6 +128,26 @@ const routes = {
|
|
|
128
128
|
};
|
|
129
129
|
const DEFAULT_SYSTEM_PROMPT = "You are a professional translator. Your task is to translate the provided content accurately while preserving the original meaning and tone.";
|
|
130
130
|
const SYSTEM_PROMPT_APPENDIX = `The user asks you to translate the text to a specific language, the language is provided via short code like "en", "fr", "de", etc.`;
|
|
131
|
+
const SYSTEM_PROMPT_FIX = `You are a JSON correction assistant. Only return valid, corrected JSON.`;
|
|
132
|
+
const USER_PROMPT_FIX_PREFIX = "Fix this invalid JSON and return ONLY the corrected JSON. No explanations allowed. The JSON is:";
|
|
133
|
+
const cleanJSONString = (content) => {
|
|
134
|
+
return content.replace(/^```json\s*\n/, "").replace(/^```\s*\n/, "").replace(/\n\s*```$/, "").replace(/\u200B/g, "").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"').trim();
|
|
135
|
+
};
|
|
136
|
+
const balanceJSONBraces = (content) => {
|
|
137
|
+
const openBraces = (content.match(/{/g) || []).length;
|
|
138
|
+
const closeBraces = (content.match(/}/g) || []).length;
|
|
139
|
+
if (openBraces > closeBraces) {
|
|
140
|
+
return content + "}".repeat(openBraces - closeBraces);
|
|
141
|
+
}
|
|
142
|
+
return content;
|
|
143
|
+
};
|
|
144
|
+
const safeJSONParse = (content) => {
|
|
145
|
+
const parsed = JSON.parse(content);
|
|
146
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
147
|
+
return parsed;
|
|
148
|
+
}
|
|
149
|
+
throw new Error("Invalid response format - not an object");
|
|
150
|
+
};
|
|
131
151
|
const openai = new openai$1.OpenAI({
|
|
132
152
|
baseURL: process.env.STRAPI_ADMIN_LLM_TRANSLATOR_LLM_BASE_URL,
|
|
133
153
|
apiKey: process.env.LLM_TRANSLATOR_LLM_API_KEY
|
|
@@ -262,7 +282,7 @@ const llmService = ({ strapi: strapi2 }) => ({
|
|
|
262
282
|
const prompt = buildPrompt(translationPayload, config2.targetLanguage);
|
|
263
283
|
const systemPrompt = await buildSystemPrompt(userConfig);
|
|
264
284
|
const response = await callLLMProvider(prompt, systemPrompt, model, userConfig);
|
|
265
|
-
const translatedData = parseLLMResponse(response);
|
|
285
|
+
const translatedData = await parseLLMResponse(response);
|
|
266
286
|
const mergedContent = mergeTranslatedContent(fields, translatedData, translatableFields);
|
|
267
287
|
const uidFields = findUIDFields(contentType);
|
|
268
288
|
const translatedUIDs = await generateUIDsForTranslatedFields(
|
|
@@ -306,8 +326,9 @@ IMPORTANT RULES:
|
|
|
306
326
|
4. Keep HTML tags intact if present
|
|
307
327
|
5. Preserve any special characters or placeholders
|
|
308
328
|
6. Return ONLY the translated JSON object
|
|
309
|
-
7.
|
|
310
|
-
8.
|
|
329
|
+
7. Ensure the JSON is valid and well-formed, all values must be strings
|
|
330
|
+
8. Do not add any explanations or comments
|
|
331
|
+
9. Ensure professional and culturally appropriate translations
|
|
311
332
|
|
|
312
333
|
SOURCE JSON:
|
|
313
334
|
${JSON.stringify(fields, null, 2)}`;
|
|
@@ -324,10 +345,16 @@ const getUserConfig = async () => {
|
|
|
324
345
|
const buildSystemPrompt = async (config2) => {
|
|
325
346
|
return `${config2.systemPrompt || DEFAULT_SYSTEM_PROMPT} ${SYSTEM_PROMPT_APPENDIX}`;
|
|
326
347
|
};
|
|
348
|
+
const createLLMRequest = (messages, temperature = 0.1) => {
|
|
349
|
+
return openai.chat.completions.create({
|
|
350
|
+
model,
|
|
351
|
+
messages,
|
|
352
|
+
temperature
|
|
353
|
+
});
|
|
354
|
+
};
|
|
327
355
|
const callLLMProvider = async (prompt, systemPrompt, model2, config2) => {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
messages: [
|
|
356
|
+
return createLLMRequest(
|
|
357
|
+
[
|
|
331
358
|
{
|
|
332
359
|
role: "system",
|
|
333
360
|
content: systemPrompt
|
|
@@ -337,37 +364,39 @@ const callLLMProvider = async (prompt, systemPrompt, model2, config2) => {
|
|
|
337
364
|
content: prompt
|
|
338
365
|
}
|
|
339
366
|
],
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
367
|
+
config2.temperature
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
const requestJSONCorrection = async (invalidJson) => {
|
|
371
|
+
const response = await createLLMRequest([
|
|
372
|
+
{
|
|
373
|
+
role: "system",
|
|
374
|
+
content: SYSTEM_PROMPT_FIX
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
role: "user",
|
|
378
|
+
content: `${USER_PROMPT_FIX_PREFIX} ${invalidJson}`
|
|
379
|
+
}
|
|
380
|
+
]);
|
|
381
|
+
const correctedContent = response.choices[0]?.message?.content;
|
|
382
|
+
if (!correctedContent) throw new Error("No content in correction response");
|
|
383
|
+
return safeJSONParse(correctedContent.trim());
|
|
343
384
|
};
|
|
344
|
-
const parseLLMResponse = (response) => {
|
|
385
|
+
const parseLLMResponse = async (response) => {
|
|
345
386
|
try {
|
|
346
387
|
const content = response.choices[0]?.message?.content;
|
|
347
388
|
if (!content) throw new Error("No content in response");
|
|
348
|
-
const cleanContent = content
|
|
389
|
+
const cleanContent = cleanJSONString(content);
|
|
349
390
|
try {
|
|
350
|
-
|
|
351
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
352
|
-
return parsed;
|
|
353
|
-
}
|
|
354
|
-
throw new Error("Invalid response format - not an object");
|
|
391
|
+
return safeJSONParse(cleanContent);
|
|
355
392
|
} catch (parseError) {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const parsed = JSON.parse(fixedContent);
|
|
363
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
364
|
-
return parsed;
|
|
365
|
-
}
|
|
366
|
-
} catch (secondError) {
|
|
367
|
-
console.error("Second parse attempt failed:", secondError);
|
|
368
|
-
}
|
|
393
|
+
const balancedContent = balanceJSONBraces(cleanContent);
|
|
394
|
+
try {
|
|
395
|
+
return safeJSONParse(balancedContent);
|
|
396
|
+
} catch (secondError) {
|
|
397
|
+
console.error("Second parse attempt failed:", secondError);
|
|
398
|
+
return await requestJSONCorrection(cleanContent);
|
|
369
399
|
}
|
|
370
|
-
throw new Error(`JSON parsing failed: ${parseError.message}`);
|
|
371
400
|
}
|
|
372
401
|
} catch (error) {
|
|
373
402
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
package/dist/server/index.mjs
CHANGED
|
@@ -127,6 +127,26 @@ const routes = {
|
|
|
127
127
|
};
|
|
128
128
|
const DEFAULT_SYSTEM_PROMPT = "You are a professional translator. Your task is to translate the provided content accurately while preserving the original meaning and tone.";
|
|
129
129
|
const SYSTEM_PROMPT_APPENDIX = `The user asks you to translate the text to a specific language, the language is provided via short code like "en", "fr", "de", etc.`;
|
|
130
|
+
const SYSTEM_PROMPT_FIX = `You are a JSON correction assistant. Only return valid, corrected JSON.`;
|
|
131
|
+
const USER_PROMPT_FIX_PREFIX = "Fix this invalid JSON and return ONLY the corrected JSON. No explanations allowed. The JSON is:";
|
|
132
|
+
const cleanJSONString = (content) => {
|
|
133
|
+
return content.replace(/^```json\s*\n/, "").replace(/^```\s*\n/, "").replace(/\n\s*```$/, "").replace(/\u200B/g, "").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"').trim();
|
|
134
|
+
};
|
|
135
|
+
const balanceJSONBraces = (content) => {
|
|
136
|
+
const openBraces = (content.match(/{/g) || []).length;
|
|
137
|
+
const closeBraces = (content.match(/}/g) || []).length;
|
|
138
|
+
if (openBraces > closeBraces) {
|
|
139
|
+
return content + "}".repeat(openBraces - closeBraces);
|
|
140
|
+
}
|
|
141
|
+
return content;
|
|
142
|
+
};
|
|
143
|
+
const safeJSONParse = (content) => {
|
|
144
|
+
const parsed = JSON.parse(content);
|
|
145
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
146
|
+
return parsed;
|
|
147
|
+
}
|
|
148
|
+
throw new Error("Invalid response format - not an object");
|
|
149
|
+
};
|
|
130
150
|
const openai = new OpenAI({
|
|
131
151
|
baseURL: process.env.STRAPI_ADMIN_LLM_TRANSLATOR_LLM_BASE_URL,
|
|
132
152
|
apiKey: process.env.LLM_TRANSLATOR_LLM_API_KEY
|
|
@@ -261,7 +281,7 @@ const llmService = ({ strapi: strapi2 }) => ({
|
|
|
261
281
|
const prompt = buildPrompt(translationPayload, config2.targetLanguage);
|
|
262
282
|
const systemPrompt = await buildSystemPrompt(userConfig);
|
|
263
283
|
const response = await callLLMProvider(prompt, systemPrompt, model, userConfig);
|
|
264
|
-
const translatedData = parseLLMResponse(response);
|
|
284
|
+
const translatedData = await parseLLMResponse(response);
|
|
265
285
|
const mergedContent = mergeTranslatedContent(fields, translatedData, translatableFields);
|
|
266
286
|
const uidFields = findUIDFields(contentType);
|
|
267
287
|
const translatedUIDs = await generateUIDsForTranslatedFields(
|
|
@@ -305,8 +325,9 @@ IMPORTANT RULES:
|
|
|
305
325
|
4. Keep HTML tags intact if present
|
|
306
326
|
5. Preserve any special characters or placeholders
|
|
307
327
|
6. Return ONLY the translated JSON object
|
|
308
|
-
7.
|
|
309
|
-
8.
|
|
328
|
+
7. Ensure the JSON is valid and well-formed, all values must be strings
|
|
329
|
+
8. Do not add any explanations or comments
|
|
330
|
+
9. Ensure professional and culturally appropriate translations
|
|
310
331
|
|
|
311
332
|
SOURCE JSON:
|
|
312
333
|
${JSON.stringify(fields, null, 2)}`;
|
|
@@ -323,10 +344,16 @@ const getUserConfig = async () => {
|
|
|
323
344
|
const buildSystemPrompt = async (config2) => {
|
|
324
345
|
return `${config2.systemPrompt || DEFAULT_SYSTEM_PROMPT} ${SYSTEM_PROMPT_APPENDIX}`;
|
|
325
346
|
};
|
|
347
|
+
const createLLMRequest = (messages, temperature = 0.1) => {
|
|
348
|
+
return openai.chat.completions.create({
|
|
349
|
+
model,
|
|
350
|
+
messages,
|
|
351
|
+
temperature
|
|
352
|
+
});
|
|
353
|
+
};
|
|
326
354
|
const callLLMProvider = async (prompt, systemPrompt, model2, config2) => {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
messages: [
|
|
355
|
+
return createLLMRequest(
|
|
356
|
+
[
|
|
330
357
|
{
|
|
331
358
|
role: "system",
|
|
332
359
|
content: systemPrompt
|
|
@@ -336,37 +363,39 @@ const callLLMProvider = async (prompt, systemPrompt, model2, config2) => {
|
|
|
336
363
|
content: prompt
|
|
337
364
|
}
|
|
338
365
|
],
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
366
|
+
config2.temperature
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
const requestJSONCorrection = async (invalidJson) => {
|
|
370
|
+
const response = await createLLMRequest([
|
|
371
|
+
{
|
|
372
|
+
role: "system",
|
|
373
|
+
content: SYSTEM_PROMPT_FIX
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
role: "user",
|
|
377
|
+
content: `${USER_PROMPT_FIX_PREFIX} ${invalidJson}`
|
|
378
|
+
}
|
|
379
|
+
]);
|
|
380
|
+
const correctedContent = response.choices[0]?.message?.content;
|
|
381
|
+
if (!correctedContent) throw new Error("No content in correction response");
|
|
382
|
+
return safeJSONParse(correctedContent.trim());
|
|
342
383
|
};
|
|
343
|
-
const parseLLMResponse = (response) => {
|
|
384
|
+
const parseLLMResponse = async (response) => {
|
|
344
385
|
try {
|
|
345
386
|
const content = response.choices[0]?.message?.content;
|
|
346
387
|
if (!content) throw new Error("No content in response");
|
|
347
|
-
const cleanContent = content
|
|
388
|
+
const cleanContent = cleanJSONString(content);
|
|
348
389
|
try {
|
|
349
|
-
|
|
350
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
351
|
-
return parsed;
|
|
352
|
-
}
|
|
353
|
-
throw new Error("Invalid response format - not an object");
|
|
390
|
+
return safeJSONParse(cleanContent);
|
|
354
391
|
} catch (parseError) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const parsed = JSON.parse(fixedContent);
|
|
362
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
363
|
-
return parsed;
|
|
364
|
-
}
|
|
365
|
-
} catch (secondError) {
|
|
366
|
-
console.error("Second parse attempt failed:", secondError);
|
|
367
|
-
}
|
|
392
|
+
const balancedContent = balanceJSONBraces(cleanContent);
|
|
393
|
+
try {
|
|
394
|
+
return safeJSONParse(balancedContent);
|
|
395
|
+
} catch (secondError) {
|
|
396
|
+
console.error("Second parse attempt failed:", secondError);
|
|
397
|
+
return await requestJSONCorrection(cleanContent);
|
|
368
398
|
}
|
|
369
|
-
throw new Error(`JSON parsing failed: ${parseError.message}`);
|
|
370
399
|
}
|
|
371
400
|
} catch (error) {
|
|
372
401
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export declare const DEFAULT_SYSTEM_PROMPT = "You are a professional translator. Your task is to translate the provided content accurately while preserving the original meaning and tone.";
|
|
2
2
|
export declare const SYSTEM_PROMPT_APPENDIX = "The user asks you to translate the text to a specific language, the language is provided via short code like \"en\", \"fr\", \"de\", etc.";
|
|
3
|
+
export declare const SYSTEM_PROMPT_FIX = "You are a JSON correction assistant. Only return valid, corrected JSON.";
|
|
4
|
+
export declare const USER_PROMPT_FIX_PREFIX = "Fix this invalid JSON and return ONLY the corrected JSON. No explanations allowed. The JSON is:";
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.9.
|
|
2
|
+
"version": "0.9.3",
|
|
3
3
|
"keywords": [
|
|
4
4
|
"strapi",
|
|
5
5
|
"plugin",
|
|
@@ -72,5 +72,13 @@
|
|
|
72
72
|
"name": "strapi-llm-translator",
|
|
73
73
|
"description": "AI-Powered Content Translation for Strapi",
|
|
74
74
|
"license": "MIT",
|
|
75
|
-
"author": "grenzbotin <franziska@vulpis.dev>"
|
|
75
|
+
"author": "grenzbotin <franziska@vulpis.dev>",
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": "https://github.com/grenzbotin/strapi-llm-translator.git"
|
|
79
|
+
},
|
|
80
|
+
"bugs": {
|
|
81
|
+
"url": "https://github.com/grenzbotin/strapi-llm-translator/issues"
|
|
82
|
+
},
|
|
83
|
+
"homepage": "https://github.com/grenzbotin/strapi-llm-translator#readme"
|
|
76
84
|
}
|