@parhelia/localization 0.1.12350 → 0.1.12390

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.
@@ -1 +1 @@
1
- {"version":3,"file":"LocalizeItemDialog.d.ts","sourceRoot":"","sources":["../src/LocalizeItemDialog.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAqBvB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,uBAAuB,CAAC,2CA6atE"}
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,2CA0btE"}
@@ -2,16 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Dialog, DialogContent, DialogHeader, DialogTitle, Button, cn, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@parhelia/core";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { AlertTriangle as LucideAlertTriangle } from "lucide-react";
5
- import { ChevronRight as LucideChevronRight } from "lucide-react";
6
5
  import { useTranslationWizard } from "./hooks/useTranslationWizard";
7
6
  import { performDefaultTranslation, generateBatchId } from "./LocalizeItemUtils";
8
- const ChevronRightIcon = LucideChevronRight;
9
7
  const AlertTriangleIcon = LucideAlertTriangle;
10
8
  // Threshold for warning about large batch translations
11
9
  const LARGE_BATCH_WARNING_THRESHOLD = 100;
12
10
  // Note: DialogButtons is an internal component that might need to be added to core exports
13
11
  // For now, we'll implement it inline
14
- const DialogButtons = ({ children, ...props }) => (_jsx("div", { className: "flex justify-end gap-2 pt-4 border-t border-[var(--color-gray-3)] mt-auto px-6 pb-6 bg-background", ...props, children: children }));
12
+ const DialogButtons = ({ children, ...props }) => (_jsx("div", { className: "mt-auto flex flex-wrap items-stretch justify-end gap-2 gap-y-2 border-t border-[var(--color-gray-3)] bg-background px-6 pt-4 pb-6", ...props, children: children }));
15
13
  export function LocalizeItemDialog(props) {
16
14
  const editContext = props.editContext;
17
15
  const configuration = editContext.configuration.translationWizard;
@@ -222,25 +220,23 @@ export function LocalizeItemDialog(props) {
222
220
  if (!open) {
223
221
  props.onClose?.(null);
224
222
  }
225
- }, children: [_jsxs(DialogContent, { className: "w-[90vw] max-w-5xl h-[85vh] max-h-[900px] min-h-[700px] overflow-hidden flex flex-col", "data-testid": "translation-wizard-dialog", onPointerDownOutside: (e) => e.preventDefault(), onEscapeKeyDown: (e) => e.preventDefault(), "aria-describedby": "translation-wizard-description", style: {
226
- width: 'min(90vw, 1280px)',
227
- height: 'min(85vh, 900px)',
228
- minHeight: '700px'
229
- }, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { "data-testid": "translation-wizard-title", children: "Translation Wizard" }), _jsx("div", { id: "translation-wizard-description", className: "sr-only", children: currentStep?.description || "Configure and start translation for your content." })] }), _jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [_jsx("div", { className: "border-b border-[var(--color-gray-3)] bg-background px-6", "data-testid": "translation-wizard-step-navigation", children: _jsx("div", { className: "flex items-center gap-3 py-3", children: activeSteps.map((step, index) => (_jsxs("div", { className: "flex items-center gap-2", "data-testid": `step-indicator-${step.id}`, children: [_jsx("div", { className: cn("flex h-8 w-8 items-center justify-center rounded-full border text-sm font-medium transition-colors", currentStepIndex === index &&
230
- "border-[#9650fb] text-[#9650fb] bg-[#f6eeff]", currentStepIndex < index && "border-[var(--color-gray-3)] text-[var(--color-gray-2)]", currentStepIndex > index &&
231
- "bg-[#9650fb] text-white border-[#9650fb]"), style: currentStepIndex > index
232
- ? { backgroundColor: "#9650fb", color: "#ffffff" }
233
- : undefined, "data-testid": `step-indicator-circle-${step.id}`, children: currentStepIndex > index ? "✓" : index + 1 }), _jsx("span", { className: cn("text-sm", currentStepIndex === index
234
- ? "text-[#9650fb] font-medium"
235
- : currentStepIndex > index
236
- ? "text-[#9650fb]"
237
- : "text-[var(--color-gray-2)]"), "data-testid": `step-indicator-label-${step.id}`, children: step.name }), index < activeSteps.length - 1 && (_jsx(ChevronRightIcon, { className: "w-4 h-4 text-[var(--color-gray-3)] mx-2", strokeWidth: 1 }))] }, step.id))) }) }), _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) => {
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: {
224
+ width: "min(90vw, 1280px)",
225
+ height: "min(85vh, 900px)",
226
+ }, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { "data-testid": "translation-wizard-title", children: "Translation Wizard" }), _jsx("div", { id: "translation-wizard-description", className: "sr-only", children: currentStep?.description || "Configure and start translation for your content." })] }), _jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [_jsx("div", { className: "border-b border-[var(--color-gray-3)] bg-background px-6", "data-testid": "translation-wizard-step-navigation", children: _jsx("div", { className: "flex min-w-0 flex-nowrap items-center gap-1 overflow-x-auto py-2.5 md:gap-1.5", children: activeSteps.map((step, index) => {
227
+ const isCurrent = currentStepIndex === index;
228
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-1", "data-testid": `step-indicator-${step.id}`, "aria-current": isCurrent ? "step" : undefined, children: [_jsx("div", { className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-sm font-medium transition-colors", isCurrent && "border-[#9650fb] bg-[#f6eeff] text-[#9650fb]", currentStepIndex < index && "border-[var(--color-gray-3)] text-[var(--color-gray-2)]", currentStepIndex > index && "border-[#9650fb] bg-[#9650fb] text-white"), style: currentStepIndex > index ? { backgroundColor: "#9650fb", color: "#ffffff" } : undefined, "data-testid": `step-indicator-circle-${step.id}`, "aria-label": step.name, children: currentStepIndex > index ? "✓" : index + 1 }), _jsx("span", { className: cn("inline-block text-sm align-middle transition-[max-width,opacity] duration-200 ease-out md:max-w-none md:opacity-100", isCurrent
229
+ ? "max-w-[min(calc(100vw-5rem),20rem)] overflow-visible text-[#9650fb] font-medium max-md:opacity-100 md:max-w-none"
230
+ : currentStepIndex > index
231
+ ? "overflow-hidden text-[#9650fb] max-md:pointer-events-none max-md:max-w-0 max-md:opacity-0"
232
+ : "overflow-hidden text-[var(--color-gray-2)] max-md:pointer-events-none max-md:max-w-0 max-md:opacity-0"), "data-testid": `step-indicator-label-${step.id}`, children: _jsx("span", { className: cn(isCurrent ? "whitespace-normal break-words md:whitespace-nowrap" : "whitespace-nowrap"), children: step.name }) })] }, step.id));
233
+ }) }) }), _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) => {
238
234
  const StepComponent = step.component;
239
235
  const isActive = index === currentStepIndex;
240
236
  if (!StepComponent)
241
237
  return null;
242
238
  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));
243
- }) }) }), _jsxs(DialogButtons, { "data-testid": "translation-wizard-dialog-buttons", children: [_jsx(Button, { onClick: () => props.onClose?.(null), variant: "outline", size: "default", "data-testid": "translation-wizard-cancel-button", children: "Cancel" }), currentStepIndex > 0 && (_jsx(Button, { onClick: handlePrevious, variant: "outline", size: "default", "data-testid": "translation-wizard-previous-button", children: "Previous" })), footerActions.map((a) => (_jsx(Button, { onClick: a.onClick, disabled: !!a.disabled, variant: "default", size: "default", "data-testid": `translation-wizard-footer-action-${a.key}`, children: a.label }, a.key))), _jsx(Button, { onClick: handleNext, disabled: !canProceed || isSubmitting, variant: "default", size: "default", "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: (() => {
239
+ }) }) }), _jsxs(DialogButtons, { "data-testid": "translation-wizard-dialog-buttons", children: [_jsx(Button, { onClick: () => props.onClose?.(null), variant: "outline", size: "default", className: "max-md:hidden", "data-testid": "translation-wizard-cancel-button", children: "Cancel" }), _jsxs("div", { className: "flex min-h-10 min-w-0 flex-1 flex-wrap items-stretch justify-end gap-2 md:contents", children: [currentStepIndex > 0 && (_jsx(Button, { onClick: handlePrevious, variant: "outline", size: "default", className: "min-w-0 flex-1 md:min-w-[6.5rem] md:flex-initial", "data-testid": "translation-wizard-previous-button", children: "Previous" })), footerActions.map((a) => (_jsx(Button, { onClick: a.onClick, disabled: !!a.disabled, variant: "default", size: "default", className: "min-w-0 flex-1 md:flex-initial md:min-w-[6.5rem]", "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: "min-w-0 flex-1 md:min-w-[6.5rem] md:flex-initial", "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: (() => {
244
240
  const { itemCount, languageCount, totalTranslations } = calculateTranslationCounts(wizardData);
245
241
  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?"] }));
246
242
  })() })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isSubmitting, children: "Cancel" }), _jsx(AlertDialogAction, { onClick: handleLargeBatchConfirm, disabled: isSubmitting, children: isSubmitting ? "Starting..." : "Continue" })] })] }) })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAatD,OAAO,EACL,mBAAmB,EAGpB,MAAM,gBAAgB,CAAC;AAUxB,QAAA,MAAM,wCAAwC;;;;;;;;;;;;8BAalB,qBAAqB;;CAchD,CAAC;AAEF,QAAA,MAAM,4CAA4C;;;;;;;CAUjD,CAAC;AAkCF,OAAO,EAAE,wCAAwC,EAAE,4CAA4C,EAAE,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,YAAY,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhF,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,mBAAmB,EAClC,OAAO,CAAC,EAAE,yBAAyB,uBAsIpC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAatD,OAAO,EACL,mBAAmB,EAGpB,MAAM,gBAAgB,CAAC;AAUxB,QAAA,MAAM,wCAAwC;;;;;;;;;;;;8BAalB,qBAAqB;;CAchD,CAAC;AAEF,QAAA,MAAM,4CAA4C;;;;;;;CAUjD,CAAC;AAkCF,OAAO,EAAE,wCAAwC,EAAE,4CAA4C,EAAE,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,YAAY,EACV,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhF,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,mBAAmB,EAClC,OAAO,CAAC,EAAE,yBAAyB,uBAiIpC"}
package/dist/index.js CHANGED
@@ -110,15 +110,6 @@ export * from "./steps/types";
110
110
  export function configureLocalization(configuration, options) {
111
111
  const enableMultiItem = options?.multiItem !== false; // Default to true
112
112
  const enableTranslationManagement = options?.translationManagement !== false; // Default to true
113
- // Add localization setup step
114
- if (!configuration.setup) {
115
- configuration.setup = { steps: [] };
116
- }
117
- // Check if the step is already added (to prevent duplicates)
118
- const hasLocalizationSetup = configuration.setup.steps.some((step) => step === LocalizationSetupStep || step.name === "LocalizationSetupStep");
119
- if (!hasLocalizationSetup) {
120
- configuration.setup.steps.push(LocalizationSetupStep);
121
- }
122
113
  // Initialize the translation wizard configuration if it doesn't exist
123
114
  if (!configuration.translationWizard) {
124
115
  configuration.translationWizard = enableMultiItem
@@ -188,35 +179,40 @@ export function configureLocalization(configuration, options) {
188
179
  icon: _jsx(LanguagesIcon, { strokeWidth: 1 }),
189
180
  content: _jsx(TranslationServicesPanel, {}),
190
181
  };
182
+ const localizationSetupPanel = {
183
+ id: "localization-setup",
184
+ title: "Localization Setup",
185
+ subtitle: "Verify translation services and create provider settings items",
186
+ icon: _jsx(LanguagesIcon, { strokeWidth: 1 }),
187
+ content: _jsx(LocalizationSetupStep, {}),
188
+ };
189
+ const ensureLocalizationPanels = (group) => {
190
+ if (!group.panels.some((p) => p.id === "translation-services")) {
191
+ group.panels.push(translationServicesPanel);
192
+ }
193
+ if (!group.panels.some((p) => p.id === "localization-setup")) {
194
+ group.panels.push(localizationSetupPanel);
195
+ }
196
+ };
191
197
  if (localizationGroupIndex >= 0) {
192
- // Group exists, check if panel is already added
193
198
  const group = configuration.settings.groups[localizationGroupIndex];
194
199
  if (group) {
195
- const hasPanel = group.panels.some((p) => p.id === "translation-services");
196
- if (!hasPanel) {
197
- group.panels.push(translationServicesPanel);
198
- }
200
+ ensureLocalizationPanels(group);
199
201
  }
200
202
  }
201
203
  else {
202
- // Group doesn't exist, create it
203
204
  const groups = configuration.settings.groups;
204
205
  const aboutIndex = groups.findIndex((g) => g.title === "About");
206
+ const localizationGroup = {
207
+ title: "Localization",
208
+ icon: _jsx(LanguagesIcon, { strokeWidth: 1 }),
209
+ panels: [translationServicesPanel, localizationSetupPanel],
210
+ };
205
211
  if (aboutIndex >= 0) {
206
- // Insert before About section
207
- groups.splice(aboutIndex, 0, {
208
- title: "Localization",
209
- icon: _jsx(LanguagesIcon, { strokeWidth: 1 }),
210
- panels: [translationServicesPanel],
211
- });
212
+ groups.splice(aboutIndex, 0, localizationGroup);
212
213
  }
213
214
  else {
214
- // No About section found, push to end
215
- groups.push({
216
- title: "Localization",
217
- icon: _jsx(LanguagesIcon, { strokeWidth: 1 }),
218
- panels: [translationServicesPanel],
219
- });
215
+ groups.push(localizationGroup);
220
216
  }
221
217
  }
222
218
  return configuration;
@@ -1 +1 @@
1
- {"version":3,"file":"TranslationServicesPanel.d.ts","sourceRoot":"","sources":["../../src/settings/TranslationServicesPanel.tsx"],"names":[],"mappings":"AA6DA;;;GAGG;AACH,wBAAgB,wBAAwB,4CAgnBvC;AAED,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"TranslationServicesPanel.d.ts","sourceRoot":"","sources":["../../src/settings/TranslationServicesPanel.tsx"],"names":[],"mappings":"AA6DA;;;GAGG;AACH,wBAAgB,wBAAwB,4CA+mBvC;AAED,eAAe,wBAAwB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useState, useEffect, } from "react";
3
- import { Button, useEditContext, Splitter, ItemConfigPanel, useMediaQuery, } from "@parhelia/core";
3
+ import { Button, useEditContext, Splitter, ItemConfigPanel, } from "@parhelia/core";
4
4
  import { CheckCircle as LucideCheckCircle, AlertCircle as LucideAlertCircle, RefreshCw as LucideRefreshCw, Plus as LucidePlus, Settings as LucideSettings, } from "lucide-react";
5
5
  import { getAvailableTranslationServices, createProviderSettings, getTranslationStructure, ensureTranslationStructure, } from "../services/translationService";
6
6
  const CheckCircleIcon = LucideCheckCircle;
@@ -28,7 +28,6 @@ export function TranslationServicesPanel() {
28
28
  const [structureState, setStructureState] = useState(null);
29
29
  const [structureLoading, setStructureLoading] = useState(false);
30
30
  const [ensuringStructure, setEnsuringStructure] = useState(false);
31
- const isMobile = useMediaQuery("(max-width: 768px)");
32
31
  const handleOpenConfig = useCallback((service) => {
33
32
  if (service.isConfigured && service.settingsItemId) {
34
33
  setSelectedConfigTarget({
@@ -218,14 +217,14 @@ export function TranslationServicesPanel() {
218
217
  structureState?.translationFolder?.itemId && (_jsxs(Button, { size: "sm", variant: selectedConfigTarget?.key ===
219
218
  `structure:${structureState.translationFolder.itemId}`
220
219
  ? "default"
221
- : "ghost", onClick: () => handleOpenStructureConfig(structureState.translationFolder.itemId, "Configure: Translation folder"), className: "shrink-0", children: [_jsx(SettingsIcon, { className: "h-4 w-4", strokeWidth: 1.5 }), isMobile ? "" : "Configure"] })) })] }), _jsxs("div", { className: `flex items-center justify-between rounded border p-3 text-sm transition-shadow hover:shadow-sm ${selectedConfigTarget?.key ===
220
+ : "ghost", onClick: () => handleOpenStructureConfig(structureState.translationFolder.itemId, "Configure: Translation folder"), className: "shrink-0", children: [_jsx(SettingsIcon, { className: "h-4 w-4", strokeWidth: 1.5 }), editContext?.isMobile ? "" : "Configure"] })) })] }), _jsxs("div", { className: `flex items-center justify-between rounded border p-3 text-sm transition-shadow hover:shadow-sm ${selectedConfigTarget?.key ===
222
221
  `structure:${structureState?.translationProvidersFolder?.itemId}`
223
222
  ? "border-blue-400 bg-blue-50"
224
223
  : "border-gray-200 bg-white"}`, children: [_jsxs("div", { className: "flex items-center gap-3 flex-1 min-w-0", children: [providersFolderExists ? (_jsx(CheckCircleIcon, { className: "h-4 w-4 text-green-600 shrink-0", strokeWidth: 1.5 })) : (_jsx(AlertCircleIcon, { className: "h-4 w-4 text-amber-500 shrink-0", strokeWidth: 1.5 })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-gray-900", children: "Translation providers folder" }), _jsx("div", { className: "text-xs text-gray-500 truncate", children: structureSettings.translationProvidersPath })] })] }), _jsx("div", { className: "flex items-center gap-2 ml-4 shrink-0", children: providersFolderExists &&
225
224
  structureState?.translationProvidersFolder?.itemId && (_jsxs(Button, { size: "sm", variant: selectedConfigTarget?.key ===
226
225
  `structure:${structureState.translationProvidersFolder.itemId}`
227
226
  ? "default"
228
- : "ghost", onClick: () => handleOpenStructureConfig(structureState.translationProvidersFolder.itemId, "Configure: Translation Providers folder"), className: "shrink-0", children: [_jsx(SettingsIcon, { className: "h-4 w-4", strokeWidth: 1.5 }), isMobile ? "" : "Configure"] })) })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold text-gray-900", children: "Providers" }), _jsxs("div", { className: "mt-1 flex items-center gap-2", children: [statusIcon(state), _jsx("span", { className: "text-sm text-gray-700", children: totalCount > 0
227
+ : "ghost", onClick: () => handleOpenStructureConfig(structureState.translationProvidersFolder.itemId, "Configure: Translation Providers folder"), className: "shrink-0", children: [_jsx(SettingsIcon, { className: "h-4 w-4", strokeWidth: 1.5 }), editContext?.isMobile ? "" : "Configure"] })) })] })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-semibold text-gray-900", children: "Providers" }), _jsxs("div", { className: "mt-1 flex items-center gap-2", children: [statusIcon(state), _jsx("span", { className: "text-sm text-gray-700", children: totalCount > 0
229
228
  ? `${configuredCount} of ${totalCount} service${totalCount !== 1 ? "s" : ""} configured`
230
229
  : "No services found" })] })] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadData, children: [_jsx(RefreshCwIcon, { strokeWidth: 1, className: "h-4 w-4" }), "Refresh"] })] }), totalCount === 0 && state === "success" && (_jsx("div", { className: "rounded border border-yellow-200 bg-yellow-50 p-3 text-sm text-yellow-800", children: "No translation services are registered in dependency injection. Please check the server configuration." })), services.length > 0 && (_jsx("div", { className: "space-y-1", children: services.map((service) => {
231
230
  const isSelected = selectedConfigTarget?.key ===
@@ -235,7 +234,7 @@ export function TranslationServicesPanel() {
235
234
  : "border-gray-200 bg-white"}`, children: [_jsxs("div", { className: "flex items-center gap-3 flex-1 min-w-0", children: [service.isConfigured ? (_jsx(CheckCircleIcon, { className: "h-4 w-4 text-green-600 shrink-0", strokeWidth: 1.5 })) : (_jsx(AlertCircleIcon, { className: "h-4 w-4 text-amber-500 shrink-0", strokeWidth: 1.5 })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-gray-900 truncate", children: service.displayName || service.serviceName }), service.displayName &&
236
235
  service.displayName !== service.serviceName && (_jsxs("div", { className: "text-xs text-gray-500 truncate", children: ["Service: ", service.serviceName] })), service.isConfigured &&
237
236
  service.supportedLanguages &&
238
- service.supportedLanguages.length > 0 && (_jsxs("div", { className: "text-xs text-gray-500 mt-1 truncate", children: ["Languages:", " ", service.supportedLanguages.join(", ")] })), !service.isConfigured && (_jsx("div", { className: "text-xs text-amber-600 mt-1", children: "Not configured" }))] })] }), _jsx("div", { className: "flex items-center gap-2 ml-4 shrink-0", children: service.isConfigured && service.settingsItemId ? (_jsxs(Button, { size: "sm", variant: isSelected ? "default" : "ghost", onClick: () => handleOpenConfig(service), className: "shrink-0", children: [_jsx(SettingsIcon, { className: "h-4 w-4", strokeWidth: 1.5 }), isMobile ? "" : "Configure"] })) : (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => createSettings(service.serviceName, service.templateId), disabled: service.creating, title: `Create settings using template: ${service.templateName}`, className: "shrink-0", children: [service.creating ? (_jsx(RefreshCwIcon, { strokeWidth: 1, className: "h-4 w-4 animate-spin" })) : (_jsx(PlusIcon, { strokeWidth: 1.5, className: "h-4 w-4" })), "Create Settings"] })) })] }, service.serviceName));
237
+ service.supportedLanguages.length > 0 && (_jsxs("div", { className: "text-xs text-gray-500 mt-1 truncate", children: ["Languages:", " ", service.supportedLanguages.join(", ")] })), !service.isConfigured && (_jsx("div", { className: "text-xs text-amber-600 mt-1", children: "Not configured" }))] })] }), _jsx("div", { className: "flex items-center gap-2 ml-4 shrink-0", children: service.isConfigured && service.settingsItemId ? (_jsxs(Button, { size: "sm", variant: isSelected ? "default" : "ghost", onClick: () => handleOpenConfig(service), className: "shrink-0", children: [_jsx(SettingsIcon, { className: "h-4 w-4", strokeWidth: 1.5 }), editContext?.isMobile ? "" : "Configure"] })) : (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => createSettings(service.serviceName, service.templateId), disabled: service.creating, title: `Create settings using template: ${service.templateName}`, className: "shrink-0", children: [service.creating ? (_jsx(RefreshCwIcon, { strokeWidth: 1, className: "h-4 w-4 animate-spin" })) : (_jsx(PlusIcon, { strokeWidth: 1.5, className: "h-4 w-4" })), "Create Settings"] })) })] }, service.serviceName));
239
238
  }) })), error && (_jsx("div", { className: "rounded border border-red-200 bg-red-50 p-3 text-sm text-red-700 whitespace-pre-wrap", children: error }))] }) }) }) }));
240
239
  // Build splitter panels - only show config panel when a service is selected
241
240
  const panels = selectedConfigTarget?.itemId
@@ -262,6 +261,6 @@ export function TranslationServicesPanel() {
262
261
  },
263
262
  ];
264
263
  const SplitterComponent = Splitter;
265
- return (_jsx(SplitterComponent, { panels: panels, localStorageKey: "translation-services-panel-splitter", direction: isMobile ? "vertical" : "horizontal", className: "h-full w-full" }));
264
+ return (_jsx(SplitterComponent, { panels: panels, localStorageKey: "translation-services-panel-splitter", direction: editContext?.isMobile ? "vertical" : "horizontal", className: "h-full w-full" }));
266
265
  }
267
266
  export default TranslationServicesPanel;
@@ -1 +1 @@
1
- {"version":3,"file":"ServiceLanguageSelectionStep.d.ts","sourceRoot":"","sources":["../../src/steps/ServiceLanguageSelectionStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAGtE,wBAAgB,4BAA4B,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,2CAiZ7K"}
1
+ {"version":3,"file":"ServiceLanguageSelectionStep.d.ts","sourceRoot":"","sources":["../../src/steps/ServiceLanguageSelectionStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAkBtE,wBAAgB,4BAA4B,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,2CA8Z7K"}
@@ -1,6 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  // Version creation now offered via VersionOnlyTranslationProvider. No custom button here.
4
+ function getTranslationStatusBadgeClass(status) {
5
+ switch (status) {
6
+ case "Completed":
7
+ return "text-green-700 bg-green-100";
8
+ case "Error":
9
+ return "text-red-700 bg-red-100";
10
+ case "In Progress":
11
+ return "text-purple-800 bg-purple-100";
12
+ case "Not started":
13
+ return "text-[var(--color-gray-2)] bg-[var(--color-gray-4)]";
14
+ default:
15
+ return "text-[var(--color-gray-2)] bg-[var(--color-gray-4)]";
16
+ }
17
+ }
4
18
  export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data, setData, onStepCompleted, editContext, setFooterActions, requestClose }) {
5
19
  const [languageSelection, setLanguageSelection] = useState({});
6
20
  const dataRef = useRef(data);
@@ -220,5 +234,8 @@ export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data,
220
234
  }
221
235
  }, onChange: handleSelectAllLanguages, className: "h-4 w-4 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": "select-all-languages-checkbox" }), _jsx("span", { className: "ml-2 text-xs text-[var(--color-gray-2)]", children: "Select All" })] }))] }), _jsx("p", { className: "text-xs text-[var(--color-gray-2)] mb-3", children: "Select the languages you want to translate this content into." }), _jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg min-h-[200px] max-h-64 overflow-y-auto bg-[var(--color-gray-5)]", children: data.translationProviders.length === 0 || allLanguages.length === 0 ? (
222
236
  // Loading skeleton
223
- _jsx("div", { className: "p-3 space-y-2", children: [...Array(4)].map((_, i) => (_jsxs("div", { className: "flex items-center animate-pulse", children: [_jsx("div", { className: "h-4 w-4 bg-[var(--color-gray-3)] rounded" }), _jsx("div", { className: "ml-2 h-4 bg-[var(--color-gray-3)] rounded w-32" }), _jsx("div", { className: "ml-auto h-4 bg-[var(--color-gray-3)] rounded w-16" })] }, i))) })) : (_jsx("div", { className: "p-3 grid grid-cols-1 gap-2", children: allLanguages.map((lang) => (_jsxs("label", { className: "flex items-center py-1.5 px-2 rounded-md hover:bg-[var(--color-gray-4)] transition-colors cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: languageSelection[lang.code] || false, onChange: () => handleLanguageToggle(lang.code), className: "h-4 w-4 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": `language-checkbox-${lang.code}` }), _jsxs("span", { className: "ml-2 text-sm text-[var(--color-dark)] flex items-center gap-2", children: [_jsxs("span", { children: [lang.name, " (", lang.code, ")"] }), lang.sourceLanguage && (_jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["from: ", lang.sourceLanguage] }))] }), !lang.hasVersions && (_jsx("span", { className: "ml-auto text-2xs text-orange-600 bg-orange-100 px-2 py-1 rounded-full font-medium", children: "No versions" }))] }, lang.code))) })) })] })] })] }));
237
+ _jsx("div", { className: "p-3 space-y-2", children: [...Array(4)].map((_, i) => (_jsxs("div", { className: "flex items-center animate-pulse", children: [_jsx("div", { className: "h-4 w-4 bg-[var(--color-gray-3)] rounded" }), _jsx("div", { className: "ml-2 h-4 bg-[var(--color-gray-3)] rounded w-32" }), _jsx("div", { className: "ml-auto h-4 bg-[var(--color-gray-3)] rounded w-16" })] }, i))) })) : (_jsx("div", { className: "p-3 grid grid-cols-1 gap-2", children: allLanguages.map((lang) => {
238
+ const statusLabel = lang.translationStatus?.status || "Not started";
239
+ return (_jsxs("label", { className: "flex items-start gap-2 py-1.5 px-2 rounded-md hover:bg-[var(--color-gray-4)] transition-colors cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: languageSelection[lang.code] || false, onChange: () => handleLanguageToggle(lang.code), className: "mt-0.5 h-4 w-4 shrink-0 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": `language-checkbox-${lang.code}` }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "text-sm text-[var(--color-dark)]", children: [lang.name, " (", lang.code, ")"] }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-[var(--color-gray-2)]", children: [lang.sourceLanguage && _jsxs("span", { children: ["from: ", lang.sourceLanguage] }), !lang.hasVersions && (_jsx("span", { className: "text-2xs text-orange-600 bg-orange-100 px-2 py-1 rounded-full font-medium", children: "No versions" })), lang.hasVersions && (_jsx("span", { title: lang.translationStatus?.message, className: `text-2xs px-2 py-1 rounded-full font-medium ${getTranslationStatusBadgeClass(statusLabel)}`, "data-testid": `translation-status-badge-${lang.code}`, children: statusLabel }))] })] })] }, lang.code));
240
+ }) })) })] })] })] }));
224
241
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SubitemDiscoveryStep.d.ts","sourceRoot":"","sources":["../../src/steps/SubitemDiscoveryStep.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAW/C,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,2CA8f5L"}
1
+ {"version":3,"file":"SubitemDiscoveryStep.d.ts","sourceRoot":"","sources":["../../src/steps/SubitemDiscoveryStep.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAW/C,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAe,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,EAAE,oBAAoB,2CA4f5L"}
@@ -269,132 +269,132 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
269
269
  setIsDiscovering(false);
270
270
  setDiscoveryComplete(true);
271
271
  };
272
- return (_jsxs("div", { className: "flex flex-col gap-4 p-6 h-full", "data-testid": "subitem-discovery-step", children: [_jsxs("div", { className: "mb-2", children: [_jsx("h3", { className: "text-lg font-semibold text-[var(--color-dark)] mb-1", children: "Discover Subitems" }), _jsx("p", { className: "text-sm text-[var(--color-gray-2)]", children: "Scanning for subitems to include in translation..." })] }), _jsxs("div", { className: "border border-[var(--color-gray-3)] rounded-lg p-4 bg-background min-h-[120px] shadow-sm", "data-testid": "discovery-status-container", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isDiscovering && _jsx(Spinner, { "data-testid": "discovery-spinner" }), _jsx("span", { className: "font-medium text-[var(--color-dark)]", "data-testid": "discovery-status-text", children: isDiscovering ? 'Discovering subitems...' :
272
+ return (_jsxs("div", { className: "flex min-h-0 flex-col gap-4 p-6", "data-testid": "subitem-discovery-step", children: [_jsxs("div", { className: "mb-2 shrink-0", children: [_jsx("h3", { className: "text-lg font-semibold text-[var(--color-dark)] mb-1", children: "Discover Subitems" }), _jsx("p", { className: "text-sm text-[var(--color-gray-2)]", children: "Scanning for subitems to include in translation..." })] }), _jsxs("div", { className: "flex w-full min-w-0 shrink-0 flex-col rounded-lg border border-[var(--color-gray-3)] bg-background p-4 shadow-sm", "data-testid": "discovery-status-container", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isDiscovering && _jsx(Spinner, { "data-testid": "discovery-spinner" }), _jsx("span", { className: "font-medium text-[var(--color-dark)]", "data-testid": "discovery-status-text", children: isDiscovering ? 'Discovering subitems...' :
273
273
  discoveryComplete ? 'Discovery Complete' :
274
- 'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", variant: "outline", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-[var(--color-gray-2)] mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-[#9650fb]", children: [totalItemsCount, " total items found"] }), totalItemsCount !== translatableItemsCount && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Translatable items: ", translatableItemsCount, " | Non-translatable: ", totalItemsCount - translatableItemsCount] })] })), _jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Original items: ", data.items.length, " | Subitems discovered: ", Math.max(0, totalItemsCount - data.items.length)] }), isDiscovering && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "text-xs text-[var(--color-gray-2)] italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { className: "text-[var(--color-gray-2)]", children: "No items discovered yet." })) }), _jsx("div", { className: "mt-4 min-h-[400px]", children: !isDiscovering && discoveryComplete && treeInitialized && allDiscoveredItems.length > 0 && treeNodes.length > 0 ? (_jsx("div", { children: _jsxs("div", { className: "border-t border-[var(--color-gray-3)] pt-4", "data-testid": "item-selection-section", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "text-lg font-semibold text-[var(--color-dark)]", children: "Select Items to Translate" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("span", { className: "text-sm text-[var(--color-gray-2)]", "data-testid": "selection-summary-header", children: selectedItemIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-[#9650fb]", children: selectedItemIds.size }), " selected"] }) : "No items selected" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
275
- if (!isActive)
276
- return;
277
- // Only select translatable items
278
- const translatableIds = new Set();
279
- const walk = (nodes) => {
280
- nodes.forEach(node => {
281
- if (node?.data?.isTranslatable === true) {
282
- translatableIds.add(node.key);
283
- }
284
- if (node.children)
285
- walk(node.children);
286
- });
287
- };
288
- walk(treeNodes);
289
- setUserHasInteracted(true);
290
- setSelectedItemIds(translatableIds);
291
- onStepCompleted(translatableIds.size > 0);
292
- }, "data-testid": "select-all-items-button", children: "Select All" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
293
- if (!isActive)
294
- return;
295
- setUserHasInteracted(true);
296
- setSelectedItemIds(new Set());
297
- onStepCompleted(false);
298
- }, "data-testid": "select-none-items-button", children: "Select None" })] })] }), _jsx("div", { className: "text-xs text-[var(--color-gray-2)] mb-3", "data-testid": "selection-summary", children: _jsx("span", { children: "Tip: Hold Shift and click a checkbox to toggle all descendants." }) }), treeNodes.length > 0 && (_jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg h-80 overflow-auto bg-[var(--color-gray-5)]", "data-testid": "item-tree-view", children: _jsx(PerfectTree, { nodes: treeNodes, expandedKeys: expandedKeys, selectedKeys: selectedKeys, onToggleExpand: (key) => {
299
- setExpandedIds(prev => {
300
- const next = new Set(prev);
301
- if (next.has(key))
302
- next.delete(key);
303
- else
304
- next.add(key);
305
- return next;
306
- });
307
- }, onSelect: (key, event) => {
308
- if (!isActive)
309
- return; // Don't handle selection when inactive
310
- setUserHasInteracted(true);
311
- const targetNode = key;
312
- const shift = event?.shiftKey === true;
313
- const next = new Set(selectedItemIds);
314
- // Only select translatable items
315
- const findNode = (nodes) => {
316
- for (const n of nodes) {
317
- if (n.key === targetNode)
318
- return n;
319
- if (n.children) {
320
- const f = findNode(n.children);
321
- if (f)
322
- return f;
323
- }
274
+ 'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", variant: "outline", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-[var(--color-gray-2)] mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-[#9650fb]", children: [totalItemsCount, " total items found"] }), totalItemsCount !== translatableItemsCount && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Translatable items: ", translatableItemsCount, " | Non-translatable: ", totalItemsCount - translatableItemsCount] })] })), _jsx("br", {}), _jsxs("span", { className: "text-xs text-[var(--color-gray-2)]", children: ["Original items: ", data.items.length, " | Subitems discovered: ", Math.max(0, totalItemsCount - data.items.length)] }), isDiscovering && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "text-xs text-[var(--color-gray-2)] italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { className: "text-[var(--color-gray-2)]", children: "No items discovered yet." })) }), _jsx("div", { className: "mt-4 flex flex-col", children: !isDiscovering && discoveryComplete && treeInitialized && allDiscoveredItems.length > 0 && treeNodes.length > 0 ? (_jsxs("div", { className: "flex w-full min-w-0 flex-col", "data-testid": "item-selection-section", children: [_jsxs("div", { className: "mb-3 flex flex-col gap-2 border-t border-[var(--color-gray-3)] pt-4", children: [_jsx("h3", { className: "text-lg font-semibold text-[var(--color-dark)]", children: "Select Items to Translate" }), _jsx("span", { className: "text-sm text-[var(--color-gray-2)]", "data-testid": "selection-summary-header", children: selectedItemIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium text-[#9650fb]", children: selectedItemIds.size }), " selected"] }) : "No items selected" }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => {
275
+ if (!isActive)
276
+ return;
277
+ // Only select translatable items
278
+ const translatableIds = new Set();
279
+ const walk = (nodes) => {
280
+ nodes.forEach(node => {
281
+ if (node?.data?.isTranslatable === true) {
282
+ translatableIds.add(node.key);
283
+ }
284
+ if (node.children)
285
+ walk(node.children);
286
+ });
287
+ };
288
+ walk(treeNodes);
289
+ setUserHasInteracted(true);
290
+ setSelectedItemIds(translatableIds);
291
+ onStepCompleted(translatableIds.size > 0);
292
+ }, "data-testid": "select-all-items-button", children: "Select All" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
293
+ if (!isActive)
294
+ return;
295
+ setUserHasInteracted(true);
296
+ setSelectedItemIds(new Set());
297
+ onStepCompleted(false);
298
+ }, "data-testid": "select-none-items-button", children: "Select None" })] })] }), _jsx("div", { className: "text-xs text-[var(--color-gray-2)] mb-3", "data-testid": "selection-summary", children: _jsx("span", { children: "Tip: Hold Shift and click a checkbox to toggle all descendants." }) }), treeNodes.length > 0 && (_jsx("div", { className: "h-80 w-full min-w-0 shrink-0 overflow-auto rounded-lg border border-[var(--color-gray-3)] bg-[var(--color-gray-5)]", "data-testid": "item-tree-view", children: _jsx(PerfectTree, { nodes: treeNodes, expandedKeys: expandedKeys, selectedKeys: selectedKeys, onToggleExpand: (key) => {
299
+ setExpandedIds(prev => {
300
+ const next = new Set(prev);
301
+ if (next.has(key))
302
+ next.delete(key);
303
+ else
304
+ next.add(key);
305
+ return next;
306
+ });
307
+ }, onSelect: (key, event) => {
308
+ if (!isActive)
309
+ return; // Don't handle selection when inactive
310
+ setUserHasInteracted(true);
311
+ const targetNode = key;
312
+ const shift = event?.shiftKey === true;
313
+ const next = new Set(selectedItemIds);
314
+ // Only select translatable items
315
+ const findNode = (nodes) => {
316
+ for (const n of nodes) {
317
+ if (n.key === targetNode)
318
+ return n;
319
+ if (n.children) {
320
+ const f = findNode(n.children);
321
+ if (f)
322
+ return f;
324
323
  }
325
- return null;
326
- };
327
- const n = findNode(treeNodes);
328
- const isTranslatable = n?.data?.isTranslatable === true;
329
- if (isTranslatable) {
330
- const ids = shift ? [key, ...getDescendantIds(key)] : [key];
331
- if (!next.has(key))
332
- ids.forEach((id) => next.add(id));
333
- else
334
- ids.forEach((id) => next.delete(id));
335
324
  }
336
- else {
337
- // Folder row click toggles all descendant translatable items
338
- const translatableIds = getDescendantTranslatableIds(key);
339
- const allSelected = translatableIds.length > 0 && translatableIds.every((id) => next.has(id));
340
- if (allSelected)
341
- translatableIds.forEach((id) => next.delete(id));
342
- else
343
- translatableIds.forEach((id) => next.add(id));
344
- }
345
- setSelectedItemIds(next);
346
- // Update completion when user changes selection
347
- onStepCompleted(next.size > 0);
348
- }, renderNode: (node) => {
349
- const isTranslatable = node?.data?.isTranslatable === true;
350
- const translatableIds = !isTranslatable ? getDescendantTranslatableIds(node.key) : [];
351
- const allSelected = !isTranslatable ? (translatableIds.length > 0 && translatableIds.every((id) => selectedItemIds.has(id))) : false;
352
- const someSelected = !isTranslatable ? (translatableIds.some((id) => selectedItemIds.has(id)) && !allSelected) : false;
353
- // Simple checkbox logic: if it's translatable, check direct selection; otherwise check descendant translatable items
354
- // Base items are treated like any other selected item - if selected, show checked
355
- const isChecked = isTranslatable ? selectedItemIds.has(node.key) : allSelected;
356
- const icon = node?.data?.icon;
357
- return (_jsxs("div", { className: `flex items-center gap-2 ${!isTranslatable ? 'opacity-70' : ''}`, children: [icon && (_jsx("img", { src: icon, alt: "", className: `w-4 h-4 ${!isTranslatable ? 'opacity-50' : ''}` })), _jsx("input", { type: "checkbox", checked: isChecked, disabled: !isTranslatable, "data-testid": `tree-item-checkbox-${node.key}`, ref: (el) => { if (el)
358
- el.indeterminate = someSelected; }, onMouseDown: (e) => {
359
- if (!isTranslatable)
360
- return; // Don't handle mouse down for untranslatable items
361
- shiftToggleRef.current = e.shiftKey;
362
- }, onChange: (e) => {
363
- if (!isActive || !isTranslatable)
364
- return; // Don't handle changes when inactive or untranslatable
365
- setUserHasInteracted(true);
366
- const next = new Set(selectedItemIds);
367
- if (isTranslatable) {
368
- const withDesc = shiftToggleRef.current && (node.children?.length ?? 0) > 0;
369
- const ids = withDesc ? [node.key, ...getDescendantIds(node.key)] : [node.key];
370
- if (e.currentTarget.checked)
371
- ids.forEach((id) => next.add(id));
372
- else
373
- ids.forEach((id) => next.delete(id));
374
- }
325
+ return null;
326
+ };
327
+ const n = findNode(treeNodes);
328
+ const isTranslatable = n?.data?.isTranslatable === true;
329
+ if (isTranslatable) {
330
+ const ids = shift ? [key, ...getDescendantIds(key)] : [key];
331
+ if (!next.has(key))
332
+ ids.forEach((id) => next.add(id));
333
+ else
334
+ ids.forEach((id) => next.delete(id));
335
+ }
336
+ else {
337
+ // Folder row click toggles all descendant translatable items
338
+ const translatableIds = getDescendantTranslatableIds(key);
339
+ const allSelected = translatableIds.length > 0 && translatableIds.every((id) => next.has(id));
340
+ if (allSelected)
341
+ translatableIds.forEach((id) => next.delete(id));
342
+ else
343
+ translatableIds.forEach((id) => next.add(id));
344
+ }
345
+ setSelectedItemIds(next);
346
+ // Update completion when user changes selection
347
+ onStepCompleted(next.size > 0);
348
+ }, renderNode: (node) => {
349
+ const isTranslatable = node?.data?.isTranslatable === true;
350
+ const translatableIds = !isTranslatable ? getDescendantTranslatableIds(node.key) : [];
351
+ const allSelected = !isTranslatable ? (translatableIds.length > 0 && translatableIds.every((id) => selectedItemIds.has(id))) : false;
352
+ const someSelected = !isTranslatable ? (translatableIds.some((id) => selectedItemIds.has(id)) && !allSelected) : false;
353
+ // Simple checkbox logic: if it's translatable, check direct selection; otherwise check descendant translatable items
354
+ // Base items are treated like any other selected item - if selected, show checked
355
+ const isChecked = isTranslatable ? selectedItemIds.has(node.key) : allSelected;
356
+ const icon = node?.data?.icon;
357
+ return (_jsxs("div", { className: `flex items-center gap-2 ${!isTranslatable ? 'opacity-70' : ''}`, children: [icon && (_jsx("img", { src: icon, alt: "", className: `w-4 h-4 ${!isTranslatable ? 'opacity-50' : ''}` })), _jsx("input", { type: "checkbox", checked: isChecked, disabled: !isTranslatable, "data-testid": `tree-item-checkbox-${node.key}`, ref: (el) => { if (el)
358
+ el.indeterminate = someSelected; }, onMouseDown: (e) => {
359
+ if (!isTranslatable)
360
+ return; // Don't handle mouse down for untranslatable items
361
+ shiftToggleRef.current = e.shiftKey;
362
+ }, onChange: (e) => {
363
+ if (!isActive || !isTranslatable)
364
+ return; // Don't handle changes when inactive or untranslatable
365
+ setUserHasInteracted(true);
366
+ const next = new Set(selectedItemIds);
367
+ if (isTranslatable) {
368
+ const withDesc = shiftToggleRef.current && (node.children?.length ?? 0) > 0;
369
+ const ids = withDesc ? [node.key, ...getDescendantIds(node.key)] : [node.key];
370
+ if (e.currentTarget.checked)
371
+ ids.forEach((id) => next.add(id));
372
+ else
373
+ ids.forEach((id) => next.delete(id));
374
+ }
375
+ else {
376
+ const ids = getDescendantTranslatableIds(node.key);
377
+ const allSel = ids.length > 0 && ids.every((id) => next.has(id));
378
+ if (e.currentTarget.checked && !allSel)
379
+ ids.forEach((id) => next.add(id));
380
+ else if (!e.currentTarget.checked && allSel)
381
+ ids.forEach((id) => next.delete(id));
375
382
  else {
376
- const ids = getDescendantTranslatableIds(node.key);
377
- const allSel = ids.length > 0 && ids.every((id) => next.has(id));
378
- if (e.currentTarget.checked && !allSel)
379
- ids.forEach((id) => next.add(id));
380
- else if (!e.currentTarget.checked && allSel)
383
+ // Toggle based on current
384
+ if (allSel)
381
385
  ids.forEach((id) => next.delete(id));
382
- else {
383
- // Toggle based on current
384
- if (allSel)
385
- ids.forEach((id) => next.delete(id));
386
- else
387
- ids.forEach((id) => next.add(id));
388
- }
386
+ else
387
+ ids.forEach((id) => next.add(id));
389
388
  }
390
- setSelectedItemIds(next);
391
- shiftToggleRef.current = false;
392
- // Update completion when user changes selection
393
- onStepCompleted(next.size > 0);
394
- } }), _jsx("span", { className: `${!isTranslatable ? 'opacity-70 italic' : ''}`, children: node.label })] }));
395
- } }) }))] }) })) : isDiscovering ? (
389
+ }
390
+ setSelectedItemIds(next);
391
+ shiftToggleRef.current = false;
392
+ // Update completion when user changes selection
393
+ onStepCompleted(next.size > 0);
394
+ } }), _jsx("span", { className: `${!isTranslatable ? 'opacity-70 italic' : ''}`, children: node.label })] }));
395
+ } }) }))] })) : isDiscovering ? (
396
396
  // Loading skeleton for discovery results
397
- _jsxs("div", { children: [_jsx("div", { className: "p-3 bg-[#f6eeff] border border-[#9650fb]/20 rounded-lg mb-4", children: _jsx("div", { className: "h-4 bg-[#9650fb]/20 rounded w-64 animate-pulse" }) }), _jsxs("div", { className: "border-t border-[var(--color-gray-3)] pt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("div", { className: "h-6 bg-[var(--color-gray-3)] rounded w-48 animate-pulse" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("div", { className: "h-8 bg-[var(--color-gray-3)] rounded w-20 animate-pulse" }), _jsx("div", { className: "h-8 bg-[var(--color-gray-3)] rounded w-20 animate-pulse" })] })] }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-64 mb-3 animate-pulse" }), _jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg h-80 overflow-auto bg-[var(--color-gray-5)]", children: _jsx("div", { className: "p-3 space-y-2", children: [...Array(8)].map((_, i) => (_jsxs("div", { className: "flex items-center gap-3 animate-pulse", children: [_jsx("div", { className: "h-4 w-4 bg-[var(--color-gray-3)] rounded" }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-48" })] }, i))) }) })] })] })) : (
397
+ _jsxs("div", { className: "flex w-full min-w-0 flex-col", children: [_jsx("div", { className: "p-3 bg-[#f6eeff] border border-[#9650fb]/20 rounded-lg mb-4", children: _jsx("div", { className: "h-4 bg-[#9650fb]/20 rounded w-64 animate-pulse" }) }), _jsxs("div", { className: "border-t border-[var(--color-gray-3)] pt-4", children: [_jsxs("div", { className: "mb-3 flex flex-col gap-2", children: [_jsx("div", { className: "h-6 bg-[var(--color-gray-3)] rounded w-48 animate-pulse" }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-40 animate-pulse" }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("div", { className: "h-8 bg-[var(--color-gray-3)] rounded w-20 animate-pulse" }), _jsx("div", { className: "h-8 bg-[var(--color-gray-3)] rounded w-20 animate-pulse" })] })] }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-64 mb-3 animate-pulse" }), _jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg h-80 shrink-0 overflow-auto bg-[var(--color-gray-5)]", children: _jsx("div", { className: "p-3 space-y-2", children: [...Array(8)].map((_, i) => (_jsxs("div", { className: "flex items-center gap-3 animate-pulse", children: [_jsx("div", { className: "h-4 w-4 bg-[var(--color-gray-3)] rounded" }), _jsx("div", { className: "h-4 bg-[var(--color-gray-3)] rounded w-48" })] }, i))) }) })] })] })) : (
398
398
  // Empty state
399
- _jsx("div", { className: "flex items-center justify-center h-80 border border-[var(--color-gray-3)] rounded-lg bg-[var(--color-gray-5)]", children: _jsxs("div", { className: "text-center text-[var(--color-gray-2)]", children: [_jsx("p", { className: "font-medium text-[var(--color-gray-1)]", children: "Ready to discover subitems" }), _jsx("p", { className: "text-sm mt-1", children: "Discovery will start automatically" })] }) })) })] })] }));
399
+ _jsx("div", { className: "flex h-80 shrink-0 items-center justify-center overflow-auto rounded-lg border border-[var(--color-gray-3)] bg-[var(--color-gray-5)]", children: _jsxs("div", { className: "text-center text-[var(--color-gray-2)]", children: [_jsx("p", { className: "font-medium text-[var(--color-gray-1)]", children: "Ready to discover subitems" }), _jsx("p", { className: "text-sm mt-1", children: "Discovery will start automatically" })] }) })) })] })] }));
400
400
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parhelia/localization",
3
- "version": "0.1.12350",
3
+ "version": "0.1.12390",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -34,8 +34,8 @@
34
34
  "@tailwindcss/postcss": "^4.1.18",
35
35
  "@turbo/gen": "^2.4.4",
36
36
  "@types/node": "^22.13.9",
37
- "@types/react": "19.2.2",
38
- "@types/react-dom": "19.2.2",
37
+ "@types/react": "19.2.14",
38
+ "@types/react-dom": "19.2.3",
39
39
  "eslint": "^9.22.0",
40
40
  "tailwindcss": "^4.1.18",
41
41
  "typescript": "5.8.2"
@@ -45,11 +45,11 @@
45
45
  "lucide-react": "^0.486.0",
46
46
  "next": "16.0.10",
47
47
  "postcss": "^8.5.3",
48
- "react": "19.2.3",
49
- "react-dom": "19.2.3"
48
+ "react": "19.2.4",
49
+ "react-dom": "19.2.4"
50
50
  },
51
51
  "overrides": {
52
- "@types/react": "19.2.2",
53
- "@types/react-dom": "19.2.2"
52
+ "@types/react": "19.2.14",
53
+ "@types/react-dom": "19.2.3"
54
54
  }
55
55
  }