@parhelia/page-wizard 0.1.10745
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +28 -0
- package/dist/PageWizard.d.ts +78 -0
- package/dist/PageWizard.js +57 -0
- package/dist/PageWizard.js.map +1 -0
- package/dist/SplashScreen.d.ts +1 -0
- package/dist/SplashScreen.js +110 -0
- package/dist/SplashScreen.js.map +1 -0
- package/dist/WizardBoxConnector.d.ts +4 -0
- package/dist/WizardBoxConnector.js +6 -0
- package/dist/WizardBoxConnector.js.map +1 -0
- package/dist/WizardSteps.d.ts +8 -0
- package/dist/WizardSteps.js +174 -0
- package/dist/WizardSteps.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/service.d.ts +15 -0
- package/dist/service.js +29 -0
- package/dist/service.js.map +1 -0
- package/dist/startPageWizardCommand.d.ts +13 -0
- package/dist/startPageWizardCommand.js +31 -0
- package/dist/startPageWizardCommand.js.map +1 -0
- package/dist/steps/BuildPageStep.d.ts +2 -0
- package/dist/steps/BuildPageStep.js +138 -0
- package/dist/steps/BuildPageStep.js.map +1 -0
- package/dist/steps/CollectStep.d.ts +2 -0
- package/dist/steps/CollectStep.js +115 -0
- package/dist/steps/CollectStep.js.map +1 -0
- package/dist/steps/ComponentTypesSelector.d.ts +13 -0
- package/dist/steps/ComponentTypesSelector.js +155 -0
- package/dist/steps/ComponentTypesSelector.js.map +1 -0
- package/dist/steps/Components.d.ts +9 -0
- package/dist/steps/Components.js +89 -0
- package/dist/steps/Components.js.map +1 -0
- package/dist/steps/ContentStep.d.ts +2 -0
- package/dist/steps/ContentStep.js +809 -0
- package/dist/steps/ContentStep.js.map +1 -0
- package/dist/steps/EditButton.d.ts +8 -0
- package/dist/steps/EditButton.js +5 -0
- package/dist/steps/EditButton.js.map +1 -0
- package/dist/steps/FieldEditor.d.ts +5 -0
- package/dist/steps/FieldEditor.js +27 -0
- package/dist/steps/FieldEditor.js.map +1 -0
- package/dist/steps/FindItemsStep.d.ts +2 -0
- package/dist/steps/FindItemsStep.js +294 -0
- package/dist/steps/FindItemsStep.js.map +1 -0
- package/dist/steps/Generate.d.ts +6 -0
- package/dist/steps/Generate.js +31 -0
- package/dist/steps/Generate.js.map +1 -0
- package/dist/steps/ImagesStep.d.ts +2 -0
- package/dist/steps/ImagesStep.js +178 -0
- package/dist/steps/ImagesStep.js.map +1 -0
- package/dist/steps/LayoutStep.d.ts +2 -0
- package/dist/steps/LayoutStep.js +128 -0
- package/dist/steps/LayoutStep.js.map +1 -0
- package/dist/steps/MetaDataStep.d.ts +2 -0
- package/dist/steps/MetaDataStep.js +112 -0
- package/dist/steps/MetaDataStep.js.map +1 -0
- package/dist/steps/SchottSelectImagesStep.d.ts +2 -0
- package/dist/steps/SchottSelectImagesStep.js +55 -0
- package/dist/steps/SchottSelectImagesStep.js.map +1 -0
- package/dist/steps/SelectStep.d.ts +2 -0
- package/dist/steps/SelectStep.js +151 -0
- package/dist/steps/SelectStep.js.map +1 -0
- package/dist/steps/StructureStep.d.ts +2 -0
- package/dist/steps/StructureStep.js +205 -0
- package/dist/steps/StructureStep.js.map +1 -0
- package/dist/steps/TranslateStep.d.ts +2 -0
- package/dist/steps/TranslateStep.js +613 -0
- package/dist/steps/TranslateStep.js.map +1 -0
- package/dist/steps/schema.d.ts +13 -0
- package/dist/steps/schema.js +139 -0
- package/dist/steps/schema.js.map +1 -0
- package/dist/steps/usePageCreator.d.ts +7 -0
- package/dist/steps/usePageCreator.js +285 -0
- package/dist/steps/usePageCreator.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usePageWizard.d.ts +22 -0
- package/dist/usePageWizard.js +69 -0
- package/dist/usePageWizard.js.map +1 -0
- package/dist/utils/dataAccessor.d.ts +57 -0
- package/dist/utils/dataAccessor.js +323 -0
- package/dist/utils/dataAccessor.js.map +1 -0
- package/package.json +48 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState, useCallback } from "react";
|
|
3
|
+
import { Card, Textarea, ActionButton, LanguageSelector, SimpleRichTextEditor, useEditContext, executePrompt } from "@parhelia/core";
|
|
4
|
+
import { createWizardAiContext } from "../service";
|
|
5
|
+
import { Languages, Upload, Wand2, CheckCircle, AlertCircle, Sparkles, RefreshCw, Edit, Save, X, } from "lucide-react";
|
|
6
|
+
export function TranslateStep({ step, data, setData, setStepCompleted, internalState, setInternalState, parentItem, }) {
|
|
7
|
+
const editContext = useEditContext();
|
|
8
|
+
const [translatableFields, setTranslatableFields] = useState({});
|
|
9
|
+
const [translatedFields, setTranslatedFields] = useState({});
|
|
10
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
11
|
+
const [isTranslating, setIsTranslating] = useState(false);
|
|
12
|
+
const [isDetectingLanguage, setIsDetectingLanguage] = useState(false);
|
|
13
|
+
const [translatingFields, setTranslatingFields] = useState(new Set());
|
|
14
|
+
const [checkingFields, setCheckingFields] = useState(new Set());
|
|
15
|
+
const [targetLanguage, setTargetLanguage] = useState(data.targetLanguage || null);
|
|
16
|
+
const [detectedLanguageName, setDetectedLanguageName] = useState(data.detectedLanguageName || "");
|
|
17
|
+
const [componentNames, setComponentNames] = useState({});
|
|
18
|
+
const [editingFields, setEditingFields] = useState(new Set());
|
|
19
|
+
// Get the current item from data (set by previous wizard steps)
|
|
20
|
+
const currentItem = data.currentItem || parentItem;
|
|
21
|
+
// Add source language state - initialize from current item's language
|
|
22
|
+
const [sourceLanguage, setSourceLanguage] = useState(data.sourceLanguage || currentItem?.language || "en");
|
|
23
|
+
// Get document content from previous wizard step
|
|
24
|
+
const documentContent = data.document;
|
|
25
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
26
|
+
const [saveResults, setSaveResults] = useState({ success: [], errors: [] });
|
|
27
|
+
const fetchComponentNames = useCallback(async (itemIds, sourceLanguageParam) => {
|
|
28
|
+
if (!editContext || itemIds.length === 0)
|
|
29
|
+
return;
|
|
30
|
+
const names = {};
|
|
31
|
+
for (const itemId of itemIds) {
|
|
32
|
+
try {
|
|
33
|
+
const item = await editContext.itemsRepository.getItem({
|
|
34
|
+
id: itemId,
|
|
35
|
+
language: sourceLanguageParam,
|
|
36
|
+
version: 0,
|
|
37
|
+
});
|
|
38
|
+
if (item?.name) {
|
|
39
|
+
names[itemId] = item.name;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
names[itemId] = itemId; // Fallback to ID if name not found
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error(`Error fetching item ${itemId}:`, error);
|
|
47
|
+
names[itemId] = itemId; // Fallback to ID on error
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
setComponentNames(names);
|
|
51
|
+
}, [editContext]);
|
|
52
|
+
const fetchTranslatableFields = useCallback(async (sourceLanguageParam) => {
|
|
53
|
+
if (!currentItem || !editContext)
|
|
54
|
+
return;
|
|
55
|
+
setIsLoading(true);
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch("/parhelia/page-wizard/getTranslatableFields", {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
id: currentItem.id,
|
|
64
|
+
language: sourceLanguageParam,
|
|
65
|
+
version: currentItem.version,
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
if (response.ok) {
|
|
69
|
+
const fieldsData = await response.json();
|
|
70
|
+
setTranslatableFields(fieldsData);
|
|
71
|
+
// Initialize translated fields structure
|
|
72
|
+
const initialTranslatedFields = {};
|
|
73
|
+
Object.keys(fieldsData).forEach((itemId) => {
|
|
74
|
+
if (fieldsData[itemId]) {
|
|
75
|
+
initialTranslatedFields[itemId] = fieldsData[itemId].map((field) => ({
|
|
76
|
+
fieldId: field.fieldId,
|
|
77
|
+
translatedValue: "",
|
|
78
|
+
isComplete: undefined,
|
|
79
|
+
missingSuggestions: undefined,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
setTranslatedFields(initialTranslatedFields);
|
|
84
|
+
// Fetch component names for all item IDs
|
|
85
|
+
const itemIds = Object.keys(fieldsData);
|
|
86
|
+
if (itemIds.length > 0) {
|
|
87
|
+
fetchComponentNames(itemIds, sourceLanguageParam);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.error("Failed to fetch translatable fields");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error("Error fetching translatable fields:", error);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
setIsLoading(false);
|
|
99
|
+
}
|
|
100
|
+
}, [currentItem, editContext, fetchComponentNames]);
|
|
101
|
+
// Track the last source language we fetched for to prevent unnecessary re-fetching
|
|
102
|
+
const [lastFetchedSourceLanguage, setLastFetchedSourceLanguage] = useState("");
|
|
103
|
+
// Reset the last fetched source language when the current item changes
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
setLastFetchedSourceLanguage("");
|
|
106
|
+
}, [currentItem]);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
// Only fetch translatable fields if:
|
|
109
|
+
// 1. We have a source language
|
|
110
|
+
// 2. It's different from the last one we fetched for
|
|
111
|
+
// 3. We're not currently saving (to avoid conflicts)
|
|
112
|
+
// 4. We're not currently loading or translating
|
|
113
|
+
if (sourceLanguage &&
|
|
114
|
+
sourceLanguage !== lastFetchedSourceLanguage &&
|
|
115
|
+
!isSaving &&
|
|
116
|
+
!isLoading &&
|
|
117
|
+
!isTranslating) {
|
|
118
|
+
fetchTranslatableFields(sourceLanguage);
|
|
119
|
+
setLastFetchedSourceLanguage(sourceLanguage);
|
|
120
|
+
}
|
|
121
|
+
}, [
|
|
122
|
+
fetchTranslatableFields,
|
|
123
|
+
sourceLanguage,
|
|
124
|
+
lastFetchedSourceLanguage,
|
|
125
|
+
isSaving,
|
|
126
|
+
isLoading,
|
|
127
|
+
isTranslating,
|
|
128
|
+
]);
|
|
129
|
+
const detectLanguage = async () => {
|
|
130
|
+
if (!editContext) {
|
|
131
|
+
console.warn("Edit context not available for language detection");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setIsDetectingLanguage(true);
|
|
135
|
+
try {
|
|
136
|
+
const prompt = `Analyze the following text and identify the language it is written in. Return only the language name in English (e.g., "Spanish", "French", "German", "Italian", etc.). Do not include any other text or explanation.
|
|
137
|
+
|
|
138
|
+
Text to analyze:
|
|
139
|
+
${documentContent.substring(0, 1000)}`;
|
|
140
|
+
const abortController = new AbortController();
|
|
141
|
+
const result = await executePrompt([
|
|
142
|
+
{
|
|
143
|
+
content: prompt,
|
|
144
|
+
role: "user",
|
|
145
|
+
name: "user",
|
|
146
|
+
id: crypto.randomUUID(),
|
|
147
|
+
},
|
|
148
|
+
], { editContext, createAiContext: createWizardAiContext }, { model: step.fields.aiModel || undefined }, { signal: abortController.signal }, (response) => {
|
|
149
|
+
try {
|
|
150
|
+
const content = response.content ||
|
|
151
|
+
response.messages?.[response.messages.length - 1]?.content;
|
|
152
|
+
if (content) {
|
|
153
|
+
const languageName = content.trim();
|
|
154
|
+
setDetectedLanguageName(languageName);
|
|
155
|
+
// Try to find matching Language object from available languages
|
|
156
|
+
const availableLanguages = editContext?.itemLanguages || [];
|
|
157
|
+
const matchingLanguage = availableLanguages.find((lang) => lang.name.toLowerCase().includes(languageName.toLowerCase()));
|
|
158
|
+
if (matchingLanguage) {
|
|
159
|
+
setTargetLanguage(matchingLanguage);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (parseError) {
|
|
164
|
+
console.error("Error parsing language detection response:", parseError);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
if (result && result.messages && result.messages.length > 0) {
|
|
168
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
169
|
+
if (lastMessage && lastMessage.content) {
|
|
170
|
+
const languageName = lastMessage.content.trim();
|
|
171
|
+
setDetectedLanguageName(languageName);
|
|
172
|
+
// Try to find matching Language object from available languages
|
|
173
|
+
const availableLanguages = editContext?.itemLanguages || [];
|
|
174
|
+
const matchingLanguage = availableLanguages.find((lang) => lang.name.toLowerCase().includes(languageName.toLowerCase()));
|
|
175
|
+
if (matchingLanguage) {
|
|
176
|
+
setTargetLanguage(matchingLanguage);
|
|
177
|
+
}
|
|
178
|
+
console.log(`Detected target language: ${languageName}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error("Language detection error:", error);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
setIsDetectingLanguage(false);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
// Keep track of the last document content we detected for
|
|
190
|
+
const [lastDetectedDocument, setLastDetectedDocument] = useState("");
|
|
191
|
+
// Auto-detect language when document content is available or changes
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (documentContent &&
|
|
194
|
+
!isDetectingLanguage &&
|
|
195
|
+
documentContent !== lastDetectedDocument) {
|
|
196
|
+
// Reset detected language when document changes, then detect again
|
|
197
|
+
if (detectedLanguageName && lastDetectedDocument) {
|
|
198
|
+
setDetectedLanguageName("");
|
|
199
|
+
setTargetLanguage(null);
|
|
200
|
+
}
|
|
201
|
+
setLastDetectedDocument(documentContent);
|
|
202
|
+
detectLanguage();
|
|
203
|
+
}
|
|
204
|
+
}, [documentContent]);
|
|
205
|
+
const handleTranslate = async () => {
|
|
206
|
+
if (!targetLanguage?.languageCode) {
|
|
207
|
+
console.warn("Missing target language");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (!editContext) {
|
|
211
|
+
console.error("Edit context not available");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
setIsTranslating(true);
|
|
215
|
+
try {
|
|
216
|
+
// Prepare the fields data for the AI prompt
|
|
217
|
+
const fieldsForTranslation = Object.entries(translatableFields).flatMap(([itemId, fields]) => fields.map((field) => ({
|
|
218
|
+
itemId,
|
|
219
|
+
fieldId: field.fieldId,
|
|
220
|
+
fieldName: field.fieldName,
|
|
221
|
+
originalValue: field.value,
|
|
222
|
+
})));
|
|
223
|
+
const prompt = `You are a translation assistant. You have been given a document in ${targetLanguage.name} and a list of fields that need to be translated from ${sourceLanguage} to ${targetLanguage.name}.
|
|
224
|
+
|
|
225
|
+
Your task is to:
|
|
226
|
+
1. Match the content from the provided document to the fields that need translation
|
|
227
|
+
2. Return ONLY the translated text from the document - do not change, modify, or create any new text
|
|
228
|
+
3. If you cannot find a suitable translation in the document for a field, leave the translatedValue empty
|
|
229
|
+
|
|
230
|
+
Document content:
|
|
231
|
+
${documentContent}
|
|
232
|
+
|
|
233
|
+
Fields to translate:
|
|
234
|
+
${JSON.stringify(fieldsForTranslation, null, 2)}
|
|
235
|
+
|
|
236
|
+
Return your response as a JSON object in this exact format:
|
|
237
|
+
{
|
|
238
|
+
"translations": [
|
|
239
|
+
{
|
|
240
|
+
"itemId": "item-id",
|
|
241
|
+
"fieldId": "field-id",
|
|
242
|
+
"translatedValue": "exact text from document"
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}`;
|
|
246
|
+
const abortController = new AbortController();
|
|
247
|
+
const result = await executePrompt([
|
|
248
|
+
{
|
|
249
|
+
content: prompt,
|
|
250
|
+
role: "user",
|
|
251
|
+
name: "user",
|
|
252
|
+
id: crypto.randomUUID(),
|
|
253
|
+
},
|
|
254
|
+
], { editContext, createAiContext: createWizardAiContext }, { model: step.fields.aiModel || undefined, stream: false }, { signal: abortController.signal });
|
|
255
|
+
if (result && result.messages && result.messages.length > 0) {
|
|
256
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
257
|
+
if (lastMessage && lastMessage.content) {
|
|
258
|
+
try {
|
|
259
|
+
const parsedResponse = JSON.parse(lastMessage.content);
|
|
260
|
+
const translations = parsedResponse.translations || [];
|
|
261
|
+
// Update translated fields with AI response
|
|
262
|
+
const updatedTranslatedFields = {
|
|
263
|
+
...translatedFields,
|
|
264
|
+
};
|
|
265
|
+
translations.forEach((translation) => {
|
|
266
|
+
const { itemId, fieldId, translatedValue } = translation;
|
|
267
|
+
if (updatedTranslatedFields[itemId]) {
|
|
268
|
+
const fieldIndex = updatedTranslatedFields[itemId].findIndex((f) => f.fieldId === fieldId);
|
|
269
|
+
if (fieldIndex !== -1 &&
|
|
270
|
+
updatedTranslatedFields[itemId][fieldIndex]) {
|
|
271
|
+
updatedTranslatedFields[itemId][fieldIndex].translatedValue =
|
|
272
|
+
translatedValue || "";
|
|
273
|
+
updatedTranslatedFields[itemId][fieldIndex].missingSuggestions = undefined;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
setTranslatedFields(updatedTranslatedFields);
|
|
278
|
+
console.log(`Translation complete: ${translations.length} fields translated`);
|
|
279
|
+
// Check translation completeness for all translated fields
|
|
280
|
+
translations.forEach((translation) => {
|
|
281
|
+
const { itemId, fieldId, translatedValue } = translation;
|
|
282
|
+
if (translatedValue && translatedValue.trim()) {
|
|
283
|
+
const originalField = fieldsForTranslation.find((f) => f.itemId === itemId && f.fieldId === fieldId);
|
|
284
|
+
if (originalField) {
|
|
285
|
+
checkTranslationCompleteness(itemId, fieldId, originalField.originalValue, translatedValue, originalField.fieldName);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch (parseError) {
|
|
291
|
+
console.error("Failed to parse AI response:", parseError);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
console.error("Translation error:", error);
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
setIsTranslating(false);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const handleSingleFieldTranslation = async (itemId, fieldId, field) => {
|
|
304
|
+
if (!targetLanguage?.languageCode || !editContext) {
|
|
305
|
+
console.warn("Missing target language or edit context");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const fieldKey = `${itemId}-${fieldId}`;
|
|
309
|
+
setTranslatingFields((prev) => new Set(prev).add(fieldKey));
|
|
310
|
+
try {
|
|
311
|
+
// Create a prompt for this specific field
|
|
312
|
+
const prompt = `You are a translation assistant. You have been given a document in ${targetLanguage.name} and a single field that needs to be translated from ${sourceLanguage} to ${targetLanguage.name}.
|
|
313
|
+
|
|
314
|
+
Your task is to:
|
|
315
|
+
1. Find content from the provided document that matches this field
|
|
316
|
+
2. Return ONLY the translated text from the document - do not change, modify, or create any new text
|
|
317
|
+
3. If you cannot find a suitable translation in the document for this field, return an empty string
|
|
318
|
+
|
|
319
|
+
Document content:
|
|
320
|
+
${documentContent}
|
|
321
|
+
|
|
322
|
+
Field to translate:
|
|
323
|
+
- Field Name: ${field.fieldName}
|
|
324
|
+
- Value (${sourceLanguage}): ${field.value}
|
|
325
|
+
|
|
326
|
+
Return your response as a JSON object in this exact format:
|
|
327
|
+
{
|
|
328
|
+
"translatedValue": "exact text from document or empty string if no match found"
|
|
329
|
+
}`;
|
|
330
|
+
const abortController = new AbortController();
|
|
331
|
+
const result = await executePrompt([
|
|
332
|
+
{
|
|
333
|
+
content: prompt,
|
|
334
|
+
role: "user",
|
|
335
|
+
name: "user",
|
|
336
|
+
id: crypto.randomUUID(),
|
|
337
|
+
},
|
|
338
|
+
], { editContext, createAiContext: createWizardAiContext }, { model: step.fields.aiModel || undefined, stream: false }, { signal: abortController.signal });
|
|
339
|
+
if (result && result.messages && result.messages.length > 0) {
|
|
340
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
341
|
+
if (lastMessage && lastMessage.content) {
|
|
342
|
+
try {
|
|
343
|
+
const parsedResponse = JSON.parse(lastMessage.content);
|
|
344
|
+
const translatedValue = parsedResponse.translatedValue || "";
|
|
345
|
+
// Update this specific field's translation
|
|
346
|
+
setTranslatedFields((prev) => ({
|
|
347
|
+
...prev,
|
|
348
|
+
[itemId]: prev[itemId]?.map((f) => f.fieldId === fieldId
|
|
349
|
+
? { ...f, translatedValue, missingSuggestions: undefined }
|
|
350
|
+
: f) || [],
|
|
351
|
+
}));
|
|
352
|
+
console.log(`Single field translation complete for ${field.fieldName}`);
|
|
353
|
+
// Check translation completeness after translation
|
|
354
|
+
if (translatedValue.trim()) {
|
|
355
|
+
checkTranslationCompleteness(itemId, fieldId, field.value, translatedValue, field.fieldName);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (parseError) {
|
|
359
|
+
console.error("Failed to parse AI response:", parseError);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
console.error("Single field translation error:", error);
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
setTranslatingFields((prev) => {
|
|
369
|
+
const newSet = new Set(prev);
|
|
370
|
+
newSet.delete(fieldKey);
|
|
371
|
+
return newSet;
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
const checkTranslationCompleteness = async (itemId, fieldId, originalText, translatedText, fieldName) => {
|
|
376
|
+
if (!editContext || !translatedText.trim()) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
const fieldKey = `${itemId}-${fieldId}`;
|
|
380
|
+
setCheckingFields((prev) => new Set(prev).add(fieldKey));
|
|
381
|
+
try {
|
|
382
|
+
const prompt = `You are a translation quality checker. Your task is to evaluate if a translation is complete and accurate.
|
|
383
|
+
|
|
384
|
+
Original text (${sourceLanguage}):
|
|
385
|
+
${originalText}
|
|
386
|
+
|
|
387
|
+
Translated text (${targetLanguage?.name || "target language"}):
|
|
388
|
+
${translatedText}
|
|
389
|
+
|
|
390
|
+
Field context: ${fieldName}
|
|
391
|
+
|
|
392
|
+
Is the translation complete (no missing content)? Do not assess the quality of the translation, just if it is complete. Only report missing sentences missing completely.
|
|
393
|
+
Do not report missing words, phrases, or content that is present in the original text. We just want to make sure we copied all content from the translation document.
|
|
394
|
+
|
|
395
|
+
If the translation is incomplete, identify specific content, phrases, or meaning that exists in the original but is missing in the translation, and suggest the exact text that should be added to make the translation complete.
|
|
396
|
+
|
|
397
|
+
Return your response as a JSON object in this exact format:
|
|
398
|
+
{
|
|
399
|
+
"isComplete": true/false,
|
|
400
|
+
"reason": "Brief explanation of your assessment",
|
|
401
|
+
"missingSuggestions": "If isComplete is false, provide specific suggestions for what needs to be added to complete the translation. Include the actual text that should be added. If isComplete is true, leave this empty."
|
|
402
|
+
}`;
|
|
403
|
+
const abortController = new AbortController();
|
|
404
|
+
const result = await executePrompt([
|
|
405
|
+
{
|
|
406
|
+
content: prompt,
|
|
407
|
+
role: "user",
|
|
408
|
+
name: "user",
|
|
409
|
+
id: crypto.randomUUID(),
|
|
410
|
+
},
|
|
411
|
+
], { editContext, createAiContext: createWizardAiContext }, { model: step.fields.aiModel || undefined, stream: false }, { signal: abortController.signal });
|
|
412
|
+
if (result && result.messages && result.messages.length > 0) {
|
|
413
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
414
|
+
if (lastMessage && lastMessage.content) {
|
|
415
|
+
try {
|
|
416
|
+
const parsedResponse = JSON.parse(lastMessage.content);
|
|
417
|
+
const isComplete = parsedResponse.isComplete || false;
|
|
418
|
+
const missingSuggestions = parsedResponse.missingSuggestions || "";
|
|
419
|
+
// Update the field's completeness status and missing suggestions
|
|
420
|
+
setTranslatedFields((prev) => ({
|
|
421
|
+
...prev,
|
|
422
|
+
[itemId]: prev[itemId]?.map((f) => f.fieldId === fieldId
|
|
423
|
+
? {
|
|
424
|
+
...f,
|
|
425
|
+
isComplete,
|
|
426
|
+
missingSuggestions: isComplete
|
|
427
|
+
? undefined
|
|
428
|
+
: missingSuggestions,
|
|
429
|
+
}
|
|
430
|
+
: f) || [],
|
|
431
|
+
}));
|
|
432
|
+
console.log(`Translation completeness check for ${fieldName}: ${isComplete ? "Complete" : "Incomplete"} - ${parsedResponse.reason}`);
|
|
433
|
+
if (!isComplete && missingSuggestions) {
|
|
434
|
+
console.log(`Missing content suggestions for ${fieldName}: ${missingSuggestions}`);
|
|
435
|
+
}
|
|
436
|
+
return isComplete;
|
|
437
|
+
}
|
|
438
|
+
catch (parseError) {
|
|
439
|
+
console.error("Failed to parse completeness check response:", parseError);
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
console.error("Translation completeness check error:", error);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
setCheckingFields((prev) => {
|
|
452
|
+
const newSet = new Set(prev);
|
|
453
|
+
newSet.delete(fieldKey);
|
|
454
|
+
return newSet;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
const isRichTextField = (fieldType) => {
|
|
459
|
+
return (fieldType?.toLowerCase() === "rich text" ||
|
|
460
|
+
fieldType?.toLowerCase() === "html");
|
|
461
|
+
};
|
|
462
|
+
const toggleFieldEdit = (itemId, fieldId) => {
|
|
463
|
+
const fieldKey = `${itemId}-${fieldId}`;
|
|
464
|
+
setEditingFields((prev) => {
|
|
465
|
+
const newSet = new Set(prev);
|
|
466
|
+
if (newSet.has(fieldKey)) {
|
|
467
|
+
newSet.delete(fieldKey);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
newSet.add(fieldKey);
|
|
471
|
+
}
|
|
472
|
+
return newSet;
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
const handleFieldChange = (itemId, fieldId, value) => {
|
|
476
|
+
setTranslatedFields((prev) => ({
|
|
477
|
+
...prev,
|
|
478
|
+
[itemId]: prev[itemId]?.map((field) => field.fieldId === fieldId
|
|
479
|
+
? {
|
|
480
|
+
...field,
|
|
481
|
+
translatedValue: value,
|
|
482
|
+
isComplete: undefined,
|
|
483
|
+
missingSuggestions: undefined,
|
|
484
|
+
}
|
|
485
|
+
: field) || [],
|
|
486
|
+
}));
|
|
487
|
+
};
|
|
488
|
+
const handleSaveTranslations = async () => {
|
|
489
|
+
if (!editContext || !currentItem || !targetLanguage?.languageCode) {
|
|
490
|
+
console.error("Missing required data for saving translations");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
setIsSaving(true);
|
|
494
|
+
setSaveResults({ success: [], errors: [] });
|
|
495
|
+
try {
|
|
496
|
+
// Get all items that have translations
|
|
497
|
+
const itemsToTranslate = Object.entries(translatedFields).filter(([itemId, fields]) => fields.some((field) => field.translatedValue.trim() !== ""));
|
|
498
|
+
console.log(`Saving translations for ${itemsToTranslate.length} items to language: ${targetLanguage.languageCode}`);
|
|
499
|
+
for (const [itemId, translatedFieldsList] of itemsToTranslate) {
|
|
500
|
+
try {
|
|
501
|
+
// Create ItemDescriptor for the component item
|
|
502
|
+
const componentItemDescriptor = {
|
|
503
|
+
id: itemId,
|
|
504
|
+
language: targetLanguage.languageCode,
|
|
505
|
+
version: 0,
|
|
506
|
+
};
|
|
507
|
+
// Pre-flight version creation removed; server-side or subsequent flows will ensure version existence as needed
|
|
508
|
+
// Update field values with translations
|
|
509
|
+
const fieldsWithTranslations = translatedFieldsList.filter((field) => field.translatedValue.trim() !== "");
|
|
510
|
+
for (const translatedField of fieldsWithTranslations) {
|
|
511
|
+
const fieldDescriptor = {
|
|
512
|
+
fieldId: translatedField.fieldId,
|
|
513
|
+
item: {
|
|
514
|
+
id: itemId,
|
|
515
|
+
language: targetLanguage.languageCode,
|
|
516
|
+
version: 0,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
console.log(`Updating field ${translatedField.fieldId} for item ${itemId}`);
|
|
520
|
+
await editContext.operations.editField({
|
|
521
|
+
field: fieldDescriptor,
|
|
522
|
+
value: translatedField.translatedValue,
|
|
523
|
+
rawValue: translatedField.translatedValue,
|
|
524
|
+
refresh: "none",
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
setSaveResults((prev) => ({
|
|
528
|
+
...prev,
|
|
529
|
+
success: [
|
|
530
|
+
...prev.success,
|
|
531
|
+
`${itemId} (${fieldsWithTranslations.length} fields)`,
|
|
532
|
+
],
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
console.error(`Error translating item ${itemId}:`, error);
|
|
537
|
+
setSaveResults((prev) => ({
|
|
538
|
+
...prev,
|
|
539
|
+
errors: [
|
|
540
|
+
...prev.errors,
|
|
541
|
+
`${itemId}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
542
|
+
],
|
|
543
|
+
}));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
console.log("Translation saving completed");
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
console.error("Error during translation saving:", error);
|
|
550
|
+
setSaveResults((prev) => ({
|
|
551
|
+
...prev,
|
|
552
|
+
errors: [
|
|
553
|
+
...prev.errors,
|
|
554
|
+
`General error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
555
|
+
],
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
finally {
|
|
559
|
+
setIsSaving(false);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
// Check if step is completed (has translations and they've been saved successfully)
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
const hasTranslations = Object.values(translatedFields).some((fields) => fields.some((field) => field.translatedValue.trim() !== ""));
|
|
565
|
+
const hasSuccessfulSave = saveResults.success.length > 0;
|
|
566
|
+
// Step is completed if we have translations and at least some have been saved successfully
|
|
567
|
+
setStepCompleted(hasTranslations && hasSuccessfulSave);
|
|
568
|
+
}, [translatedFields, saveResults, setStepCompleted]);
|
|
569
|
+
// Save translated fields and save results to wizard data
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
setData((prev) => ({
|
|
572
|
+
...prev,
|
|
573
|
+
translatedFields,
|
|
574
|
+
saveResults,
|
|
575
|
+
targetLanguage,
|
|
576
|
+
detectedLanguageName,
|
|
577
|
+
sourceLanguage,
|
|
578
|
+
}));
|
|
579
|
+
}, [
|
|
580
|
+
translatedFields,
|
|
581
|
+
saveResults,
|
|
582
|
+
targetLanguage,
|
|
583
|
+
detectedLanguageName,
|
|
584
|
+
sourceLanguage,
|
|
585
|
+
setData,
|
|
586
|
+
]);
|
|
587
|
+
const totalFields = Object.values(translatableFields).reduce((total, fields) => total + fields.length, 0);
|
|
588
|
+
const translatedCount = Object.values(translatedFields).reduce((total, fields) => total +
|
|
589
|
+
fields.filter((field) => field.translatedValue.trim() !== "").length, 0);
|
|
590
|
+
return (_jsxs("div", { className: "mx-auto flex flex-col gap-6 p-6", children: [_jsxs(Card, { title: "Translation Setup", description: "Select source and target languages, target language will be auto-detected from the document", icon: _jsx(Languages, { className: "h-5 w-5" }), children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-2 block text-sm font-medium", children: "Source Language" }), _jsx(LanguageSelector, { selectedLanguage: sourceLanguage, onLanguageSelected: (language) => setSourceLanguage(language.languageCode), disabled: false, showAllLanguagesSwitch: false, showAllLanguages: true }), _jsx("p", { className: "mt-1 text-xs text-gray-600", children: "Select the language of the content to translate from" })] }), _jsxs("div", { children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("label", { className: "block text-sm font-medium", children: "Target Language" }), isDetectingLanguage && (_jsxs("div", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(RefreshCw, { className: "h-3 w-3 animate-spin" }), "Detecting..."] })), detectedLanguageName && !isDetectingLanguage && (_jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "Auto-detected"] }))] }), _jsxs("div", { className: "relative", children: [_jsx(LanguageSelector, { selectedLanguage: targetLanguage?.languageCode || "", onLanguageSelected: (language) => setTargetLanguage(language), disabled: false, showAllLanguagesSwitch: false, showAllLanguages: true }), detectedLanguageName && (_jsx("button", { type: "button", onClick: detectLanguage, className: "absolute top-1/2 right-2 -translate-y-1/2 transform text-blue-600 hover:text-blue-800", title: "Re-detect language", children: _jsx(RefreshCw, { className: "h-4 w-4" }) }))] }), detectedLanguageName && (_jsxs("p", { className: "mt-1 text-xs text-green-600", children: ["Detected: ", detectedLanguageName, targetLanguage && ` → Selected: ${targetLanguage.name}`] }))] })] }), _jsxs(ActionButton, { onClick: handleTranslate, isLoading: isTranslating, disabled: !targetLanguage?.languageCode, className: "mt-4", variant: "outline", children: [_jsx(Wand2, { className: "mr-2 h-4 w-4" }), "Translate Fields"] })] }), _jsx(Card, { title: "Translation Progress", description: `${translatedCount} of ${totalFields} fields translated`, icon: _jsx(Upload, { className: "h-5 w-5" }), children: isLoading ? (_jsx("div", { className: "py-8 text-center", children: _jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx(RefreshCw, { className: "h-4 w-4 animate-spin" }), "Loading translatable fields..."] }) })) : isTranslating ? (_jsx("div", { className: "py-8 text-center", children: _jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx(RefreshCw, { className: "h-4 w-4 animate-spin" }), "Translating fields..."] }) })) : isSaving ? (_jsx("div", { className: "py-8 text-center", children: _jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx(RefreshCw, { className: "h-4 w-4 animate-spin" }), "Saving translations..."] }) })) : saveResults.success.length > 0 || saveResults.errors.length > 0 ? (_jsxs("div", { className: "space-y-4", children: [saveResults.success.length > 0 && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-4", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 font-medium text-green-800", children: [_jsx(CheckCircle, { className: "h-4 w-4" }), "Successfully Saved (", saveResults.success.length, " items)"] }), _jsx("ul", { className: "list-inside list-disc text-sm text-green-700", children: saveResults.success.map((item, index) => (_jsx("li", { children: item }, index))) })] })), saveResults.errors.length > 0 && (_jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 p-4", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 font-medium text-red-800", children: [_jsx(AlertCircle, { className: "h-4 w-4" }), "Errors (", saveResults.errors.length, ")"] }), _jsx("ul", { className: "list-inside list-disc text-sm text-red-700", children: saveResults.errors.map((error, index) => (_jsx("li", { children: error }, index))) })] }))] })) : translatedCount > 0 ? (_jsx("div", { className: "space-y-6", children: Object.entries(translatableFields).map(([itemId, fields]) => (_jsxs("div", { className: "rounded-lg p-4", children: [_jsx("h4", { className: "mb-4 font-medium", children: componentNames[itemId] || "Component" }), _jsx("div", { className: "space-y-4", children: fields.map((field) => {
|
|
591
|
+
const translatedField = translatedFields[itemId]?.find((f) => f.fieldId === field.fieldId);
|
|
592
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx("div", { children: _jsxs("label", { className: "block text-xs font-medium", children: [field.fieldName, " (", field.fieldType, ")"] }) }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "block text-xs font-medium", children: field.fieldName }), translatedField?.translatedValue && (_jsx("div", { className: "flex items-center", children: checkingFields.has(`${itemId}-${field.fieldId}`) ? (_jsx(RefreshCw, { className: "h-3 w-3 animate-spin text-gray-500" })) : translatedField.isComplete === true ? (_jsx("span", { title: "Translation is complete", children: _jsx(CheckCircle, { className: "h-3 w-3 text-green-600" }) })) : translatedField.isComplete === false ? (_jsx("span", { title: "Translation may be incomplete", children: _jsx(AlertCircle, { className: "h-3 w-3 text-red-600" }) })) : (_jsx("div", { className: "h-3 w-3" })) }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [!editingFields.has(`${itemId}-${field.fieldId}`) &&
|
|
593
|
+
translatedField?.translatedValue && (_jsxs("button", { type: "button", onClick: () => checkTranslationCompleteness(itemId, field.fieldId, field.value, translatedField.translatedValue, field.fieldName), disabled: checkingFields.has(`${itemId}-${field.fieldId}`) ||
|
|
594
|
+
!translatedField.translatedValue.trim(), className: "flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-600 hover:bg-gray-50 hover:text-gray-800 disabled:cursor-not-allowed disabled:text-gray-400 disabled:hover:bg-transparent", title: "Check translation completeness", children: [checkingFields.has(`${itemId}-${field.fieldId}`) ? (_jsx(RefreshCw, { className: "h-3 w-3 animate-spin" })) : (_jsx(CheckCircle, { className: "h-3 w-3" })), "Check"] })), !editingFields.has(`${itemId}-${field.fieldId}`) && (_jsxs("button", { type: "button", onClick: () => handleSingleFieldTranslation(itemId, field.fieldId, field), disabled: !targetLanguage?.languageCode ||
|
|
595
|
+
translatingFields.has(`${itemId}-${field.fieldId}`) ||
|
|
596
|
+
isTranslating, className: "flex items-center gap-1 rounded px-2 py-1 text-xs text-blue-600 hover:bg-blue-50 hover:text-blue-800 disabled:cursor-not-allowed disabled:text-gray-400 disabled:hover:bg-transparent", title: "Retranslate this field", children: [translatingFields.has(`${itemId}-${field.fieldId}`) ? (_jsx(RefreshCw, { className: "h-3 w-3 animate-spin" })) : (_jsx(Wand2, { className: "h-3 w-3" })), "Retranslate"] })), editingFields.has(`${itemId}-${field.fieldId}`) ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => toggleFieldEdit(itemId, field.fieldId), className: "flex items-center gap-1 rounded px-2 py-1 text-xs text-green-600 hover:bg-green-50 hover:text-green-800", title: "Save changes", children: [_jsx(Save, { className: "h-3 w-3", strokeWidth: 1 }), "Save"] }), _jsxs("button", { type: "button", onClick: () => toggleFieldEdit(itemId, field.fieldId), className: "flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-600 hover:bg-gray-50 hover:text-gray-800", title: "Cancel edit", children: [_jsx(X, { className: "h-3 w-3", strokeWidth: 1 }), "Cancel"] })] })) : (_jsxs("button", { type: "button", onClick: () => toggleFieldEdit(itemId, field.fieldId), className: "flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-600 hover:bg-gray-50 hover:text-gray-800", title: "Edit translation", children: [_jsx(Edit, { className: "h-3 w-3", strokeWidth: 1 }), "Edit"] }))] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx("div", { className: "rounded border bg-gray-50 p-1 text-xs", children: isRichTextField(field.fieldType) ? (_jsx("div", { className: "rich-text-content", dangerouslySetInnerHTML: {
|
|
597
|
+
__html: field.value,
|
|
598
|
+
} })) : (field.value) }), _jsxs("div", { className: "space-y-2", children: [translatedField?.isComplete === false &&
|
|
599
|
+
translatedField?.missingSuggestions && (_jsxs("div", { className: "rounded border border-orange-200 bg-orange-50 p-2", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1 text-xs font-medium text-orange-800", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "AI Suggestions for Missing Content:"] }), _jsx("p", { className: "text-xs text-orange-700 select-text", children: translatedField.missingSuggestions })] })), editingFields.has(`${itemId}-${field.fieldId}`) ? (_jsx("div", { children: isRichTextField(field.fieldType) ? (_jsx(SimpleRichTextEditor, { value: translatedField?.translatedValue || "", onChange: (value) => handleFieldChange(itemId, field.fieldId, value), readOnly: false, placeholder: "Enter translation here...", className: "min-h-[70px] text-xs" })) : (_jsx(Textarea, { value: translatedField?.translatedValue || "", onChange: (e) => handleFieldChange(itemId, field.fieldId, e.target.value), placeholder: "Enter translation here...", rows: 4, className: "h-full min-h-[70px] w-full text-xs" })) })) : (_jsx("div", { className: "h-full min-h-[70px] rounded border bg-gray-50 p-1 text-xs", children: translatedField?.translatedValue ? (isRichTextField(field.fieldType) ? (_jsx("div", { className: "rich-text-content", dangerouslySetInnerHTML: {
|
|
600
|
+
__html: translatedField.translatedValue,
|
|
601
|
+
} })) : (translatedField.translatedValue)) : (_jsx("span", { className: "text-gray-400", children: "Translation will appear here..." })) }))] })] })] }, field.fieldId));
|
|
602
|
+
}) })] }, itemId))) })) : (_jsx("div", { className: "py-8 text-center text-sm text-gray-500", children: Object.keys(translatableFields).length > 0
|
|
603
|
+
? "Click 'Translate Fields' to start the translation process."
|
|
604
|
+
: "No translatable fields found." })) }), translatedCount > 0 &&
|
|
605
|
+
saveResults.success.length === 0 &&
|
|
606
|
+
saveResults.errors.length === 0 && (_jsx(Card, { title: "Save Translations", description: "Create language versions and apply translations", icon: _jsx(CheckCircle, { className: "h-5 w-5" }), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-4", children: [_jsx("h4", { className: "mb-2 font-medium text-blue-900", children: "Ready to Save" }), _jsxs("p", { className: "text-sm text-blue-800", children: [translatedCount, " fields ready to be saved to", " ", _jsx("strong", { children: targetLanguage?.name }), " language", detectedLanguageName &&
|
|
607
|
+
targetLanguage?.name
|
|
608
|
+
?.toLowerCase()
|
|
609
|
+
.includes(detectedLanguageName.toLowerCase()) && (_jsx("span", { className: "text-green-700", children: " (auto-detected)" })), ". This will update translated field values for all selected items."] })] }), _jsxs(ActionButton, { onClick: handleSaveTranslations, isLoading: isSaving, disabled: translatedCount === 0 || !targetLanguage?.languageCode, className: "w-full", variant: "outline", children: [_jsx(CheckCircle, { className: "mr-2 h-4 w-4" }), isSaving
|
|
610
|
+
? "Saving Translations..."
|
|
611
|
+
: `Save ${translatedCount} Translations to ${targetLanguage?.name || "Target Language"}`] })] }) }))] }));
|
|
612
|
+
}
|
|
613
|
+
//# sourceMappingURL=TranslateStep.js.map
|