@parhelia/localization 0.1.11092 → 0.1.11143

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":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAiBvB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,uBAAuB,CAAC,2CAmVtE"}
1
+ {"version":3,"file":"LocalizeItemDialog.d.ts","sourceRoot":"","sources":["../src/LocalizeItemDialog.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAiBvB,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,uBAAuB,CAAC,2CA8VtE"}
@@ -41,11 +41,17 @@ export function LocalizeItemDialog(props) {
41
41
  }, [props.onClose]);
42
42
  // Memoize activeSteps to prevent unnecessary recalculations and new object references
43
43
  // This prevents infinite loops when wizardData changes but skip conditions don't
44
- // The skip condition for subitem-discovery step only checks includeSubitems
44
+ // The skip condition for subitem-discovery step checks includeSubitems
45
+ // The skip condition for prompt-customization step checks serviceCustomData and translationProvider
45
46
  const activeSteps = useMemo(() => {
46
47
  const steps = configuration.steps.filter((step) => !step.skipCondition || !step.skipCondition(wizardData));
47
48
  return steps;
48
- }, [configuration.steps, wizardData.includeSubitems]); // Only depend on what affects skip conditions
49
+ }, [
50
+ configuration.steps,
51
+ wizardData.includeSubitems,
52
+ wizardData.serviceCustomData,
53
+ wizardData.translationProvider
54
+ ]); // Depend on what affects skip conditions
49
55
  const currentStep = activeSteps[currentStepIndex];
50
56
  // Refs for stable callbacks - updated during render to avoid dependency issues
51
57
  const currentStepIdRef = useRef(currentStep?.id);
@@ -137,12 +143,16 @@ export function LocalizeItemDialog(props) {
137
143
  const setWizardDataStable = useCallback((newData) => {
138
144
  // Create a stable key from the data to detect actual changes
139
145
  // Only compare the fields that matter for re-renders
146
+ const serviceCustomDataKey = newData.serviceCustomData
147
+ ? JSON.stringify(Array.from(newData.serviceCustomData.entries()))
148
+ : null;
140
149
  const dataKey = JSON.stringify({
141
150
  translationProvider: newData.translationProvider,
142
151
  targetLanguages: [...newData.targetLanguages].sort(),
143
152
  includeSubitems: newData.includeSubitems,
144
153
  discoveredItemsCount: newData.discoveredItems?.length || 0,
145
154
  itemsCount: newData.items?.length || 0,
155
+ serviceCustomData: serviceCustomDataKey,
146
156
  });
147
157
  // Skip if data hasn't actually changed
148
158
  if (lastSetWizardDataRef.current === dataKey) {
@@ -1 +1 @@
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,CAgF5B;AAGD,eAAO,MAAM,mBAAmB,GAAU,eAAe,MAAM,EAAE,EAAE,mBAAmB,iBAAiB,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,QAAQ,EAAE,qBAAqB,MAAM,+BA0BxK,CAAC"}
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,CAiG5B;AAGD,eAAO,MAAM,mBAAmB,GAAU,eAAe,MAAM,EAAE,EAAE,mBAAmB,iBAAiB,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,QAAQ,EAAE,qBAAqB,MAAM,+BA0BxK,CAAC"}
@@ -41,12 +41,28 @@ export async function performDefaultTranslation(wizardData, editContext, predefi
41
41
  error.name = 'NoTranslationsNeededError';
42
42
  throw error;
43
43
  }
44
+ // Prepare batch metadata including service-specific custom data
45
+ let batchMetadata = wizardData.metadata || null;
46
+ if (wizardData.serviceCustomData && wizardData.serviceCustomData.size > 0) {
47
+ const serviceData = wizardData.serviceCustomData.get(wizardData.translationProvider);
48
+ if (serviceData && serviceData.enableCustomPrompt) {
49
+ // Merge service-specific custom data into batch metadata
50
+ const metadataObj = batchMetadata ? (typeof batchMetadata === 'string' ? JSON.parse(batchMetadata) : batchMetadata) : {};
51
+ metadataObj.serviceCustomData = {
52
+ [wizardData.translationProvider]: {
53
+ enableCustomPrompt: serviceData.enableCustomPrompt,
54
+ customPrompt: serviceData.customPrompt || ""
55
+ }
56
+ };
57
+ batchMetadata = JSON.stringify(metadataObj);
58
+ }
59
+ }
44
60
  try {
45
61
  const batchResult = await requestBatchTranslation({
46
62
  sessionId: editContext.sessionId,
47
63
  batchId,
48
64
  provider: wizardData.translationProvider,
49
- batchMetadata: wizardData.metadata,
65
+ batchMetadata: batchMetadata,
50
66
  translations: translationRequests,
51
67
  });
52
68
  if (batchResult.type === "error") {
@@ -17,6 +17,7 @@ export type BackendTreeNode = {
17
17
  name: string;
18
18
  path: string;
19
19
  hasLayout: boolean;
20
+ isTranslatable: boolean;
20
21
  isFolder: boolean;
21
22
  icon?: string;
22
23
  children: BackendTreeNode[];
@@ -1 +1 @@
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;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,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,SAAS,EAAE,OAAO,CAAC;IACnB,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,4BAA4B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,cAAc,EAAE,CAQvF"}
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;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,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,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,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,4BAA4B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,cAAc,EAAE,CAQvF"}
@@ -12,7 +12,7 @@ export function convertBackendTreeToTreeNodes(trees) {
12
12
  const convert = (n) => ({
13
13
  key: n.id,
14
14
  label: n.name,
15
- data: { id: n.id, name: n.name, path: n.path, hasLayout: n.hasLayout, icon: n.icon, parentId: undefined, hasChildren: (n.children?.length ?? 0) > 0 },
15
+ data: { id: n.id, name: n.name, path: n.path, hasLayout: n.hasLayout, isTranslatable: n.isTranslatable, icon: n.icon, parentId: undefined, hasChildren: (n.children?.length ?? 0) > 0 },
16
16
  children: n.children?.map(convert) || [],
17
17
  });
18
18
  return trees.map(convert);
@@ -20,7 +20,7 @@ export function convertBackendTreeToTreeNodes(trees) {
20
20
  export function flattenPagesFromBackendTrees(trees) {
21
21
  const out = [];
22
22
  const walk = (n) => {
23
- if (n.hasLayout)
23
+ if (n.isTranslatable)
24
24
  out.push({ id: n.id, name: n.name, path: n.path, hasChildren: (n.children?.length ?? 0) > 0, parentId: undefined });
25
25
  n.children?.forEach(walk);
26
26
  };
@@ -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;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAWtD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,gBAAgB,CAAC;AASzE,QAAA,MAAM,wCAAwC;;;;;;;;;;;;8BAalB,qBAAqB;;CAGhD,CAAC;AAEF,QAAA,MAAM,4CAA4C;;;;;;;CAUjD,CAAC;AAEF,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,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,uBAmGpC"}
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;AAWtD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,gBAAgB,CAAC;AASzE,QAAA,MAAM,wCAAwC;;;;;;;;;;;;8BAalB,qBAAqB;;CAchD,CAAC;AAEF,QAAA,MAAM,4CAA4C;;;;;;;CAUjD,CAAC;AAEF,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,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,uBAmGpC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ServiceLanguageSelectionStep } from "./steps/ServiceLanguageSelectionStep";
3
3
  import { SubitemDiscoveryStep } from "./steps/SubitemDiscoveryStep";
4
+ import { PromptCustomizationStep } from "./steps/PromptCustomizationStep";
4
5
  import { localizeItemCommand } from "./LocalizeItemCommand";
5
6
  import { TranslationSidebar } from "./sidebar/TranslationSidebar";
6
7
  import { TranslationManagement } from "./translation-center/TranslationManagement";
@@ -25,6 +26,17 @@ const DEFAULT_TRANSLATION_WIZARD_CONFIGURATION = {
25
26
  component: SubitemDiscoveryStep,
26
27
  skipCondition: (data) => !data.includeSubitems,
27
28
  },
29
+ {
30
+ id: "prompt-customization",
31
+ name: "Customize Prompt",
32
+ description: "Optionally customize the translation prompt.",
33
+ component: PromptCustomizationStep,
34
+ skipCondition: (data) => {
35
+ // Skip if custom prompt is not enabled (checkbox unchecked)
36
+ const serviceData = data.serviceCustomData?.get(data.translationProvider);
37
+ return !serviceData || !serviceData.enableCustomPrompt;
38
+ },
39
+ },
28
40
  ],
29
41
  };
30
42
  const SINGLE_ITEM_TRANSLATION_WIZARD_CONFIGURATION = {
@@ -0,0 +1,3 @@
1
+ import { TranslationStepProps } from "./types";
2
+ export declare function PromptCustomizationStep({ stepIndex, isActive, data, setData, onStepCompleted, editContext, }: TranslationStepProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=PromptCustomizationStep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromptCustomizationStep.d.ts","sourceRoot":"","sources":["../../src/steps/PromptCustomizationStep.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAEtE,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,QAAe,EACf,IAAI,EACJ,OAAO,EACP,eAAe,EACf,WAAW,GACZ,EAAE,oBAAoB,2CA6MtB"}
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setData, onStepCompleted, editContext, }) {
4
+ const [customPrompt, setCustomPrompt] = useState("");
5
+ const [customizationType, setCustomizationType] = useState("extend");
6
+ // Get service-specific custom data for the selected provider
7
+ const serviceData = useMemo(() => {
8
+ if (!data.serviceCustomData || !data.translationProvider)
9
+ return null;
10
+ return data.serviceCustomData.get(data.translationProvider);
11
+ }, [data.serviceCustomData, data.translationProvider]);
12
+ const enableCustomPrompt = serviceData?.enableCustomPrompt === true;
13
+ // Initialize from existing data
14
+ useEffect(() => {
15
+ if (enableCustomPrompt && serviceData) {
16
+ setCustomPrompt(serviceData.customPrompt || "");
17
+ setCustomizationType(serviceData.promptCustomizationType || "extend");
18
+ }
19
+ else {
20
+ setCustomPrompt("");
21
+ setCustomizationType("extend");
22
+ }
23
+ }, [enableCustomPrompt, serviceData]);
24
+ // Get default prompt from provider settings (no fallback)
25
+ const defaultPrompt = useMemo(() => {
26
+ const provider = data.translationProviders.find(p => p.name === data.translationProvider);
27
+ const prompt = provider?.defaultPrompt;
28
+ // Return null if prompt is null, undefined, or empty string
29
+ return prompt && prompt.trim() ? prompt : null;
30
+ }, [data.translationProviders, data.translationProvider]);
31
+ const hasDefaultPrompt = defaultPrompt != null && defaultPrompt.trim().length > 0;
32
+ // Preview of final prompt
33
+ const previewPrompt = useMemo(() => {
34
+ if (!enableCustomPrompt || !customPrompt.trim()) {
35
+ return defaultPrompt || "";
36
+ }
37
+ // If no default prompt, just show custom prompt
38
+ if (!hasDefaultPrompt) {
39
+ return customPrompt;
40
+ }
41
+ // If default prompt exists, show based on customization type
42
+ if (customizationType === "replace") {
43
+ return customPrompt;
44
+ }
45
+ return `${defaultPrompt}\n\n${customPrompt}`;
46
+ }, [enableCustomPrompt, customPrompt, customizationType, defaultPrompt, hasDefaultPrompt]);
47
+ // Update wizard data when settings change
48
+ useEffect(() => {
49
+ if (!isActive)
50
+ return;
51
+ const newServiceCustomData = new Map(data.serviceCustomData || new Map());
52
+ // Compute final custom prompt based on extend/replace of Default Prompt
53
+ let finalCustomPrompt = "";
54
+ if (enableCustomPrompt && customPrompt.trim()) {
55
+ if (hasDefaultPrompt) {
56
+ // If default prompt exists, apply extend/replace logic
57
+ if (customizationType === "replace") {
58
+ // Replace Default Prompt entirely
59
+ finalCustomPrompt = customPrompt.trim();
60
+ }
61
+ else {
62
+ // Extend Default Prompt
63
+ finalCustomPrompt = `${defaultPrompt}\n\n${customPrompt.trim()}`;
64
+ }
65
+ }
66
+ else {
67
+ // No default prompt, just use custom prompt as-is
68
+ finalCustomPrompt = customPrompt.trim();
69
+ }
70
+ }
71
+ if (enableCustomPrompt) {
72
+ newServiceCustomData.set(data.translationProvider, {
73
+ enableCustomPrompt: true,
74
+ customPrompt: finalCustomPrompt,
75
+ // Note: promptCustomizationType is no longer sent to backend
76
+ });
77
+ }
78
+ else {
79
+ // Remove custom data if disabled
80
+ newServiceCustomData.delete(data.translationProvider);
81
+ }
82
+ const newData = {
83
+ ...data,
84
+ serviceCustomData: newServiceCustomData,
85
+ };
86
+ setData(newData);
87
+ }, [enableCustomPrompt, customPrompt, customizationType, defaultPrompt, hasDefaultPrompt, data.translationProvider, isActive, setData]);
88
+ // Update completion status
89
+ useEffect(() => {
90
+ if (!isActive)
91
+ return;
92
+ // Step is always complete (it's optional)
93
+ onStepCompleted(true);
94
+ }, [isActive, onStepCompleted]);
95
+ // Skip condition: hide step when checkbox is disabled
96
+ // This is handled by skipCondition in wizard config
97
+ return (_jsxs("div", { className: "p-6 space-y-6 h-full flex flex-col", "data-testid": "prompt-customization-step", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-xl font-semibold mb-2", children: "Customize Translation Prompt" }), _jsxs("p", { className: "text-sm text-gray-600 mb-6", children: ["Optionally customize the prompt used for translation. This allows you to provide specific instructions or context for the translation service.", _jsx("br", {}), _jsx("span", { className: "text-xs text-gray-500 mt-1 block", children: "Note: Your custom prompt will be appended to the system instructions that ensure proper translation structure." })] })] }), _jsxs("div", { className: "space-y-6 flex-1", children: [!enableCustomPrompt && (_jsx("div", { className: "border border-gray-200 rounded-md p-4 bg-gray-50", children: _jsx("p", { className: "text-sm text-gray-600", children: "Enable \"Customize translation prompt\" in the previous step to customize the prompt." }) })), enableCustomPrompt && (_jsxs(_Fragment, { children: [hasDefaultPrompt && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Default Prompt" }), _jsx("div", { className: "border border-gray-200 rounded-md p-3 bg-gray-50", children: _jsx("pre", { className: "text-xs text-gray-700 whitespace-pre-wrap font-mono", children: defaultPrompt }) })] }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Customization Type" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "radio", name: "customizationType", value: "extend", checked: customizationType === "extend", onChange: () => setCustomizationType("extend"), className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300", "data-testid": "customization-type-extend" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Extend (append to default)" })] }), _jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "radio", name: "customizationType", value: "replace", checked: customizationType === "replace", onChange: () => setCustomizationType("replace"), className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300", "data-testid": "customization-type-replace" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Replace (use custom prompt only)" })] })] })] })] })), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Custom Prompt" }), _jsx("textarea", { value: customPrompt, onChange: (e) => setCustomPrompt(e.target.value), className: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm font-mono", rows: 6, placeholder: "Enter your custom prompt instructions here...", "data-testid": "custom-prompt-textarea" }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: hasDefaultPrompt
98
+ ? customizationType === "extend"
99
+ ? "This will be appended to the default prompt. The final prompt (default + custom) will then be appended to the system instructions on the backend."
100
+ : "This will replace the default prompt. The custom prompt will then be appended to the system instructions on the backend."
101
+ : "Your custom prompt will be appended to the system instructions on the backend." })] }), hasDefaultPrompt && _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Preview" }), _jsx("div", { className: "border border-gray-200 rounded-md p-3 bg-gray-50 max-h-64 overflow-y-auto", children: _jsx("pre", { className: "text-xs text-gray-700 whitespace-pre-wrap font-mono", children: previewPrompt }) }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: "This is how the final prompt will look when sent to the translation service." })] })] }))] })] }));
102
+ }
@@ -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,2CAyU7K"}
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,2CA4Y7K"}
@@ -121,9 +121,20 @@ export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data,
121
121
  }, [languageSelection, setData, data.targetLanguages]);
122
122
  const handleProviderChange = (e) => {
123
123
  const newProvider = e.target.value;
124
+ // Always create a new Map instance to ensure React detects the change
125
+ const newServiceCustomData = new Map();
126
+ if (data.serviceCustomData) {
127
+ data.serviceCustomData.forEach((value, key) => {
128
+ // Only preserve custom data if not switching away from OpenAI
129
+ if (key !== "OpenAI" || newProvider === "OpenAI") {
130
+ newServiceCustomData.set(key, value);
131
+ }
132
+ });
133
+ }
124
134
  const newData = {
125
135
  ...data,
126
136
  translationProvider: newProvider,
137
+ serviceCustomData: newServiceCustomData,
127
138
  // Clear target languages when provider changes since language availability might change
128
139
  targetLanguages: []
129
140
  };
@@ -167,7 +178,40 @@ export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data,
167
178
  const areSomeLanguagesSelected = useMemo(() => {
168
179
  return allLanguages.some(lang => languageSelection[lang.code]);
169
180
  }, [allLanguages, languageSelection]);
170
- return (_jsxs("div", { className: "p-6 space-y-6 h-full flex flex-col", "data-testid": "service-language-selection-step", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-xl font-semibold mb-2", children: "Configure Translation" }), _jsx("p", { className: "text-sm text-gray-600 mb-6", children: "Select translation provider and target languages for your content." })] }), _jsxs("div", { className: "space-y-6 flex-1", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Translation Provider" }), _jsx("p", { className: "text-xs text-gray-600 mb-3", children: "Choose how to translate your content. \"Create Versions\" will create new language versions without automatic translation." }), _jsxs("select", { value: data.translationProvider || "", onChange: handleProviderChange, className: "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", "data-testid": "translation-provider-select", children: [_jsx("option", { value: "", disabled: true, children: "Select a provider..." }), data.translationProviders.map((provider) => (_jsx("option", { value: provider.name, children: provider.displayName || provider.name }, provider.name)))] })] }), multiItemEnabled && (_jsxs("div", { children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", checked: data.includeSubitems, onChange: handleSubitemsToggle, className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded", "data-testid": "include-subitems-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Include subitems" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-1 ml-6", children: "Also translate any child components and nested content within this item." })] })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("h3", { className: "text-sm font-medium text-gray-900", children: "Target Languages" }), allLanguages.length > 1 && (_jsxs("label", { className: "flex items-center cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
181
+ // Compute checkbox state for prompt customization
182
+ const isPromptCustomizationEnabled = useMemo(() => {
183
+ if (data.translationProvider !== "OpenAI") {
184
+ return false;
185
+ }
186
+ const serviceData = data.serviceCustomData?.get("OpenAI");
187
+ return serviceData?.enableCustomPrompt === true;
188
+ }, [data.translationProvider, data.serviceCustomData]);
189
+ return (_jsxs("div", { className: "p-6 space-y-6 h-full flex flex-col", "data-testid": "service-language-selection-step", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-xl font-semibold mb-2", children: "Configure Translation" }), _jsx("p", { className: "text-sm text-gray-600 mb-6", children: "Select translation provider and target languages for your content." })] }), _jsxs("div", { className: "space-y-6 flex-1", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 mb-2", children: "Translation Provider" }), _jsx("p", { className: "text-xs text-gray-600 mb-3", children: "Choose how to translate your content. \"Create Versions\" will create new language versions without automatic translation." }), _jsxs("select", { value: data.translationProvider || "", onChange: handleProviderChange, className: "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", "data-testid": "translation-provider-select", children: [_jsx("option", { value: "", disabled: true, children: "Select a provider..." }), data.translationProviders.map((provider) => (_jsx("option", { value: provider.name, children: provider.displayName || provider.name }, provider.name)))] })] }), multiItemEnabled && (_jsxs("div", { children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", checked: data.includeSubitems, onChange: handleSubitemsToggle, className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded", "data-testid": "include-subitems-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Include subitems" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-1 ml-6", children: "Also translate any child components and nested content within this item." })] })), data.translationProvider === "OpenAI" && (_jsxs("div", { children: [_jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", checked: isPromptCustomizationEnabled, onChange: (e) => {
190
+ const isChecked = e.target.checked;
191
+ // Always create a new Map instance to ensure React detects the change
192
+ const newServiceCustomData = new Map();
193
+ if (data.serviceCustomData) {
194
+ data.serviceCustomData.forEach((value, key) => {
195
+ newServiceCustomData.set(key, value);
196
+ });
197
+ }
198
+ if (isChecked) {
199
+ newServiceCustomData.set("OpenAI", {
200
+ enableCustomPrompt: true,
201
+ customPrompt: "",
202
+ promptCustomizationType: "extend",
203
+ });
204
+ }
205
+ else {
206
+ // Remove the service data when unchecked
207
+ newServiceCustomData.delete("OpenAI");
208
+ }
209
+ const newData = {
210
+ ...data,
211
+ serviceCustomData: newServiceCustomData
212
+ };
213
+ setData(newData);
214
+ }, className: "h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded", "data-testid": "enable-custom-prompt-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-gray-900", children: "Customize translation prompt" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-1 ml-6", children: "Enable advanced prompt customization for the translation service." })] })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("h3", { className: "text-sm font-medium text-gray-900", children: "Target Languages" }), allLanguages.length > 1 && (_jsxs("label", { className: "flex items-center cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
171
215
  if (input) {
172
216
  input.indeterminate = areSomeLanguagesSelected && !areAllLanguagesSelected;
173
217
  }
@@ -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,2CA4b5L"}
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,2CAuf5L"}
@@ -14,6 +14,8 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
14
14
  const [allDiscoveredItems, setAllDiscoveredItems] = useState([]);
15
15
  const [userHasInteracted, setUserHasInteracted] = useState(false);
16
16
  const [discoveredCount, setDiscoveredCount] = useState(0);
17
+ const [totalItemsCount, setTotalItemsCount] = useState(0);
18
+ const [translatableItemsCount, setTranslatableItemsCount] = useState(0);
17
19
  const [treeInitialized, setTreeInitialized] = useState(false);
18
20
  const [expandedIds, setExpandedIds] = useState(new Set());
19
21
  // Refs for async safety
@@ -45,6 +47,26 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
45
47
  const nodes = buildTreeNodes(stubs);
46
48
  setTreeNodes(nodes);
47
49
  setExpandedIds(new Set(nodes.map(n => n.key)));
50
+ // Count total items and translatable items in the tree
51
+ const countItems = (nodeList) => {
52
+ let total = 0;
53
+ let translatable = 0;
54
+ const walk = (nodeList) => {
55
+ nodeList.forEach(node => {
56
+ total++;
57
+ if (node?.data?.isTranslatable === true) {
58
+ translatable++;
59
+ }
60
+ if (node.children)
61
+ walk(node.children);
62
+ });
63
+ };
64
+ walk(nodeList);
65
+ return { total, translatable };
66
+ };
67
+ const counts = countItems(nodes);
68
+ setTotalItemsCount(counts.total);
69
+ setTranslatableItemsCount(counts.translatable);
48
70
  setDiscoveryComplete(true);
49
71
  setTreeInitialized(true);
50
72
  return;
@@ -87,6 +109,26 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
87
109
  const nodes = convertBackendTreeToTreeNodes(resp.trees);
88
110
  setTreeNodes(nodes);
89
111
  setExpandedIds(new Set(nodes.map(n => n.key)));
112
+ // Count total items and translatable items in the tree
113
+ const countItems = (nodeList) => {
114
+ let total = 0;
115
+ let translatable = 0;
116
+ const walk = (nodeList) => {
117
+ nodeList.forEach(node => {
118
+ total++;
119
+ if (node?.data?.isTranslatable === true) {
120
+ translatable++;
121
+ }
122
+ if (node.children)
123
+ walk(node.children);
124
+ });
125
+ };
126
+ walk(nodeList);
127
+ return { total, translatable };
128
+ };
129
+ const counts = countItems(nodes);
130
+ setTotalItemsCount(counts.total);
131
+ setTranslatableItemsCount(counts.translatable);
90
132
  setDiscoveryComplete(true);
91
133
  setTreeInitialized(true);
92
134
  }
@@ -111,19 +153,19 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
111
153
  initialSelection = new Set(data.discoveredItems.map(item => item.descriptor.id));
112
154
  }
113
155
  else {
114
- // Default: select all pages in the tree
115
- const allPageIds = new Set();
156
+ // Default: select all translatable items in the tree
157
+ const allTranslatableIds = new Set();
116
158
  const walk = (nodes) => {
117
159
  nodes.forEach(node => {
118
- if (node?.data?.hasLayout) {
119
- allPageIds.add(node.key);
160
+ if (node?.data?.isTranslatable) {
161
+ allTranslatableIds.add(node.key);
120
162
  }
121
163
  if (node.children)
122
164
  walk(node.children);
123
165
  });
124
166
  };
125
167
  walk(treeNodes);
126
- initialSelection = allPageIds;
168
+ initialSelection = allTranslatableIds;
127
169
  }
128
170
  setSelectedItemIds(initialSelection);
129
171
  }, [isActive, discoveryComplete, treeNodes, data.discoveredItems, userHasInteracted]);
@@ -187,7 +229,7 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
187
229
  traverse(node.children);
188
230
  return descendants;
189
231
  };
190
- const getDescendantPageIds = (nodeKey) => {
232
+ const getDescendantTranslatableIds = (nodeKey) => {
191
233
  const findNode = (nodes) => {
192
234
  for (const node of nodes) {
193
235
  if (node.key === nodeKey)
@@ -206,8 +248,8 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
206
248
  const out = [];
207
249
  const walk = (children) => {
208
250
  children.forEach(child => {
209
- const isPage = child?.data?.hasLayout === true;
210
- if (isPage)
251
+ const isTranslatable = child?.data?.isTranslatable === true;
252
+ if (isTranslatable)
211
253
  out.push(child.key);
212
254
  if (child.children)
213
255
  walk(child.children);
@@ -224,13 +266,24 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
224
266
  };
225
267
  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-medium mb-1", children: "Discover Subitems" }), _jsx("p", { className: "text-sm text-gray-600", children: "Scanning for subitems to include in translation..." })] }), _jsxs("div", { className: "border rounded p-4 bg-gray-50 min-h-[120px]", "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", "data-testid": "discovery-status-text", children: isDiscovering ? 'Discovering subitems...' :
226
268
  discoveryComplete ? 'Discovery Complete' :
227
- 'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-gray-600 mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-blue-600", children: [discoveredCount, " total items found"] }), _jsx("br", {}), _jsxs("span", { className: "text-xs", children: ["Original items: ", data.items.length, " | Subitems discovered: ", Math.max(0, discoveredCount - data.items.length)] }), isDiscovering && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "text-xs text-gray-500 italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { 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 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-medium", children: "Select Items to Translate" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("span", { className: "text-sm text-gray-600", "data-testid": "selection-summary-header", children: selectedItemIds.size > 0 ? `${selectedItemIds.size} selected` : "No items selected" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
269
+ 'Ready to discover' })] }), isDiscovering && (_jsx(Button, { size: "sm", onClick: handleCancel, "data-testid": "discovery-cancel-button", children: "Cancel" }))] }), _jsx("div", { className: "text-sm text-gray-600 mb-3", "data-testid": "discovery-summary", children: discoveredCount > 0 ? (_jsxs("p", { "data-testid": "discovery-total-count", children: [_jsxs("span", { className: "font-medium text-blue-600", children: [totalItemsCount, " total items found"] }), totalItemsCount !== translatableItemsCount && (_jsxs(_Fragment, { children: [_jsx("br", {}), _jsxs("span", { className: "text-xs", children: ["Translatable items: ", translatableItemsCount, " | Non-translatable: ", totalItemsCount - translatableItemsCount] })] })), _jsx("br", {}), _jsxs("span", { className: "text-xs", 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-gray-500 italic", children: "Item tree will appear when discovery completes" })] }))] })) : (_jsx("p", { 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 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-medium", children: "Select Items to Translate" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("span", { className: "text-sm text-gray-600", "data-testid": "selection-summary-header", children: selectedItemIds.size > 0 ? `${selectedItemIds.size} selected` : "No items selected" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
228
270
  if (!isActive)
229
271
  return;
230
- const allIds = new Set(allDiscoveredItems.map(i => i.id));
272
+ // Only select translatable items
273
+ const translatableIds = new Set();
274
+ const walk = (nodes) => {
275
+ nodes.forEach(node => {
276
+ if (node?.data?.isTranslatable === true) {
277
+ translatableIds.add(node.key);
278
+ }
279
+ if (node.children)
280
+ walk(node.children);
281
+ });
282
+ };
283
+ walk(treeNodes);
231
284
  setUserHasInteracted(true);
232
- setSelectedItemIds(allIds);
233
- onStepCompleted(allIds.size > 0);
285
+ setSelectedItemIds(translatableIds);
286
+ onStepCompleted(translatableIds.size > 0);
234
287
  }, "data-testid": "select-all-items-button", children: "Select All" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
235
288
  if (!isActive)
236
289
  return;
@@ -253,7 +306,7 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
253
306
  const targetNode = key;
254
307
  const shift = event?.shiftKey === true;
255
308
  const next = new Set(selectedItemIds);
256
- // Only select pages
309
+ // Only select translatable items
257
310
  const findNode = (nodes) => {
258
311
  for (const n of nodes) {
259
312
  if (n.key === targetNode)
@@ -267,35 +320,34 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
267
320
  return null;
268
321
  };
269
322
  const n = findNode(treeNodes);
270
- const isPage = n?.data?.hasLayout === true;
271
- if (isPage) {
323
+ const isTranslatable = n?.data?.isTranslatable === true;
324
+ if (isTranslatable) {
272
325
  const ids = shift ? [key, ...getDescendantIds(key)] : [key];
273
326
  if (!next.has(key))
274
- ids.forEach(id => next.add(id));
327
+ ids.forEach((id) => next.add(id));
275
328
  else
276
- ids.forEach(id => next.delete(id));
329
+ ids.forEach((id) => next.delete(id));
277
330
  }
278
331
  else {
279
- // Folder row click toggles all descendant pages
280
- const pageIds = getDescendantPageIds(key);
281
- const allSelected = pageIds.length > 0 && pageIds.every(id => next.has(id));
332
+ // Folder row click toggles all descendant translatable items
333
+ const translatableIds = getDescendantTranslatableIds(key);
334
+ const allSelected = translatableIds.length > 0 && translatableIds.every((id) => next.has(id));
282
335
  if (allSelected)
283
- pageIds.forEach(id => next.delete(id));
336
+ translatableIds.forEach((id) => next.delete(id));
284
337
  else
285
- pageIds.forEach(id => next.add(id));
338
+ translatableIds.forEach((id) => next.add(id));
286
339
  }
287
340
  setSelectedItemIds(next);
288
341
  // Update completion when user changes selection
289
342
  onStepCompleted(next.size > 0);
290
343
  }, renderNode: (node) => {
291
- const isPage = node?.data?.hasLayout === true;
292
- const isTranslatable = isPage;
293
- const pageIds = !isPage ? getDescendantPageIds(node.key) : [];
294
- const allSelected = !isPage ? (pageIds.length > 0 && pageIds.every(id => selectedItemIds.has(id))) : false;
295
- const someSelected = !isPage ? (pageIds.some(id => selectedItemIds.has(id)) && !allSelected) : false;
296
- // Simple checkbox logic: if it's a page, check direct selection; otherwise check descendant pages
344
+ const isTranslatable = node?.data?.isTranslatable === true;
345
+ const translatableIds = !isTranslatable ? getDescendantTranslatableIds(node.key) : [];
346
+ const allSelected = !isTranslatable ? (translatableIds.length > 0 && translatableIds.every((id) => selectedItemIds.has(id))) : false;
347
+ const someSelected = !isTranslatable ? (translatableIds.some((id) => selectedItemIds.has(id)) && !allSelected) : false;
348
+ // Simple checkbox logic: if it's translatable, check direct selection; otherwise check descendant translatable items
297
349
  // Base items are treated like any other selected item - if selected, show checked
298
- const isChecked = isPage ? selectedItemIds.has(node.key) : allSelected;
350
+ const isChecked = isTranslatable ? selectedItemIds.has(node.key) : allSelected;
299
351
  const icon = node?.data?.icon;
300
352
  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)
301
353
  el.indeterminate = someSelected; }, onMouseDown: (e) => {
@@ -307,27 +359,27 @@ export function SubitemDiscoveryStep({ stepIndex, isActive = true, data, setData
307
359
  return; // Don't handle changes when inactive or untranslatable
308
360
  setUserHasInteracted(true);
309
361
  const next = new Set(selectedItemIds);
310
- if (isPage) {
362
+ if (isTranslatable) {
311
363
  const withDesc = shiftToggleRef.current && (node.children?.length ?? 0) > 0;
312
364
  const ids = withDesc ? [node.key, ...getDescendantIds(node.key)] : [node.key];
313
365
  if (e.currentTarget.checked)
314
- ids.forEach(id => next.add(id));
366
+ ids.forEach((id) => next.add(id));
315
367
  else
316
- ids.forEach(id => next.delete(id));
368
+ ids.forEach((id) => next.delete(id));
317
369
  }
318
370
  else {
319
- const ids = getDescendantPageIds(node.key);
320
- const allSel = ids.length > 0 && ids.every(id => next.has(id));
371
+ const ids = getDescendantTranslatableIds(node.key);
372
+ const allSel = ids.length > 0 && ids.every((id) => next.has(id));
321
373
  if (e.currentTarget.checked && !allSel)
322
- ids.forEach(id => next.add(id));
374
+ ids.forEach((id) => next.add(id));
323
375
  else if (!e.currentTarget.checked && allSel)
324
- ids.forEach(id => next.delete(id));
376
+ ids.forEach((id) => next.delete(id));
325
377
  else {
326
378
  // Toggle based on current
327
379
  if (allSel)
328
- ids.forEach(id => next.delete(id));
380
+ ids.forEach((id) => next.delete(id));
329
381
  else
330
- ids.forEach(id => next.add(id));
382
+ ids.forEach((id) => next.add(id));
331
383
  }
332
384
  }
333
385
  setSelectedItemIds(next);
@@ -3,6 +3,7 @@ export type TranslationProviderInfo = {
3
3
  name: string;
4
4
  displayName: string;
5
5
  supportedLanguages: string[];
6
+ defaultPrompt?: string;
6
7
  };
7
8
  export type TranslationStatus = {
8
9
  targetLanguage: string;
@@ -43,6 +44,7 @@ export type TranslationWizardData = {
43
44
  translationProviders: TranslationProviderInfo[];
44
45
  itemMetadata: Map<string, Map<string, string>>;
45
46
  metadata?: any;
47
+ serviceCustomData?: Map<string, any>;
46
48
  [key: string]: any;
47
49
  };
48
50
  export type TranslationStepProps = {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/steps/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/steps/types.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import "react-json-view-lite/dist/index.css";
1
2
  interface BatchTranslationViewProps {
2
3
  batchId: string;
3
4
  onBack?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AA2BA,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAoCD,wBAAgB,oBAAoB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,yBAAyB,2CAg5BlF"}
1
+ {"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AAwBA,OAAO,qCAAqC,CAAC;AAa7C,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAoCD,wBAAgB,oBAAoB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,yBAAyB,2CA07BlF"}
@@ -2,8 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React, { useEffect, useState, useMemo, useRef, useCallback, } from "react";
3
3
  import { useEditContext, SimpleTable, Progress as CoreProgress, Button, } from "@parhelia/core";
4
4
  import { listBatchTranslationJobs, subscribeToBatch, unsubscribeFromBatch, getBatchInfo, getTranslationProviders } from "../services/translationService";
5
+ import { ChevronDown, ChevronUp } from "lucide-react";
6
+ import { JsonView, defaultStyles } from "react-json-view-lite";
7
+ import "react-json-view-lite/dist/index.css";
5
8
  // Wrapper so React 19 sees a plain function component instead of a forwardRef exotic component.
6
9
  const Progress = (props) => React.createElement(CoreProgress, props);
10
+ // Wrappers for lucide-react icons to work with React 19
11
+ const ChevronUpIcon = (props) => React.createElement(ChevronUp, props);
12
+ const ChevronDownIcon = (props) => React.createElement(ChevronDown, props);
7
13
  // Normalize API shape/casing to what the UI expects
8
14
  function normalizeBatchInfo(raw) {
9
15
  if (!raw)
@@ -39,6 +45,7 @@ export function BatchTranslationView({ batchId, onBack }) {
39
45
  const [isLoading, setIsLoading] = useState(false);
40
46
  const [translationProgress, setTranslationProgress] = useState(new Map());
41
47
  const [itemNames, setItemNames] = useState(new Map());
48
+ const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
42
49
  const lastWsAtRef = useRef(0);
43
50
  const batchJobsRef = useRef([]);
44
51
  const hasLoadedRef = useRef(false);
@@ -52,6 +59,17 @@ export function BatchTranslationView({ batchId, onBack }) {
52
59
  const provider = providers.find(p => p.name === serviceName);
53
60
  return provider?.displayName || serviceName;
54
61
  }, [providers]);
62
+ // Parse metadata for display
63
+ const parsedMetadata = useMemo(() => {
64
+ if (!batchInfo?.metadata)
65
+ return null;
66
+ try {
67
+ return JSON.parse(batchInfo.metadata);
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }, [batchInfo?.metadata]);
55
73
  // Keep refs in sync with state
56
74
  useEffect(() => {
57
75
  batchJobsRef.current = batchJobs;
@@ -482,7 +500,7 @@ export function BatchTranslationView({ batchId, onBack }) {
482
500
  ? 'bg-blue-100 text-blue-700'
483
501
  : effectiveStatus === 'Error'
484
502
  ? 'bg-red-100 text-red-700'
485
- : 'bg-gray-100 text-gray-700'}`, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-gray-700", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-gray-700", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-gray-700", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-gray-700 truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-gray-500", children: [batchInfo.startedAtUtc && (_jsxs(_Fragment, { children: ["Started: ", new Date(batchInfo.startedAtUtc).toLocaleString()] })), batchInfo.completedAtUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Completed: ", new Date(batchInfo.completedAtUtc).toLocaleString()] })), !batchInfo.completedAtUtc && batchInfo.lastUpdatedUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Updated: ", new Date(batchInfo.lastUpdatedUtc).toLocaleString()] }))] }))] }))] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "text-sm text-gray-600", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", children: [_jsx("i", { className: `pi pi-refresh ${isLoading ? 'pi-spin' : ''}` }), "Refresh"] })] })] }), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-600", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 bg-gray-200 rounded-full overflow-hidden", children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "bg-green-500 h-full transition-all duration-300", style: { width: `${progressSegments.completed}%` } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "bg-blue-500 h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%` } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-red-500 h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "bg-gray-300 h-full transition-all duration-300", style: { width: `${progressSegments.pending}%` } }))] }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsxs("span", { className: "text-xs font-medium text-white drop-shadow-sm", children: [overallProgress, "%"] }) })] }), _jsxs("div", { className: "flex gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-green-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-blue-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-red-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 min-h-0", children: isLoading && batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-500", children: [_jsx("i", { className: "pi pi-spin pi-spinner" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2" }), _jsx("p", { children: "No translations found for this batch" }), _jsx("p", { className: "text-sm", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-6", children: sortedItemGroups.map(([itemId, jobs]) => {
503
+ : 'bg-gray-100 text-gray-700'}`, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-gray-700", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-gray-700", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-gray-700", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-gray-700 truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-gray-500", children: [batchInfo.startedAtUtc && (_jsxs(_Fragment, { children: ["Started: ", new Date(batchInfo.startedAtUtc).toLocaleString()] })), batchInfo.completedAtUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Completed: ", new Date(batchInfo.completedAtUtc).toLocaleString()] })), !batchInfo.completedAtUtc && batchInfo.lastUpdatedUtc && (_jsxs(_Fragment, { children: [batchInfo.startedAtUtc ? ' • ' : '', "Updated: ", new Date(batchInfo.lastUpdatedUtc).toLocaleString()] }))] }))] }))] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "text-sm text-gray-600", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", children: [_jsx("i", { className: `pi pi-refresh ${isLoading ? 'pi-spin' : ''}` }), "Refresh"] })] })] }), parsedMetadata && (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50 mt-4", children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("i", { className: "pi pi-info-circle text-gray-500" }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isMetadataExpanded && (_jsx("div", { className: "max-h-96 overflow-y-auto px-4 pb-3", children: _jsx("div", { className: "rounded-md border border-gray-200 bg-white p-3 text-xs shadow-sm mt-2", children: _jsx(JsonView, { data: parsedMetadata, shouldExpandNode: (level) => level < 2, style: defaultStyles }) }) }))] })), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-600", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 bg-gray-200 rounded-full overflow-hidden", children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "bg-green-500 h-full transition-all duration-300", style: { width: `${progressSegments.completed}%` } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "bg-blue-500 h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%` } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-red-500 h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "bg-gray-300 h-full transition-all duration-300", style: { width: `${progressSegments.pending}%` } }))] }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsxs("span", { className: "text-xs font-medium text-white drop-shadow-sm", children: [overallProgress, "%"] }) })] }), _jsxs("div", { className: "flex gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-green-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-blue-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-red-500 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "w-3 h-3 bg-gray-300 rounded-sm" }), _jsxs("span", { className: "text-gray-700", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 min-h-0", children: isLoading && batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-500", children: [_jsx("i", { className: "pi pi-spin pi-spinner" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2" }), _jsx("p", { children: "No translations found for this batch" }), _jsx("p", { className: "text-sm", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-6", children: sortedItemGroups.map(([itemId, jobs]) => {
486
504
  const itemCompleted = jobs.filter(j => j.status === "Completed").length;
487
505
  // Calculate item progress including in-progress job progress
488
506
  // Performance: item-level calculation is always fast (typically 2-10 jobs per item)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parhelia/localization",
3
- "version": "0.1.11092",
3
+ "version": "0.1.11143",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"