@strapi-ai/translator 0.0.1

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/admin/SettingsPage-C5nm32bD.mjs +466 -0
  4. package/dist/admin/SettingsPage-CD90jfwT.js +466 -0
  5. package/dist/admin/en-CU6ocWlA.mjs +97 -0
  6. package/dist/admin/en-I9cvzxfF.js +97 -0
  7. package/dist/admin/index-CS0LCA35.mjs +1694 -0
  8. package/dist/admin/index-DQ3-ym--.js +1693 -0
  9. package/dist/admin/index.js +4 -0
  10. package/dist/admin/index.mjs +4 -0
  11. package/dist/admin/src/components/DiffView.d.ts +31 -0
  12. package/dist/admin/src/components/Initializer.d.ts +3 -0
  13. package/dist/admin/src/components/PluginIcon.d.ts +4 -0
  14. package/dist/admin/src/components/ProgressBar.d.ts +7 -0
  15. package/dist/admin/src/components/TranslationModal.d.ts +23 -0
  16. package/dist/admin/src/components/TranslationPanel.d.ts +4 -0
  17. package/dist/admin/src/components/TranslationWorkflow.d.ts +2 -0
  18. package/dist/admin/src/components/settings/ProviderConnectionFields.d.ts +12 -0
  19. package/dist/admin/src/components/settings/TranslationSettingsFields.d.ts +7 -0
  20. package/dist/admin/src/hooks/useProviderModels.d.ts +13 -0
  21. package/dist/admin/src/hooks/useTranslationConfig.d.ts +8 -0
  22. package/dist/admin/src/index.d.ts +12 -0
  23. package/dist/admin/src/pages/SettingsPage.d.ts +2 -0
  24. package/dist/admin/src/pluginId.d.ts +1 -0
  25. package/dist/admin/src/utils/clampNumber.d.ts +1 -0
  26. package/dist/admin/src/utils/configPayload.d.ts +23 -0
  27. package/dist/admin/src/utils/contentManagerLocale.d.ts +1 -0
  28. package/dist/admin/src/utils/getTranslation.d.ts +1 -0
  29. package/dist/admin/src/utils/providerDefaults.d.ts +10 -0
  30. package/dist/admin/src/utils/sse.d.ts +2 -0
  31. package/dist/admin/src/utils/translatableFields.d.ts +3 -0
  32. package/dist/admin/src/utils/translationPatch.d.ts +13 -0
  33. package/dist/server/index.js +1217 -0
  34. package/dist/server/index.mjs +1217 -0
  35. package/dist/server/src/application/assemble-translation-patch.d.ts +3 -0
  36. package/dist/server/src/application/extract-translatable-units.d.ts +9 -0
  37. package/dist/server/src/application/translate-entry.d.ts +17 -0
  38. package/dist/server/src/application/translation-provider.d.ts +33 -0
  39. package/dist/server/src/bootstrap.d.ts +5 -0
  40. package/dist/server/src/config/index.d.ts +5 -0
  41. package/dist/server/src/controllers/index.d.ts +27 -0
  42. package/dist/server/src/controllers/translation.controller.d.ts +28 -0
  43. package/dist/server/src/domain/field-path.d.ts +6 -0
  44. package/dist/server/src/domain/translation-config.d.ts +27 -0
  45. package/dist/server/src/domain/translation-event.d.ts +59 -0
  46. package/dist/server/src/domain/translation-patch.d.ts +11 -0
  47. package/dist/server/src/domain/translation-unit.d.ts +28 -0
  48. package/dist/server/src/domain/value-codecs/index.d.ts +3 -0
  49. package/dist/server/src/domain/value-codecs/string-codec.d.ts +2 -0
  50. package/dist/server/src/domain/value-codecs/structured-value-codec.d.ts +3 -0
  51. package/dist/server/src/domain/value-codecs/types.d.ts +11 -0
  52. package/dist/server/src/index.d.ts +64 -0
  53. package/dist/server/src/infrastructure/llm/chat-completion-client.d.ts +18 -0
  54. package/dist/server/src/infrastructure/llm/llm-translation-provider.d.ts +15 -0
  55. package/dist/server/src/infrastructure/llm/model-list-client.d.ts +19 -0
  56. package/dist/server/src/infrastructure/llm/openai-chat-completion-client.d.ts +6 -0
  57. package/dist/server/src/infrastructure/strapi/reconcile-schema.d.ts +7 -0
  58. package/dist/server/src/infrastructure/strapi/secret-cipher.d.ts +8 -0
  59. package/dist/server/src/infrastructure/strapi/translation-config-repository.d.ts +16 -0
  60. package/dist/server/src/infrastructure/strapi/translation-session-service.d.ts +37 -0
  61. package/dist/server/src/routes/index.d.ts +19 -0
  62. package/dist/server/src/services/index.d.ts +7 -0
  63. package/package.json +87 -0
@@ -0,0 +1,466 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const designSystem = require("@strapi/design-system");
6
+ const reactIntl = require("react-intl");
7
+ const index = require("./index-DQ3-ym--.js");
8
+ const icons = require("@strapi/icons");
9
+ const admin = require("@strapi/strapi/admin");
10
+ const PluginIcon = (props) => /* @__PURE__ */ jsxRuntime.jsx(icons.Earth, { ...props });
11
+ const DEFAULT_SYSTEM_PROMPT = "You are a CMS content translator. Output only the translated text, with no explanations, no quotes, and no metadata. Preserve formatting and markup exactly: Markdown, HTML, custom tags, links, images. Leave code blocks, URLs, query parameters, variables, and placeholders unchanged. Keep brand names, product names, and currency values in their original form. Translate meaning by meaning, using natural, concise wording and the standard terminology of the target locale.";
12
+ const DEFAULT_PROVIDER_BASE_URL = "";
13
+ const DEFAULT_PROVIDER_MODEL = "";
14
+ const DEFAULT_PROVIDER_TEMPERATURE = 0.3;
15
+ const MAX_PROVIDER_TEMPERATURE = 2;
16
+ const MIN_PROVIDER_TEMPERATURE = 0;
17
+ const DEFAULT_MAX_TOKENS = 0;
18
+ const DEFAULT_MAX_RETRIES = 3;
19
+ const DEFAULT_RETRY_BASE_DELAY_MS = 1e3;
20
+ const DEFAULT_RETRY_MAX_DELAY_MS = 3e4;
21
+ const toEditablePayload = (settings) => {
22
+ const { apiKey, apiKeyConfigured, apiKeyPreview, ...editable } = settings;
23
+ const trimmedApiKey = apiKey.trim();
24
+ return trimmedApiKey ? { ...editable, apiKey: trimmedApiKey } : editable;
25
+ };
26
+ const DEFAULT_CONFIG = {
27
+ apiKey: "",
28
+ baseUrl: DEFAULT_PROVIDER_BASE_URL,
29
+ model: DEFAULT_PROVIDER_MODEL,
30
+ fallbackModel: "",
31
+ systemPrompt: DEFAULT_SYSTEM_PROMPT,
32
+ temperature: DEFAULT_PROVIDER_TEMPERATURE,
33
+ maxTokens: DEFAULT_MAX_TOKENS,
34
+ maxRetries: DEFAULT_MAX_RETRIES,
35
+ retryBaseDelayMs: DEFAULT_RETRY_BASE_DELAY_MS,
36
+ retryMaxDelayMs: DEFAULT_RETRY_MAX_DELAY_MS,
37
+ apiKeyConfigured: false,
38
+ apiKeyPreview: ""
39
+ };
40
+ const useTranslationConfig = () => {
41
+ const [config, setConfig] = react.useState({ ...DEFAULT_CONFIG });
42
+ const { get, post } = admin.useFetchClient();
43
+ const { toggleNotification } = admin.useNotification();
44
+ const { formatMessage } = reactIntl.useIntl();
45
+ const applyPublicConfig = (settings) => {
46
+ setConfig({ ...DEFAULT_CONFIG, ...settings, apiKey: "" });
47
+ };
48
+ const notifySaveFailed = (error) => {
49
+ const serverError = error?.response?.data?.error;
50
+ const serverMessage = typeof serverError === "string" ? serverError : serverError?.message;
51
+ toggleNotification({
52
+ type: "danger",
53
+ message: serverMessage || formatMessage({
54
+ id: index.getTranslation("notification.config.save_failed"),
55
+ defaultMessage: "Could not save settings."
56
+ })
57
+ });
58
+ };
59
+ react.useEffect(() => {
60
+ const fetchData = async () => {
61
+ const response = await get(`/${index.PLUGIN_ID}/config`);
62
+ applyPublicConfig(response.data);
63
+ };
64
+ fetchData().catch(() => {
65
+ });
66
+ }, []);
67
+ const save = async (next) => {
68
+ try {
69
+ const response = await post(
70
+ `/${index.PLUGIN_ID}/config`,
71
+ toEditablePayload(next)
72
+ );
73
+ applyPublicConfig(response.data);
74
+ toggleNotification({
75
+ type: "success",
76
+ message: formatMessage({
77
+ id: index.getTranslation("notification.config.saved"),
78
+ defaultMessage: "Settings saved."
79
+ })
80
+ });
81
+ } catch (error) {
82
+ notifySaveFailed(error);
83
+ }
84
+ };
85
+ const clearApiKey = async () => {
86
+ try {
87
+ const response = await post(`/${index.PLUGIN_ID}/config`, { apiKey: null });
88
+ applyPublicConfig(response.data);
89
+ toggleNotification({
90
+ type: "success",
91
+ message: formatMessage({
92
+ id: index.getTranslation("notification.config.key_removed"),
93
+ defaultMessage: "Saved API key removed."
94
+ })
95
+ });
96
+ } catch (error) {
97
+ notifySaveFailed(error);
98
+ }
99
+ };
100
+ const restore = async () => {
101
+ try {
102
+ const response = await post(
103
+ `/${index.PLUGIN_ID}/config`,
104
+ toEditablePayload(DEFAULT_CONFIG)
105
+ );
106
+ applyPublicConfig(response.data);
107
+ toggleNotification({
108
+ type: "success",
109
+ message: formatMessage({
110
+ id: index.getTranslation("notification.config.restored"),
111
+ defaultMessage: "Default settings restored."
112
+ })
113
+ });
114
+ } catch (error) {
115
+ notifySaveFailed(error);
116
+ }
117
+ };
118
+ const updateField = (field, value) => {
119
+ setConfig((previous) => ({ ...previous, [field]: value }));
120
+ };
121
+ return { config, save, restore, clearApiKey, updateField };
122
+ };
123
+ const useProviderModels = () => {
124
+ const [models, setModels] = react.useState([]);
125
+ const [error, setError] = react.useState(null);
126
+ const { formatMessage } = reactIntl.useIntl();
127
+ const { post } = admin.useFetchClient();
128
+ const requestSeq = react.useRef(0);
129
+ const load = async (draft = {}) => {
130
+ const seq = ++requestSeq.current;
131
+ setError(null);
132
+ try {
133
+ const response = await post(`/${index.PLUGIN_ID}/models`, {
134
+ apiKey: draft.apiKey,
135
+ baseUrl: draft.baseUrl
136
+ });
137
+ const next = Array.isArray(response.data?.models) ? response.data.models.filter(
138
+ (model) => model && typeof model.id === "string" && model.id.trim() !== ""
139
+ ) : [];
140
+ if (next.length === 0) {
141
+ throw new Error(
142
+ formatMessage({
143
+ id: index.getTranslation("settings.form.model_empty"),
144
+ defaultMessage: "No models were returned by the provider."
145
+ })
146
+ );
147
+ }
148
+ if (seq !== requestSeq.current) return;
149
+ setModels(next);
150
+ } catch (e) {
151
+ if (seq !== requestSeq.current) return;
152
+ setModels([]);
153
+ const serverError = e?.response?.data?.error;
154
+ setError(
155
+ serverError?.message || (typeof serverError === "string" ? serverError : "") || e?.message || formatMessage({
156
+ id: index.getTranslation("settings.form.model_load_failed"),
157
+ defaultMessage: "Failed to load provider models."
158
+ })
159
+ );
160
+ }
161
+ };
162
+ return { models, error, load };
163
+ };
164
+ const ProviderConnectionFields = ({
165
+ config,
166
+ updateField,
167
+ onClearApiKey,
168
+ providerModels,
169
+ providerModelsError,
170
+ onLoadModels
171
+ }) => {
172
+ const { formatMessage } = reactIntl.useIntl();
173
+ const trimmedBaseUrl = config.baseUrl.trim().replace(/\/+$/, "");
174
+ const baseUrlMissingVersion = trimmedBaseUrl !== "" && !/\/v\d+$/.test(trimmedBaseUrl);
175
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
176
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", marginBottom: 4, children: formatMessage({
177
+ id: index.getTranslation("settings.section.connection"),
178
+ defaultMessage: "Provider Connection"
179
+ }) }),
180
+ /* @__PURE__ */ jsxRuntime.jsxs(
181
+ designSystem.Field.Root,
182
+ {
183
+ id: "api_key",
184
+ hint: config.apiKeyConfigured ? formatMessage(
185
+ {
186
+ id: index.getTranslation("settings.form.api_key_hint_configured"),
187
+ defaultMessage: "Currently saved: {preview}. Paste a new key to replace it, or leave blank to keep the current one."
188
+ },
189
+ { preview: config.apiKeyPreview }
190
+ ) : formatMessage({
191
+ id: index.getTranslation("settings.form.api_key_hint_empty"),
192
+ defaultMessage: "No key saved. Paste an API key to enable translation. Saved keys are never returned by the API."
193
+ }),
194
+ children: [
195
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
196
+ id: index.getTranslation("settings.form.api_key"),
197
+ defaultMessage: "API Key"
198
+ }) }),
199
+ /* @__PURE__ */ jsxRuntime.jsx(
200
+ designSystem.TextInput,
201
+ {
202
+ id: "api_key",
203
+ value: config.apiKey,
204
+ onChange: (e) => updateField("apiKey", e.target.value),
205
+ onBlur: onLoadModels,
206
+ name: "api_key",
207
+ type: "password",
208
+ autoComplete: "new-password",
209
+ placeholder: config.apiKeyConfigured ? formatMessage({
210
+ id: index.getTranslation("settings.form.api_key_placeholder_configured"),
211
+ defaultMessage: "Configured - enter a new key to replace it"
212
+ }) : formatMessage({
213
+ id: index.getTranslation("settings.form.api_key_placeholder_empty"),
214
+ defaultMessage: "Not configured"
215
+ })
216
+ }
217
+ ),
218
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
219
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
220
+ ]
221
+ }
222
+ ),
223
+ config.apiKeyConfigured ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", type: "button", size: "S", onClick: onClearApiKey, children: formatMessage({
224
+ id: index.getTranslation("settings.form.api_key_remove"),
225
+ defaultMessage: "Remove saved key"
226
+ }) }) }) : null,
227
+ /* @__PURE__ */ jsxRuntime.jsxs(
228
+ designSystem.Field.Root,
229
+ {
230
+ id: "llm_base_url",
231
+ marginTop: 6,
232
+ hint: baseUrlMissingVersion ? formatMessage({
233
+ id: index.getTranslation("settings.form.base_url_hint_missing_version"),
234
+ defaultMessage: "Most OpenAI-compatible APIs require a version segment (e.g. ending with /v1)."
235
+ }) : formatMessage({
236
+ id: index.getTranslation("settings.form.base_url_hint"),
237
+ defaultMessage: "API endpoint for the model provider."
238
+ }),
239
+ children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
241
+ id: index.getTranslation("settings.form.base_url"),
242
+ defaultMessage: "Base URL"
243
+ }) }),
244
+ /* @__PURE__ */ jsxRuntime.jsx(
245
+ designSystem.TextInput,
246
+ {
247
+ id: "llm_base_url",
248
+ value: config.baseUrl,
249
+ onChange: (e) => updateField("baseUrl", e.target.value),
250
+ onBlur: onLoadModels,
251
+ name: "llm_base_url",
252
+ placeholder: "https://api.openai.com/v1"
253
+ }
254
+ ),
255
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
256
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
257
+ ]
258
+ }
259
+ ),
260
+ /* @__PURE__ */ jsxRuntime.jsxs(
261
+ designSystem.Field.Root,
262
+ {
263
+ id: "llm_model",
264
+ marginTop: 6,
265
+ hint: providerModelsError || formatMessage({
266
+ id: index.getTranslation("settings.form.model_hint"),
267
+ defaultMessage: "Primary model for translations."
268
+ }),
269
+ children: [
270
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
271
+ id: index.getTranslation("settings.form.model"),
272
+ defaultMessage: "Model"
273
+ }) }),
274
+ /* @__PURE__ */ jsxRuntime.jsx(
275
+ designSystem.Combobox,
276
+ {
277
+ id: "llm_model",
278
+ value: config.model,
279
+ textValue: config.model,
280
+ onChange: (value) => updateField("model", value ?? ""),
281
+ onTextValueChange: (value) => updateField("model", value),
282
+ onClear: () => updateField("model", ""),
283
+ autocomplete: { type: "list", filter: "contains" },
284
+ allowCustomValue: true,
285
+ name: "llm_model",
286
+ children: providerModels.map((model) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: model.id, children: model.label }, model.id))
287
+ }
288
+ ),
289
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
290
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
291
+ ]
292
+ }
293
+ )
294
+ ] });
295
+ };
296
+ const clampNumber = (value, defaultVal, min, max) => {
297
+ if (value === void 0) return defaultVal;
298
+ if (value < min) return min;
299
+ if (value > max) return max;
300
+ return value;
301
+ };
302
+ const TranslationSettingsFields = ({
303
+ config,
304
+ updateField
305
+ }) => {
306
+ const { formatMessage } = reactIntl.useIntl();
307
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
308
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", marginTop: 8, marginBottom: 4, children: formatMessage({
309
+ id: index.getTranslation("settings.section.translation"),
310
+ defaultMessage: "Translation Settings"
311
+ }) }),
312
+ /* @__PURE__ */ jsxRuntime.jsxs(
313
+ designSystem.Field.Root,
314
+ {
315
+ id: "system_prompt",
316
+ hint: formatMessage({
317
+ id: index.getTranslation("settings.form.system_prompt_hint"),
318
+ defaultMessage: "Instructions for the translation provider."
319
+ }),
320
+ required: true,
321
+ children: [
322
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
323
+ id: index.getTranslation("settings.form.system_prompt"),
324
+ defaultMessage: "System prompt"
325
+ }) }),
326
+ /* @__PURE__ */ jsxRuntime.jsx(
327
+ designSystem.Textarea,
328
+ {
329
+ id: "system_prompt",
330
+ value: config.systemPrompt,
331
+ onChange: (e) => updateField("systemPrompt", e.target.value),
332
+ required: true,
333
+ placeholder: DEFAULT_SYSTEM_PROMPT,
334
+ name: "system_prompt"
335
+ }
336
+ ),
337
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
338
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
339
+ ]
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 4, marginTop: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(
343
+ designSystem.Field.Root,
344
+ {
345
+ id: "llm_temperature",
346
+ flex: 1,
347
+ hint: formatMessage({
348
+ id: index.getTranslation("settings.form.temperature_hint"),
349
+ defaultMessage: "Higher = more creative, lower = more deterministic."
350
+ }),
351
+ children: [
352
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
353
+ id: index.getTranslation("settings.form.temperature"),
354
+ defaultMessage: "Temperature"
355
+ }) }),
356
+ /* @__PURE__ */ jsxRuntime.jsx(
357
+ designSystem.NumberInput,
358
+ {
359
+ id: "llm_temperature",
360
+ value: config.temperature,
361
+ name: "llm_temperature",
362
+ step: 0.1,
363
+ min: MIN_PROVIDER_TEMPERATURE,
364
+ max: MAX_PROVIDER_TEMPERATURE,
365
+ onValueChange: (value) => {
366
+ updateField(
367
+ "temperature",
368
+ clampNumber(
369
+ value,
370
+ DEFAULT_PROVIDER_TEMPERATURE,
371
+ MIN_PROVIDER_TEMPERATURE,
372
+ MAX_PROVIDER_TEMPERATURE
373
+ )
374
+ );
375
+ }
376
+ }
377
+ ),
378
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {}),
379
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
380
+ ]
381
+ }
382
+ ) })
383
+ ] });
384
+ };
385
+ const SettingsPage = () => {
386
+ const { formatMessage } = reactIntl.useIntl();
387
+ const { config, save, restore, clearApiKey, updateField } = useTranslationConfig();
388
+ const providerModels = useProviderModels();
389
+ const loadModels = () => {
390
+ if (!config.apiKeyConfigured && config.apiKey.trim() === "") return;
391
+ providerModels.load({ apiKey: config.apiKey, baseUrl: config.baseUrl });
392
+ };
393
+ react.useEffect(() => {
394
+ loadModels();
395
+ }, [config.apiKeyConfigured]);
396
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
397
+ /* @__PURE__ */ jsxRuntime.jsxs(
398
+ designSystem.Box,
399
+ {
400
+ paddingLeft: 10,
401
+ paddingRight: 10,
402
+ paddingBottom: 8,
403
+ paddingTop: 8,
404
+ "data-strapi-header": true,
405
+ background: "neutral100",
406
+ children: [
407
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, marginBottom: 4, children: [
408
+ /* @__PURE__ */ jsxRuntime.jsx(PluginIcon, { width: 42, height: 42 }),
409
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", fontWeight: "bold", children: formatMessage({
410
+ id: index.getTranslation("settings.page.title"),
411
+ defaultMessage: "AI Translator Configuration"
412
+ }) })
413
+ ] }),
414
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", textColor: "neutral600", fontWeight: "normal", children: formatMessage({
415
+ id: index.getTranslation("settings.page.description"),
416
+ defaultMessage: "Configure AI translation settings for localized Strapi content."
417
+ }) })
418
+ ]
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 10, paddingRight: 10, children: /* @__PURE__ */ jsxRuntime.jsx(
422
+ designSystem.Box,
423
+ {
424
+ paddingLeft: 10,
425
+ paddingRight: 10,
426
+ paddingTop: 8,
427
+ paddingBottom: 8,
428
+ background: "neutral0",
429
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
430
+ "form",
431
+ {
432
+ onSubmit: (e) => {
433
+ e.preventDefault();
434
+ save(config);
435
+ },
436
+ children: [
437
+ /* @__PURE__ */ jsxRuntime.jsx(
438
+ ProviderConnectionFields,
439
+ {
440
+ onClearApiKey: clearApiKey,
441
+ config,
442
+ updateField,
443
+ providerModels: providerModels.models,
444
+ providerModelsError: providerModels.error,
445
+ onLoadModels: loadModels
446
+ }
447
+ ),
448
+ /* @__PURE__ */ jsxRuntime.jsx(TranslationSettingsFields, { config, updateField }),
449
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginTop: 6, justifyContent: "flex-end", children: [
450
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", type: "button", onClick: restore, marginTop: 4, children: formatMessage({
451
+ id: index.getTranslation("settings.form.restore"),
452
+ defaultMessage: "Restore Defaults"
453
+ }) }),
454
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "default", type: "submit", marginTop: 4, children: formatMessage({
455
+ id: index.getTranslation("settings.form.save"),
456
+ defaultMessage: "Save"
457
+ }) })
458
+ ] })
459
+ ]
460
+ }
461
+ )
462
+ }
463
+ ) })
464
+ ] });
465
+ };
466
+ exports.SettingsPage = SettingsPage;
@@ -0,0 +1,97 @@
1
+ const en = {
2
+ "ai-translator.panel.status.idle": "Translate content",
3
+ "ai-translator.panel.status.loading": "Translating...",
4
+ "ai-translator.panel.status.success": "Applied",
5
+ "ai-translator.panel.status.error": "Retry translation",
6
+ "ai-translator.panel.no_other_locales": "Add another locale in Settings → Internationalization to enable AI translation.",
7
+ "ai-translator.actions.accept": "Accept Translation ({count})",
8
+ "ai-translator.actions.cancel": "Cancel",
9
+ "ai-translator.actions.cancel_streaming": "Cancel",
10
+ "ai-translator.dialog.title": "AI translation",
11
+ "ai-translator.dialog.body": "Translatable fields will be translated by AI. Non-translatable fields (media, dates, relations) will be copied from the source locale.",
12
+ "ai-translator.dialog.source.label": "Select source locale",
13
+ "ai-translator.dialog.source.hint": "Content from this locale will be used as the translation source.",
14
+ "ai-translator.dialog.confirm": "Translate",
15
+ "ai-translator.modal.title": "Translation Review",
16
+ "ai-translator.modal.waiting": "Starting translation...",
17
+ "ai-translator.notification.success": "Translation applied - remember to save.",
18
+ "ai-translator.notification.error": "Translation failed: {error}",
19
+ "ai-translator.notification.no-source": "Please select a source language different from the current locale.",
20
+ "ai-translator.notification.no-source-content": 'No content found for source locale "{locale}".',
21
+ "ai-translator.locale.status.none": "No content",
22
+ "ai-translator.locale.status.published": "Published",
23
+ "ai-translator.locale.status.modified": "Modified",
24
+ "ai-translator.locale.status.draft": "Draft",
25
+ "ai-translator.workflow.retrying": "{field} — retrying (attempt {attempt})",
26
+ "ai-translator.workflow.fallback": "{field} — using fallback model",
27
+ "ai-translator.modal.close": "Close",
28
+ "ai-translator.modal.confirm_cancel": "Translation is in progress. Cancel and discard?",
29
+ "ai-translator.modal.completed_in": "Completed in {seconds}s",
30
+ "ai-translator.modal.follow": "Follow latest",
31
+ "ai-translator.modal.failed_fields": "{count} field(s) failed: {fields}",
32
+ "ai-translator.diff.chars": "{originalChars} → {translatedChars} chars",
33
+ "ai-translator.diff.field_status.translated": "Translated",
34
+ "ai-translator.diff.field_status.unchanged": "Unchanged",
35
+ "ai-translator.diff.summary.translated": "{count} translated",
36
+ "ai-translator.diff.summary.unchanged": "{count} unchanged",
37
+ "ai-translator.notification.config.saved": "Settings saved.",
38
+ "ai-translator.notification.config.restored": "Default settings restored.",
39
+ "ai-translator.notification.config.save_failed": "Could not save settings.",
40
+ "ai-translator.plugin.name": "AI Translator",
41
+ "ai-translator.settings.label": "Configuration",
42
+ "ai-translator.settings.page.title": "AI Translator Configuration",
43
+ "ai-translator.settings.page.description": "Configure AI translation settings for localized Strapi content.",
44
+ "ai-translator.settings.section.connection": "Provider Connection",
45
+ "ai-translator.settings.section.translation": "Translation Settings",
46
+ "ai-translator.settings.form.api_key": "API Key",
47
+ "ai-translator.settings.form.api_key_hint_configured": "Currently saved: {preview}. Paste a new key to replace it, or leave blank to keep the current one.",
48
+ "ai-translator.settings.form.api_key_hint_empty": "No key saved. Paste an API key to enable translation. Saved keys are never returned by the API.",
49
+ "ai-translator.settings.form.api_key_placeholder_configured": "Configured - enter a new key to replace it",
50
+ "ai-translator.settings.form.api_key_placeholder_empty": "Not configured",
51
+ "ai-translator.settings.form.base_url": "Base URL",
52
+ "ai-translator.settings.form.base_url_hint": "API endpoint for the model provider.",
53
+ "ai-translator.settings.form.base_url_hint_missing_version": "Most OpenAI-compatible APIs require a version segment (e.g. ending with /v1).",
54
+ "ai-translator.settings.form.model": "Model",
55
+ "ai-translator.settings.form.model_hint": "Primary model for translations.",
56
+ "ai-translator.settings.form.model_empty": "No models were returned by the provider.",
57
+ "ai-translator.settings.form.model_load_failed": "Failed to load provider models.",
58
+ "ai-translator.settings.form.system_prompt": "System prompt",
59
+ "ai-translator.settings.form.system_prompt_hint": "Instructions for the translation provider.",
60
+ "ai-translator.settings.form.temperature": "Temperature",
61
+ "ai-translator.settings.form.temperature_hint": "Higher = more creative, lower = more deterministic.",
62
+ "ai-translator.settings.form.restore": "Restore Defaults",
63
+ "ai-translator.settings.form.save": "Save",
64
+ "ai-translator.settings.form.api_key_remove": "Remove saved key",
65
+ "ai-translator.notification.config.key_removed": "Saved API key removed.",
66
+ "ai-translator.diff.pane.source": "Source",
67
+ "ai-translator.diff.pane.translation": "Translation",
68
+ "ai-translator.modal.full_screen": "Full screen",
69
+ "ai-translator.modal.exit_full_screen": "Exit full screen",
70
+ "ai-translator.diff.invariants.warning": "{count} URL/code/tag elements were altered or dropped by the translation:",
71
+ "ai-translator.diff.untranslated.warning": "{count} paragraph(s) appear untranslated:",
72
+ "ai-translator.diff.use_translation": "Use translation",
73
+ "ai-translator.diff.use_all": "Use all",
74
+ "ai-translator.diff.skip_all": "Skip all",
75
+ "ai-translator.diff.summary.skipped": "{count} skipped",
76
+ "ai-translator.diff.field_status.skipped": "Skipped",
77
+ "ai-translator.diff.pane.current": "Current",
78
+ "ai-translator.diff.overwrite": "Overwrites existing",
79
+ "ai-translator.diff.collapse": "Collapse",
80
+ "ai-translator.diff.expand": "Expand",
81
+ "ai-translator.diff.view.split": "Compare",
82
+ "ai-translator.diff.view.split.hint": "Source text and translation side by side, with inline editing",
83
+ "ai-translator.diff.view.preview": "Preview",
84
+ "ai-translator.diff.view.preview.hint": "Translation only, as it will read after applying",
85
+ "ai-translator.diff.view.diff": "Changes",
86
+ "ai-translator.diff.view.diff.hint": "What will change compared to the current content of this locale",
87
+ "ai-translator.dialog.fields.has_content": "{field} — already has content here",
88
+ "ai-translator.dialog.fields.label": "Fields to translate",
89
+ "ai-translator.dialog.fields.all": "All fields ({count})",
90
+ "ai-translator.actions.retry_failed": "Retry failed ({count})",
91
+ "ai-translator.diff.field_status.pending": "Waiting",
92
+ "ai-translator.diff.field_status.translating": "Translating…",
93
+ "ai-translator.diff.field_status.failed": "Failed"
94
+ };
95
+ export {
96
+ en as default
97
+ };
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const en = {
4
+ "ai-translator.panel.status.idle": "Translate content",
5
+ "ai-translator.panel.status.loading": "Translating...",
6
+ "ai-translator.panel.status.success": "Applied",
7
+ "ai-translator.panel.status.error": "Retry translation",
8
+ "ai-translator.panel.no_other_locales": "Add another locale in Settings → Internationalization to enable AI translation.",
9
+ "ai-translator.actions.accept": "Accept Translation ({count})",
10
+ "ai-translator.actions.cancel": "Cancel",
11
+ "ai-translator.actions.cancel_streaming": "Cancel",
12
+ "ai-translator.dialog.title": "AI translation",
13
+ "ai-translator.dialog.body": "Translatable fields will be translated by AI. Non-translatable fields (media, dates, relations) will be copied from the source locale.",
14
+ "ai-translator.dialog.source.label": "Select source locale",
15
+ "ai-translator.dialog.source.hint": "Content from this locale will be used as the translation source.",
16
+ "ai-translator.dialog.confirm": "Translate",
17
+ "ai-translator.modal.title": "Translation Review",
18
+ "ai-translator.modal.waiting": "Starting translation...",
19
+ "ai-translator.notification.success": "Translation applied - remember to save.",
20
+ "ai-translator.notification.error": "Translation failed: {error}",
21
+ "ai-translator.notification.no-source": "Please select a source language different from the current locale.",
22
+ "ai-translator.notification.no-source-content": 'No content found for source locale "{locale}".',
23
+ "ai-translator.locale.status.none": "No content",
24
+ "ai-translator.locale.status.published": "Published",
25
+ "ai-translator.locale.status.modified": "Modified",
26
+ "ai-translator.locale.status.draft": "Draft",
27
+ "ai-translator.workflow.retrying": "{field} — retrying (attempt {attempt})",
28
+ "ai-translator.workflow.fallback": "{field} — using fallback model",
29
+ "ai-translator.modal.close": "Close",
30
+ "ai-translator.modal.confirm_cancel": "Translation is in progress. Cancel and discard?",
31
+ "ai-translator.modal.completed_in": "Completed in {seconds}s",
32
+ "ai-translator.modal.follow": "Follow latest",
33
+ "ai-translator.modal.failed_fields": "{count} field(s) failed: {fields}",
34
+ "ai-translator.diff.chars": "{originalChars} → {translatedChars} chars",
35
+ "ai-translator.diff.field_status.translated": "Translated",
36
+ "ai-translator.diff.field_status.unchanged": "Unchanged",
37
+ "ai-translator.diff.summary.translated": "{count} translated",
38
+ "ai-translator.diff.summary.unchanged": "{count} unchanged",
39
+ "ai-translator.notification.config.saved": "Settings saved.",
40
+ "ai-translator.notification.config.restored": "Default settings restored.",
41
+ "ai-translator.notification.config.save_failed": "Could not save settings.",
42
+ "ai-translator.plugin.name": "AI Translator",
43
+ "ai-translator.settings.label": "Configuration",
44
+ "ai-translator.settings.page.title": "AI Translator Configuration",
45
+ "ai-translator.settings.page.description": "Configure AI translation settings for localized Strapi content.",
46
+ "ai-translator.settings.section.connection": "Provider Connection",
47
+ "ai-translator.settings.section.translation": "Translation Settings",
48
+ "ai-translator.settings.form.api_key": "API Key",
49
+ "ai-translator.settings.form.api_key_hint_configured": "Currently saved: {preview}. Paste a new key to replace it, or leave blank to keep the current one.",
50
+ "ai-translator.settings.form.api_key_hint_empty": "No key saved. Paste an API key to enable translation. Saved keys are never returned by the API.",
51
+ "ai-translator.settings.form.api_key_placeholder_configured": "Configured - enter a new key to replace it",
52
+ "ai-translator.settings.form.api_key_placeholder_empty": "Not configured",
53
+ "ai-translator.settings.form.base_url": "Base URL",
54
+ "ai-translator.settings.form.base_url_hint": "API endpoint for the model provider.",
55
+ "ai-translator.settings.form.base_url_hint_missing_version": "Most OpenAI-compatible APIs require a version segment (e.g. ending with /v1).",
56
+ "ai-translator.settings.form.model": "Model",
57
+ "ai-translator.settings.form.model_hint": "Primary model for translations.",
58
+ "ai-translator.settings.form.model_empty": "No models were returned by the provider.",
59
+ "ai-translator.settings.form.model_load_failed": "Failed to load provider models.",
60
+ "ai-translator.settings.form.system_prompt": "System prompt",
61
+ "ai-translator.settings.form.system_prompt_hint": "Instructions for the translation provider.",
62
+ "ai-translator.settings.form.temperature": "Temperature",
63
+ "ai-translator.settings.form.temperature_hint": "Higher = more creative, lower = more deterministic.",
64
+ "ai-translator.settings.form.restore": "Restore Defaults",
65
+ "ai-translator.settings.form.save": "Save",
66
+ "ai-translator.settings.form.api_key_remove": "Remove saved key",
67
+ "ai-translator.notification.config.key_removed": "Saved API key removed.",
68
+ "ai-translator.diff.pane.source": "Source",
69
+ "ai-translator.diff.pane.translation": "Translation",
70
+ "ai-translator.modal.full_screen": "Full screen",
71
+ "ai-translator.modal.exit_full_screen": "Exit full screen",
72
+ "ai-translator.diff.invariants.warning": "{count} URL/code/tag elements were altered or dropped by the translation:",
73
+ "ai-translator.diff.untranslated.warning": "{count} paragraph(s) appear untranslated:",
74
+ "ai-translator.diff.use_translation": "Use translation",
75
+ "ai-translator.diff.use_all": "Use all",
76
+ "ai-translator.diff.skip_all": "Skip all",
77
+ "ai-translator.diff.summary.skipped": "{count} skipped",
78
+ "ai-translator.diff.field_status.skipped": "Skipped",
79
+ "ai-translator.diff.pane.current": "Current",
80
+ "ai-translator.diff.overwrite": "Overwrites existing",
81
+ "ai-translator.diff.collapse": "Collapse",
82
+ "ai-translator.diff.expand": "Expand",
83
+ "ai-translator.diff.view.split": "Compare",
84
+ "ai-translator.diff.view.split.hint": "Source text and translation side by side, with inline editing",
85
+ "ai-translator.diff.view.preview": "Preview",
86
+ "ai-translator.diff.view.preview.hint": "Translation only, as it will read after applying",
87
+ "ai-translator.diff.view.diff": "Changes",
88
+ "ai-translator.diff.view.diff.hint": "What will change compared to the current content of this locale",
89
+ "ai-translator.dialog.fields.has_content": "{field} — already has content here",
90
+ "ai-translator.dialog.fields.label": "Fields to translate",
91
+ "ai-translator.dialog.fields.all": "All fields ({count})",
92
+ "ai-translator.actions.retry_failed": "Retry failed ({count})",
93
+ "ai-translator.diff.field_status.pending": "Waiting",
94
+ "ai-translator.diff.field_status.translating": "Translating…",
95
+ "ai-translator.diff.field_status.failed": "Failed"
96
+ };
97
+ exports.default = en;