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