@parhelia/localization 0.1.11464 → 0.1.11519

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,2CA8VtE"}
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,2CAmWtE"}
@@ -186,7 +186,9 @@ export function LocalizeItemDialog(props) {
186
186
  minHeight: '700px'
187
187
  }, 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 &&
188
188
  "border-[#9650fb] text-[#9650fb] bg-[#f6eeff]", currentStepIndex < index && "border-[var(--color-gray-3)] text-[var(--color-gray-2)]", currentStepIndex > index &&
189
- "bg-[#9650fb] text-white border-[#9650fb]"), "data-testid": `step-indicator-circle-${step.id}`, children: currentStepIndex > index ? "✓" : index + 1 }), _jsx("span", { className: cn("text-sm", currentStepIndex === index
189
+ "bg-[#9650fb] text-white border-[#9650fb]"), style: currentStepIndex > index
190
+ ? { backgroundColor: "#9650fb", color: "#ffffff" }
191
+ : undefined, "data-testid": `step-indicator-circle-${step.id}`, children: currentStepIndex > index ? "✓" : index + 1 }), _jsx("span", { className: cn("text-sm", currentStepIndex === index
190
192
  ? "text-[#9650fb] font-medium"
191
193
  : currentStepIndex > index
192
194
  ? "text-[#9650fb]"
@@ -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,CAiG5B;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,CAqH5B;AAGD,eAAO,MAAM,mBAAmB,GAAU,eAAe,MAAM,EAAE,EAAE,mBAAmB,iBAAiB,EAAE,EAAE,WAAW,MAAM,EAAE,MAAM,QAAQ,EAAE,qBAAqB,MAAM,+BA0BxK,CAAC"}
@@ -46,12 +46,27 @@ export async function performDefaultTranslation(wizardData, editContext, predefi
46
46
  if (wizardData.serviceCustomData && wizardData.serviceCustomData.size > 0) {
47
47
  const serviceData = wizardData.serviceCustomData.get(wizardData.translationProvider);
48
48
  if (serviceData && serviceData.enableCustomPrompt) {
49
+ const provider = wizardData.translationProviders.find(p => p.name === wizardData.translationProvider);
50
+ const defaultPrompt = provider?.defaultPrompt && provider.defaultPrompt.trim()
51
+ ? provider.defaultPrompt
52
+ : null;
53
+ const customizationType = serviceData.promptCustomizationType === "replace" ? "replace" : "extend";
54
+ const rawCustomPrompt = (serviceData.customPrompt || "").trim();
55
+ let finalCustomPrompt = "";
56
+ if (rawCustomPrompt) {
57
+ if (defaultPrompt && customizationType === "extend") {
58
+ finalCustomPrompt = `${defaultPrompt}\n\n${rawCustomPrompt}`;
59
+ }
60
+ else {
61
+ finalCustomPrompt = rawCustomPrompt;
62
+ }
63
+ }
49
64
  // Merge service-specific custom data into batch metadata
50
65
  const metadataObj = batchMetadata ? (typeof batchMetadata === 'string' ? JSON.parse(batchMetadata) : batchMetadata) : {};
51
66
  metadataObj.serviceCustomData = {
52
67
  [wizardData.translationProvider]: {
53
68
  enableCustomPrompt: serviceData.enableCustomPrompt,
54
- customPrompt: serviceData.customPrompt || ""
69
+ customPrompt: finalCustomPrompt
55
70
  }
56
71
  };
57
72
  batchMetadata = JSON.stringify(metadataObj);
@@ -1 +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"}
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,2CAwPtB"}
@@ -1,8 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useEffect, useMemo } from "react";
2
+ import { useState, useEffect, useMemo, useRef } from "react";
3
3
  export function PromptCustomizationStep({ stepIndex, isActive = true, data, setData, onStepCompleted, editContext, }) {
4
4
  const [customPrompt, setCustomPrompt] = useState("");
5
5
  const [customizationType, setCustomizationType] = useState("extend");
6
+ const customPromptRef = useRef(customPrompt);
7
+ const customizationTypeRef = useRef(customizationType);
8
+ useEffect(() => {
9
+ customPromptRef.current = customPrompt;
10
+ }, [customPrompt]);
11
+ useEffect(() => {
12
+ customizationTypeRef.current = customizationType;
13
+ }, [customizationType]);
6
14
  // Get service-specific custom data for the selected provider
7
15
  const serviceData = useMemo(() => {
8
16
  if (!data.serviceCustomData || !data.translationProvider)
@@ -10,17 +18,6 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
10
18
  return data.serviceCustomData.get(data.translationProvider);
11
19
  }, [data.serviceCustomData, data.translationProvider]);
12
20
  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
21
  // Get default prompt from provider settings (no fallback)
25
22
  const defaultPrompt = useMemo(() => {
26
23
  const provider = data.translationProviders.find(p => p.name === data.translationProvider);
@@ -29,6 +26,37 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
29
26
  return prompt && prompt.trim() ? prompt : null;
30
27
  }, [data.translationProviders, data.translationProvider]);
31
28
  const hasDefaultPrompt = defaultPrompt != null && defaultPrompt.trim().length > 0;
29
+ // Initialize from existing data
30
+ useEffect(() => {
31
+ const nextCustomizationType = serviceData?.promptCustomizationType || "extend";
32
+ let nextCustomPrompt = serviceData?.customPrompt || "";
33
+ // If stored prompt already includes default (legacy), strip it for editing
34
+ if (hasDefaultPrompt && nextCustomizationType === "extend" && nextCustomPrompt.startsWith(defaultPrompt || "")) {
35
+ nextCustomPrompt = nextCustomPrompt.slice((defaultPrompt || "").length);
36
+ nextCustomPrompt = nextCustomPrompt.replace(/^\s*\n\s*\n?/, "");
37
+ }
38
+ if (enableCustomPrompt && serviceData) {
39
+ if (customPromptRef.current !== nextCustomPrompt) {
40
+ setCustomPrompt(nextCustomPrompt);
41
+ }
42
+ if (customizationTypeRef.current !== nextCustomizationType) {
43
+ setCustomizationType(nextCustomizationType);
44
+ }
45
+ }
46
+ else {
47
+ if (customPromptRef.current !== "") {
48
+ setCustomPrompt("");
49
+ }
50
+ if (customizationTypeRef.current !== "extend") {
51
+ setCustomizationType("extend");
52
+ }
53
+ }
54
+ }, [
55
+ enableCustomPrompt,
56
+ serviceData,
57
+ hasDefaultPrompt,
58
+ defaultPrompt,
59
+ ]);
32
60
  // Preview of final prompt
33
61
  const previewPrompt = useMemo(() => {
34
62
  if (!enableCustomPrompt || !customPrompt.trim()) {
@@ -49,34 +77,29 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
49
77
  if (!isActive)
50
78
  return;
51
79
  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
- }
80
+ const trimmedCustomPrompt = customPrompt.trim();
81
+ const nextCustomizationType = customizationType === "replace" ? "replace" : "extend";
82
+ const currentServiceData = data.serviceCustomData?.get(data.translationProvider);
71
83
  if (enableCustomPrompt) {
72
- newServiceCustomData.set(data.translationProvider, {
84
+ const nextServiceData = {
73
85
  enableCustomPrompt: true,
74
- customPrompt: finalCustomPrompt,
75
- // Note: promptCustomizationType is no longer sent to backend
76
- });
86
+ // Store raw custom prompt only; final prompt is composed when sending metadata.
87
+ customPrompt: trimmedCustomPrompt,
88
+ promptCustomizationType: nextCustomizationType,
89
+ };
90
+ const isSame = currentServiceData?.enableCustomPrompt === true &&
91
+ (currentServiceData.customPrompt || "") === trimmedCustomPrompt &&
92
+ (currentServiceData.promptCustomizationType || "extend") === nextCustomizationType;
93
+ if (isSame) {
94
+ return;
95
+ }
96
+ newServiceCustomData.set(data.translationProvider, nextServiceData);
77
97
  }
78
98
  else {
79
99
  // Remove custom data if disabled
100
+ if (!currentServiceData) {
101
+ return;
102
+ }
80
103
  newServiceCustomData.delete(data.translationProvider);
81
104
  }
82
105
  const newData = {
@@ -84,7 +107,15 @@ export function PromptCustomizationStep({ stepIndex, isActive = true, data, setD
84
107
  serviceCustomData: newServiceCustomData,
85
108
  };
86
109
  setData(newData);
87
- }, [enableCustomPrompt, customPrompt, customizationType, defaultPrompt, hasDefaultPrompt, data.translationProvider, isActive, setData]);
110
+ }, [
111
+ enableCustomPrompt,
112
+ customPrompt,
113
+ customizationType,
114
+ data.serviceCustomData,
115
+ data.translationProvider,
116
+ isActive,
117
+ setData,
118
+ ]);
88
119
  // Update completion status
89
120
  useEffect(() => {
90
121
  if (!isActive)
@@ -1 +1 @@
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,2CAq8BlF"}
1
+ {"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AAyBA,OAAO,qCAAqC,CAAC;AAsB7C,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,2CAo9BlF"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useEffect, useState, useMemo, useRef, useCallback, } from "react";
2
+ import React, { useEffect, useState, useMemo, useRef, useCallback, memo, } 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
5
  import { ChevronDown, ChevronUp } from "lucide-react";
@@ -10,6 +10,9 @@ const Progress = (props) => React.createElement(CoreProgress, props);
10
10
  // Wrappers for lucide-react icons to work with React 19
11
11
  const ChevronUpIcon = (props) => React.createElement(ChevronUp, props);
12
12
  const ChevronDownIcon = (props) => React.createElement(ChevronDown, props);
13
+ const MemoizedJsonView = memo(({ data }) => (_jsx(JsonView, { data: data, shouldExpandNode: (level) => level < 2, style: defaultStyles })), (prevProps, nextProps) => {
14
+ return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
15
+ });
13
16
  // Normalize API shape/casing to what the UI expects
14
17
  function normalizeBatchInfo(raw) {
15
18
  if (!raw)
@@ -52,6 +55,7 @@ export function BatchTranslationView({ batchId, onBack }) {
52
55
  const isSubscribedRef = useRef(false);
53
56
  const initGuardRef = useRef(false);
54
57
  const lastBatchIdRef = useRef(null);
58
+ const completionRefreshRef = useRef(null);
55
59
  // Helper function to get display name from service name
56
60
  const getProviderDisplayName = useCallback((serviceName) => {
57
61
  if (!serviceName)
@@ -165,6 +169,7 @@ export function BatchTranslationView({ batchId, onBack }) {
165
169
  hasLoadedRef.current = false;
166
170
  isSubscribedRef.current = false;
167
171
  initGuardRef.current = false;
172
+ completionRefreshRef.current = null;
168
173
  }
169
174
  let cancelled = false;
170
175
  const init = async () => {
@@ -302,6 +307,7 @@ export function BatchTranslationView({ batchId, onBack }) {
302
307
  const info = normalizeBatchInfo(raw);
303
308
  if (info)
304
309
  setBatchInfo(info);
310
+ await loadBatchJobs();
305
311
  }
306
312
  catch { }
307
313
  })();
@@ -495,13 +501,32 @@ export function BatchTranslationView({ batchId, onBack }) {
495
501
  progressSegments.inProgressCount === 0
496
502
  ? "Completed"
497
503
  : batchInfo?.status;
504
+ // Ensure terminal state is reflected in UI even if a websocket message was missed.
505
+ useEffect(() => {
506
+ if (effectiveStatus !== "Completed" && effectiveStatus !== "Error")
507
+ return;
508
+ if (completionRefreshRef.current === batchId)
509
+ return;
510
+ completionRefreshRef.current = batchId;
511
+ (async () => {
512
+ try {
513
+ const resInfo = await getBatchInfo(batchId);
514
+ const raw = resInfo?.data ?? resInfo;
515
+ const info = normalizeBatchInfo(raw);
516
+ if (info)
517
+ setBatchInfo(info);
518
+ await loadBatchJobs();
519
+ }
520
+ catch { }
521
+ })();
522
+ }, [effectiveStatus, batchId, loadBatchJobs]);
498
523
  return (_jsxs("div", { className: "flex h-full flex-col min-h-0 bg-[var(--color-gray-5)]", "data-testid": "batch-translation-view", children: [_jsxs("div", { className: "flex-shrink-0 border-b border-[var(--color-gray-3)] bg-background p-4 md:p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [onBack && (_jsxs(Button, { size: "sm", variant: "outline", onClick: onBack, children: [_jsx("i", { className: "pi pi-arrow-left" }), "Back"] })), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold text-[var(--color-dark)]", children: "Translation Batch" }), _jsxs("p", { className: "text-sm text-[var(--color-gray-2)] mt-1", children: ["Batch ID: ", _jsx("span", { className: "font-mono text-xs bg-[var(--color-gray-4)] px-2 py-0.5 rounded-md", children: batchId })] }), batchInfo && (_jsxs("div", { className: "mt-2 flex flex-wrap gap-2 items-center text-xs", children: [effectiveStatus && (_jsx("span", { "data-testid": "translation-job-status", className: `inline-flex items-center rounded-full px-2.5 py-1 font-medium ${effectiveStatus === 'Completed'
499
524
  ? 'bg-green-100 text-green-700'
500
525
  : effectiveStatus === 'In Progress'
501
526
  ? ''
502
527
  : effectiveStatus === 'Error'
503
528
  ? 'bg-red-100 text-red-600'
504
- : 'bg-[var(--color-gray-4)] text-[var(--color-gray-1)]'}`, style: effectiveStatus === 'In Progress' ? { backgroundColor: '#f6eeff', color: '#9650fb' } : undefined, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-[var(--color-gray-1)] truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-[var(--color-gray-2)]", 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-[var(--color-gray-2)]", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", variant: "outline", 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-[var(--color-gray-3)] bg-[var(--color-gray-5)] mt-4 rounded-b-lg", children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2.5 text-left transition-colors hover:bg-[var(--color-gray-4)]", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("i", { className: "pi pi-info-circle text-[var(--color-gray-2)]" }), _jsx("span", { className: "text-xs font-medium text-[var(--color-gray-1)]", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 }))] }), isMetadataExpanded && (_jsx("div", { className: "max-h-96 overflow-y-auto px-4 pb-3", children: _jsx("div", { className: "rounded-lg border border-[var(--color-gray-3)] bg-background 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 text-[var(--color-dark)]", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-[var(--color-gray-2)]", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 rounded-full overflow-hidden", style: { backgroundColor: 'var(--color-gray-3)' }, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.completed}%`, backgroundColor: '#8ae048' } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%`, backgroundColor: '#9650fb' } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-destructive h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.pending}%`, backgroundColor: 'var(--color-gray-4)' } }))] }), _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 flex-wrap gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 rounded-sm", style: { backgroundColor: '#8ae048' } }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 rounded-sm", style: { backgroundColor: '#9650fb' } }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-destructive rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-[var(--color-gray-4)] rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-[var(--color-gray-4)] rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 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-[var(--color-gray-2)]", children: [_jsx("i", { className: "pi pi-spin pi-spinner text-[#9650fb]" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-[var(--color-gray-2)]", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2 text-[var(--color-gray-3)]" }), _jsx("p", { className: "font-medium text-[var(--color-gray-1)]", children: "No translations found for this batch" }), _jsx("p", { className: "text-sm mt-1", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-4", children: sortedItemGroups.map(([itemId, jobs]) => {
529
+ : 'bg-[var(--color-gray-4)] text-[var(--color-gray-1)]'}`, style: effectiveStatus === 'In Progress' ? { backgroundColor: '#f6eeff', color: '#9650fb' } : undefined, children: effectiveStatus })), batchInfo.provider && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Provider: ", _jsx("span", { className: "font-medium", children: getProviderDisplayName(batchInfo.provider) })] })), batchInfo.initiatedByUser && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["By: ", _jsx("span", { className: "font-medium", children: batchInfo.initiatedByUser })] })), batchInfo.includeSubitems !== undefined && batchInfo.includeSubitems !== null && (_jsxs("span", { className: "text-[var(--color-gray-1)]", children: ["Include subitems: ", _jsx("span", { className: "font-medium", children: batchInfo.includeSubitems ? 'Yes' : 'No' })] })), batchInfo.scopeItemPath && (_jsxs("span", { className: "text-[var(--color-gray-1)] truncate max-w-[40rem]", children: ["Scope: ", _jsx("span", { className: "font-mono", children: batchInfo.scopeItemPath })] })), (batchInfo.startedAtUtc || batchInfo.completedAtUtc || batchInfo.lastUpdatedUtc) && (_jsxs("span", { className: "text-[var(--color-gray-2)]", 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-[var(--color-gray-2)]", children: [`${progressSegments.completedCount}/${progressSegments.total} completed`, progressSegments.errorCount ? `, ${progressSegments.errorCount} errors` : ''] }), _jsxs(Button, { size: "sm", variant: "outline", 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-[var(--color-gray-3)] bg-[var(--color-gray-5)] mt-4 rounded-b-lg", children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2.5 text-left transition-colors hover:bg-[var(--color-gray-4)]", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("i", { className: "pi pi-info-circle text-[var(--color-gray-2)]" }), _jsx("span", { className: "text-xs font-medium text-[var(--color-gray-1)]", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-[var(--color-gray-2)]", strokeWidth: 1 }))] }), isMetadataExpanded && (_jsx("div", { className: "max-h-96 overflow-y-auto px-4 pb-3", children: _jsx("div", { className: "rounded-lg border border-[var(--color-gray-3)] bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(MemoizedJsonView, { data: parsedMetadata }) }) }))] })), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium text-[var(--color-dark)]", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-[var(--color-gray-2)]", children: [progressSegments.completedCount, " / ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: "relative h-4 rounded-full overflow-hidden", style: { backgroundColor: 'var(--color-gray-3)' }, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.completed}%`, backgroundColor: '#8ae048' } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.inProgress}%`, backgroundColor: '#9650fb' } })), progressSegments.error > 0 && (_jsx("div", { className: "bg-destructive h-full transition-all duration-300", style: { width: `${progressSegments.error}%` } })), progressSegments.pending > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: { width: `${progressSegments.pending}%`, backgroundColor: 'var(--color-gray-4)' } }))] }), _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 flex-wrap gap-3 mt-2 text-xs", children: [progressSegments.completedCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 rounded-sm", style: { backgroundColor: '#8ae048' } }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.completedCount, " completed"] })] })), progressSegments.inProgressCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 rounded-sm", style: { backgroundColor: '#9650fb' } }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.inProgressCount, " in progress"] })] })), progressSegments.errorCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-destructive rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.errorCount, " errors"] })] })), progressSegments.pendingCount > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-[var(--color-gray-4)] rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [progressSegments.pendingCount, " pending"] })] })), typeof batchInfo?.expectedJobs === 'number' && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "w-3 h-3 bg-[var(--color-gray-4)] rounded-sm" }), _jsxs("span", { className: "text-[var(--color-gray-1)]", children: [(batchInfo.expectedJobs || 0) - (batchInfo.completedJobs || 0) - (batchInfo.errorJobs || 0), " pending (server)"] })] }))] })] })] }), _jsx("div", { className: "flex-1 overflow-auto p-4 md:p-6 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-[var(--color-gray-2)]", children: [_jsx("i", { className: "pi pi-spin pi-spinner text-[#9650fb]" }), "Loading batch translations..."] }) })) : batchJobs.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-[var(--color-gray-2)]", children: [_jsx("i", { className: "pi pi-language text-2xl block mb-2 text-[var(--color-gray-3)]" }), _jsx("p", { className: "font-medium text-[var(--color-gray-1)]", children: "No translations found for this batch" }), _jsx("p", { className: "text-sm mt-1", children: "The batch may not exist or no translation jobs have started yet" })] }) })) : (_jsx("div", { className: "space-y-4", children: sortedItemGroups.map(([itemId, jobs]) => {
505
530
  const itemCompleted = jobs.filter(j => j.status === "Completed").length;
506
531
  // Calculate item progress including in-progress job progress
507
532
  // 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.11464",
3
+ "version": "0.1.11519",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"