@alpaca-editor/core 1.0.4017 → 1.0.4026
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SimpleLanguageSelector.d.ts +2 -1
- package/dist/components/SimpleLanguageSelector.js +9 -2
- package/dist/components/SimpleLanguageSelector.js.map +1 -1
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/tooltip.d.ts +3 -1
- package/dist/components/ui/tooltip.js +2 -2
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/config/config.js +4 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/editor/ContentTree.js +1 -1
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/ContextMenu.js +26 -0
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/FieldHistory.js +1 -1
- package/dist/editor/FieldHistory.js.map +1 -1
- package/dist/editor/FieldListField.js +2 -2
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/Terminal.js +3 -1
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +19 -10
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +63 -5
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.js +7 -7
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/ai/AiToolCall.js +3 -3
- package/dist/editor/ai/AiToolCall.js.map +1 -1
- package/dist/editor/client/EditorClient.js +5 -1
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -0
- package/dist/editor/client/operations.js +7 -0
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +1 -1
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/field-types/ImageFieldEditor.js +1 -1
- package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +1 -1
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/PictureFieldEditor.js +1 -1
- package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +1 -1
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +16 -17
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +1 -1
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/richtext/components/SimpleDropdown.d.ts +18 -0
- package/dist/editor/field-types/richtext/components/SimpleDropdown.js +71 -0
- package/dist/editor/field-types/richtext/components/SimpleDropdown.js.map +1 -0
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.d.ts +5 -0
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js +359 -0
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js.map +1 -0
- package/dist/editor/field-types/richtext/components/SimpleToolbar.d.ts +16 -0
- package/dist/editor/field-types/richtext/components/SimpleToolbar.js +181 -0
- package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -0
- package/dist/editor/field-types/richtext/components/SimpleToolbarButton.d.ts +9 -0
- package/dist/editor/field-types/richtext/components/SimpleToolbarButton.js +14 -0
- package/dist/editor/field-types/richtext/components/SimpleToolbarButton.js.map +1 -0
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +4 -0
- package/dist/editor/field-types/richtext/contextMenuFactory.js +193 -0
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -0
- package/dist/editor/field-types/richtext/index.d.ts +6 -5
- package/dist/editor/field-types/richtext/index.js +6 -5
- package/dist/editor/field-types/richtext/index.js.map +1 -1
- package/dist/editor/field-types/richtext/types.d.ts +16 -16
- package/dist/editor/field-types/richtext/types.js +84 -84
- package/dist/editor/field-types/richtext/types.js.map +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +5 -5
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +3 -2
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +14 -4
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.js +1 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/page-wizard/PageWizard.d.ts +2 -0
- package/dist/page-wizard/PageWizard.js +6 -13
- package/dist/page-wizard/PageWizard.js.map +1 -1
- package/dist/page-wizard/WizardSteps.js +3 -1
- package/dist/page-wizard/WizardSteps.js.map +1 -1
- package/dist/page-wizard/service.d.ts +1 -0
- package/dist/page-wizard/service.js +7 -0
- package/dist/page-wizard/service.js.map +1 -1
- package/dist/page-wizard/steps/ContentStep.js +210 -33
- package/dist/page-wizard/steps/ContentStep.js.map +1 -1
- package/dist/page-wizard/steps/FindItemsStep.js +11 -3
- package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
- package/dist/page-wizard/steps/LayoutStep.js +1 -1
- package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
- package/dist/page-wizard/steps/MetaDataStep.js +1 -1
- package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
- package/dist/page-wizard/steps/SchottSelectImagesStep.d.ts +2 -0
- package/dist/page-wizard/steps/SchottSelectImagesStep.js +55 -0
- package/dist/page-wizard/steps/SchottSelectImagesStep.js.map +1 -0
- package/dist/page-wizard/steps/StructureStep.js +20 -5
- package/dist/page-wizard/steps/StructureStep.js.map +1 -1
- package/dist/page-wizard/steps/TranslateStep.d.ts +2 -0
- package/dist/page-wizard/steps/TranslateStep.js +413 -0
- package/dist/page-wizard/steps/TranslateStep.js.map +1 -0
- package/dist/page-wizard/utils/dataAccessor.d.ts +7 -0
- package/dist/page-wizard/utils/dataAccessor.js +76 -0
- package/dist/page-wizard/utils/dataAccessor.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +5 -4
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/OpenPage.js +2 -1
- package/dist/splash-screen/OpenPage.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +3 -1
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/styles.css +57 -0
- package/package.json +5 -4
- package/src/components/SimpleLanguageSelector.tsx +11 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/tooltip.tsx +3 -2
- package/src/config/config.tsx +4 -0
- package/src/config/types.ts +6 -0
- package/src/editor/ContentTree.tsx +1 -1
- package/src/editor/ContextMenu.tsx +39 -0
- package/src/editor/FieldHistory.tsx +1 -1
- package/src/editor/FieldListField.tsx +2 -6
- package/src/editor/Terminal.tsx +5 -1
- package/src/editor/ai/Agents.tsx +27 -16
- package/src/editor/ai/AiResponseMessage.tsx +138 -23
- package/src/editor/ai/AiTerminal.tsx +43 -29
- package/src/editor/ai/AiToolCall.tsx +14 -4
- package/src/editor/client/EditorClient.tsx +9 -1
- package/src/editor/client/editContext.ts +2 -0
- package/src/editor/client/operations.ts +9 -0
- package/src/editor/commands/componentCommands.tsx +1 -1
- package/src/editor/field-types/ImageFieldEditor.tsx +1 -0
- package/src/editor/field-types/MultiLineText.tsx +1 -0
- package/src/editor/field-types/PictureFieldEditor.tsx +1 -0
- package/src/editor/field-types/RawEditor.tsx +1 -0
- package/src/editor/field-types/RichTextEditorComponent.tsx +27 -25
- package/src/editor/field-types/SingleLineText.tsx +1 -0
- package/src/editor/field-types/richtext/components/SimpleDropdown.tsx +165 -0
- package/src/editor/field-types/richtext/components/SimpleRichTextEditor.css +261 -0
- package/src/editor/field-types/richtext/components/SimpleRichTextEditor.tsx +505 -0
- package/src/editor/field-types/richtext/components/SimpleToolbar.tsx +362 -0
- package/src/editor/field-types/richtext/components/SimpleToolbarButton.tsx +36 -0
- package/src/editor/field-types/richtext/contextMenuFactory.tsx +264 -0
- package/src/editor/field-types/richtext/index.ts +6 -5
- package/src/editor/field-types/richtext/types.ts +168 -148
- package/src/editor/page-editor-chrome/CommentHighlighting.tsx +1 -1
- package/src/editor/page-editor-chrome/FrameMenu.tsx +16 -11
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
- package/src/editor/page-viewer/PageViewerFrame.tsx +4 -3
- package/src/editor/services/agentService.ts +16 -5
- package/src/editor/services/aiService.ts +2 -0
- package/src/page-wizard/PageWizard.tsx +10 -13
- package/src/page-wizard/WizardSteps.tsx +3 -1
- package/src/page-wizard/service.ts +11 -0
- package/src/page-wizard/steps/ContentStep.tsx +376 -43
- package/src/page-wizard/steps/FindItemsStep.tsx +23 -3
- package/src/page-wizard/steps/LayoutStep.tsx +1 -1
- package/src/page-wizard/steps/MetaDataStep.tsx +1 -1
- package/src/page-wizard/steps/SchottSelectImagesStep.tsx +141 -0
- package/src/page-wizard/steps/StructureStep.tsx +40 -5
- package/src/page-wizard/steps/TranslateStep.tsx +772 -0
- package/src/page-wizard/utils/dataAccessor.ts +85 -0
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +18 -3
- package/src/splash-screen/OpenPage.tsx +14 -1
- package/src/splash-screen/RecentPages.tsx +4 -2
- package/tsconfig.build.json +1 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import { StepComponentProps } from "../../config/types";
|
|
2
|
+
import { useEffect, useState, useCallback } from "react";
|
|
3
|
+
import { useEditContext } from "../../editor/client/editContext";
|
|
4
|
+
import { ItemDescriptor, Language } from "../../editor/pageModel";
|
|
5
|
+
import { ActionButton } from "../../components/ActionButton";
|
|
6
|
+
import { Input } from "../../components/ui/input";
|
|
7
|
+
import { InputTextarea } from "primereact/inputtextarea";
|
|
8
|
+
import { executePrompt } from "../../editor/services/aiService";
|
|
9
|
+
import { createWizardAiContext } from "../service";
|
|
10
|
+
import { SimpleLanguageSelector } from "../../components/SimpleLanguageSelector";
|
|
11
|
+
import { getLanguagesAndVersions } from "../../editor/services/contentService";
|
|
12
|
+
import {
|
|
13
|
+
Languages,
|
|
14
|
+
Upload,
|
|
15
|
+
Wand2,
|
|
16
|
+
CheckCircle,
|
|
17
|
+
AlertCircle,
|
|
18
|
+
Sparkles,
|
|
19
|
+
RefreshCw,
|
|
20
|
+
} from "lucide-react";
|
|
21
|
+
import { WizardBox } from "../WizardBox";
|
|
22
|
+
|
|
23
|
+
interface TranslatableField {
|
|
24
|
+
fieldId: string;
|
|
25
|
+
fieldName: string;
|
|
26
|
+
value: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TranslatableFieldsResponse {
|
|
30
|
+
[itemId: string]: TranslatableField[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface TranslatedField {
|
|
34
|
+
fieldId: string;
|
|
35
|
+
translatedValue: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface TranslatedFieldsResponse {
|
|
39
|
+
[itemId: string]: TranslatedField[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function TranslateStep({
|
|
43
|
+
step,
|
|
44
|
+
data,
|
|
45
|
+
setData,
|
|
46
|
+
setStepCompleted,
|
|
47
|
+
internalState,
|
|
48
|
+
setInternalState,
|
|
49
|
+
parentItem,
|
|
50
|
+
}: StepComponentProps) {
|
|
51
|
+
const editContext = useEditContext();
|
|
52
|
+
const [translatableFields, setTranslatableFields] =
|
|
53
|
+
useState<TranslatableFieldsResponse>({});
|
|
54
|
+
const [translatedFields, setTranslatedFields] =
|
|
55
|
+
useState<TranslatedFieldsResponse>({});
|
|
56
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
57
|
+
const [isTranslating, setIsTranslating] = useState(false);
|
|
58
|
+
const [isDetectingLanguage, setIsDetectingLanguage] = useState(false);
|
|
59
|
+
const [targetLanguage, setTargetLanguage] = useState<Language | null>(
|
|
60
|
+
data.targetLanguage || null,
|
|
61
|
+
);
|
|
62
|
+
const [detectedLanguageName, setDetectedLanguageName] = useState<string>(
|
|
63
|
+
data.detectedLanguageName || "",
|
|
64
|
+
);
|
|
65
|
+
const [availableSourceLanguages, setAvailableSourceLanguages] = useState<
|
|
66
|
+
Language[]
|
|
67
|
+
>([]);
|
|
68
|
+
const [componentNames, setComponentNames] = useState<Record<string, string>>(
|
|
69
|
+
{},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Get the current item from data (set by previous wizard steps)
|
|
73
|
+
const currentItem = data.currentItem || parentItem;
|
|
74
|
+
|
|
75
|
+
// Add source language state - initialize from current item's language
|
|
76
|
+
const [sourceLanguage, setSourceLanguage] = useState<string>(
|
|
77
|
+
data.sourceLanguage || currentItem?.language || "en",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Get document content from previous wizard step
|
|
81
|
+
const documentContent = data.document;
|
|
82
|
+
|
|
83
|
+
const [isPublishing, setIsPublishing] = useState(false);
|
|
84
|
+
const [publishResults, setPublishResults] = useState<{
|
|
85
|
+
success: string[];
|
|
86
|
+
errors: string[];
|
|
87
|
+
}>({ success: [], errors: [] });
|
|
88
|
+
|
|
89
|
+
const fetchAvailableLanguages = useCallback(async () => {
|
|
90
|
+
if (!currentItem || !editContext) return;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const response = await getLanguagesAndVersions({
|
|
94
|
+
id: currentItem.id,
|
|
95
|
+
language: currentItem.language,
|
|
96
|
+
version: currentItem.version,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (response.data?.languages) {
|
|
100
|
+
// Filter to only languages that have versions (versions > 0)
|
|
101
|
+
const languagesWithVersions = response.data.languages
|
|
102
|
+
.filter((lang) => lang.versions > 0)
|
|
103
|
+
.map((lang) => ({
|
|
104
|
+
languageCode: lang.languageCode,
|
|
105
|
+
name: lang.name,
|
|
106
|
+
icon: lang.icon,
|
|
107
|
+
versions: lang.versions,
|
|
108
|
+
}));
|
|
109
|
+
setAvailableSourceLanguages(languagesWithVersions);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("Error fetching available languages:", error);
|
|
113
|
+
// Fallback to all languages if there's an error
|
|
114
|
+
setAvailableSourceLanguages(editContext?.itemLanguages || []);
|
|
115
|
+
}
|
|
116
|
+
}, [currentItem, editContext]);
|
|
117
|
+
|
|
118
|
+
const fetchComponentNames = useCallback(
|
|
119
|
+
async (itemIds: string[]) => {
|
|
120
|
+
if (!editContext || itemIds.length === 0) return;
|
|
121
|
+
|
|
122
|
+
const names: Record<string, string> = {};
|
|
123
|
+
|
|
124
|
+
for (const itemId of itemIds) {
|
|
125
|
+
try {
|
|
126
|
+
const item = await editContext.itemsRepository.getItem({
|
|
127
|
+
id: itemId,
|
|
128
|
+
language: sourceLanguage,
|
|
129
|
+
version: 0,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (item?.name) {
|
|
133
|
+
names[itemId] = item.name;
|
|
134
|
+
} else {
|
|
135
|
+
names[itemId] = itemId; // Fallback to ID if name not found
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`Error fetching item ${itemId}:`, error);
|
|
139
|
+
names[itemId] = itemId; // Fallback to ID on error
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setComponentNames(names);
|
|
144
|
+
},
|
|
145
|
+
[editContext, sourceLanguage],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const fetchTranslatableFields = useCallback(async () => {
|
|
149
|
+
if (!currentItem || !editContext) return;
|
|
150
|
+
|
|
151
|
+
setIsLoading(true);
|
|
152
|
+
try {
|
|
153
|
+
const response = await fetch(
|
|
154
|
+
"/alpaca/editor/page-wizard/getTranslatableFields",
|
|
155
|
+
{
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
"Content-Type": "application/json",
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
id: currentItem.id,
|
|
162
|
+
language: sourceLanguage,
|
|
163
|
+
version: currentItem.version,
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (response.ok) {
|
|
169
|
+
const fieldsData: TranslatableFieldsResponse = await response.json();
|
|
170
|
+
setTranslatableFields(fieldsData);
|
|
171
|
+
|
|
172
|
+
// Initialize translated fields structure
|
|
173
|
+
const initialTranslatedFields: TranslatedFieldsResponse = {};
|
|
174
|
+
Object.keys(fieldsData).forEach((itemId) => {
|
|
175
|
+
if (fieldsData[itemId]) {
|
|
176
|
+
initialTranslatedFields[itemId] = fieldsData[itemId].map(
|
|
177
|
+
(field) => ({
|
|
178
|
+
fieldId: field.fieldId,
|
|
179
|
+
translatedValue: "",
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
setTranslatedFields(initialTranslatedFields);
|
|
185
|
+
|
|
186
|
+
// Fetch component names for all item IDs
|
|
187
|
+
const itemIds = Object.keys(fieldsData);
|
|
188
|
+
if (itemIds.length > 0) {
|
|
189
|
+
fetchComponentNames(itemIds);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
console.error("Failed to fetch translatable fields");
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error("Error fetching translatable fields:", error);
|
|
196
|
+
} finally {
|
|
197
|
+
setIsLoading(false);
|
|
198
|
+
}
|
|
199
|
+
}, [currentItem, editContext, sourceLanguage, fetchComponentNames]);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
fetchAvailableLanguages();
|
|
203
|
+
}, [fetchAvailableLanguages]);
|
|
204
|
+
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
fetchTranslatableFields();
|
|
207
|
+
}, [fetchTranslatableFields, sourceLanguage]);
|
|
208
|
+
|
|
209
|
+
const detectLanguage = async () => {
|
|
210
|
+
if (!editContext) {
|
|
211
|
+
console.warn("Edit context not available for language detection");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setIsDetectingLanguage(true);
|
|
216
|
+
try {
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
Text to analyze:
|
|
220
|
+
${documentContent.substring(0, 1000)}`;
|
|
221
|
+
|
|
222
|
+
const abortController = new AbortController();
|
|
223
|
+
|
|
224
|
+
const result = await executePrompt(
|
|
225
|
+
[
|
|
226
|
+
{
|
|
227
|
+
content: prompt,
|
|
228
|
+
role: "user",
|
|
229
|
+
name: "user",
|
|
230
|
+
id: crypto.randomUUID(),
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
{ editContext, createAiContext: createWizardAiContext },
|
|
234
|
+
{ model: "gpt-4o-mini" },
|
|
235
|
+
{ signal: abortController.signal },
|
|
236
|
+
(response: any) => {
|
|
237
|
+
try {
|
|
238
|
+
const content =
|
|
239
|
+
response.content ||
|
|
240
|
+
response.messages?.[response.messages.length - 1]?.content;
|
|
241
|
+
if (content) {
|
|
242
|
+
const languageName = content.trim();
|
|
243
|
+
setDetectedLanguageName(languageName);
|
|
244
|
+
// Try to find matching Language object from available languages
|
|
245
|
+
const availableLanguages = editContext?.itemLanguages || [];
|
|
246
|
+
const matchingLanguage = availableLanguages.find((lang) =>
|
|
247
|
+
lang.name.toLowerCase().includes(languageName.toLowerCase()),
|
|
248
|
+
);
|
|
249
|
+
if (matchingLanguage) {
|
|
250
|
+
setTargetLanguage(matchingLanguage);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch (parseError: unknown) {
|
|
254
|
+
console.error(
|
|
255
|
+
"Error parsing language detection response:",
|
|
256
|
+
parseError,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (result && result.messages && result.messages.length > 0) {
|
|
263
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
264
|
+
if (lastMessage && lastMessage.content) {
|
|
265
|
+
const languageName = lastMessage.content.trim();
|
|
266
|
+
setDetectedLanguageName(languageName);
|
|
267
|
+
// Try to find matching Language object from available languages
|
|
268
|
+
const availableLanguages = editContext?.itemLanguages || [];
|
|
269
|
+
const matchingLanguage = availableLanguages.find((lang) =>
|
|
270
|
+
lang.name.toLowerCase().includes(languageName.toLowerCase()),
|
|
271
|
+
);
|
|
272
|
+
if (matchingLanguage) {
|
|
273
|
+
setTargetLanguage(matchingLanguage);
|
|
274
|
+
}
|
|
275
|
+
console.log(`Detected target language: ${languageName}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error("Language detection error:", error);
|
|
280
|
+
} finally {
|
|
281
|
+
setIsDetectingLanguage(false);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Auto-detect language when document content is available
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
if (!detectedLanguageName && !isDetectingLanguage) {
|
|
288
|
+
detectLanguage();
|
|
289
|
+
}
|
|
290
|
+
}, [detectedLanguageName, isDetectingLanguage]);
|
|
291
|
+
|
|
292
|
+
const handleTranslate = async () => {
|
|
293
|
+
if (!targetLanguage?.languageCode) {
|
|
294
|
+
console.warn("Missing target language");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!editContext) {
|
|
299
|
+
console.error("Edit context not available");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
setIsTranslating(true);
|
|
304
|
+
try {
|
|
305
|
+
// Prepare the fields data for the AI prompt
|
|
306
|
+
const fieldsForTranslation = Object.entries(translatableFields).flatMap(
|
|
307
|
+
([itemId, fields]) =>
|
|
308
|
+
fields.map((field) => ({
|
|
309
|
+
itemId,
|
|
310
|
+
fieldId: field.fieldId,
|
|
311
|
+
fieldName: field.fieldName,
|
|
312
|
+
originalValue: field.value,
|
|
313
|
+
})),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
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}.
|
|
317
|
+
|
|
318
|
+
Your task is to:
|
|
319
|
+
1. Match the content from the provided document to the fields that need translation
|
|
320
|
+
2. Return ONLY the translated text from the document - do not change, modify, or create any new text
|
|
321
|
+
3. If you cannot find a suitable translation in the document for a field, leave the translatedValue empty
|
|
322
|
+
|
|
323
|
+
Document content:
|
|
324
|
+
${documentContent}
|
|
325
|
+
|
|
326
|
+
Fields to translate:
|
|
327
|
+
${JSON.stringify(fieldsForTranslation, null, 2)}
|
|
328
|
+
|
|
329
|
+
Return your response as a JSON object in this exact format:
|
|
330
|
+
{
|
|
331
|
+
"translations": [
|
|
332
|
+
{
|
|
333
|
+
"itemId": "item-id",
|
|
334
|
+
"fieldId": "field-id",
|
|
335
|
+
"translatedValue": "exact text from document"
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
}`;
|
|
339
|
+
|
|
340
|
+
const abortController = new AbortController();
|
|
341
|
+
|
|
342
|
+
const result = await executePrompt(
|
|
343
|
+
[
|
|
344
|
+
{
|
|
345
|
+
content: prompt,
|
|
346
|
+
role: "user",
|
|
347
|
+
name: "user",
|
|
348
|
+
id: crypto.randomUUID(),
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
{ editContext, createAiContext: createWizardAiContext },
|
|
352
|
+
{ model: "gpt-4o-mini" },
|
|
353
|
+
{ signal: abortController.signal },
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (result && result.messages && result.messages.length > 0) {
|
|
357
|
+
const lastMessage = result.messages[result.messages.length - 1];
|
|
358
|
+
if (lastMessage && lastMessage.content) {
|
|
359
|
+
try {
|
|
360
|
+
const parsedResponse = JSON.parse(lastMessage.content);
|
|
361
|
+
const translations = parsedResponse.translations || [];
|
|
362
|
+
|
|
363
|
+
// Update translated fields with AI response
|
|
364
|
+
const updatedTranslatedFields: TranslatedFieldsResponse = {
|
|
365
|
+
...translatedFields,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
translations.forEach((translation: any) => {
|
|
369
|
+
const { itemId, fieldId, translatedValue } = translation;
|
|
370
|
+
if (updatedTranslatedFields[itemId]) {
|
|
371
|
+
const fieldIndex = updatedTranslatedFields[itemId].findIndex(
|
|
372
|
+
(f) => f.fieldId === fieldId,
|
|
373
|
+
);
|
|
374
|
+
if (
|
|
375
|
+
fieldIndex !== -1 &&
|
|
376
|
+
updatedTranslatedFields[itemId][fieldIndex]
|
|
377
|
+
) {
|
|
378
|
+
updatedTranslatedFields[itemId][fieldIndex].translatedValue =
|
|
379
|
+
translatedValue || "";
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
setTranslatedFields(updatedTranslatedFields);
|
|
385
|
+
console.log(
|
|
386
|
+
`Translation complete: ${translations.length} fields translated`,
|
|
387
|
+
);
|
|
388
|
+
} catch (parseError) {
|
|
389
|
+
console.error("Failed to parse AI response:", parseError);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error("Translation error:", error);
|
|
395
|
+
} finally {
|
|
396
|
+
setIsTranslating(false);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const handleFieldChange = (
|
|
401
|
+
itemId: string,
|
|
402
|
+
fieldId: string,
|
|
403
|
+
value: string,
|
|
404
|
+
) => {
|
|
405
|
+
setTranslatedFields((prev) => ({
|
|
406
|
+
...prev,
|
|
407
|
+
[itemId]:
|
|
408
|
+
prev[itemId]?.map((field) =>
|
|
409
|
+
field.fieldId === fieldId
|
|
410
|
+
? { ...field, translatedValue: value }
|
|
411
|
+
: field,
|
|
412
|
+
) || [],
|
|
413
|
+
}));
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const handleConfirmTranslation = async () => {
|
|
417
|
+
if (!editContext || !currentItem || !targetLanguage?.languageCode) {
|
|
418
|
+
console.error("Missing required data for publishing translations");
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
setIsPublishing(true);
|
|
423
|
+
setPublishResults({ success: [], errors: [] });
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
// Get all items that have translations
|
|
427
|
+
const itemsToTranslate = Object.entries(translatedFields).filter(
|
|
428
|
+
([itemId, fields]) =>
|
|
429
|
+
fields.some((field) => field.translatedValue.trim() !== ""),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
console.log(
|
|
433
|
+
`Publishing translations for ${itemsToTranslate.length} items to language: ${targetLanguage.languageCode}`,
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
for (const [itemId, translatedFieldsList] of itemsToTranslate) {
|
|
437
|
+
try {
|
|
438
|
+
// Create ItemDescriptor for the component item
|
|
439
|
+
const componentItemDescriptor = {
|
|
440
|
+
id: itemId,
|
|
441
|
+
language: targetLanguage.languageCode,
|
|
442
|
+
version: 0,
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Create language version for this item
|
|
446
|
+
console.log(
|
|
447
|
+
`Creating language version for item ${itemId} in language ${targetLanguage.languageCode}`,
|
|
448
|
+
);
|
|
449
|
+
await editContext.operations.createVersion(componentItemDescriptor);
|
|
450
|
+
|
|
451
|
+
// Update field values with translations
|
|
452
|
+
const fieldsWithTranslations = translatedFieldsList.filter(
|
|
453
|
+
(field) => field.translatedValue.trim() !== "",
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
for (const translatedField of fieldsWithTranslations) {
|
|
457
|
+
const fieldDescriptor = {
|
|
458
|
+
fieldId: translatedField.fieldId,
|
|
459
|
+
item: {
|
|
460
|
+
id: itemId,
|
|
461
|
+
language: targetLanguage.languageCode,
|
|
462
|
+
version: 0,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
console.log(
|
|
467
|
+
`Updating field ${translatedField.fieldId} for item ${itemId}`,
|
|
468
|
+
);
|
|
469
|
+
await editContext.operations.editField({
|
|
470
|
+
field: fieldDescriptor,
|
|
471
|
+
value: translatedField.translatedValue,
|
|
472
|
+
rawValue: translatedField.translatedValue,
|
|
473
|
+
refresh: "none",
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
setPublishResults((prev) => ({
|
|
478
|
+
...prev,
|
|
479
|
+
success: [
|
|
480
|
+
...prev.success,
|
|
481
|
+
`${itemId} (${fieldsWithTranslations.length} fields)`,
|
|
482
|
+
],
|
|
483
|
+
}));
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error(`Error translating item ${itemId}:`, error);
|
|
486
|
+
setPublishResults((prev) => ({
|
|
487
|
+
...prev,
|
|
488
|
+
errors: [
|
|
489
|
+
...prev.errors,
|
|
490
|
+
`${itemId}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
491
|
+
],
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
console.log("Translation publishing completed");
|
|
497
|
+
} catch (error) {
|
|
498
|
+
console.error("Error during translation publishing:", error);
|
|
499
|
+
setPublishResults((prev) => ({
|
|
500
|
+
...prev,
|
|
501
|
+
errors: [
|
|
502
|
+
...prev.errors,
|
|
503
|
+
`General error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
504
|
+
],
|
|
505
|
+
}));
|
|
506
|
+
} finally {
|
|
507
|
+
setIsPublishing(false);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Check if step is completed (has translations and they've been published successfully)
|
|
512
|
+
useEffect(() => {
|
|
513
|
+
const hasTranslations = Object.values(translatedFields).some((fields) =>
|
|
514
|
+
fields.some((field) => field.translatedValue.trim() !== ""),
|
|
515
|
+
);
|
|
516
|
+
const hasSuccessfulPublish = publishResults.success.length > 0;
|
|
517
|
+
|
|
518
|
+
// Step is completed if we have translations and at least some have been published successfully
|
|
519
|
+
setStepCompleted(hasTranslations && hasSuccessfulPublish);
|
|
520
|
+
}, [translatedFields, publishResults, setStepCompleted]);
|
|
521
|
+
|
|
522
|
+
// Save translated fields and publish results to wizard data
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
setData((prev) => ({
|
|
525
|
+
...prev,
|
|
526
|
+
translatedFields,
|
|
527
|
+
publishResults,
|
|
528
|
+
targetLanguage,
|
|
529
|
+
detectedLanguageName,
|
|
530
|
+
sourceLanguage,
|
|
531
|
+
}));
|
|
532
|
+
}, [
|
|
533
|
+
translatedFields,
|
|
534
|
+
publishResults,
|
|
535
|
+
targetLanguage,
|
|
536
|
+
detectedLanguageName,
|
|
537
|
+
sourceLanguage,
|
|
538
|
+
setData,
|
|
539
|
+
]);
|
|
540
|
+
|
|
541
|
+
const totalFields = Object.values(translatableFields).reduce(
|
|
542
|
+
(total, fields) => total + fields.length,
|
|
543
|
+
0,
|
|
544
|
+
);
|
|
545
|
+
const translatedCount = Object.values(translatedFields).reduce(
|
|
546
|
+
(total, fields) =>
|
|
547
|
+
total +
|
|
548
|
+
fields.filter((field) => field.translatedValue.trim() !== "").length,
|
|
549
|
+
0,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
<div className="mx-auto flex flex-col gap-6 p-6">
|
|
554
|
+
<WizardBox
|
|
555
|
+
title="Translation Setup"
|
|
556
|
+
description="Select source and target languages, target language will be auto-detected from the document"
|
|
557
|
+
icon={<Languages className="h-5 w-5" />}
|
|
558
|
+
>
|
|
559
|
+
<div className="grid grid-cols-2 gap-4">
|
|
560
|
+
<div>
|
|
561
|
+
<label className="mb-2 block text-sm font-medium">
|
|
562
|
+
Source Language
|
|
563
|
+
</label>
|
|
564
|
+
<SimpleLanguageSelector
|
|
565
|
+
selectedLanguage={sourceLanguage}
|
|
566
|
+
onLanguageSelected={(language: Language) =>
|
|
567
|
+
setSourceLanguage(language.languageCode)
|
|
568
|
+
}
|
|
569
|
+
disabled={false}
|
|
570
|
+
availableLanguages={availableSourceLanguages}
|
|
571
|
+
/>
|
|
572
|
+
<p className="mt-1 text-xs text-gray-600">
|
|
573
|
+
Select the language of the content to translate from
|
|
574
|
+
</p>
|
|
575
|
+
</div>
|
|
576
|
+
<div>
|
|
577
|
+
<div className="mb-2 flex items-center justify-between">
|
|
578
|
+
<label className="block text-sm font-medium">
|
|
579
|
+
Target Language
|
|
580
|
+
</label>
|
|
581
|
+
{isDetectingLanguage && (
|
|
582
|
+
<div className="flex items-center gap-1 text-xs text-blue-600">
|
|
583
|
+
<RefreshCw className="h-3 w-3 animate-spin" />
|
|
584
|
+
Detecting...
|
|
585
|
+
</div>
|
|
586
|
+
)}
|
|
587
|
+
{detectedLanguageName && !isDetectingLanguage && (
|
|
588
|
+
<div className="flex items-center gap-1 text-xs text-green-600">
|
|
589
|
+
<Sparkles className="h-3 w-3" />
|
|
590
|
+
Auto-detected
|
|
591
|
+
</div>
|
|
592
|
+
)}
|
|
593
|
+
</div>
|
|
594
|
+
<div className="relative">
|
|
595
|
+
<SimpleLanguageSelector
|
|
596
|
+
selectedLanguage={targetLanguage?.languageCode || ""}
|
|
597
|
+
onLanguageSelected={(language: Language) =>
|
|
598
|
+
setTargetLanguage(language)
|
|
599
|
+
}
|
|
600
|
+
disabled={false}
|
|
601
|
+
className={
|
|
602
|
+
detectedLanguageName ? "border-green-200 bg-green-50" : ""
|
|
603
|
+
}
|
|
604
|
+
/>
|
|
605
|
+
{detectedLanguageName && (
|
|
606
|
+
<button
|
|
607
|
+
type="button"
|
|
608
|
+
onClick={detectLanguage}
|
|
609
|
+
className="absolute top-1/2 right-2 -translate-y-1/2 transform text-blue-600 hover:text-blue-800"
|
|
610
|
+
title="Re-detect language"
|
|
611
|
+
>
|
|
612
|
+
<RefreshCw className="h-4 w-4" />
|
|
613
|
+
</button>
|
|
614
|
+
)}
|
|
615
|
+
</div>
|
|
616
|
+
{detectedLanguageName && (
|
|
617
|
+
<p className="mt-1 text-xs text-green-600">
|
|
618
|
+
Detected: {detectedLanguageName}
|
|
619
|
+
{targetLanguage && ` → Selected: ${targetLanguage.name}`}
|
|
620
|
+
</p>
|
|
621
|
+
)}
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
<ActionButton
|
|
626
|
+
onClick={handleTranslate}
|
|
627
|
+
isLoading={isTranslating}
|
|
628
|
+
disabled={!targetLanguage?.languageCode}
|
|
629
|
+
className="mt-4"
|
|
630
|
+
variant="outline"
|
|
631
|
+
>
|
|
632
|
+
<Wand2 className="mr-2 h-4 w-4" />
|
|
633
|
+
Translate Fields
|
|
634
|
+
</ActionButton>
|
|
635
|
+
</WizardBox>
|
|
636
|
+
|
|
637
|
+
<WizardBox
|
|
638
|
+
title="Translation Progress"
|
|
639
|
+
description={`${translatedCount} of ${totalFields} fields translated`}
|
|
640
|
+
icon={<Upload className="h-5 w-5" />}
|
|
641
|
+
>
|
|
642
|
+
{isLoading ? (
|
|
643
|
+
<div className="py-8 text-center">Loading translatable fields...</div>
|
|
644
|
+
) : (
|
|
645
|
+
<div className="space-y-6">
|
|
646
|
+
{Object.entries(translatableFields).map(([itemId, fields]) => (
|
|
647
|
+
<div key={itemId} className="rounded-lg p-4">
|
|
648
|
+
<h4 className="mb-4 font-medium">
|
|
649
|
+
{componentNames[itemId] || "Component"}
|
|
650
|
+
</h4>
|
|
651
|
+
<div className="space-y-4">
|
|
652
|
+
{fields.map((field) => {
|
|
653
|
+
const translatedField = translatedFields[itemId]?.find(
|
|
654
|
+
(f) => f.fieldId === field.fieldId,
|
|
655
|
+
);
|
|
656
|
+
return (
|
|
657
|
+
<div
|
|
658
|
+
key={field.fieldId}
|
|
659
|
+
className="grid grid-cols-2 gap-4"
|
|
660
|
+
>
|
|
661
|
+
<div className="flex flex-col">
|
|
662
|
+
<label className="mb-1 block text-xs font-medium">
|
|
663
|
+
{field.fieldName}
|
|
664
|
+
</label>
|
|
665
|
+
<div className="min-h-[100px] flex-1 rounded border bg-gray-50 p-3 text-xs">
|
|
666
|
+
{field.value}
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
<div className="flex flex-col">
|
|
670
|
+
<label className="mb-1 block text-xs font-medium">
|
|
671
|
+
{field.fieldName}
|
|
672
|
+
</label>
|
|
673
|
+
<InputTextarea
|
|
674
|
+
value={translatedField?.translatedValue || ""}
|
|
675
|
+
onChange={(e) =>
|
|
676
|
+
handleFieldChange(
|
|
677
|
+
itemId,
|
|
678
|
+
field.fieldId,
|
|
679
|
+
e.target.value,
|
|
680
|
+
)
|
|
681
|
+
}
|
|
682
|
+
placeholder="Translation will appear here..."
|
|
683
|
+
rows={4}
|
|
684
|
+
className="min-h-[100px] w-full flex-1 text-xs"
|
|
685
|
+
/>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
);
|
|
689
|
+
})}
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
))}
|
|
693
|
+
</div>
|
|
694
|
+
)}
|
|
695
|
+
</WizardBox>
|
|
696
|
+
|
|
697
|
+
{translatedCount > 0 && (
|
|
698
|
+
<WizardBox
|
|
699
|
+
title="Publish Translations"
|
|
700
|
+
description="Create language versions and apply translations"
|
|
701
|
+
icon={<CheckCircle className="h-5 w-5" />}
|
|
702
|
+
>
|
|
703
|
+
<div className="space-y-4">
|
|
704
|
+
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
|
705
|
+
<h4 className="mb-2 font-medium text-blue-900">
|
|
706
|
+
Ready to Publish
|
|
707
|
+
</h4>
|
|
708
|
+
<p className="text-sm text-blue-800">
|
|
709
|
+
{translatedCount} fields ready to be published to{" "}
|
|
710
|
+
<strong>{targetLanguage?.name}</strong> language versions
|
|
711
|
+
{detectedLanguageName &&
|
|
712
|
+
targetLanguage?.name
|
|
713
|
+
?.toLowerCase()
|
|
714
|
+
.includes(detectedLanguageName.toLowerCase()) && (
|
|
715
|
+
<span className="text-green-700"> (auto-detected)</span>
|
|
716
|
+
)}
|
|
717
|
+
. This will create new language versions for all translated
|
|
718
|
+
items and update their field values.
|
|
719
|
+
</p>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
{(publishResults.success.length > 0 ||
|
|
723
|
+
publishResults.errors.length > 0) && (
|
|
724
|
+
<div className="space-y-2">
|
|
725
|
+
{publishResults.success.length > 0 && (
|
|
726
|
+
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
|
|
727
|
+
<div className="mb-1 flex items-center gap-2 font-medium text-green-800">
|
|
728
|
+
<CheckCircle className="h-4 w-4" />
|
|
729
|
+
Successfully Published ({publishResults.success.length})
|
|
730
|
+
</div>
|
|
731
|
+
<ul className="list-inside list-disc text-sm text-green-700">
|
|
732
|
+
{publishResults.success.map((item, index) => (
|
|
733
|
+
<li key={index}>{item}</li>
|
|
734
|
+
))}
|
|
735
|
+
</ul>
|
|
736
|
+
</div>
|
|
737
|
+
)}
|
|
738
|
+
|
|
739
|
+
{publishResults.errors.length > 0 && (
|
|
740
|
+
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
|
741
|
+
<div className="mb-1 flex items-center gap-2 font-medium text-red-800">
|
|
742
|
+
<AlertCircle className="h-4 w-4" />
|
|
743
|
+
Errors ({publishResults.errors.length})
|
|
744
|
+
</div>
|
|
745
|
+
<ul className="list-inside list-disc text-sm text-red-700">
|
|
746
|
+
{publishResults.errors.map((error, index) => (
|
|
747
|
+
<li key={index}>{error}</li>
|
|
748
|
+
))}
|
|
749
|
+
</ul>
|
|
750
|
+
</div>
|
|
751
|
+
)}
|
|
752
|
+
</div>
|
|
753
|
+
)}
|
|
754
|
+
|
|
755
|
+
<ActionButton
|
|
756
|
+
onClick={handleConfirmTranslation}
|
|
757
|
+
isLoading={isPublishing}
|
|
758
|
+
disabled={translatedCount === 0 || !targetLanguage?.languageCode}
|
|
759
|
+
className="w-full"
|
|
760
|
+
variant="outline"
|
|
761
|
+
>
|
|
762
|
+
<CheckCircle className="mr-2 h-4 w-4" />
|
|
763
|
+
{isPublishing
|
|
764
|
+
? "Publishing Translations..."
|
|
765
|
+
: `Publish ${translatedCount} Translations to ${targetLanguage?.name || "Target Language"}`}
|
|
766
|
+
</ActionButton>
|
|
767
|
+
</div>
|
|
768
|
+
</WizardBox>
|
|
769
|
+
)}
|
|
770
|
+
</div>
|
|
771
|
+
);
|
|
772
|
+
}
|