@goenhance/strapi-plugins-translate 1.1.6 → 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.
- package/dist/server/index.js +161 -1
- package/dist/server/index.mjs +161 -1
- package/dist/server/src/controllers/api.controller.d.ts +11 -0
- package/dist/server/src/controllers/index.d.ts +19 -0
- package/dist/server/src/routes/content-api.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +14 -0
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -1097,8 +1097,129 @@ const controllers$1 = ({ strapi: strapi2 }) => ({
|
|
|
1097
1097
|
}
|
|
1098
1098
|
}
|
|
1099
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
|
+
});
|
|
1100
1220
|
const controllers = {
|
|
1101
|
-
admin: controllers$1
|
|
1221
|
+
admin: controllers$1,
|
|
1222
|
+
api: apiController
|
|
1102
1223
|
};
|
|
1103
1224
|
const middlewares = {};
|
|
1104
1225
|
const policies = {};
|
|
@@ -1160,10 +1281,49 @@ const adminRoutes = [
|
|
|
1160
1281
|
}
|
|
1161
1282
|
}
|
|
1162
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
|
+
];
|
|
1163
1319
|
const routes = {
|
|
1164
1320
|
admin: {
|
|
1165
1321
|
type: "admin",
|
|
1166
1322
|
routes: adminRoutes
|
|
1323
|
+
},
|
|
1324
|
+
"content-api": {
|
|
1325
|
+
type: "content-api",
|
|
1326
|
+
routes: contentApiRoutes
|
|
1167
1327
|
}
|
|
1168
1328
|
};
|
|
1169
1329
|
const cleanJSONString = (content) => {
|
package/dist/server/index.mjs
CHANGED
|
@@ -1096,8 +1096,129 @@ const controllers$1 = ({ strapi: strapi2 }) => ({
|
|
|
1096
1096
|
}
|
|
1097
1097
|
}
|
|
1098
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
|
+
});
|
|
1099
1219
|
const controllers = {
|
|
1100
|
-
admin: controllers$1
|
|
1220
|
+
admin: controllers$1,
|
|
1221
|
+
api: apiController
|
|
1101
1222
|
};
|
|
1102
1223
|
const middlewares = {};
|
|
1103
1224
|
const policies = {};
|
|
@@ -1159,10 +1280,49 @@ const adminRoutes = [
|
|
|
1159
1280
|
}
|
|
1160
1281
|
}
|
|
1161
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
|
+
];
|
|
1162
1318
|
const routes = {
|
|
1163
1319
|
admin: {
|
|
1164
1320
|
type: "admin",
|
|
1165
1321
|
routes: adminRoutes
|
|
1322
|
+
},
|
|
1323
|
+
"content-api": {
|
|
1324
|
+
type: "content-api",
|
|
1325
|
+
routes: contentApiRoutes
|
|
1166
1326
|
}
|
|
1167
1327
|
};
|
|
1168
1328
|
const cleanJSONString = (content) => {
|
|
@@ -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;
|
|
@@ -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