@goenhance/strapi-plugins-translate 1.1.5 → 1.1.8

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.
@@ -28,12 +28,9 @@ ALLOWED ENGLISH CONTENT
28
28
 
29
29
  English is allowed ONLY for:
30
30
 
31
- 1) Official brand names
32
- 2) Product model names
33
- 3) Version numbers
34
- 4) Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K, etc.)
35
- 5) URLs
36
- 6) Code variables or placeholders
31
+ 1) Version numbers
32
+ 2) Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K, etc.)
33
+ 3) Code variables or placeholders
37
34
 
38
35
  All other English words must be translated.
39
36
 
@@ -48,6 +45,7 @@ STRUCTURE REQUIREMENTS (STRICT)
48
45
  - Do NOT reorder fields.
49
46
  - Only translate string values that contain natural language.
50
47
  - Preserve placeholders, variables, HTML, Markdown syntax, and URLs exactly.
48
+ - HTML link labels also need to be translated
51
49
  - Maintain valid JSON formatting.
52
50
 
53
51
  ────────────────────────
@@ -1099,8 +1097,129 @@ const controllers$1 = ({ strapi: strapi2 }) => ({
1099
1097
  }
1100
1098
  }
1101
1099
  });
1100
+ const apiController = ({ strapi: strapi2 }) => ({
1101
+ // Get all i18n-enabled content types
1102
+ async getContentTypes(ctx) {
1103
+ try {
1104
+ const contentTypes2 = strapi2.contentTypes;
1105
+ const i18nContentTypes = [];
1106
+ Object.entries(contentTypes2).forEach(([uid, contentType]) => {
1107
+ const isI18nEnabled = contentType.pluginOptions?.i18n?.localized === true;
1108
+ const isApiContentType = uid.startsWith("api::");
1109
+ if (isI18nEnabled && isApiContentType) {
1110
+ i18nContentTypes.push({
1111
+ uid,
1112
+ displayName: contentType.info?.displayName || uid,
1113
+ apiID: contentType.info?.singularName || uid.split(".").pop() || uid,
1114
+ kind: contentType.kind || "collectionType"
1115
+ });
1116
+ }
1117
+ });
1118
+ ctx.body = {
1119
+ data: i18nContentTypes,
1120
+ meta: { ok: true }
1121
+ };
1122
+ } catch (error) {
1123
+ console.error("Error getting content types:", error);
1124
+ ctx.status = 500;
1125
+ ctx.body = {
1126
+ data: [],
1127
+ meta: { ok: false, message: "Failed to get content types" }
1128
+ };
1129
+ }
1130
+ },
1131
+ // Get all available locales
1132
+ async getLocales(ctx) {
1133
+ try {
1134
+ const locales = await strapi2.plugin("i18n").service("locales").find();
1135
+ ctx.body = {
1136
+ data: locales,
1137
+ meta: { ok: true }
1138
+ };
1139
+ } catch (error) {
1140
+ console.error("Error getting locales:", error);
1141
+ ctx.status = 500;
1142
+ ctx.body = {
1143
+ data: [],
1144
+ meta: { ok: false, message: "Failed to get locales" }
1145
+ };
1146
+ }
1147
+ },
1148
+ // Trigger batch translation (async - returns immediately)
1149
+ async batchTranslate(ctx) {
1150
+ const { contentType, entries, sourceLocale, targetLocales, publish = false } = ctx.request.body;
1151
+ if (!contentType || !entries || entries.length === 0 || !sourceLocale || !targetLocales || targetLocales.length === 0) {
1152
+ ctx.status = 400;
1153
+ ctx.body = {
1154
+ data: null,
1155
+ meta: { ok: false, message: "Invalid request parameters" }
1156
+ };
1157
+ return;
1158
+ }
1159
+ const contentTypeSchema = strapi2.contentTypes[contentType];
1160
+ if (!contentTypeSchema) {
1161
+ ctx.status = 404;
1162
+ ctx.body = {
1163
+ data: null,
1164
+ meta: { ok: false, message: "Content type not found" }
1165
+ };
1166
+ return;
1167
+ }
1168
+ const taskId = `${contentType}-${Date.now()}`;
1169
+ ctx.body = {
1170
+ data: {
1171
+ taskId,
1172
+ message: "Translation task started in background"
1173
+ },
1174
+ meta: {
1175
+ ok: true,
1176
+ message: "Translation task queued. You will receive a Feishu notification upon completion."
1177
+ }
1178
+ };
1179
+ setImmediate(async () => {
1180
+ try {
1181
+ const contentTypeDisplayName = contentTypeSchema.info?.displayName || contentType;
1182
+ const mainField = contentTypeSchema.info?.mainField || Object.keys(contentTypeSchema.attributes).find(
1183
+ (key) => contentTypeSchema.attributes[key].type === "string" && ["title", "name", "label"].includes(key.toLowerCase())
1184
+ ) || "id";
1185
+ const getDisplayValue = (value) => {
1186
+ if (value === null || value === void 0) return "";
1187
+ if (typeof value === "string" || typeof value === "number") return String(value);
1188
+ if (typeof value === "object") {
1189
+ return value.title || value.name || value.label || value.id || JSON.stringify(value);
1190
+ }
1191
+ return String(value);
1192
+ };
1193
+ const entryInfoPromises = entries.map(async (entryId) => {
1194
+ try {
1195
+ const entry = await strapi2.documents(contentType).findOne({
1196
+ documentId: entryId,
1197
+ locale: sourceLocale,
1198
+ populate: []
1199
+ });
1200
+ if (!entry) return { documentId: entryId, title: entryId };
1201
+ const title = getDisplayValue(entry[mainField]) || getDisplayValue(entry.id) || entryId;
1202
+ return { documentId: entryId, title };
1203
+ } catch (error) {
1204
+ return { documentId: entryId, title: entryId };
1205
+ }
1206
+ });
1207
+ const entryInfoList = await Promise.all(entryInfoPromises);
1208
+ await sendTaskStartNotification(contentTypeDisplayName, sourceLocale, targetLocales, entryInfoList);
1209
+ } catch (error) {
1210
+ console.error("Failed to send task start notification:", error);
1211
+ }
1212
+ });
1213
+ setImmediate(() => {
1214
+ executeBatchTranslation(strapi2, contentType, entries, sourceLocale, targetLocales, publish).catch((error) => {
1215
+ console.error("Background translation error:", error);
1216
+ });
1217
+ });
1218
+ }
1219
+ });
1102
1220
  const controllers = {
1103
- admin: controllers$1
1221
+ admin: controllers$1,
1222
+ api: apiController
1104
1223
  };
1105
1224
  const middlewares = {};
1106
1225
  const policies = {};
@@ -1162,10 +1281,49 @@ const adminRoutes = [
1162
1281
  }
1163
1282
  }
1164
1283
  ];
1284
+ const contentApiRoutes = [
1285
+ {
1286
+ method: "POST",
1287
+ path: "/batch-translate",
1288
+ handler: "api.batchTranslate",
1289
+ config: {
1290
+ policies: [],
1291
+ auth: {
1292
+ scope: []
1293
+ }
1294
+ }
1295
+ },
1296
+ {
1297
+ method: "GET",
1298
+ path: "/content-types",
1299
+ handler: "api.getContentTypes",
1300
+ config: {
1301
+ policies: [],
1302
+ auth: {
1303
+ scope: []
1304
+ }
1305
+ }
1306
+ },
1307
+ {
1308
+ method: "GET",
1309
+ path: "/locales",
1310
+ handler: "api.getLocales",
1311
+ config: {
1312
+ policies: [],
1313
+ auth: {
1314
+ scope: []
1315
+ }
1316
+ }
1317
+ }
1318
+ ];
1165
1319
  const routes = {
1166
1320
  admin: {
1167
1321
  type: "admin",
1168
1322
  routes: adminRoutes
1323
+ },
1324
+ "content-api": {
1325
+ type: "content-api",
1326
+ routes: contentApiRoutes
1169
1327
  }
1170
1328
  };
1171
1329
  const cleanJSONString = (content) => {
@@ -1408,11 +1566,8 @@ CRITICAL LANGUAGE LOCK:
1408
1566
 
1409
1567
  APPROVED ENGLISH TERMS (ONLY THESE CATEGORIES):
1410
1568
 
1411
- - Brand names
1412
- - Product model names
1413
1569
  - Version numbers
1414
1570
  - Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K)
1415
- - URLs
1416
1571
  - Code variables or placeholders
1417
1572
 
1418
1573
  All other English words must be translated.
@@ -1433,6 +1588,7 @@ STRUCTURE RULES:
1433
1588
  - Do not modify keys.
1434
1589
  - Do not add or remove fields.
1435
1590
  - Preserve placeholders, HTML, Markdown, URLs.
1591
+ - HTML link labels also need to be translated
1436
1592
 
1437
1593
  SOURCE:
1438
1594
  ${JSON.stringify(fields, null, 2)}
@@ -27,12 +27,9 @@ ALLOWED ENGLISH CONTENT
27
27
 
28
28
  English is allowed ONLY for:
29
29
 
30
- 1) Official brand names
31
- 2) Product model names
32
- 3) Version numbers
33
- 4) Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K, etc.)
34
- 5) URLs
35
- 6) Code variables or placeholders
30
+ 1) Version numbers
31
+ 2) Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K, etc.)
32
+ 3) Code variables or placeholders
36
33
 
37
34
  All other English words must be translated.
38
35
 
@@ -47,6 +44,7 @@ STRUCTURE REQUIREMENTS (STRICT)
47
44
  - Do NOT reorder fields.
48
45
  - Only translate string values that contain natural language.
49
46
  - Preserve placeholders, variables, HTML, Markdown syntax, and URLs exactly.
47
+ - HTML link labels also need to be translated
50
48
  - Maintain valid JSON formatting.
51
49
 
52
50
  ────────────────────────
@@ -1098,8 +1096,129 @@ const controllers$1 = ({ strapi: strapi2 }) => ({
1098
1096
  }
1099
1097
  }
1100
1098
  });
1099
+ const apiController = ({ strapi: strapi2 }) => ({
1100
+ // Get all i18n-enabled content types
1101
+ async getContentTypes(ctx) {
1102
+ try {
1103
+ const contentTypes2 = strapi2.contentTypes;
1104
+ const i18nContentTypes = [];
1105
+ Object.entries(contentTypes2).forEach(([uid, contentType]) => {
1106
+ const isI18nEnabled = contentType.pluginOptions?.i18n?.localized === true;
1107
+ const isApiContentType = uid.startsWith("api::");
1108
+ if (isI18nEnabled && isApiContentType) {
1109
+ i18nContentTypes.push({
1110
+ uid,
1111
+ displayName: contentType.info?.displayName || uid,
1112
+ apiID: contentType.info?.singularName || uid.split(".").pop() || uid,
1113
+ kind: contentType.kind || "collectionType"
1114
+ });
1115
+ }
1116
+ });
1117
+ ctx.body = {
1118
+ data: i18nContentTypes,
1119
+ meta: { ok: true }
1120
+ };
1121
+ } catch (error) {
1122
+ console.error("Error getting content types:", error);
1123
+ ctx.status = 500;
1124
+ ctx.body = {
1125
+ data: [],
1126
+ meta: { ok: false, message: "Failed to get content types" }
1127
+ };
1128
+ }
1129
+ },
1130
+ // Get all available locales
1131
+ async getLocales(ctx) {
1132
+ try {
1133
+ const locales = await strapi2.plugin("i18n").service("locales").find();
1134
+ ctx.body = {
1135
+ data: locales,
1136
+ meta: { ok: true }
1137
+ };
1138
+ } catch (error) {
1139
+ console.error("Error getting locales:", error);
1140
+ ctx.status = 500;
1141
+ ctx.body = {
1142
+ data: [],
1143
+ meta: { ok: false, message: "Failed to get locales" }
1144
+ };
1145
+ }
1146
+ },
1147
+ // Trigger batch translation (async - returns immediately)
1148
+ async batchTranslate(ctx) {
1149
+ const { contentType, entries, sourceLocale, targetLocales, publish = false } = ctx.request.body;
1150
+ if (!contentType || !entries || entries.length === 0 || !sourceLocale || !targetLocales || targetLocales.length === 0) {
1151
+ ctx.status = 400;
1152
+ ctx.body = {
1153
+ data: null,
1154
+ meta: { ok: false, message: "Invalid request parameters" }
1155
+ };
1156
+ return;
1157
+ }
1158
+ const contentTypeSchema = strapi2.contentTypes[contentType];
1159
+ if (!contentTypeSchema) {
1160
+ ctx.status = 404;
1161
+ ctx.body = {
1162
+ data: null,
1163
+ meta: { ok: false, message: "Content type not found" }
1164
+ };
1165
+ return;
1166
+ }
1167
+ const taskId = `${contentType}-${Date.now()}`;
1168
+ ctx.body = {
1169
+ data: {
1170
+ taskId,
1171
+ message: "Translation task started in background"
1172
+ },
1173
+ meta: {
1174
+ ok: true,
1175
+ message: "Translation task queued. You will receive a Feishu notification upon completion."
1176
+ }
1177
+ };
1178
+ setImmediate(async () => {
1179
+ try {
1180
+ const contentTypeDisplayName = contentTypeSchema.info?.displayName || contentType;
1181
+ const mainField = contentTypeSchema.info?.mainField || Object.keys(contentTypeSchema.attributes).find(
1182
+ (key) => contentTypeSchema.attributes[key].type === "string" && ["title", "name", "label"].includes(key.toLowerCase())
1183
+ ) || "id";
1184
+ const getDisplayValue = (value) => {
1185
+ if (value === null || value === void 0) return "";
1186
+ if (typeof value === "string" || typeof value === "number") return String(value);
1187
+ if (typeof value === "object") {
1188
+ return value.title || value.name || value.label || value.id || JSON.stringify(value);
1189
+ }
1190
+ return String(value);
1191
+ };
1192
+ const entryInfoPromises = entries.map(async (entryId) => {
1193
+ try {
1194
+ const entry = await strapi2.documents(contentType).findOne({
1195
+ documentId: entryId,
1196
+ locale: sourceLocale,
1197
+ populate: []
1198
+ });
1199
+ if (!entry) return { documentId: entryId, title: entryId };
1200
+ const title = getDisplayValue(entry[mainField]) || getDisplayValue(entry.id) || entryId;
1201
+ return { documentId: entryId, title };
1202
+ } catch (error) {
1203
+ return { documentId: entryId, title: entryId };
1204
+ }
1205
+ });
1206
+ const entryInfoList = await Promise.all(entryInfoPromises);
1207
+ await sendTaskStartNotification(contentTypeDisplayName, sourceLocale, targetLocales, entryInfoList);
1208
+ } catch (error) {
1209
+ console.error("Failed to send task start notification:", error);
1210
+ }
1211
+ });
1212
+ setImmediate(() => {
1213
+ executeBatchTranslation(strapi2, contentType, entries, sourceLocale, targetLocales, publish).catch((error) => {
1214
+ console.error("Background translation error:", error);
1215
+ });
1216
+ });
1217
+ }
1218
+ });
1101
1219
  const controllers = {
1102
- admin: controllers$1
1220
+ admin: controllers$1,
1221
+ api: apiController
1103
1222
  };
1104
1223
  const middlewares = {};
1105
1224
  const policies = {};
@@ -1161,10 +1280,49 @@ const adminRoutes = [
1161
1280
  }
1162
1281
  }
1163
1282
  ];
1283
+ const contentApiRoutes = [
1284
+ {
1285
+ method: "POST",
1286
+ path: "/batch-translate",
1287
+ handler: "api.batchTranslate",
1288
+ config: {
1289
+ policies: [],
1290
+ auth: {
1291
+ scope: []
1292
+ }
1293
+ }
1294
+ },
1295
+ {
1296
+ method: "GET",
1297
+ path: "/content-types",
1298
+ handler: "api.getContentTypes",
1299
+ config: {
1300
+ policies: [],
1301
+ auth: {
1302
+ scope: []
1303
+ }
1304
+ }
1305
+ },
1306
+ {
1307
+ method: "GET",
1308
+ path: "/locales",
1309
+ handler: "api.getLocales",
1310
+ config: {
1311
+ policies: [],
1312
+ auth: {
1313
+ scope: []
1314
+ }
1315
+ }
1316
+ }
1317
+ ];
1164
1318
  const routes = {
1165
1319
  admin: {
1166
1320
  type: "admin",
1167
1321
  routes: adminRoutes
1322
+ },
1323
+ "content-api": {
1324
+ type: "content-api",
1325
+ routes: contentApiRoutes
1168
1326
  }
1169
1327
  };
1170
1328
  const cleanJSONString = (content) => {
@@ -1407,11 +1565,8 @@ CRITICAL LANGUAGE LOCK:
1407
1565
 
1408
1566
  APPROVED ENGLISH TERMS (ONLY THESE CATEGORIES):
1409
1567
 
1410
- - Brand names
1411
- - Product model names
1412
1568
  - Version numbers
1413
1569
  - Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K)
1414
- - URLs
1415
1570
  - Code variables or placeholders
1416
1571
 
1417
1572
  All other English words must be translated.
@@ -1432,6 +1587,7 @@ STRUCTURE RULES:
1432
1587
  - Do not modify keys.
1433
1588
  - Do not add or remove fields.
1434
1589
  - Preserve placeholders, HTML, Markdown, URLs.
1590
+ - HTML link labels also need to be translated
1435
1591
 
1436
1592
  SOURCE:
1437
1593
  ${JSON.stringify(fields, null, 2)}
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_SYSTEM_PROMPT = "You are a professional localization engineer specializing in SEO-driven multilingual websites.\n\nYour task is to translate structured JSON content into ONE strictly defined target language provided in the user instructions.\n\nYou must strictly follow the language specified by the user.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nGLOBAL LANGUAGE RULES\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n- Output must contain only ONE language.\n- Do not mix languages.\n- Do not auto-switch to a similar language.\n- All visible natural language text must be fully localized.\n- Partial translation is forbidden.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nALLOWED ENGLISH CONTENT\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nEnglish is allowed ONLY for:\n\n1) Official brand names\n2) Product model names\n3) Version numbers\n4) Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K, etc.)\n5) URLs\n6) Code variables or placeholders\n\nAll other English words must be translated.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSTRUCTURE REQUIREMENTS (STRICT)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n- Preserve JSON structure exactly.\n- Do NOT modify keys.\n- Do NOT add fields.\n- Do NOT remove fields.\n- Do NOT reorder fields.\n- Only translate string values that contain natural language.\n- Preserve placeholders, variables, HTML, Markdown syntax, and URLs exactly.\n- Maintain valid JSON formatting.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nIMMUTABLE VALUE RULE (CRITICAL)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nIf a string value is:\n\n- A pure URL\n- A file path\n- A CDN link\n- An image link\n- A video link\n- A hash\n- An ID\n- A slug\n- A filename\n- Or contains no natural language words\n\nYou MUST return it exactly as-is.\n\nDo NOT:\n- Wrap it in Markdown\n- Convert it to ![]()\n- Add alt text\n- Add HTML tags\n- Reformat it\n- Modify it in any way\n\nThe value must remain byte-identical to the source.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nNO FORMAT TRANSFORMATION RULE\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nYou are performing translation ONLY.\n\nYou are NOT:\n- Improving formatting\n- Converting plain URLs into Markdown\n- Generating new markup\n- Optimizing structure\n- Rewriting fields\n- Enhancing content\n\nDo not reinterpret field meaning.\nDo not transform content type.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSEO LOCALIZATION\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n- Use natural terminology used by native speakers.\n- Avoid literal translation.\n- Maintain marketing fluency where appropriate.\n- Preserve technical accuracy where required.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nMANDATORY VALIDATION\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nBefore output:\n\n1. Verify that only ONE target language is used.\n2. Ensure no unintended foreign words remain.\n3. Ensure allowed English is limited to approved categories.\n4. Ensure all immutable values remain byte-identical.\n5. Ensure JSON structure is unchanged.\n6. Ensure valid JSON format.\n\nReturn ONLY the localized JSON.\nNo explanations.\nNo comments.\nNo extra formatting.";
1
+ export declare const DEFAULT_SYSTEM_PROMPT = "You are a professional localization engineer specializing in SEO-driven multilingual websites.\n\nYour task is to translate structured JSON content into ONE strictly defined target language provided in the user instructions.\n\nYou must strictly follow the language specified by the user.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nGLOBAL LANGUAGE RULES\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n- Output must contain only ONE language.\n- Do not mix languages.\n- Do not auto-switch to a similar language.\n- All visible natural language text must be fully localized.\n- Partial translation is forbidden.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nALLOWED ENGLISH CONTENT\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nEnglish is allowed ONLY for:\n\n1) Version numbers\n2) Standard technical acronyms (AI, API, UI, UX, JSON, HTML, CSS, FPS, 4K, etc.)\n3) Code variables or placeholders\n\nAll other English words must be translated.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSTRUCTURE REQUIREMENTS (STRICT)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n- Preserve JSON structure exactly.\n- Do NOT modify keys.\n- Do NOT add fields.\n- Do NOT remove fields.\n- Do NOT reorder fields.\n- Only translate string values that contain natural language.\n- Preserve placeholders, variables, HTML, Markdown syntax, and URLs exactly.\n- HTML link labels also need to be translated\n- Maintain valid JSON formatting.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nIMMUTABLE VALUE RULE (CRITICAL)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nIf a string value is:\n\n- A pure URL\n- A file path\n- A CDN link\n- An image link\n- A video link\n- A hash\n- An ID\n- A slug\n- A filename\n- Or contains no natural language words\n\nYou MUST return it exactly as-is.\n\nDo NOT:\n- Wrap it in Markdown\n- Convert it to ![]()\n- Add alt text\n- Add HTML tags\n- Reformat it\n- Modify it in any way\n\nThe value must remain byte-identical to the source.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nNO FORMAT TRANSFORMATION RULE\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nYou are performing translation ONLY.\n\nYou are NOT:\n- Improving formatting\n- Converting plain URLs into Markdown\n- Generating new markup\n- Optimizing structure\n- Rewriting fields\n- Enhancing content\n\nDo not reinterpret field meaning.\nDo not transform content type.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSEO LOCALIZATION\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n- Use natural terminology used by native speakers.\n- Avoid literal translation.\n- Maintain marketing fluency where appropriate.\n- Preserve technical accuracy where required.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nMANDATORY VALIDATION\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nBefore output:\n\n1. Verify that only ONE target language is used.\n2. Ensure no unintended foreign words remain.\n3. Ensure allowed English is limited to approved categories.\n4. Ensure all immutable values remain byte-identical.\n5. Ensure JSON structure is unchanged.\n6. Ensure valid JSON format.\n\nReturn ONLY the localized JSON.\nNo explanations.\nNo comments.\nNo extra formatting.";
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
3
  export declare const DEFAULT_LLM_TEMPERATURE = 0.3;
4
4
  export declare const DEFAULT_LLM_MODEL = "gpt-4o";
@@ -0,0 +1,11 @@
1
+ import { BatchTranslateRequestBody, RequestContext, StrapiContext } from 'src/types';
2
+ declare const apiController: ({ strapi }: StrapiContext) => {
3
+ getContentTypes(ctx: RequestContext): Promise<void>;
4
+ getLocales(ctx: RequestContext): Promise<void>;
5
+ batchTranslate(ctx: RequestContext & {
6
+ request: {
7
+ body: BatchTranslateRequestBody;
8
+ };
9
+ }): Promise<void>;
10
+ };
11
+ export default apiController;
@@ -54,5 +54,24 @@ declare const _default: {
54
54
  };
55
55
  }): Promise<void>;
56
56
  };
57
+ api: ({ strapi }: import("../types").StrapiContext) => {
58
+ getContentTypes(ctx: import("../types").RequestContext): Promise<void>;
59
+ getLocales(ctx: import("../types").RequestContext): Promise<void>;
60
+ batchTranslate(ctx: Omit<import("koa").Context, "body" | "query" | "request"> & {
61
+ body: object;
62
+ query: object;
63
+ params: object;
64
+ request: Omit<import("koa").Request, "body"> & {
65
+ body: object;
66
+ };
67
+ state: {
68
+ user?: import("../types").AdminUser;
69
+ };
70
+ } & {
71
+ request: {
72
+ body: import("../types").BatchTranslateRequestBody;
73
+ };
74
+ }): Promise<void>;
75
+ };
57
76
  };
58
77
  export default _default;
@@ -0,0 +1,12 @@
1
+ declare const _default: {
2
+ method: string;
3
+ path: string;
4
+ handler: string;
5
+ config: {
6
+ policies: any[];
7
+ auth: {
8
+ scope: any[];
9
+ };
10
+ };
11
+ }[];
12
+ export default _default;
@@ -10,5 +10,19 @@ declare const routes: {
10
10
  };
11
11
  }[];
12
12
  };
13
+ 'content-api': {
14
+ type: string;
15
+ routes: {
16
+ method: string;
17
+ path: string;
18
+ handler: string;
19
+ config: {
20
+ policies: any[];
21
+ auth: {
22
+ scope: any[];
23
+ };
24
+ };
25
+ }[];
26
+ };
13
27
  };
14
28
  export default routes;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.5",
2
+ "version": "1.1.8",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "plugin",