@goenhance/strapi-plugins-translate 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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/dist/_chunks/App-DvJ8tPer.js +674 -0
  4. package/dist/_chunks/App-uB_CPrcd.mjs +674 -0
  5. package/dist/_chunks/en-BOBGCAB6.mjs +65 -0
  6. package/dist/_chunks/en-BaJyCZ_c.js +65 -0
  7. package/dist/_chunks/index-B8MBOdIV.js +276 -0
  8. package/dist/_chunks/index-DkZZ45sW.mjs +277 -0
  9. package/dist/_chunks/zh-CtGwhmjc.mjs +65 -0
  10. package/dist/_chunks/zh-DUCnaWvE.js +65 -0
  11. package/dist/_chunks/zh-Hans-CJW3RUKL.js +65 -0
  12. package/dist/_chunks/zh-Hans-pTiPqgIS.mjs +65 -0
  13. package/dist/admin/index.js +3 -0
  14. package/dist/admin/index.mjs +4 -0
  15. package/dist/admin/src/components/Initializer.d.ts +5 -0
  16. package/dist/admin/src/components/LLMButton.d.ts +2 -0
  17. package/dist/admin/src/components/PluginIcon.d.ts +18 -0
  18. package/dist/admin/src/index.d.ts +11 -0
  19. package/dist/admin/src/pages/App.d.ts +2 -0
  20. package/dist/admin/src/pages/BatchTranslatePage.d.ts +2 -0
  21. package/dist/admin/src/pages/SettingsPage.d.ts +2 -0
  22. package/dist/admin/src/pluginId.d.ts +1 -0
  23. package/dist/admin/src/utils/constants.d.ts +4 -0
  24. package/dist/admin/src/utils/getLocaleFromUrl.d.ts +1 -0
  25. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  26. package/dist/server/index.js +754 -0
  27. package/dist/server/index.mjs +755 -0
  28. package/dist/server/src/bootstrap.d.ts +5 -0
  29. package/dist/server/src/config/constants.d.ts +7 -0
  30. package/dist/server/src/config/index.d.ts +11 -0
  31. package/dist/server/src/content-types/index.d.ts +2 -0
  32. package/dist/server/src/controllers/admin.controller.d.ts +21 -0
  33. package/dist/server/src/controllers/index.d.ts +43 -0
  34. package/dist/server/src/destroy.d.ts +5 -0
  35. package/dist/server/src/index.d.ts +2 -0
  36. package/dist/server/src/middlewares/index.d.ts +2 -0
  37. package/dist/server/src/policies/index.d.ts +2 -0
  38. package/dist/server/src/register.d.ts +5 -0
  39. package/dist/server/src/routes/admin.d.ts +9 -0
  40. package/dist/server/src/routes/index.d.ts +14 -0
  41. package/dist/server/src/services/index.d.ts +2 -0
  42. package/dist/server/src/services/llm-service.d.ts +6 -0
  43. package/dist/server/src/types/controllers.d.ts +22 -0
  44. package/dist/server/src/types/index.d.ts +53 -0
  45. package/dist/server/src/utils/json-utils.d.ts +4 -0
  46. package/package.json +88 -0
@@ -0,0 +1,674 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const react = require("react");
7
+ const reactIntl = require("react-intl");
8
+ const designSystem = require("@strapi/design-system");
9
+ const icons = require("@strapi/icons");
10
+ const index = require("./index-B8MBOdIV.js");
11
+ const BatchTranslatePage = () => {
12
+ const { formatMessage } = reactIntl.useIntl();
13
+ const { get, post } = admin.useFetchClient();
14
+ const { toggleNotification } = admin.useNotification();
15
+ const [contentTypes, setContentTypes] = react.useState([]);
16
+ const [locales, setLocales] = react.useState([]);
17
+ const [entries, setEntries] = react.useState([]);
18
+ const [selectedContentType, setSelectedContentType] = react.useState("");
19
+ const [sourceLocale, setSourceLocale] = react.useState("");
20
+ const [targetLocales, setTargetLocales] = react.useState([]);
21
+ const [selectedEntries, setSelectedEntries] = react.useState([]);
22
+ const [loading, setLoading] = react.useState(false);
23
+ const [translating, setTranslating] = react.useState(false);
24
+ const [loadingEntries, setLoadingEntries] = react.useState(false);
25
+ const [pagination, setPagination] = react.useState({
26
+ page: 1,
27
+ pageSize: 10,
28
+ total: 0,
29
+ pageCount: 0
30
+ });
31
+ const [results, setResults] = react.useState([]);
32
+ const [publishAfterTranslate, setPublishAfterTranslate] = react.useState(false);
33
+ react.useEffect(() => {
34
+ const fetchInitialData = async () => {
35
+ setLoading(true);
36
+ try {
37
+ const [contentTypesRes, localesRes] = await Promise.all([
38
+ get(`/${index.PLUGIN_ID}/content-types`),
39
+ get(`/${index.PLUGIN_ID}/locales`)
40
+ ]);
41
+ setContentTypes(contentTypesRes.data.data || []);
42
+ setLocales(localesRes.data.data || []);
43
+ const defaultLocale = localesRes.data.data?.find((l) => l.isDefault);
44
+ if (defaultLocale) {
45
+ setSourceLocale(defaultLocale.code);
46
+ }
47
+ } catch (error) {
48
+ console.error("Error fetching initial data:", error);
49
+ toggleNotification({
50
+ type: "danger",
51
+ message: formatMessage({
52
+ id: index.getTranslation("batch.error.fetchInitial"),
53
+ defaultMessage: "Failed to load initial data"
54
+ })
55
+ });
56
+ } finally {
57
+ setLoading(false);
58
+ }
59
+ };
60
+ fetchInitialData();
61
+ }, []);
62
+ const fetchEntries = react.useCallback(async () => {
63
+ if (!selectedContentType || !sourceLocale) return;
64
+ setLoadingEntries(true);
65
+ try {
66
+ const response = await get(
67
+ `/${index.PLUGIN_ID}/entries/${encodeURIComponent(selectedContentType)}?locale=${sourceLocale}&page=${pagination.page}&pageSize=${pagination.pageSize}`
68
+ );
69
+ setEntries(response.data.data || []);
70
+ setPagination((prev) => ({
71
+ ...prev,
72
+ total: response.data.meta?.pagination?.total || 0,
73
+ pageCount: response.data.meta?.pagination?.pageCount || 0
74
+ }));
75
+ } catch (error) {
76
+ console.error("Error fetching entries:", error);
77
+ toggleNotification({
78
+ type: "danger",
79
+ message: formatMessage({
80
+ id: index.getTranslation("batch.error.fetchEntries"),
81
+ defaultMessage: "Failed to load entries"
82
+ })
83
+ });
84
+ } finally {
85
+ setLoadingEntries(false);
86
+ }
87
+ }, [selectedContentType, sourceLocale, pagination.page, pagination.pageSize]);
88
+ react.useEffect(() => {
89
+ fetchEntries();
90
+ setSelectedEntries([]);
91
+ }, [selectedContentType, sourceLocale, pagination.page]);
92
+ const handleSelectAll = () => {
93
+ if (selectedEntries.length === entries.length) {
94
+ setSelectedEntries([]);
95
+ } else {
96
+ setSelectedEntries(entries.map((e) => e.documentId));
97
+ }
98
+ };
99
+ const handleSelectEntry = (documentId) => {
100
+ setSelectedEntries(
101
+ (prev) => prev.includes(documentId) ? prev.filter((id) => id !== documentId) : [...prev, documentId]
102
+ );
103
+ };
104
+ const availableTargetLocales = locales.filter((l) => l.code !== sourceLocale);
105
+ const handleBatchTranslate = async () => {
106
+ if (!selectedContentType || !sourceLocale || targetLocales.length === 0 || selectedEntries.length === 0) {
107
+ toggleNotification({
108
+ type: "warning",
109
+ message: formatMessage({
110
+ id: index.getTranslation("batch.error.missingSelection"),
111
+ defaultMessage: "Please select content type, locales, and entries to translate"
112
+ })
113
+ });
114
+ return;
115
+ }
116
+ setTranslating(true);
117
+ setResults([]);
118
+ try {
119
+ const response = await post(`/${index.PLUGIN_ID}/batch-translate`, {
120
+ contentType: selectedContentType,
121
+ entries: selectedEntries,
122
+ sourceLocale,
123
+ targetLocales,
124
+ publish: publishAfterTranslate
125
+ });
126
+ setResults(response.data.data || []);
127
+ const { successCount, failureCount } = response.data.meta || {};
128
+ toggleNotification({
129
+ type: successCount > 0 ? "success" : "warning",
130
+ message: formatMessage(
131
+ {
132
+ id: index.getTranslation("batch.result.summary"),
133
+ defaultMessage: `Translation completed: ${successCount} succeeded, ${failureCount} failed`
134
+ },
135
+ { successCount, failureCount }
136
+ )
137
+ });
138
+ fetchEntries();
139
+ } catch (error) {
140
+ console.error("Error during batch translation:", error);
141
+ toggleNotification({
142
+ type: "danger",
143
+ message: formatMessage({
144
+ id: index.getTranslation("batch.error.translate"),
145
+ defaultMessage: "Batch translation failed"
146
+ })
147
+ });
148
+ } finally {
149
+ setTranslating(false);
150
+ }
151
+ };
152
+ const getMissingLocales = (entry) => {
153
+ return targetLocales.filter((locale) => !entry.existingLocales.includes(locale));
154
+ };
155
+ const getLocaleDisplay = (code) => {
156
+ const locale = locales.find((l) => l.code === code);
157
+ return locale ? `${locale.name} (${code})` : code;
158
+ };
159
+ if (loading) {
160
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", alignItems: "center", height: "400px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) }) });
161
+ }
162
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
163
+ /* @__PURE__ */ jsxRuntime.jsxs(
164
+ designSystem.Box,
165
+ {
166
+ paddingLeft: 10,
167
+ paddingRight: 10,
168
+ paddingBottom: 8,
169
+ paddingTop: 8,
170
+ background: "neutral100",
171
+ children: [
172
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", justifyContent: "space-between", marginBottom: 4, children: [
173
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, children: [
174
+ /* @__PURE__ */ jsxRuntime.jsx(index.PluginIcon, { width: 42, height: 42 }),
175
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", fontWeight: "bold", children: formatMessage({
176
+ id: index.getTranslation("batch.title"),
177
+ defaultMessage: "Batch Translation"
178
+ }) })
179
+ ] }),
180
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/plugins/${index.PLUGIN_ID}/settings`, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Cog, {}), children: formatMessage({
181
+ id: index.getTranslation("batch.settings"),
182
+ defaultMessage: "Settings"
183
+ }) }) })
184
+ ] }),
185
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", textColor: "neutral600", children: formatMessage({
186
+ id: index.getTranslation("batch.description"),
187
+ defaultMessage: "Select a content type and entries to translate them to multiple languages at once. Translated content will be saved as drafts."
188
+ }) })
189
+ ]
190
+ }
191
+ ),
192
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingLeft: 10, paddingRight: 10, paddingTop: 6, children: [
193
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, marginBottom: 6, children: [
194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", marginBottom: 4, tag: "h2", children: formatMessage({
195
+ id: index.getTranslation("batch.filters.title"),
196
+ defaultMessage: "Translation Settings"
197
+ }) }),
198
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", children: [
199
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
200
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
201
+ id: index.getTranslation("batch.filters.contentType"),
202
+ defaultMessage: "Content Type"
203
+ }) }),
204
+ /* @__PURE__ */ jsxRuntime.jsx(
205
+ designSystem.SingleSelect,
206
+ {
207
+ placeholder: formatMessage({
208
+ id: index.getTranslation("batch.filters.contentTypePlaceholder"),
209
+ defaultMessage: "Select a content type"
210
+ }),
211
+ value: selectedContentType,
212
+ onChange: (value) => {
213
+ setSelectedContentType(String(value));
214
+ setPagination((prev) => ({ ...prev, page: 1 }));
215
+ },
216
+ children: contentTypes.map((ct) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: ct.uid, children: ct.displayName }, ct.uid))
217
+ }
218
+ )
219
+ ] }) }),
220
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
221
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
222
+ id: index.getTranslation("batch.filters.sourceLocale"),
223
+ defaultMessage: "Source Language"
224
+ }) }),
225
+ /* @__PURE__ */ jsxRuntime.jsx(
226
+ designSystem.SingleSelect,
227
+ {
228
+ placeholder: formatMessage({
229
+ id: index.getTranslation("batch.filters.sourceLocalePlaceholder"),
230
+ defaultMessage: "Select source language"
231
+ }),
232
+ value: sourceLocale,
233
+ onChange: (value) => {
234
+ const newValue = String(value);
235
+ setSourceLocale(newValue);
236
+ setTargetLocales((prev) => prev.filter((l) => l !== newValue));
237
+ setPagination((prev) => ({ ...prev, page: 1 }));
238
+ },
239
+ children: locales.map((locale) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: locale.code, children: [
240
+ locale.name,
241
+ " (",
242
+ locale.code,
243
+ ")",
244
+ locale.isDefault && " - Default"
245
+ ] }, locale.code))
246
+ }
247
+ )
248
+ ] }) }),
249
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", minWidth: "200px", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
250
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
251
+ id: index.getTranslation("batch.filters.targetLocales"),
252
+ defaultMessage: "Target Languages"
253
+ }) }),
254
+ /* @__PURE__ */ jsxRuntime.jsx(
255
+ designSystem.MultiSelect,
256
+ {
257
+ placeholder: formatMessage({
258
+ id: index.getTranslation("batch.filters.targetLocalesPlaceholder"),
259
+ defaultMessage: "Select target languages"
260
+ }),
261
+ value: targetLocales,
262
+ onChange: (values) => setTargetLocales(values),
263
+ children: availableTargetLocales.map((locale) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.MultiSelectOption, { value: locale.code, children: [
264
+ locale.name,
265
+ " (",
266
+ locale.code,
267
+ ")"
268
+ ] }, locale.code))
269
+ }
270
+ )
271
+ ] }) })
272
+ ] }),
273
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { marginTop: 4, alignItems: "center", gap: 3, children: [
274
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: formatMessage({
275
+ id: index.getTranslation("batch.filters.publishAfterTranslate"),
276
+ defaultMessage: "Publish after translation"
277
+ }) }),
278
+ /* @__PURE__ */ jsxRuntime.jsx(
279
+ designSystem.Toggle,
280
+ {
281
+ checked: publishAfterTranslate,
282
+ onChange: (e) => setPublishAfterTranslate(e.target.checked),
283
+ onLabel: formatMessage({
284
+ id: index.getTranslation("batch.filters.publishOn"),
285
+ defaultMessage: "Publish"
286
+ }),
287
+ offLabel: formatMessage({
288
+ id: index.getTranslation("batch.filters.publishOff"),
289
+ defaultMessage: "Draft"
290
+ })
291
+ }
292
+ ),
293
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatMessage({
294
+ id: index.getTranslation("batch.filters.publishAfterTranslateHint"),
295
+ defaultMessage: "When enabled, translated content will be published immediately. Otherwise, it will be saved as draft."
296
+ }) })
297
+ ] })
298
+ ] }),
299
+ selectedContentType && sourceLocale ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, children: [
300
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, children: [
301
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", tag: "h2", children: formatMessage(
302
+ {
303
+ id: index.getTranslation("batch.entries.title"),
304
+ defaultMessage: "Entries ({count})"
305
+ },
306
+ { count: pagination.total }
307
+ ) }),
308
+ /* @__PURE__ */ jsxRuntime.jsx(
309
+ designSystem.Button,
310
+ {
311
+ variant: "default",
312
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Magic, {}),
313
+ loading: translating,
314
+ disabled: translating || selectedEntries.length === 0 || targetLocales.length === 0,
315
+ onClick: handleBatchTranslate,
316
+ children: translating ? formatMessage({
317
+ id: index.getTranslation("batch.button.translating"),
318
+ defaultMessage: "Translating..."
319
+ }) : formatMessage(
320
+ {
321
+ id: index.getTranslation("batch.button.translate"),
322
+ defaultMessage: "Translate Selected ({count})"
323
+ },
324
+ { count: selectedEntries.length }
325
+ )
326
+ }
327
+ )
328
+ ] }),
329
+ loadingEntries ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) : entries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
330
+ designSystem.EmptyStateLayout,
331
+ {
332
+ content: formatMessage({
333
+ id: index.getTranslation("batch.entries.empty"),
334
+ defaultMessage: "No entries found for this content type and locale"
335
+ })
336
+ }
337
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
338
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: entries.length + 1, children: [
339
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
340
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { width: "40px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
341
+ designSystem.Checkbox,
342
+ {
343
+ checked: selectedEntries.length === entries.length && entries.length > 0,
344
+ onCheckedChange: handleSelectAll
345
+ }
346
+ ) }),
347
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { maxWidth: "160px" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
348
+ id: index.getTranslation("batch.table.id"),
349
+ defaultMessage: "ID"
350
+ }) }) }),
351
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { maxWidth: "200px" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
352
+ id: index.getTranslation("batch.table.title"),
353
+ defaultMessage: "Title"
354
+ }) }) }),
355
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
356
+ id: index.getTranslation("batch.table.existingLocales"),
357
+ defaultMessage: "Existing Translations"
358
+ }) }) }),
359
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
360
+ id: index.getTranslation("batch.table.missingLocales"),
361
+ defaultMessage: "Missing Translations"
362
+ }) }) }),
363
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({
364
+ id: index.getTranslation("batch.table.status"),
365
+ defaultMessage: "Status"
366
+ }) }) })
367
+ ] }) }),
368
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: entries.map((entry) => {
369
+ const missingLocales = getMissingLocales(entry);
370
+ const entryResults = results.filter(
371
+ (r) => r.documentId === entry.documentId
372
+ );
373
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
374
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(
375
+ designSystem.Checkbox,
376
+ {
377
+ checked: selectedEntries.includes(entry.documentId),
378
+ onCheckedChange: () => handleSelectEntry(entry.documentId)
379
+ }
380
+ ) }),
381
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: "160px", wordBreak: "break-word" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "omega", style: { wordBreak: "break-word", whiteSpace: "wrap" }, children: entry.customId || entry.id }) }),
382
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: "200px", wordBreak: "break-word" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { style: { wordBreak: "break-word", whiteSpace: "wrap" }, children: entry.title }) }),
383
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, wrap: "wrap", children: entry.existingLocales.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", children: getLocaleDisplay(locale) }, locale)) }) }),
384
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, wrap: "wrap", children: targetLocales.length > 0 ? missingLocales.length > 0 ? missingLocales.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "warning100", textColor: "warning700", children: getLocaleDisplay(locale) }, locale)) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", children: formatMessage({
385
+ id: index.getTranslation("batch.table.allTranslated"),
386
+ defaultMessage: "All translated"
387
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: formatMessage({
388
+ id: index.getTranslation("batch.table.selectTargets"),
389
+ defaultMessage: "Select target languages"
390
+ }) }) }) }),
391
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: entryResults.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, wrap: "wrap", children: entryResults.map((result) => /* @__PURE__ */ jsxRuntime.jsx(
392
+ designSystem.Badge,
393
+ {
394
+ backgroundColor: result.success ? "success100" : "danger100",
395
+ textColor: result.success ? "success700" : "danger700",
396
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
397
+ result.success ? /* @__PURE__ */ jsxRuntime.jsx(icons.Check, { width: 12, height: 12 }) : /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, { width: 12, height: 12 }),
398
+ result.locale
399
+ ] })
400
+ },
401
+ result.locale
402
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "-" }) })
403
+ ] }, entry.documentId);
404
+ }) })
405
+ ] }),
406
+ pagination.pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginTop: 4, children: [
407
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage(
408
+ {
409
+ id: index.getTranslation("batch.pagination.info"),
410
+ defaultMessage: "Page {page} of {pageCount}"
411
+ },
412
+ { page: pagination.page, pageCount: pagination.pageCount }
413
+ ) }),
414
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
415
+ /* @__PURE__ */ jsxRuntime.jsx(
416
+ designSystem.Button,
417
+ {
418
+ variant: "tertiary",
419
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ChevronLeft, {}),
420
+ disabled: pagination.page <= 1,
421
+ onClick: () => setPagination((prev) => ({ ...prev, page: prev.page - 1 })),
422
+ children: formatMessage({
423
+ id: index.getTranslation("batch.pagination.prev"),
424
+ defaultMessage: "Previous"
425
+ })
426
+ }
427
+ ),
428
+ /* @__PURE__ */ jsxRuntime.jsx(
429
+ designSystem.Button,
430
+ {
431
+ variant: "tertiary",
432
+ endIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ChevronRight, {}),
433
+ disabled: pagination.page >= pagination.pageCount,
434
+ onClick: () => setPagination((prev) => ({ ...prev, page: prev.page + 1 })),
435
+ children: formatMessage({
436
+ id: index.getTranslation("batch.pagination.next"),
437
+ defaultMessage: "Next"
438
+ })
439
+ }
440
+ )
441
+ ] })
442
+ ] })
443
+ ] })
444
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(
445
+ designSystem.EmptyStateLayout,
446
+ {
447
+ content: formatMessage({
448
+ id: index.getTranslation("batch.entries.selectContentType"),
449
+ defaultMessage: "Please select a content type and source language to view entries"
450
+ })
451
+ }
452
+ ) })
453
+ ] })
454
+ ] });
455
+ };
456
+ 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.";
457
+ const DEFAULT_LLM_TEMPERATURE = 0.3;
458
+ const MAX_LLM_TEMPERATURE = 2;
459
+ const MIN_LLM_TEMPERATURE = 0.1;
460
+ const SettingsPage = () => {
461
+ const [config, setConfig] = react.useState({ systemPrompt: "", temperature: 0.3 });
462
+ const { formatMessage } = reactIntl.useIntl();
463
+ const { get, post } = admin.useFetchClient();
464
+ react.useEffect(() => {
465
+ const fetchData = async () => {
466
+ const response = await get(`/${index.PLUGIN_ID}/config`);
467
+ setConfig({ ...config, ...response.data });
468
+ };
469
+ fetchData();
470
+ }, []);
471
+ const handleSubmit = async (e) => {
472
+ e.preventDefault();
473
+ await post(`/${index.PLUGIN_ID}/config`, { ...config });
474
+ };
475
+ const handleRestore = async () => {
476
+ setConfig({ systemPrompt: DEFAULT_SYSTEM_PROMPT, temperature: DEFAULT_LLM_TEMPERATURE });
477
+ await post(`/${index.PLUGIN_ID}/config`, {
478
+ systemPrompt: DEFAULT_SYSTEM_PROMPT,
479
+ temperature: DEFAULT_LLM_TEMPERATURE
480
+ });
481
+ };
482
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
483
+ /* @__PURE__ */ jsxRuntime.jsxs(
484
+ designSystem.Box,
485
+ {
486
+ paddingLeft: 10,
487
+ paddingRight: 10,
488
+ paddingBottom: 8,
489
+ paddingTop: 8,
490
+ "data-strapi-header": true,
491
+ background: "neutral100",
492
+ children: [
493
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { alignItems: "center", gap: 3, marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/plugins/${index.PLUGIN_ID}`, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), children: formatMessage({
494
+ id: index.getTranslation("settings.back"),
495
+ defaultMessage: "Back"
496
+ }) }) }) }),
497
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, marginBottom: 4, children: [
498
+ /* @__PURE__ */ jsxRuntime.jsx(index.PluginIcon, { width: 42, height: 42 }),
499
+ " ",
500
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", fontWeight: "bold", children: formatMessage({
501
+ id: index.getTranslation("plugin.page.title"),
502
+ defaultMessage: "LLM Translator (Configuration)"
503
+ }) })
504
+ ] }),
505
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", textColor: "neutral600", fontWeight: "normal", children: formatMessage({
506
+ id: index.getTranslation("plugin.page.description"),
507
+ defaultMessage: "Configure the LLM Translator plugin settings. Be aware that Base Model, API Key and LLM Base URL need to be set as environment variables."
508
+ }) })
509
+ ]
510
+ }
511
+ ),
512
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 10, paddingRight: 10, children: /* @__PURE__ */ jsxRuntime.jsx(
513
+ designSystem.Box,
514
+ {
515
+ paddingLeft: 10,
516
+ paddingRight: 10,
517
+ paddingTop: 8,
518
+ paddingBottom: 8,
519
+ background: "neutral0",
520
+ children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [
521
+ /* @__PURE__ */ jsxRuntime.jsxs(
522
+ designSystem.Field.Root,
523
+ {
524
+ id: "system_prompt",
525
+ hint: formatMessage({
526
+ id: index.getTranslation("plugin.page.form.system_prompt_hint"),
527
+ defaultMessage: "This is (part) of the prompt that will be used to instruct the LLM. It should be clear and concise."
528
+ }),
529
+ required: true,
530
+ children: [
531
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
532
+ id: index.getTranslation("plugin.page.form.system_prompt"),
533
+ defaultMessage: "System prompt"
534
+ }) }),
535
+ /* @__PURE__ */ jsxRuntime.jsx(
536
+ designSystem.Textarea,
537
+ {
538
+ id: "system_prompt",
539
+ value: config.systemPrompt,
540
+ onChange: (e) => setConfig({ ...config, systemPrompt: e.target.value }),
541
+ required: true,
542
+ placeholder: DEFAULT_SYSTEM_PROMPT,
543
+ name: "system_prompt"
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
547
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
548
+ ]
549
+ }
550
+ ),
551
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, marginTop: 6, flex: 1, children: [
552
+ /* @__PURE__ */ jsxRuntime.jsxs(
553
+ designSystem.Field.Root,
554
+ {
555
+ id: "llm_temperature",
556
+ hint: formatMessage({
557
+ id: index.getTranslation("plugin.page.form.llm_temperature_hint"),
558
+ defaultMessage: "Temperature setting for the LLM. A higher value will make the output more random, while a lower value will make it more focused and deterministic."
559
+ }),
560
+ flex: 1,
561
+ children: [
562
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
563
+ id: index.getTranslation("plugin.page.form.llm_temperature"),
564
+ defaultMessage: "LLM Temperature"
565
+ }) }),
566
+ /* @__PURE__ */ jsxRuntime.jsx(
567
+ designSystem.NumberInput,
568
+ {
569
+ id: "llm_temperature",
570
+ value: config.temperature,
571
+ name: "llm_temperature",
572
+ step: 0.1,
573
+ min: MIN_LLM_TEMPERATURE,
574
+ max: MAX_LLM_TEMPERATURE,
575
+ onValueChange: (value) => {
576
+ let tempValue = DEFAULT_LLM_TEMPERATURE;
577
+ if (value === void 0) {
578
+ tempValue = DEFAULT_LLM_TEMPERATURE;
579
+ } else if (value < MIN_LLM_TEMPERATURE) {
580
+ tempValue = MIN_LLM_TEMPERATURE;
581
+ } else if (value > MAX_LLM_TEMPERATURE) {
582
+ tempValue = MAX_LLM_TEMPERATURE;
583
+ } else {
584
+ tempValue = value;
585
+ }
586
+ setConfig({ ...config, temperature: tempValue });
587
+ }
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
591
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
592
+ ]
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsxRuntime.jsxs(
596
+ designSystem.Field.Root,
597
+ {
598
+ id: "llm_model",
599
+ flex: 1,
600
+ hint: formatMessage({
601
+ id: index.getTranslation("plugin.page.form.llm_model_hint"),
602
+ defaultMessage: "Model that will be used to generate the translations. It should be set as an environment variable."
603
+ }),
604
+ children: [
605
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
606
+ id: index.getTranslation("plugin.page.form.llm_model"),
607
+ defaultMessage: "LLM Model"
608
+ }) }),
609
+ /* @__PURE__ */ jsxRuntime.jsx(
610
+ designSystem.TextInput,
611
+ {
612
+ id: "llm_model",
613
+ value: process.env.STRAPI_ADMIN_LLM_TRANSLATOR_LLM_MODEL,
614
+ disabled: true,
615
+ name: "llm_model"
616
+ }
617
+ ),
618
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
619
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
620
+ ]
621
+ }
622
+ ),
623
+ /* @__PURE__ */ jsxRuntime.jsxs(
624
+ designSystem.Field.Root,
625
+ {
626
+ id: "llm_base_url",
627
+ flex: 1,
628
+ hint: formatMessage({
629
+ id: index.getTranslation("plugin.page.form.llm_base_url_hint"),
630
+ defaultMessage: "Base URL for the LLM API. It needs to be set as an environment variable."
631
+ }),
632
+ children: [
633
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
634
+ id: index.getTranslation("plugin.page.form.llm_base_url"),
635
+ defaultMessage: "LLM Base Url"
636
+ }) }),
637
+ /* @__PURE__ */ jsxRuntime.jsx(
638
+ designSystem.TextInput,
639
+ {
640
+ id: "llm_base_url",
641
+ value: process.env.STRAPI_ADMIN_LLM_TRANSLATOR_LLM_BASE_URL,
642
+ disabled: true,
643
+ name: "llm_base_url"
644
+ }
645
+ ),
646
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
647
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
648
+ ]
649
+ }
650
+ )
651
+ ] }),
652
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginTop: 6, justifyContent: "flex-end", children: [
653
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", type: "button", onClick: handleRestore, marginTop: 4, children: formatMessage({
654
+ id: index.getTranslation("plugin.page.form.restore"),
655
+ defaultMessage: "Restore to default"
656
+ }) }),
657
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "default", type: "submit", marginTop: 4, children: formatMessage({
658
+ id: index.getTranslation("plugin.page.form.save"),
659
+ defaultMessage: "Save"
660
+ }) })
661
+ ] })
662
+ ] })
663
+ }
664
+ ) })
665
+ ] });
666
+ };
667
+ const App = () => {
668
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
669
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(BatchTranslatePage, {}) }),
670
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "settings", element: /* @__PURE__ */ jsxRuntime.jsx(SettingsPage, {}) }),
671
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
672
+ ] });
673
+ };
674
+ exports.App = App;