@parhelia/localization 0.1.12902 → 0.1.12904
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/LocalizeItemCommand.d.ts.map +1 -1
- package/dist/LocalizeItemCommand.js +2 -4
- package/dist/LocalizeItemDialog.d.ts.map +1 -1
- package/dist/LocalizeItemDialog.js +35 -97
- package/dist/LocalizeItemUtils.d.ts +2 -1
- package/dist/LocalizeItemUtils.d.ts.map +1 -1
- package/dist/LocalizeItemUtils.js +36 -78
- package/dist/api/discovery.d.ts +0 -25
- package/dist/api/discovery.d.ts.map +1 -1
- package/dist/api/discovery.js +2 -106
- package/dist/constants.d.ts +15 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +21 -0
- package/dist/hooks/useTranslationWizard.d.ts.map +1 -1
- package/dist/hooks/useTranslationWizard.js +3 -3
- package/dist/index.d.ts +11 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -36
- package/dist/services/translationService.d.ts +10 -41
- package/dist/services/translationService.d.ts.map +1 -1
- package/dist/services/translationService.js +6 -48
- package/dist/settings/TranslationServicesPanel.d.ts.map +1 -1
- package/dist/settings/TranslationServicesPanel.js +36 -21
- package/dist/setup/LocalizationSetupStep.d.ts.map +1 -1
- package/dist/setup/LocalizationSetupStep.js +18 -29
- package/dist/sidebar/TranslationSidebar.d.ts.map +1 -1
- package/dist/sidebar/TranslationSidebar.js +10 -20
- package/dist/steps/MetadataInputStep.d.ts +4 -0
- package/dist/steps/MetadataInputStep.d.ts.map +1 -0
- package/dist/steps/MetadataInputStep.js +41 -0
- package/dist/steps/PromptCustomizationStep.d.ts +1 -1
- package/dist/steps/PromptCustomizationStep.d.ts.map +1 -1
- package/dist/steps/PromptCustomizationStep.js +56 -159
- package/dist/steps/ServiceLanguageSelectionStep.d.ts +1 -6
- package/dist/steps/ServiceLanguageSelectionStep.d.ts.map +1 -1
- package/dist/steps/ServiceLanguageSelectionStep.js +163 -56
- package/dist/steps/SubitemDiscoveryStep.d.ts +3 -0
- package/dist/steps/SubitemDiscoveryStep.d.ts.map +1 -0
- package/dist/steps/SubitemDiscoveryStep.js +313 -0
- package/dist/steps/index.d.ts +5 -0
- package/dist/steps/index.d.ts.map +1 -0
- package/dist/steps/index.js +4 -0
- package/dist/steps/types.d.ts +1 -17
- package/dist/steps/types.d.ts.map +1 -1
- package/dist/translation-center/BatchTranslationView.d.ts +8 -0
- package/dist/translation-center/BatchTranslationView.d.ts.map +1 -0
- package/dist/translation-center/BatchTranslationView.js +870 -0
- package/dist/translation-center/RecentTranslations.d.ts +2 -0
- package/dist/translation-center/RecentTranslations.d.ts.map +1 -0
- package/dist/translation-center/RecentTranslations.js +309 -0
- package/dist/translation-center/TranslationManagement.d.ts.map +1 -1
- package/dist/translation-center/TranslationManagement.js +15 -25
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/createVersions.d.ts +14 -0
- package/dist/utils/createVersions.d.ts.map +1 -0
- package/dist/utils/createVersions.js +26 -0
- package/package.json +1 -1
- package/dist/steps/ItemSelectionStep.d.ts +0 -3
- package/dist/steps/ItemSelectionStep.d.ts.map +0 -1
- package/dist/steps/ItemSelectionStep.js +0 -24
- package/dist/steps/ItemSelectionTree.d.ts +0 -13
- package/dist/steps/ItemSelectionTree.d.ts.map +0 -1
- package/dist/steps/ItemSelectionTree.js +0 -327
- package/dist/steps/WizardStepShell.d.ts +0 -17
- package/dist/steps/WizardStepShell.d.ts.map +0 -1
- package/dist/steps/WizardStepShell.js +0 -11
- package/dist/translation-center/TranslationBatches.d.ts +0 -2
- package/dist/translation-center/TranslationBatches.d.ts.map +0 -1
- package/dist/translation-center/TranslationBatches.js +0 -1160
- package/dist/translation-center/TranslationsTitlebar.d.ts +0 -6
- package/dist/translation-center/TranslationsTitlebar.d.ts.map +0 -1
- package/dist/translation-center/TranslationsTitlebar.js +0 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizeItemCommand.d.ts","sourceRoot":"","sources":["../src/LocalizeItemCommand.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAUhF,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAClD,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,
|
|
1
|
+
{"version":3,"file":"LocalizeItemCommand.d.ts","sourceRoot":"","sources":["../src/LocalizeItemCommand.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAUhF,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAClD,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,cAAc,CAAC,uBAAuB,CAAC,CAAC;AACjF,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEnE,eAAO,MAAM,mBAAmB,EAAE,mBAgDjC,CAAC"}
|
|
@@ -14,8 +14,7 @@ export const localizeItemCommand = {
|
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
16
|
// Check if multi-item is disabled and multiple items are selected
|
|
17
|
-
const multiItemEnabled = context.editContext?.configuration?.localization?.multiItem !==
|
|
18
|
-
false;
|
|
17
|
+
const multiItemEnabled = context.editContext?.configuration?.localization?.multiItem !== false;
|
|
19
18
|
if (!multiItemEnabled && context.data.items.length > 1) {
|
|
20
19
|
return true; // Disable command when multiple items selected in single-item mode
|
|
21
20
|
}
|
|
@@ -32,8 +31,7 @@ export const localizeItemCommand = {
|
|
|
32
31
|
});
|
|
33
32
|
// If translation was started (result contains batchId), navigate to the batch view
|
|
34
33
|
if (result?.batchId) {
|
|
35
|
-
const translationManagementEnabled = context.editContext?.configuration?.localization
|
|
36
|
-
?.translationManagement !== false;
|
|
34
|
+
const translationManagementEnabled = context.editContext?.configuration?.localization?.translationManagement !== false;
|
|
37
35
|
if (translationManagementEnabled) {
|
|
38
36
|
// Synchronous URL update to avoid race with workspace URL sync
|
|
39
37
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizeItemDialog.d.ts","sourceRoot":"","sources":["../src/LocalizeItemDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalizeItemDialog.d.ts","sourceRoot":"","sources":["../src/LocalizeItemDialog.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAuBvB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,uBAAuB,CAAC,2CAybtE"}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { Dialog, DialogContent,
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState
|
|
4
|
-
import { AlertTriangle as LucideAlertTriangle
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, Button, cn, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@parhelia/core";
|
|
3
|
+
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { AlertTriangle as LucideAlertTriangle } from "lucide-react";
|
|
5
5
|
import { useTranslationWizard } from "./hooks/useTranslationWizard";
|
|
6
|
-
import { performDefaultTranslation, } from "./LocalizeItemUtils";
|
|
6
|
+
import { performDefaultTranslation, generateBatchId } from "./LocalizeItemUtils";
|
|
7
7
|
const AlertTriangleIcon = LucideAlertTriangle;
|
|
8
|
-
const GlobeIcon = LucideGlobe;
|
|
9
|
-
const ArrowRightIcon = LucideArrowRight;
|
|
10
8
|
// Threshold for warning about large batch translations
|
|
11
9
|
const LARGE_BATCH_WARNING_THRESHOLD = 100;
|
|
12
|
-
const getNextButtonLabel = (step) => step?.nextButtonLabel || step?.name || "Next";
|
|
13
10
|
// Note: DialogButtons is an internal component that might need to be added to core exports
|
|
14
11
|
// For now, we'll implement it inline
|
|
15
|
-
const DialogButtons = ({ children, ...props }) => (_jsx("div", { className: "mt-auto flex shrink-0 items-center gap-3 border-t border-
|
|
12
|
+
const DialogButtons = ({ children, ...props }) => (_jsx("div", { className: "mt-auto flex shrink-0 items-center gap-3 border-t border-gray-3 bg-background px-6 py-4", ...props, children: children }));
|
|
16
13
|
export function LocalizeItemDialog(props) {
|
|
17
14
|
const editContext = props.editContext;
|
|
18
15
|
const configuration = editContext.configuration.translationWizard;
|
|
@@ -36,12 +33,7 @@ export function LocalizeItemDialog(props) {
|
|
|
36
33
|
// Provide stable callbacks to steps to avoid update loops
|
|
37
34
|
const lastActionsSigRef = useRef("");
|
|
38
35
|
const provideFooterActions = useCallback((actions) => {
|
|
39
|
-
const normalized = (actions || []).map(
|
|
40
|
-
key: a.key,
|
|
41
|
-
label: a.label,
|
|
42
|
-
disabled: !!a.disabled,
|
|
43
|
-
signature: a.signature || "",
|
|
44
|
-
}));
|
|
36
|
+
const normalized = (actions || []).map(a => ({ key: a.key, label: a.label, disabled: !!a.disabled, signature: a.signature || "" }));
|
|
45
37
|
const signature = JSON.stringify(normalized);
|
|
46
38
|
if (signature === lastActionsSigRef.current)
|
|
47
39
|
return;
|
|
@@ -53,6 +45,7 @@ export function LocalizeItemDialog(props) {
|
|
|
53
45
|
}, [props.onClose]);
|
|
54
46
|
// Memoize activeSteps to prevent unnecessary recalculations and new object references
|
|
55
47
|
// This prevents infinite loops when wizardData changes but skip conditions don't
|
|
48
|
+
// The skip condition for subitem-discovery step checks includeSubitems
|
|
56
49
|
// The skip condition for prompt-customization step checks serviceCustomData and translationProvider
|
|
57
50
|
const activeSteps = useMemo(() => {
|
|
58
51
|
const steps = configuration.steps.filter((step) => !step.skipCondition || !step.skipCondition(wizardData));
|
|
@@ -61,13 +54,13 @@ export function LocalizeItemDialog(props) {
|
|
|
61
54
|
configuration.steps,
|
|
62
55
|
wizardData.includeSubitems,
|
|
63
56
|
wizardData.serviceCustomData,
|
|
64
|
-
wizardData.translationProvider
|
|
57
|
+
wizardData.translationProvider
|
|
65
58
|
]); // Depend on what affects skip conditions
|
|
66
59
|
const currentStep = activeSteps[currentStepIndex];
|
|
67
60
|
// Refs for stable callbacks - updated during render to avoid dependency issues
|
|
68
61
|
const currentStepIdRef = useRef(currentStep?.id);
|
|
69
62
|
const currentStepIndexRef = useRef(currentStepIndex);
|
|
70
|
-
const lastSetWizardDataRef = useRef(
|
|
63
|
+
const lastSetWizardDataRef = useRef('');
|
|
71
64
|
// Track step changes for logging
|
|
72
65
|
if (currentStepIdRef.current !== currentStep?.id) {
|
|
73
66
|
currentStepIdRef.current = currentStep?.id;
|
|
@@ -91,7 +84,7 @@ export function LocalizeItemDialog(props) {
|
|
|
91
84
|
const isLastStep = currentStepIndex === activeSteps.length - 1;
|
|
92
85
|
const canProceed = stepCompleted >= currentStepIndex;
|
|
93
86
|
const handleNext = async () => {
|
|
94
|
-
if (beforeNextCallback && typeof beforeNextCallback ===
|
|
87
|
+
if (beforeNextCallback && typeof beforeNextCallback === 'function') {
|
|
95
88
|
const canProceed = await beforeNextCallback();
|
|
96
89
|
if (!canProceed)
|
|
97
90
|
return;
|
|
@@ -113,40 +106,13 @@ export function LocalizeItemDialog(props) {
|
|
|
113
106
|
switchStep(currentStepIndex - 1);
|
|
114
107
|
}
|
|
115
108
|
};
|
|
116
|
-
// Calculate translation counts for
|
|
117
|
-
// the large-batch warning. Sums each selected item plus its streamed
|
|
118
|
-
// subitem count (when includeSubitems is on). `isStreaming` is true
|
|
119
|
-
// while any selected-and-flagged item is still being counted, so the
|
|
120
|
-
// UI can append a "+" to indicate the total is not yet final.
|
|
109
|
+
// Calculate translation counts for large batch warning
|
|
121
110
|
const calculateTranslationCounts = useCallback((data) => {
|
|
111
|
+
const itemsToTranslate = data.includeSubitems ? data.discoveredItems : data.items;
|
|
112
|
+
const itemCount = itemsToTranslate.length;
|
|
122
113
|
const languageCount = data.targetLanguages.length;
|
|
123
|
-
const subitemCounts = data.subitemCounts ?? {};
|
|
124
|
-
let itemCount = 0;
|
|
125
|
-
let isStreaming = false;
|
|
126
|
-
if (data.selectionTreeItems && data.selectionTreeItems.length > 0) {
|
|
127
|
-
for (const entry of data.selectionTreeItems) {
|
|
128
|
-
itemCount += 1;
|
|
129
|
-
if (!entry.includeSubitems)
|
|
130
|
-
continue;
|
|
131
|
-
const c = subitemCounts[entry.descriptor.id];
|
|
132
|
-
if (c === undefined || c === "loading") {
|
|
133
|
-
isStreaming = true;
|
|
134
|
-
}
|
|
135
|
-
else if (c !== "error") {
|
|
136
|
-
itemCount += c.count;
|
|
137
|
-
if (!c.complete)
|
|
138
|
-
isStreaming = true;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
const baseItems = data.discoveredItems && data.discoveredItems.length > 0
|
|
144
|
-
? data.discoveredItems
|
|
145
|
-
: data.items;
|
|
146
|
-
itemCount = baseItems.length;
|
|
147
|
-
}
|
|
148
114
|
const totalTranslations = itemCount * languageCount;
|
|
149
|
-
return { itemCount, languageCount, totalTranslations
|
|
115
|
+
return { itemCount, languageCount, totalTranslations };
|
|
150
116
|
}, []);
|
|
151
117
|
const executeTranslation = useCallback(async () => {
|
|
152
118
|
setIsSubmitting(true);
|
|
@@ -158,36 +124,33 @@ export function LocalizeItemDialog(props) {
|
|
|
158
124
|
targetLanguages: currentWizardData.targetLanguages,
|
|
159
125
|
includeSubitems: currentWizardData.includeSubitems,
|
|
160
126
|
discoveredItems: currentWizardData.discoveredItems,
|
|
161
|
-
selectionTreeItems: currentWizardData.selectionTreeItems,
|
|
162
127
|
};
|
|
163
128
|
try {
|
|
164
|
-
const
|
|
129
|
+
const batchId = generateBatchId();
|
|
130
|
+
const translationResult = await performDefaultTranslation(currentWizardData, editContext, batchId);
|
|
165
131
|
// Include batchId in result for navigation to translation management
|
|
166
|
-
const resultWithBatch = {
|
|
167
|
-
...result,
|
|
168
|
-
batchId: translationResult.batchId,
|
|
169
|
-
};
|
|
132
|
+
const resultWithBatch = { ...result, batchId };
|
|
170
133
|
// Close dialog and let parent component handle navigation to translation management
|
|
171
134
|
props.onClose?.(resultWithBatch);
|
|
172
135
|
}
|
|
173
136
|
catch (error) {
|
|
174
137
|
// Handle specific error types
|
|
175
|
-
if (error.name ===
|
|
138
|
+
if (error.name === 'NoTranslationsNeededError') {
|
|
176
139
|
// Show user-friendly message via toast and close dialog
|
|
177
|
-
editContext.showToast?.(
|
|
140
|
+
editContext.showToast?.('No translations needed. All selected target languages match the source languages of the items.');
|
|
178
141
|
props.onClose?.(result);
|
|
179
142
|
return;
|
|
180
143
|
}
|
|
181
144
|
// For other errors, show toast and stay in dialog
|
|
182
|
-
console.error(
|
|
183
|
-
const errorMessage = error.message ||
|
|
145
|
+
console.error('Translation failed:', error);
|
|
146
|
+
const errorMessage = error.message || 'Translation request failed. Please try again.';
|
|
184
147
|
editContext.showToast?.(errorMessage);
|
|
185
148
|
// Don't close the dialog - let user try again or cancel
|
|
186
149
|
}
|
|
187
150
|
}
|
|
188
151
|
catch (error) {
|
|
189
|
-
console.error(
|
|
190
|
-
editContext.showToast?.(
|
|
152
|
+
console.error('Unexpected error in translation:', error);
|
|
153
|
+
editContext.showToast?.('An unexpected error occurred. Please try again.');
|
|
191
154
|
// Don't close the dialog on error
|
|
192
155
|
}
|
|
193
156
|
finally {
|
|
@@ -218,40 +181,18 @@ export function LocalizeItemDialog(props) {
|
|
|
218
181
|
: null;
|
|
219
182
|
// Serialize metadata for comparison (supports both string and object formats)
|
|
220
183
|
const metadataKey = newData.metadata
|
|
221
|
-
? typeof newData.metadata ===
|
|
184
|
+
? (typeof newData.metadata === 'string'
|
|
222
185
|
? newData.metadata
|
|
223
|
-
: JSON.stringify(newData.metadata)
|
|
186
|
+
: JSON.stringify(newData.metadata))
|
|
224
187
|
: null;
|
|
225
188
|
const dataKey = JSON.stringify({
|
|
226
189
|
translationProvider: newData.translationProvider,
|
|
227
190
|
targetLanguages: [...newData.targetLanguages].sort(),
|
|
228
191
|
includeSubitems: newData.includeSubitems,
|
|
229
|
-
|
|
230
|
-
.map((item) => item?.descriptor?.id || item?.id)
|
|
231
|
-
.filter(Boolean)
|
|
232
|
-
.sort(),
|
|
192
|
+
discoveredItemsCount: newData.discoveredItems?.length || 0,
|
|
233
193
|
itemsCount: newData.items?.length || 0,
|
|
234
194
|
serviceCustomData: serviceCustomDataKey,
|
|
235
195
|
metadata: metadataKey,
|
|
236
|
-
// batchName must be part of the key — otherwise the PromptCustomizationStep's
|
|
237
|
-
// setData({...prev, batchName}) call sees an "unchanged" key here and the
|
|
238
|
-
// update is silently dropped, so the user-entered name never reaches submit.
|
|
239
|
-
batchName: newData.batchName ?? "",
|
|
240
|
-
// subitemCounts must be part of the key — otherwise live count
|
|
241
|
-
// updates from the subitem-count stream are silently dropped and
|
|
242
|
-
// the Start Translation button can't reflect them.
|
|
243
|
-
subitemCounts: newData.subitemCounts
|
|
244
|
-
? Object.keys(newData.subitemCounts)
|
|
245
|
-
.sort()
|
|
246
|
-
.map((id) => {
|
|
247
|
-
const c = newData.subitemCounts[id];
|
|
248
|
-
if (c === undefined)
|
|
249
|
-
return [id, "missing"];
|
|
250
|
-
if (c === "loading" || c === "error")
|
|
251
|
-
return [id, c];
|
|
252
|
-
return [id, c.count, c.complete];
|
|
253
|
-
})
|
|
254
|
-
: null,
|
|
255
196
|
});
|
|
256
197
|
// Skip if data hasn't actually changed
|
|
257
198
|
if (lastSetWizardDataRef.current === dataKey) {
|
|
@@ -282,21 +223,18 @@ export function LocalizeItemDialog(props) {
|
|
|
282
223
|
}, children: [_jsxs(DialogContent, { className: "flex h-[85vh] max-h-[900px] min-h-0 w-[90vw] max-w-5xl flex-col overflow-hidden md:min-h-[700px]", "data-testid": "translation-wizard-dialog", onPointerDownOutside: (e) => e.preventDefault(), onEscapeKeyDown: (e) => e.preventDefault(), "aria-describedby": "translation-wizard-description", style: {
|
|
283
224
|
width: "min(90vw, 1280px)",
|
|
284
225
|
height: "min(85vh, 900px)",
|
|
285
|
-
}, children: [_jsx(
|
|
286
|
-
|
|
287
|
-
|
|
226
|
+
}, children: [_jsx(DialogHeader, { className: "border-b border-gray-3 bg-background px-6 py-4 pr-14", children: _jsx(DialogTitle, { "data-testid": "translation-wizard-title", children: "Translate" }) }), _jsx("div", { id: "translation-wizard-description", className: "sr-only", children: currentStep?.description || "Configure and start translation for your content." }), _jsx("div", { className: "border-b border-gray-3 bg-background px-6 py-3 pr-14", children: _jsx("div", { "data-testid": "translation-wizard-step-navigation", children: _jsx("div", { className: "flex min-w-0 flex-nowrap items-center overflow-x-auto", children: activeSteps.map((step, index) => {
|
|
227
|
+
const isCurrent = currentStepIndex === index;
|
|
228
|
+
const isCompleted = currentStepIndex > index;
|
|
229
|
+
return (_jsxs(Fragment, { children: [_jsxs("div", { className: "flex shrink-0 items-center gap-2", "data-testid": `step-indicator-${step.id}`, "aria-current": isCurrent ? "step" : undefined, children: [_jsx("div", { className: cn("flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-sm font-medium transition-colors", isCurrent && "border-theme-secondary bg-theme-secondary-light text-theme-secondary", isCompleted && "border-theme-secondary bg-theme-secondary text-white", !isCurrent && !isCompleted && "border-gray-3 bg-background text-gray-2"), "data-testid": `step-indicator-circle-${step.id}`, "aria-label": step.name, children: isCompleted ? "✓" : index + 1 }), _jsx("span", { className: cn("whitespace-nowrap text-sm transition-colors", isCurrent && "font-medium text-theme-secondary", isCompleted && "text-theme-secondary", !isCurrent && !isCompleted && "text-gray-2"), "data-testid": `step-indicator-label-${step.id}`, children: step.name })] }), index < activeSteps.length - 1 && (_jsx("div", { className: cn("mx-3 h-px w-8 shrink-0 transition-colors", currentStepIndex > index ? "bg-theme-secondary" : "bg-gray-3") }))] }, step.id));
|
|
230
|
+
}) }) }) }), _jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [_jsx("div", { className: "flex-1 overflow-y-auto min-h-0 bg-[var(--color-gray-5)]", "data-testid": "translation-wizard-step-content", children: _jsx("div", { className: "h-full relative", children: activeSteps.map((step, index) => {
|
|
288
231
|
const StepComponent = step.component;
|
|
289
232
|
const isActive = index === currentStepIndex;
|
|
290
233
|
if (!StepComponent)
|
|
291
234
|
return null;
|
|
292
|
-
return (_jsx("div", { className: "h-full", style: { display: isActive ?
|
|
293
|
-
}) }) }), _jsxs(DialogButtons, { "data-testid": "translation-wizard-dialog-buttons", children: [_jsx(Button, { onClick: () => props.onClose?.(null), variant: "outline", size: "
|
|
294
|
-
const { totalTranslations, isStreaming } = calculateTranslationCounts(wizardData);
|
|
295
|
-
if (totalTranslations <= 0)
|
|
296
|
-
return "Start Translation";
|
|
297
|
-
return `Start Translation (${totalTranslations}${isStreaming ? "+" : ""})`;
|
|
298
|
-
})()) : (_jsxs(_Fragment, { children: [getNextButtonLabel(activeSteps[currentStepIndex + 1]), _jsx(ArrowRightIcon, { className: "h-4 w-4", strokeWidth: 2 })] })) })] })] })] })] }), _jsx(AlertDialog, { open: showLargeBatchWarning, onOpenChange: setShowLargeBatchWarning, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsxs(AlertDialogTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangleIcon, { className: "h-5 w-5 text-feedback-orange" }), "Large Translation Batch"] }), _jsx(AlertDialogDescription, { children: (() => {
|
|
235
|
+
return (_jsx("div", { className: "h-full", style: { display: isActive ? 'block' : 'none' }, "aria-hidden": !isActive, "data-testid": `step-content-${step.id}`, children: _jsx(StepComponent, { stepIndex: index, isActive: isActive, data: wizardData, setData: setWizardDataStable, editContext: editContext, onStepCompleted: (completed) => handleStepCompleted(completed, index), setBeforeNextCallback: isActive ? setBeforeNextCallbackStable : undefined, setFooterActions: isActive ? provideFooterActions : undefined, requestClose: isActive ? requestCloseCb : undefined }) }, step.id));
|
|
236
|
+
}) }) }), _jsxs(DialogButtons, { "data-testid": "translation-wizard-dialog-buttons", children: [_jsx(Button, { onClick: () => props.onClose?.(null), variant: "outline", size: "default", className: "w-auto min-w-24 flex-none", "data-testid": "translation-wizard-cancel-button", children: "Cancel" }), _jsxs("div", { className: "ml-auto flex min-w-0 flex-wrap items-center justify-end gap-3", children: [currentStepIndex > 0 && (_jsx(Button, { onClick: handlePrevious, variant: "outline", size: "default", className: "w-auto min-w-32 flex-none", "data-testid": "translation-wizard-previous-button", children: "Previous" })), footerActions.map((a) => (_jsx(Button, { onClick: a.onClick, disabled: !!a.disabled, variant: "default", size: "default", className: "w-auto min-w-32 flex-none", "data-testid": `translation-wizard-footer-action-${a.key}`, children: a.label }, a.key))), _jsx(Button, { onClick: handleNext, disabled: !canProceed || isSubmitting, variant: "default", size: "default", className: "w-auto min-w-40 flex-none", "data-testid": "translation-wizard-next-button", children: isSubmitting ? "Starting..." : isLastStep ? "Start Translation" : "Next" })] })] })] })] }), _jsx(AlertDialog, { open: showLargeBatchWarning, onOpenChange: setShowLargeBatchWarning, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsxs(AlertDialogTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangleIcon, { className: "h-5 w-5 text-amber-500" }), "Large Translation Batch"] }), _jsx(AlertDialogDescription, { children: (() => {
|
|
299
237
|
const { itemCount, languageCount, totalTranslations } = calculateTranslationCounts(wizardData);
|
|
300
|
-
return (_jsxs(_Fragment, { children: ["You are about to start
|
|
238
|
+
return (_jsxs(_Fragment, { children: ["You are about to start ", _jsxs("strong", { children: [totalTranslations, " translations"] }), " (", itemCount, " items \u00D7 ", languageCount, " languages). Large batches may take a significant amount of time to process.", _jsx("br", {}), _jsx("br", {}), "Are you sure you want to continue?"] }));
|
|
301
239
|
})() })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isSubmitting, children: "Cancel" }), _jsx(AlertDialogAction, { onClick: handleLargeBatchConfirm, disabled: isSubmitting, children: isSubmitting ? "Starting..." : "Continue" })] })] }) })] }));
|
|
302
240
|
}
|
|
@@ -2,6 +2,7 @@ import { TranslationStatus } from "./types";
|
|
|
2
2
|
import { FullItem } from "@parhelia/core";
|
|
3
3
|
import { TranslationWizardData } from "./steps/types";
|
|
4
4
|
import { EditContextType } from "@parhelia/core";
|
|
5
|
+
export declare function generateBatchId(): string;
|
|
5
6
|
export type StartedTranslation = {
|
|
6
7
|
itemId: string;
|
|
7
8
|
targetLanguage: string;
|
|
@@ -11,6 +12,6 @@ export type TranslationResult = {
|
|
|
11
12
|
started: StartedTranslation[];
|
|
12
13
|
batchId?: string;
|
|
13
14
|
};
|
|
14
|
-
export declare function performDefaultTranslation(wizardData: TranslationWizardData, editContext: EditContextType): Promise<TranslationResult>;
|
|
15
|
+
export declare function performDefaultTranslation(wizardData: TranslationWizardData, editContext: EditContextType, predefinedBatchId?: string): Promise<TranslationResult>;
|
|
15
16
|
export declare const defaultTranslateAll: (languageCodes: string[], translationStatus: TranslationStatus[], sessionId: string, item: FullItem, translationProvider: string) => Promise<TranslationResult>;
|
|
16
17
|
//# sourceMappingURL=LocalizeItemUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizeItemUtils.d.ts","sourceRoot":"","sources":["../src/LocalizeItemUtils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalizeItemUtils.d.ts","sourceRoot":"","sources":["../src/LocalizeItemUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAC,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIjD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,qBAAqB,EACjC,WAAW,EAAE,eAAe,EAC5B,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAyH5B;AAGD,eAAO,MAAM,mBAAmB,GAAU,eAAe,MAAM,EAAE,EAAE,mBAAmB,iBAAiB,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,QAAQ,EAAE,qBAAqB,MAAM,+BA0BxK,CAAC"}
|
|
@@ -1,43 +1,15 @@
|
|
|
1
|
-
import { requestBatchTranslation
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* any selection-tree item whose includeSubitems flag is true.
|
|
6
|
-
*
|
|
7
|
-
* Selection priority:
|
|
8
|
-
* 1. `selectionTreeItems` (new model) - per-item flags drive expansion
|
|
9
|
-
* 2. `discoveredItems` (legacy fallback) - used as-is
|
|
10
|
-
* 3. `items` (initial wizard input) - used as-is
|
|
11
|
-
*/
|
|
12
|
-
async function resolveItemIdsToTranslate(wizardData, sessionId) {
|
|
13
|
-
if (wizardData.selectionTreeItems &&
|
|
14
|
-
wizardData.selectionTreeItems.length > 0) {
|
|
15
|
-
const directIds = [];
|
|
16
|
-
const rootsToExpand = [];
|
|
17
|
-
for (const entry of wizardData.selectionTreeItems) {
|
|
18
|
-
if (entry.includeSubitems) {
|
|
19
|
-
rootsToExpand.push(entry.descriptor.id);
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
directIds.push(entry.descriptor.id);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const expandedIds = [];
|
|
26
|
-
if (rootsToExpand.length > 0) {
|
|
27
|
-
const language = wizardData.selectionTreeItems[0]?.descriptor.language;
|
|
28
|
-
const tree = await discoverItemsTree({ rootItemIds: rootsToExpand, language }, sessionId);
|
|
29
|
-
expandedIds.push(...flattenSelectableItemsFromBackendTrees(tree.trees).map((n) => n.id));
|
|
30
|
-
}
|
|
31
|
-
return Array.from(new Set([...directIds, ...expandedIds]));
|
|
32
|
-
}
|
|
33
|
-
const fallback = wizardData.discoveredItems && wizardData.discoveredItems.length > 0
|
|
34
|
-
? wizardData.discoveredItems
|
|
35
|
-
: wizardData.items;
|
|
36
|
-
return fallback.map((item) => item.descriptor.id);
|
|
1
|
+
import { requestBatchTranslation } from "./services/translationService";
|
|
2
|
+
// Shared utility for generating unique batch IDs
|
|
3
|
+
export function generateBatchId() {
|
|
4
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}-${performance.now().toString(36).substring(2, 8)}`;
|
|
37
5
|
}
|
|
38
6
|
// Unified translation function that handles both single and batch translations
|
|
39
7
|
// Now always uses batch logic for traceability in recentTranslation view
|
|
40
|
-
export async function performDefaultTranslation(wizardData, editContext) {
|
|
8
|
+
export async function performDefaultTranslation(wizardData, editContext, predefinedBatchId) {
|
|
9
|
+
const batchId = predefinedBatchId || generateBatchId();
|
|
10
|
+
const itemsToTranslate = wizardData.includeSubitems
|
|
11
|
+
? wizardData.discoveredItems
|
|
12
|
+
: wizardData.items;
|
|
41
13
|
// Build language mappings (source -> target) from the wizard data
|
|
42
14
|
// The source language is consistent for each target language across all items
|
|
43
15
|
const languageMappings = [];
|
|
@@ -52,19 +24,19 @@ export async function performDefaultTranslation(wizardData, editContext) {
|
|
|
52
24
|
targetLanguage: language,
|
|
53
25
|
});
|
|
54
26
|
}
|
|
55
|
-
//
|
|
56
|
-
const itemIds =
|
|
27
|
+
// Extract item IDs
|
|
28
|
+
const itemIds = itemsToTranslate.map(item => item.descriptor.id);
|
|
57
29
|
// Validate that we have translation requests to process
|
|
58
30
|
if (languageMappings.length === 0 || itemIds.length === 0) {
|
|
59
|
-
const error = new Error(
|
|
60
|
-
error.name =
|
|
31
|
+
const error = new Error('No translations needed. All selected target languages match the source languages of the items.');
|
|
32
|
+
error.name = 'NoTranslationsNeededError';
|
|
61
33
|
throw error;
|
|
62
34
|
}
|
|
63
35
|
// Prepare batch metadata including service-specific custom data
|
|
64
36
|
// Start with wizardData.metadata as an object
|
|
65
37
|
let metadataObj = {};
|
|
66
38
|
if (wizardData.metadata) {
|
|
67
|
-
if (typeof wizardData.metadata ===
|
|
39
|
+
if (typeof wizardData.metadata === 'string') {
|
|
68
40
|
try {
|
|
69
41
|
metadataObj = JSON.parse(wizardData.metadata);
|
|
70
42
|
}
|
|
@@ -72,27 +44,19 @@ export async function performDefaultTranslation(wizardData, editContext) {
|
|
|
72
44
|
// Invalid JSON, ignore
|
|
73
45
|
}
|
|
74
46
|
}
|
|
75
|
-
else if (typeof wizardData.metadata ===
|
|
47
|
+
else if (typeof wizardData.metadata === 'object') {
|
|
76
48
|
metadataObj = { ...wizardData.metadata };
|
|
77
49
|
}
|
|
78
50
|
}
|
|
79
|
-
//
|
|
80
|
-
// Translations and the batch detail view.
|
|
81
|
-
const trimmedName = wizardData.batchName?.trim();
|
|
82
|
-
if (trimmedName) {
|
|
83
|
-
metadataObj.name = trimmedName;
|
|
84
|
-
}
|
|
85
|
-
// Merge service-specific custom data (e.g., custom prompts for AI providers)
|
|
51
|
+
// Merge service-specific custom data (e.g., custom prompts for OpenAI)
|
|
86
52
|
if (wizardData.serviceCustomData && wizardData.serviceCustomData.size > 0) {
|
|
87
53
|
const serviceData = wizardData.serviceCustomData.get(wizardData.translationProvider);
|
|
88
54
|
if (serviceData && serviceData.enableCustomPrompt) {
|
|
89
|
-
const provider = wizardData.translationProviders.find(
|
|
55
|
+
const provider = wizardData.translationProviders.find(p => p.name === wizardData.translationProvider);
|
|
90
56
|
const defaultPrompt = provider?.defaultPrompt && provider.defaultPrompt.trim()
|
|
91
57
|
? provider.defaultPrompt
|
|
92
58
|
: null;
|
|
93
|
-
const customizationType = serviceData.promptCustomizationType === "replace"
|
|
94
|
-
? "replace"
|
|
95
|
-
: "extend";
|
|
59
|
+
const customizationType = serviceData.promptCustomizationType === "replace" ? "replace" : "extend";
|
|
96
60
|
const rawCustomPrompt = (serviceData.customPrompt || "").trim();
|
|
97
61
|
let finalCustomPrompt = "";
|
|
98
62
|
if (rawCustomPrompt) {
|
|
@@ -106,37 +70,34 @@ export async function performDefaultTranslation(wizardData, editContext) {
|
|
|
106
70
|
metadataObj.serviceCustomData = {
|
|
107
71
|
[wizardData.translationProvider]: {
|
|
108
72
|
enableCustomPrompt: serviceData.enableCustomPrompt,
|
|
109
|
-
customPrompt: finalCustomPrompt
|
|
110
|
-
}
|
|
73
|
+
customPrompt: finalCustomPrompt
|
|
74
|
+
}
|
|
111
75
|
};
|
|
112
76
|
}
|
|
113
77
|
}
|
|
114
78
|
// Serialize metadata to JSON string (or undefined if empty)
|
|
115
|
-
const batchMetadata = Object.keys(metadataObj).length > 0
|
|
116
|
-
? JSON.stringify(metadataObj)
|
|
117
|
-
: undefined;
|
|
79
|
+
const batchMetadata = Object.keys(metadataObj).length > 0 ? JSON.stringify(metadataObj) : undefined;
|
|
118
80
|
const batchResult = await requestBatchTranslation({
|
|
119
81
|
sessionId: editContext.sessionId,
|
|
82
|
+
batchId,
|
|
120
83
|
provider: wizardData.translationProvider,
|
|
121
84
|
batchMetadata,
|
|
122
85
|
languageMappings,
|
|
123
86
|
itemIds,
|
|
124
87
|
});
|
|
125
88
|
if (batchResult.type === "error") {
|
|
126
|
-
console.error(
|
|
89
|
+
console.error('Translation request failed:', batchResult);
|
|
127
90
|
// Throw an error so the caller can handle it (e.g., show a toast)
|
|
128
|
-
const errorMessage = batchResult.details ||
|
|
129
|
-
batchResult.summary ||
|
|
130
|
-
"Translation request failed";
|
|
91
|
+
const errorMessage = batchResult.details || batchResult.summary || 'Translation request failed';
|
|
131
92
|
const error = new Error(errorMessage);
|
|
132
|
-
error.name =
|
|
93
|
+
error.name = 'TranslationRequestError';
|
|
133
94
|
throw error;
|
|
134
95
|
}
|
|
135
96
|
const batchResponse = batchResult.data;
|
|
136
97
|
if (!batchResponse) {
|
|
137
|
-
console.error(
|
|
138
|
-
const error = new Error(
|
|
139
|
-
error.name =
|
|
98
|
+
console.error('Batch response is undefined');
|
|
99
|
+
const error = new Error('Server returned an empty response');
|
|
100
|
+
error.name = 'TranslationRequestError';
|
|
140
101
|
throw error;
|
|
141
102
|
}
|
|
142
103
|
const started = batchResponse.started?.map((result) => ({
|
|
@@ -145,9 +106,9 @@ export async function performDefaultTranslation(wizardData, editContext) {
|
|
|
145
106
|
jobId: result.jobId,
|
|
146
107
|
})) || [];
|
|
147
108
|
if (batchResponse.skipped && batchResponse.skipped.length > 0) {
|
|
148
|
-
console.warn(
|
|
109
|
+
console.warn('Some translations were skipped:', batchResponse.skipped);
|
|
149
110
|
}
|
|
150
|
-
return { started, batchId
|
|
111
|
+
return { started, batchId };
|
|
151
112
|
}
|
|
152
113
|
// Legacy function for backward compatibility
|
|
153
114
|
export const defaultTranslateAll = async (languageCodes, translationStatus, sessionId, item, translationProvider) => {
|
|
@@ -155,24 +116,21 @@ export const defaultTranslateAll = async (languageCodes, translationStatus, sess
|
|
|
155
116
|
const wizardData = {
|
|
156
117
|
items: [item],
|
|
157
118
|
targetLanguages: languageCodes,
|
|
158
|
-
languageData: new Map(languageCodes.map(
|
|
159
|
-
const status = translationStatus?.find(
|
|
160
|
-
return [
|
|
161
|
-
lang,
|
|
162
|
-
{
|
|
119
|
+
languageData: new Map(languageCodes.map(lang => {
|
|
120
|
+
const status = translationStatus?.find(x => x.targetLanguage === lang);
|
|
121
|
+
return [lang, {
|
|
163
122
|
name: lang,
|
|
164
123
|
languageCode: lang,
|
|
165
124
|
items: [item.descriptor],
|
|
166
|
-
translationStatus: status
|
|
167
|
-
}
|
|
168
|
-
];
|
|
125
|
+
translationStatus: status
|
|
126
|
+
}];
|
|
169
127
|
})),
|
|
170
128
|
translationProvider,
|
|
171
129
|
includeSubitems: false,
|
|
172
130
|
discoveredItems: [],
|
|
173
131
|
itemMetadata: new Map(),
|
|
174
132
|
metadata: null,
|
|
175
|
-
translationProviders: []
|
|
133
|
+
translationProviders: []
|
|
176
134
|
};
|
|
177
135
|
const editContext = { sessionId };
|
|
178
136
|
const result = await performDefaultTranslation(wizardData, editContext);
|
package/dist/api/discovery.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export declare function suggestBatchName(req: {
|
|
2
|
-
itemIds: string[];
|
|
3
|
-
targetLanguages: string[];
|
|
4
|
-
includeSubitems?: boolean;
|
|
5
|
-
}, sessionId?: string): Promise<string | null>;
|
|
6
1
|
export type DiscoveredItem = {
|
|
7
2
|
id: string;
|
|
8
3
|
name: string;
|
|
@@ -36,24 +31,4 @@ export type DiscoverItemsTreeResponse = {
|
|
|
36
31
|
export declare function discoverItemsTree(req: DiscoverItemsTreeRequest, sessionId?: string): Promise<DiscoverItemsTreeResponse>;
|
|
37
32
|
export declare function convertBackendTreeToTreeNodes(trees: BackendTreeNode[]): TreeNode[];
|
|
38
33
|
export declare function flattenSelectableItemsFromBackendTrees(trees: BackendTreeNode[]): DiscoveredItem[];
|
|
39
|
-
export type SubitemCountUpdate = {
|
|
40
|
-
count: number;
|
|
41
|
-
complete: boolean;
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* Streams the running subitem count for a given root item from the
|
|
45
|
-
* backend. The endpoint returns newline-delimited JSON; each parsed line
|
|
46
|
-
* triggers `onProgress` so the UI can show the count climbing. Filtered
|
|
47
|
-
* server-side via IItemExportFilter + excluded templates.
|
|
48
|
-
*
|
|
49
|
-
* Pass an `AbortSignal` to cancel mid-stream (e.g. user toggles the
|
|
50
|
-
* subitems flag off again).
|
|
51
|
-
*/
|
|
52
|
-
export declare function streamSubitemCount(req: {
|
|
53
|
-
itemId: string;
|
|
54
|
-
language?: string;
|
|
55
|
-
}, onProgress: (update: SubitemCountUpdate) => void, options?: {
|
|
56
|
-
sessionId?: string;
|
|
57
|
-
signal?: AbortSignal;
|
|
58
|
-
}): Promise<number>;
|
|
59
34
|
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/api/discovery.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/api/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAC;AAInG,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,wBAAwB,EAC7B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,yBAAyB,CAAC,CAWpC;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,QAAQ,EAAE,CAQlF;AAED,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,cAAc,EAAE,CAQjG"}
|