@parhelia/localization 0.1.12485 → 0.1.12515

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,5 +1,11 @@
1
1
  import { TranslationStatus } from "../types";
2
2
  import { TranslationProviderInfo } from "../steps/types";
3
+ export type TranslationApiResponse<T> = {
4
+ success: boolean;
5
+ data?: T;
6
+ error?: string;
7
+ message?: string;
8
+ };
3
9
  export type TranslationRequestResponse = {
4
10
  jobId: string;
5
11
  };
@@ -23,13 +29,11 @@ export type AvailableTranslationService = {
23
29
  templateId: string;
24
30
  templateName: string;
25
31
  };
26
- export declare function getAvailableTranslationServices(): Promise<import("@parhelia/core").ExecutionResult<AvailableTranslationService[]>>;
27
- export declare function createProviderSettings(serviceName: string, templateId: string, language?: string, structureSettings?: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<{
28
- success: boolean;
32
+ export declare function getAvailableTranslationServices(): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<AvailableTranslationService[]>>>;
33
+ export declare function createProviderSettings(serviceName: string, templateId: string, language?: string, structureSettings?: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<{
29
34
  itemId: string;
30
35
  itemPath: string;
31
- message: string;
32
- }>>;
36
+ }>>>;
33
37
  export type TranslationStructureSettings = {
34
38
  translationFolderPath: string;
35
39
  translationFolderTemplateId: string;
@@ -46,9 +50,10 @@ export type TranslationStructureResponse = {
46
50
  translationFolder: TranslationStructureItemInfo;
47
51
  translationProvidersFolder: TranslationStructureItemInfo;
48
52
  };
49
- export declare function getTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationStructureResponse>>;
50
- export declare function ensureTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationStructureResponse>>;
53
+ export declare function getTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<TranslationStructureResponse>>>;
54
+ export declare function ensureTranslationStructure(settings: TranslationStructureSettings): Promise<import("@parhelia/core").ExecutionResult<TranslationApiResponse<TranslationStructureResponse>>>;
51
55
  export type TranslationJobRecord = {
56
+ id?: number;
52
57
  itemId: string;
53
58
  targetLanguage: string;
54
59
  sourceLanguage: string;
@@ -57,6 +62,7 @@ export type TranslationJobRecord = {
57
62
  hash?: string;
58
63
  message?: string;
59
64
  batchId?: string;
65
+ metadata?: string | null;
60
66
  };
61
67
  export declare function listBatchTranslationJobs(batchId: string): Promise<import("@parhelia/core").ExecutionResult<TranslationJobRecord[]>>;
62
68
  export type TranslationBatchInfo = {
@@ -112,4 +118,39 @@ export declare function requestBatchTranslation({ sessionId, batchId, provider,
112
118
  languageMappings: LanguageMapping[];
113
119
  itemIds: string[];
114
120
  }): Promise<import("@parhelia/core").ExecutionResult<BatchTranslationResponse>>;
121
+ export type RetryBatchTranslationJobRequest = {
122
+ sourceTranslationId?: number;
123
+ itemId: string;
124
+ sourceLanguage: string;
125
+ targetLanguage: string;
126
+ metadata?: string | null;
127
+ };
128
+ export type RetryBatchTranslationStartedJob = {
129
+ sourceTranslationId?: number;
130
+ itemId: string;
131
+ sourceLanguage: string;
132
+ targetLanguage: string;
133
+ metadata?: string | null;
134
+ jobId: string;
135
+ };
136
+ export type RetryBatchTranslationSkippedJob = {
137
+ sourceTranslationId?: number;
138
+ itemId: string;
139
+ sourceLanguage: string;
140
+ targetLanguage: string;
141
+ error: string;
142
+ };
143
+ export type RetryBatchTranslationResponse = {
144
+ sourceBatchId: string;
145
+ retryBatchId: string;
146
+ provider: string;
147
+ started: RetryBatchTranslationStartedJob[];
148
+ skipped: RetryBatchTranslationSkippedJob[];
149
+ };
150
+ export declare function retryBatchTranslation({ sessionId, sourceBatchId, provider, jobs, }: {
151
+ sessionId: string;
152
+ sourceBatchId: string;
153
+ provider?: string;
154
+ jobs: RetryBatchTranslationJobRequest[];
155
+ }): Promise<import("@parhelia/core").ExecutionResult<RetryBatchTranslationResponse>>;
115
156
  //# sourceMappingURL=translationService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"translationService.d.ts","sourceRoot":"","sources":["../../src/services/translationService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"translationService.d.ts","sourceRoot":"","sources":["../../src/services/translationService.ts"],"names":[],"mappings":"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"}
@@ -56,3 +56,6 @@ export async function unsubscribeFromBatch(batchId, sessionId) {
56
56
  export async function requestBatchTranslation({ sessionId, batchId, provider, batchMetadata, languageMappings, itemIds }) {
57
57
  return await post("/parhelia/translation/requestBatchTranslation", { sessionId, batchId, provider, batchMetadata: batchMetadata || "", languageMappings, itemIds });
58
58
  }
59
+ export async function retryBatchTranslation({ sessionId, sourceBatchId, provider, jobs, }) {
60
+ return await post("/parhelia/translation/retryBatchTranslation", { sessionId, sourceBatchId, provider, jobs });
61
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"TranslationServicesPanel.d.ts","sourceRoot":"","sources":["../../src/settings/TranslationServicesPanel.tsx"],"names":[],"mappings":"AAgEA;;;GAGG;AACH,wBAAgB,wBAAwB,4CAktBvC;AAED,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"TranslationServicesPanel.d.ts","sourceRoot":"","sources":["../../src/settings/TranslationServicesPanel.tsx"],"names":[],"mappings":"AAgEA;;;GAGG;AACH,wBAAgB,wBAAwB,4CAusBvC;AAED,eAAe,wBAAwB,CAAC"}
@@ -53,12 +53,8 @@ export function TranslationServicesPanel() {
53
53
  setStructureLoading(true);
54
54
  const result = await getTranslationStructure(structureSettings);
55
55
  if (result.type === "success" && result.data) {
56
- const success = result.data.success !== false;
57
- const response = result.data.success !== undefined
58
- ? result.data.data
59
- : result.data;
60
- if (success && response) {
61
- setStructureState(response);
56
+ if (result.data.success && result.data.data) {
57
+ setStructureState(result.data.data);
62
58
  }
63
59
  else {
64
60
  throw new Error(result.data.error ||
@@ -83,12 +79,8 @@ export function TranslationServicesPanel() {
83
79
  setError(null);
84
80
  const result = await ensureTranslationStructure(structureSettings);
85
81
  if (result.type === "success" && result.data) {
86
- const success = result.data.success !== false;
87
- const response = result.data.success !== undefined
88
- ? result.data.data
89
- : result.data;
90
- if (success && response) {
91
- setStructureState(response);
82
+ if (result.data.success && result.data.data) {
83
+ setStructureState(result.data.data);
92
84
  return true;
93
85
  }
94
86
  throw new Error(result.data.error ||
@@ -112,10 +104,12 @@ export function TranslationServicesPanel() {
112
104
  await loadStructure();
113
105
  const result = await getAvailableTranslationServices();
114
106
  if (result.type === "success" && result.data) {
115
- // Backend returns ApiResponse<T>, so data is wrapped: { success: true, data: [...] }
116
- const availableServices = Array.isArray(result.data)
117
- ? result.data
118
- : result.data.data || [];
107
+ if (!result.data.success || !result.data.data) {
108
+ throw new Error(result.data.error ||
109
+ result.details ||
110
+ "Failed to load translation services. The translation API may not be available.");
111
+ }
112
+ const availableServices = result.data.data;
119
113
  const serviceStates = availableServices.map((svc) => ({
120
114
  ...svc,
121
115
  creating: false,
@@ -150,18 +144,15 @@ export function TranslationServicesPanel() {
150
144
  "en";
151
145
  const result = await createProviderSettings(serviceName, templateId, language, structureSettings);
152
146
  if (result.type === "success" && result.data) {
153
- // Backend returns ApiResponse<T>, so unwrap: { success: true, data: { itemId, itemPath } }
154
- const response = result.data.success
155
- ? result.data.data
156
- : result.data;
157
- const success = result.data.success !== false;
158
- if (success && response) {
147
+ const payload = result.data.data;
148
+ if (result.data.success && payload) {
149
+ const settingsItemId = payload.itemId;
159
150
  setServices((prev) => prev.map((s) => s.serviceName === serviceName
160
151
  ? {
161
152
  ...s,
162
153
  isConfigured: true,
163
154
  creating: false,
164
- settingsItemId: response.itemId,
155
+ settingsItemId,
165
156
  }
166
157
  : s));
167
158
  // Reload data to ensure consistency
@@ -1 +1 @@
1
- {"version":3,"file":"LocalizationSetupStep.d.ts","sourceRoot":"","sources":["../../src/setup/LocalizationSetupStep.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,qBAAqB,4CA2NpC;AAED,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"LocalizationSetupStep.d.ts","sourceRoot":"","sources":["../../src/setup/LocalizationSetupStep.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,qBAAqB,4CA6NpC;AAED,eAAe,qBAAqB,CAAC"}
@@ -29,8 +29,12 @@ export function LocalizationSetupStep() {
29
29
  setServices([]);
30
30
  const result = await getAvailableTranslationServices();
31
31
  if (result.type === "success" && result.data) {
32
- // Backend returns ApiResponse<T>, so data is wrapped: { success: true, data: [...] }
33
- const availableServices = Array.isArray(result.data) ? result.data : result.data.data || [];
32
+ if (!result.data.success || !result.data.data) {
33
+ throw new Error(result.data.error ||
34
+ result.details ||
35
+ "Failed to check translation services. The translation API may not be available.");
36
+ }
37
+ const availableServices = result.data.data;
34
38
  if (availableServices.length === 0) {
35
39
  setState("error");
36
40
  setError("No translation services registered in dependency injection.");
@@ -75,12 +79,9 @@ export function LocalizationSetupStep() {
75
79
  "en";
76
80
  const result = await createProviderSettings(serviceName, templateId, language);
77
81
  if (result.type === "success" && result.data) {
78
- // Backend returns ApiResponse<T>, so unwrap: { success: true, data: { itemId, itemPath } }
79
- const response = result.data.success ? result.data.data : result.data;
80
- const success = result.data.success !== false;
81
- if (success && response) {
82
+ if (result.data.success && result.data.data) {
82
83
  setServices(prev => prev.map(s => s.serviceName === serviceName
83
- ? { ...s, isConfigured: true, creating: false, settingsItemId: response.itemId }
84
+ ? { ...s, isConfigured: true, creating: false, settingsItemId: result.data?.data?.itemId }
84
85
  : s));
85
86
  setTimeout(() => checkLocalization(), 500);
86
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AAmCA,OAAO,qCAAqC,CAAC;AAyC7C,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAkCD,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,GACP,EAAE,yBAAyB,2CAw0C3B"}
1
+ {"version":3,"file":"BatchTranslationView.d.ts","sourceRoot":"","sources":["../../src/translation-center/BatchTranslationView.tsx"],"names":[],"mappings":"AA0CA,OAAO,qCAAqC,CAAC;AA4C7C,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAwED,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,GACP,EAAE,yBAAyB,2CAmoD3B"}
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState, useMemo, useRef, useCallback, memo, } from "react";
3
- import { useEditContext, SimpleTable, Progress as CoreProgress, Button, } from "@parhelia/core";
4
- import { listBatchTranslationJobs, subscribeToBatch, unsubscribeFromBatch, getBatchInfo, getTranslationProviders, } from "../services/translationService";
5
- import { ChevronDown, ChevronUp, RefreshCw, ArrowLeft, Info, Loader2, Languages, } from "lucide-react";
3
+ import { useEditContext, SimpleTable, Progress as CoreProgress, Button, SimpleIconButton, CopyButton, } from "@parhelia/core";
4
+ import { listBatchTranslationJobs, retryBatchTranslation, subscribeToBatch, unsubscribeFromBatch, getBatchInfo, getTranslationProviders, } from "../services/translationService";
5
+ import { ChevronDown, ChevronUp, RefreshCw, ArrowLeft, Info, Loader2, Languages, ExternalLink, } from "lucide-react";
6
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
7
+ import { toast } from "sonner";
6
8
  import { JsonView, defaultStyles } from "react-json-view-lite";
7
9
  import "react-json-view-lite/dist/index.css";
8
10
  // Wrapper so React 19 sees a plain function component instead of a forwardRef exotic component.
@@ -15,6 +17,7 @@ const ArrowLeftIcon = (props) => React.createElement(ArrowLeft, props);
15
17
  const InfoIcon = (props) => React.createElement(Info, props);
16
18
  const LoaderIcon = (props) => React.createElement(Loader2, props);
17
19
  const LanguagesIcon = (props) => React.createElement(Languages, props);
20
+ const ExternalLinkIcon = (props) => React.createElement(ExternalLink, props);
18
21
  const MemoizedJsonView = memo(({ data }) => (_jsx(JsonView, { data: data, shouldExpandNode: (level) => level < 2, style: defaultStyles })), (prevProps, nextProps) => {
19
22
  return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
20
23
  });
@@ -22,32 +25,66 @@ const MemoizedJsonView = memo(({ data }) => (_jsx(JsonView, { data: data, should
22
25
  function normalizeBatchInfo(raw) {
23
26
  if (!raw)
24
27
  return null;
25
- const pick = (a, b) => (a !== undefined ? a : b);
26
28
  const toNum = (v) => typeof v === "number" ? v : v != null ? parseInt(v, 10) : undefined;
27
29
  const info = {
28
- batchId: pick(raw?.batchId, raw?.BatchId),
29
- createdAtUtc: pick(raw?.createdAtUtc, raw?.CreatedAtUtc),
30
- startedAtUtc: pick(raw?.startedAtUtc, raw?.StartedAtUtc),
31
- completedAtUtc: pick(raw?.completedAtUtc, raw?.CompletedAtUtc),
32
- initiatedByUser: pick(raw?.initiatedByUser, raw?.InitiatedByUser),
33
- initiatorSessionId: pick(raw?.initiatorSessionId, raw?.InitiatorSessionId),
34
- provider: pick(raw?.provider, raw?.Provider),
35
- status: pick(raw?.status, raw?.Status),
36
- expectedJobs: toNum(pick(raw?.expectedJobs, raw?.ExpectedJobs)),
37
- completedJobs: toNum(pick(raw?.completedJobs, raw?.CompletedJobs)),
38
- errorJobs: toNum(pick(raw?.errorJobs, raw?.ErrorJobs)),
39
- metadata: pick(raw?.metadata, raw?.Metadata),
40
- lastUpdatedUtc: pick(raw?.lastUpdatedUtc, raw?.LastUpdatedUtc),
41
- exists: pick(raw?.exists, raw?.Exists),
30
+ batchId: raw?.batchId,
31
+ createdAtUtc: raw?.createdAtUtc,
32
+ startedAtUtc: raw?.startedAtUtc,
33
+ completedAtUtc: raw?.completedAtUtc,
34
+ initiatedByUser: raw?.initiatedByUser,
35
+ initiatorSessionId: raw?.initiatorSessionId,
36
+ provider: raw?.provider,
37
+ status: raw?.status,
38
+ expectedJobs: toNum(raw?.expectedJobs),
39
+ completedJobs: toNum(raw?.completedJobs),
40
+ errorJobs: toNum(raw?.errorJobs),
41
+ metadata: raw?.metadata,
42
+ lastUpdatedUtc: raw?.lastUpdatedUtc,
43
+ exists: raw?.exists,
42
44
  };
43
45
  return info.batchId ? info : null;
44
46
  }
47
+ function normalizeJobItemId(itemId) {
48
+ return (itemId ?? "").toString().toLowerCase();
49
+ }
50
+ function getJobKey(itemId, language) {
51
+ return `${normalizeJobItemId(itemId)}-${(language ?? "").toString()}`;
52
+ }
53
+ function getSocketPayloadMessage(payload) {
54
+ return payload?.message || payload?.error || "";
55
+ }
56
+ function normalizeJobRecord(raw) {
57
+ return {
58
+ id: typeof raw?.id === "number"
59
+ ? raw.id
60
+ : typeof raw?.Id === "number"
61
+ ? raw.Id
62
+ : undefined,
63
+ itemId: normalizeJobItemId(raw?.itemId ?? raw?.ItemId),
64
+ targetLanguage: raw?.targetLanguage ?? raw?.TargetLanguage ?? "",
65
+ sourceLanguage: raw?.sourceLanguage ?? raw?.SourceLanguage ?? "",
66
+ status: raw?.status ?? raw?.Status ?? "",
67
+ timestamp: raw?.timestamp ?? raw?.Timestamp ?? "",
68
+ hash: raw?.hash ?? raw?.Hash,
69
+ message: raw?.message ?? raw?.Message,
70
+ batchId: raw?.batchId ?? raw?.BatchId,
71
+ metadata: raw?.metadata ?? raw?.Metadata ?? null,
72
+ };
73
+ }
74
+ function getRetryJobKey(job) {
75
+ return `${job.id ?? "no-id"}|${normalizeJobItemId(job.itemId)}|${job.sourceLanguage}|${job.targetLanguage}`;
76
+ }
45
77
  export function BatchTranslationView({ batchId, onBack, }) {
46
78
  const editContext = useEditContext();
79
+ const router = useRouter();
80
+ const pathname = usePathname();
81
+ const searchParams = useSearchParams();
47
82
  const [batchJobs, setBatchJobs] = useState([]);
48
83
  const [batchInfo, setBatchInfo] = useState(null);
49
84
  const [providers, setProviders] = useState([]);
50
85
  const [isLoading, setIsLoading] = useState(false);
86
+ const [isRetryingAll, setIsRetryingAll] = useState(false);
87
+ const [retryingJobKeys, setRetryingJobKeys] = useState(new Set());
51
88
  const [translationProgress, setTranslationProgress] = useState(new Map());
52
89
  const [itemNames, setItemNames] = useState(new Map());
53
90
  const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
@@ -65,6 +102,90 @@ export function BatchTranslationView({ batchId, onBack, }) {
65
102
  const provider = providers.find((p) => p.name === serviceName);
66
103
  return provider?.displayName || serviceName;
67
104
  }, [providers]);
105
+ const openItemInEditor = useCallback((itemId, language) => {
106
+ editContext?.loadItem({
107
+ id: normalizeJobItemId(itemId),
108
+ language,
109
+ version: 0,
110
+ });
111
+ editContext?.switchWorkspace?.("editor");
112
+ }, [editContext]);
113
+ const openBatchInView = useCallback((nextBatchId) => {
114
+ const current = new URLSearchParams(Array.from(searchParams.entries()));
115
+ current.set("batchId", nextBatchId);
116
+ router.push(`${pathname}?${current.toString()}`, { scroll: false });
117
+ }, [pathname, router, searchParams]);
118
+ const buildRetryJobs = useCallback((jobs) => {
119
+ const uniqueJobs = new Map();
120
+ for (const job of jobs) {
121
+ if (job.status !== "Error")
122
+ continue;
123
+ const key = getRetryJobKey(job);
124
+ if (uniqueJobs.has(key))
125
+ continue;
126
+ uniqueJobs.set(key, {
127
+ sourceTranslationId: job.id,
128
+ itemId: normalizeJobItemId(job.itemId),
129
+ sourceLanguage: job.sourceLanguage,
130
+ targetLanguage: job.targetLanguage,
131
+ metadata: job.metadata ?? undefined,
132
+ });
133
+ }
134
+ return Array.from(uniqueJobs.values());
135
+ }, []);
136
+ const handleRetryJobs = useCallback(async (jobs, retryAll) => {
137
+ const retryJobs = buildRetryJobs(jobs);
138
+ if (retryJobs.length === 0) {
139
+ toast.error("No failed translations available to retry.");
140
+ return;
141
+ }
142
+ if (!editContext?.sessionId) {
143
+ toast.error("Cannot retry without an active session.");
144
+ return;
145
+ }
146
+ if (!batchInfo?.provider) {
147
+ toast.error("Retry provider could not be resolved for this batch.");
148
+ return;
149
+ }
150
+ if (retryAll) {
151
+ setIsRetryingAll(true);
152
+ }
153
+ else {
154
+ setRetryingJobKeys(new Set(jobs.map((job) => getRetryJobKey(job)).filter((key) => retryJobs.some((retryJob) => key ===
155
+ `${retryJob.sourceTranslationId ?? "no-id"}|${retryJob.itemId}|${retryJob.sourceLanguage}|${retryJob.targetLanguage}`))));
156
+ }
157
+ try {
158
+ const result = await retryBatchTranslation({
159
+ sessionId: editContext.sessionId,
160
+ sourceBatchId: batchId,
161
+ provider: batchInfo.provider,
162
+ jobs: retryJobs,
163
+ });
164
+ if (result.type !== "success" || !result.data) {
165
+ toast.error(result.details ||
166
+ result.summary ||
167
+ "Failed to start retry translations.");
168
+ return;
169
+ }
170
+ const response = result.data;
171
+ const startedCount = response.started?.length ?? 0;
172
+ const skippedCount = response.skipped?.length ?? 0;
173
+ if (startedCount === 0) {
174
+ toast.error(skippedCount > 0
175
+ ? response.skipped[0]?.error || "No retries were started."
176
+ : "No retries were started.");
177
+ return;
178
+ }
179
+ toast.success(retryAll
180
+ ? `Started retry batch with ${startedCount} translation${startedCount === 1 ? "" : "s"}.`
181
+ : "Started retry batch for failed translation.");
182
+ openBatchInView(response.retryBatchId);
183
+ }
184
+ finally {
185
+ setIsRetryingAll(false);
186
+ setRetryingJobKeys(new Set());
187
+ }
188
+ }, [batchId, batchInfo?.provider, buildRetryJobs, editContext?.sessionId, openBatchInView]);
68
189
  // Parse metadata for display
69
190
  const parsedMetadata = useMemo(() => {
70
191
  if (!batchInfo?.metadata)
@@ -97,43 +218,45 @@ export function BatchTranslationView({ batchId, onBack, }) {
97
218
  try {
98
219
  // Use efficient batch-specific endpoint
99
220
  const res = await listBatchTranslationJobs(batchId);
100
- const apiJobs = (res?.data ??
101
- res ??
102
- []);
221
+ const apiJobsRaw = (res?.data ?? res ?? []);
222
+ const apiJobs = apiJobsRaw.map(normalizeJobRecord);
103
223
  // Merge API response with existing state to prevent flickering
104
224
  setBatchJobs((prevJobs) => {
105
225
  // Create a map of existing jobs keyed by itemId-targetLanguage
106
226
  const existingMap = new Map();
107
227
  for (const job of prevJobs) {
108
- const key = `${job.itemId}-${job.targetLanguage}`;
228
+ const key = getJobKey(job.itemId, job.targetLanguage);
109
229
  existingMap.set(key, job);
110
230
  }
111
231
  // Merge API jobs into the map (API data takes precedence)
112
232
  for (const apiJob of apiJobs) {
113
- const key = `${apiJob.itemId}-${apiJob.targetLanguage}`;
233
+ const normalizedJob = normalizeJobRecord(apiJob);
234
+ const key = getJobKey(normalizedJob.itemId, normalizedJob.targetLanguage);
114
235
  const existingJob = existingMap.get(key);
115
236
  if (existingJob) {
116
237
  // Prefer API data if timestamp is newer or status changed
117
- const apiTimestamp = apiJob.timestamp
118
- ? new Date(apiJob.timestamp).getTime()
238
+ const apiTimestamp = normalizedJob.timestamp
239
+ ? new Date(normalizedJob.timestamp).getTime()
119
240
  : 0;
120
241
  const existingTimestamp = existingJob.timestamp
121
242
  ? new Date(existingJob.timestamp).getTime()
122
243
  : 0;
123
244
  if (apiTimestamp >= existingTimestamp ||
124
- apiJob.status !== existingJob.status) {
245
+ normalizedJob.status !== existingJob.status) {
125
246
  // Preserve sourceLanguage from API if available, otherwise keep existing
126
247
  existingMap.set(key, {
127
- ...apiJob,
128
- sourceLanguage: apiJob.sourceLanguage || existingJob.sourceLanguage || "",
248
+ ...normalizedJob,
249
+ sourceLanguage: normalizedJob.sourceLanguage ||
250
+ existingJob.sourceLanguage ||
251
+ "",
129
252
  });
130
253
  }
131
254
  }
132
255
  else {
133
256
  // New job from API - ensure sourceLanguage is set
134
257
  existingMap.set(key, {
135
- ...apiJob,
136
- sourceLanguage: apiJob.sourceLanguage || "",
258
+ ...normalizedJob,
259
+ sourceLanguage: normalizedJob.sourceLanguage || "",
137
260
  });
138
261
  }
139
262
  }
@@ -244,7 +367,9 @@ export function BatchTranslationView({ batchId, onBack, }) {
244
367
  if (message.payload.batchId === batchId) {
245
368
  // Update the specific job in our local state instead of refetching all jobs
246
369
  setBatchJobs((prevJobs) => {
247
- const jobIndex = prevJobs.findIndex((job) => job.itemId === message.payload.itemId &&
370
+ const normalizedItemId = normalizeJobItemId(message.payload.itemId);
371
+ const payloadMessage = getSocketPayloadMessage(message.payload);
372
+ const jobIndex = prevJobs.findIndex((job) => normalizeJobItemId(job.itemId) === normalizedItemId &&
248
373
  job.targetLanguage === message.payload.language);
249
374
  if (jobIndex >= 0) {
250
375
  // Update existing job
@@ -253,9 +378,10 @@ export function BatchTranslationView({ batchId, onBack, }) {
253
378
  if (existingJob) {
254
379
  updatedJobs[jobIndex] = {
255
380
  ...existingJob,
381
+ itemId: normalizedItemId,
256
382
  status: message.payload.status,
257
383
  timestamp: message.payload.timestamp || existingJob.timestamp,
258
- message: message.payload.message || existingJob.message,
384
+ message: payloadMessage || existingJob.message,
259
385
  // Preserve sourceLanguage from existing job if not provided in message
260
386
  sourceLanguage: message.payload.sourceLanguage ||
261
387
  existingJob.sourceLanguage ||
@@ -264,34 +390,32 @@ export function BatchTranslationView({ batchId, onBack, }) {
264
390
  }
265
391
  return updatedJobs;
266
392
  }
267
- else if (message.type === "translation-started") {
268
- // Add new job if it doesn't exist (shouldn't happen with batch subscriptions, but safe fallback)
393
+ else {
394
+ // Add new job if it doesn't exist yet so terminal updates are still visible.
269
395
  const newJob = {
270
- itemId: message.payload.itemId,
396
+ itemId: normalizedItemId,
271
397
  targetLanguage: message.payload.language,
272
398
  sourceLanguage: message.payload.sourceLanguage || "", // Keep this, but we'll try to populate from API
273
399
  status: message.payload.status,
274
400
  timestamp: message.payload.timestamp || new Date().toISOString(),
275
401
  batchId: batchId,
276
- message: message.payload.message || "",
402
+ message: payloadMessage,
277
403
  };
278
404
  return [...prevJobs, newJob];
279
405
  }
280
- return prevJobs;
281
406
  });
282
407
  }
283
408
  }
284
409
  if (message.type === "translation-progress") {
285
410
  // Only track progress for jobs in this batch
286
411
  if (message.payload.batchId === batchId) {
287
- const itemId = (message.payload.itemId ?? "")
288
- .toString()
289
- .toLowerCase();
412
+ const itemId = normalizeJobItemId(message.payload.itemId);
290
413
  const language = message.payload.language;
291
- const progressKey = `${itemId}-${language}`;
414
+ const progressKey = getJobKey(itemId, language);
292
415
  // Ensure a row exists; if not, add a placeholder "In Progress" job
293
416
  setBatchJobs((prevJobs) => {
294
- const idx = prevJobs.findIndex((j) => j.itemId === itemId && j.targetLanguage === language);
417
+ const idx = prevJobs.findIndex((j) => normalizeJobItemId(j.itemId) === itemId &&
418
+ j.targetLanguage === language);
295
419
  if (idx >= 0)
296
420
  return prevJobs;
297
421
  const newJob = {
@@ -498,7 +622,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
498
622
  let totalActualProgress = completedCount * 100;
499
623
  for (const job of batchJobs) {
500
624
  if (job.status === "In Progress") {
501
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
625
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
502
626
  const jobProgress = translationProgress.get(progressKey);
503
627
  totalActualProgress += jobProgress?.progress || 0;
504
628
  }
@@ -569,7 +693,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
569
693
  ? { backgroundColor: "#f6eeff", color: "#9650fb" }
570
694
  : undefined, children: effectiveStatus }))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 uppercase font-bold tracking-wider", children: "Provider" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1) truncate", children: getProviderDisplayName(batchInfo.provider) })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 uppercase font-bold tracking-wider", children: "Initiated By" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1) truncate", children: batchInfo.initiatedByUser || "System" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 uppercase font-bold tracking-wider", children: "Activity" }), _jsx("span", { className: "text-[10px] text-gray-2 leading-tight", children: batchInfo.completedAtUtc ? (_jsxs(_Fragment, { children: ["Done:", " ", new Date(batchInfo.completedAtUtc).toLocaleTimeString()] })) : batchInfo.startedAtUtc ? (_jsxs(_Fragment, { children: ["Started:", " ", new Date(batchInfo.startedAtUtc).toLocaleTimeString()] })) : batchInfo.lastUpdatedUtc ? (_jsxs(_Fragment, { children: ["Updated:", " ", new Date(batchInfo.lastUpdatedUtc).toLocaleTimeString()] })) : (_jsx(_Fragment, { children: "Created" })) })] })] })), _jsxs("div", { className: `flex items-center ${isMobile ? "w-full justify-between border-t border-gray-3 pt-3 mt-1" : "gap-3"}`, children: [_jsxs("div", { className: "text-xs md:text-sm text-gray-2", children: [_jsx("span", { className: "font-semibold text-(--color-dark)", children: progressSegments.completedCount }), "/", progressSegments.total, " completed", progressSegments.errorCount
571
695
  ? `, ${progressSegments.errorCount} errors`
572
- : ""] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadBatchJobs, disabled: isLoading, title: "Manual refresh - normally updates come through websocket subscriptions", className: "md:w-auto w-8 h-8 md:h-8 p-0 md:px-3", children: [_jsx(RefreshIcon, { className: `h-4 w-4 ${isLoading ? "animate-spin" : ""}` }), _jsx("span", { className: "hidden md:inline", children: "Refresh" })] })] })] }), parsedMetadata && (_jsxs("div", { className: `border-t border-gray-3 bg-gray-5 mt-4 rounded-b-lg ${isMobile ? "-mx-4 border-x-0" : ""}`, children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: `flex w-full cursor-pointer items-center justify-between ${isMobile ? "px-4 py-3" : "px-4 py-2.5"} text-left transition-colors hover:bg-gray-4`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InfoIcon, { className: "h-4 w-4 text-gray-2" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1)", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-gray-2", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-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-gray-3 bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(MemoizedJsonView, { data: parsedMetadata }) }) }))] })), _jsxs("div", { className: "mt-6 md:mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium text-(--color-dark)", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-2", children: [_jsx("span", { className: "font-semibold text-(--color-dark)", children: progressSegments.completedCount }), " ", "/ ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: `relative ${isMobile ? "h-5" : "h-4"} rounded-full overflow-hidden bg-gray-3`, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
696
+ : ""] }), _jsxs("div", { className: "flex items-center gap-2", children: [progressSegments.errorCount > 0 && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => void handleRetryJobs(batchJobs.filter((job) => job.status === "Error"), true), disabled: isRetryingAll || isLoading, className: "md:w-auto h-8 px-3", children: [_jsx(RefreshIcon, { className: `h-4 w-4 ${isRetryingAll ? "animate-spin" : ""}` }), _jsx("span", { children: "Retry All Failed" })] })), _jsxs(Button, { size: "sm", variant: "outline", onClick: loadBatchJobs, disabled: isLoading || isRetryingAll, title: "Manual refresh - normally updates come through websocket subscriptions", className: "md:w-auto w-8 h-8 md:h-8 p-0 md:px-3", children: [_jsx(RefreshIcon, { className: `h-4 w-4 ${isLoading ? "animate-spin" : ""}` }), _jsx("span", { className: "hidden md:inline", children: "Refresh" })] })] })] })] }), parsedMetadata && (_jsxs("div", { className: `border-t border-gray-3 bg-gray-5 mt-4 rounded-b-lg ${isMobile ? "-mx-4 border-x-0" : ""}`, children: [_jsxs("button", { onClick: () => setIsMetadataExpanded(!isMetadataExpanded), className: `flex w-full cursor-pointer items-center justify-between ${isMobile ? "px-4 py-3" : "px-4 py-2.5"} text-left transition-colors hover:bg-gray-4`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InfoIcon, { className: "h-4 w-4 text-gray-2" }), _jsx("span", { className: "text-xs font-medium text-(--color-gray-1)", children: "Batch Metadata" })] }), isMetadataExpanded ? (_jsx(ChevronUpIcon, { className: "h-4 w-4 text-gray-2", strokeWidth: 1 })) : (_jsx(ChevronDownIcon, { className: "h-4 w-4 text-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-gray-3 bg-background p-3 text-xs shadow-sm mt-2", children: _jsx(MemoizedJsonView, { data: parsedMetadata }) }) }))] })), _jsxs("div", { className: "mt-6 md:mt-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "text-sm font-medium text-(--color-dark)", children: "Overall Progress" }), _jsxs("span", { className: "text-sm text-gray-2", children: [_jsx("span", { className: "font-semibold text-(--color-dark)", children: progressSegments.completedCount }), " ", "/ ", progressSegments.total, " completed"] })] }), _jsxs("div", { className: `relative ${isMobile ? "h-5" : "h-4"} rounded-full overflow-hidden bg-gray-3`, children: [_jsxs("div", { className: "absolute inset-0 flex", children: [progressSegments.completed > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
573
697
  width: `${progressSegments.completed}%`,
574
698
  backgroundColor: "#8ae048",
575
699
  } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
@@ -589,7 +713,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
589
713
  }
590
714
  else if (job.status === "In Progress") {
591
715
  // Get progress for this specific job
592
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
716
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
593
717
  const jobProgress = translationProgress.get(progressKey);
594
718
  totalProgress += jobProgress?.progress || 0; // Use actual progress or 0
595
719
  }
@@ -600,13 +724,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
600
724
  const itemProgress = Math.round(totalProgress / jobs.length);
601
725
  const itemInProgress = jobs.some((j) => j.status === "In Progress");
602
726
  const itemError = jobs.some((j) => j.status === "Error");
727
+ const itemTerminalError = itemError && !itemInProgress;
728
+ const itemVisualProgress = itemTerminalError ? 100 : itemProgress;
603
729
  return (_jsxs("div", { className: `border border-gray-3 rounded-lg bg-background ${isMobile ? "p-4" : "p-6"} shadow-sm`, children: [_jsxs("div", { className: "mb-4", children: [_jsx("div", { className: "mb-3", children: (() => {
604
730
  const itemName = itemNames.get(itemId.toLowerCase());
605
- return itemName ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `${isMobile ? "text-sm" : "text-base"} font-bold text-(--color-dark) mb-1`, children: itemName }), _jsxs("div", { className: `text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: ["Item ID: ", itemId] })] })) : (_jsxs("div", { className: `text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: ["Item ID: ", itemId] }));
731
+ return itemName ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `${isMobile ? "text-sm" : "text-base"} font-bold text-(--color-dark) mb-1`, children: itemName }), _jsxs("div", { className: `flex items-center gap-1.5 text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: [_jsxs("span", { className: "break-all", children: ["Item ID: ", itemId] }), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(itemId, "en"), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: "Open item in English", className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] })] })) : (_jsxs("div", { className: `flex items-center gap-1.5 text-[10px] md:text-xs text-gray-2 font-mono break-all`, children: [_jsxs("span", { className: "break-all", children: ["Item ID: ", itemId] }), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(itemId, "en"), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: "Open item in English", className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] }));
606
732
  })() }), _jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: `${isMobile ? "text-xs" : "text-sm"} text-gray-2`, children: [itemCompleted, " / ", jobs.length, " languages"] }), itemInProgress && (_jsx("span", { className: `${isMobile ? "text-[10px]" : "text-xs"} px-2 py-0.5 md:px-2.5 md:py-1 rounded-full font-medium`, style: {
607
733
  backgroundColor: "#f6eeff",
608
734
  color: "#9650fb",
609
- }, children: "In Progress" })), itemError && (_jsx("span", { className: `${isMobile ? "text-[10px]" : "text-xs"} bg-red-100 text-red-600 px-2 py-0.5 md:px-2.5 md:py-1 rounded-full font-medium`, children: "Error" }))] }), _jsxs("span", { className: `${isMobile ? "text-xs" : "text-sm"} font-bold text-(--color-dark)`, children: [itemProgress, "%"] })] }), _jsx("div", { className: "relative", children: _jsx(Progress, { value: itemProgress, className: `${isMobile ? "h-2" : "h-3"} mb-2`, indicatorClassName: itemError ? "bg-destructive" : undefined, indicatorStyle: itemError
735
+ }, children: "In Progress" })), itemError && (_jsx("span", { className: `${isMobile ? "text-[10px]" : "text-xs"} bg-red-100 text-red-600 px-2 py-0.5 md:px-2.5 md:py-1 rounded-full font-medium`, children: "Error" }))] }), _jsxs("span", { className: `${isMobile ? "text-xs" : "text-sm"} font-bold text-(--color-dark)`, children: [itemVisualProgress, "%"] })] }), _jsx("div", { className: "relative", children: _jsx(Progress, { value: itemVisualProgress, className: `${isMobile ? "h-2" : "h-3"} mb-2`, indicatorClassName: itemError ? "bg-destructive" : undefined, indicatorStyle: itemError
610
736
  ? undefined
611
737
  : itemProgress === 100
612
738
  ? { backgroundColor: "#8ae048" }
@@ -629,9 +755,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
629
755
  return priorityA - priorityB;
630
756
  })
631
757
  .map((job) => {
632
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
758
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
633
759
  const progress = translationProgress.get(progressKey);
634
760
  const date = new Date(job.timestamp);
761
+ const displayMessage = job.message ||
762
+ (job.status === "Error"
763
+ ? progress?.message || "Translation failed"
764
+ : "");
765
+ const jobRetryKey = getRetryJobKey(job);
766
+ const isRetryingJob = isRetryingAll || retryingJobKeys.has(jobRetryKey);
635
767
  return (_jsxs("div", { className: "rounded-md border border-gray-4 bg-gray-5 p-3", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-bold text-(--color-dark)", children: job.targetLanguage }), _jsx("span", { className: "text-gray-2", children: "\u2190" }), _jsx("span", { className: "text-xs text-(--color-gray-1)", children: job.sourceLanguage || "—" })] }), _jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-bold ${job.status === "Completed"
636
768
  ? "bg-green-100 text-green-700"
637
769
  : job.status === "In Progress"
@@ -643,12 +775,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
643
775
  backgroundColor: "#f6eeff",
644
776
  color: "#9650fb",
645
777
  }
646
- : undefined, children: job.status })] }), job.message && (_jsx("div", { className: "text-xs text-gray-2 break-all whitespace-normal mb-2 leading-relaxed", children: job.message })), job.status === "In Progress" && progress && (_jsxs("div", { className: "mb-2", children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 truncate pr-2", children: progress.message }), _jsxs("span", { className: "text-[10px] font-bold text-(--color-gray-1) shrink-0", children: [Math.round(progress.progress), "%"] })] }), _jsx(Progress, { value: progress.progress, className: "h-2", indicatorStyle: {
778
+ : undefined, children: job.status })] }), displayMessage && (_jsxs("div", { className: "mb-2 flex items-start gap-1.5", children: [_jsx("div", { className: "min-w-0 flex-1 text-xs text-gray-2 break-words [overflow-wrap:anywhere] whitespace-normal leading-relaxed line-clamp-3", title: displayMessage, children: displayMessage }), _jsx(CopyButton, { textToCopy: displayMessage, iconOnly: true, className: "mt-0.5 shrink-0 text-gray-2 hover:text-(--color-gray-1)" })] })), job.status === "In Progress" && progress && (_jsxs("div", { className: "mb-2", children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("span", { className: "text-[10px] text-gray-2 truncate pr-2", children: progress.message }), _jsxs("span", { className: "text-[10px] font-bold text-(--color-gray-1) shrink-0", children: [Math.round(progress.progress), "%"] })] }), _jsx(Progress, { value: progress.progress, className: "h-2", indicatorStyle: {
647
779
  backgroundColor: "#9650fb",
648
- }, showValue: false })] })), _jsx("div", { className: "text-[10px] text-gray-2 text-right mt-1", children: date.toLocaleTimeString([], {
780
+ }, showValue: false })] })), job.status === "Error" && (_jsx("div", { className: "mb-2", children: _jsx(Progress, { value: 100, className: "h-2", indicatorClassName: "bg-destructive", showValue: false }) })), _jsx("div", { className: "text-[10px] text-gray-2 text-right mt-1", children: date.toLocaleTimeString([], {
649
781
  hour: "2-digit",
650
782
  minute: "2-digit",
651
- }) })] }, job.targetLanguage));
783
+ }) }), _jsx("div", { className: "mt-2 flex justify-end", children: _jsxs("div", { className: "flex items-center gap-2", children: [job.status === "Error" && (_jsx(SimpleIconButton, { onClick: () => void handleRetryJobs([job], false), icon: _jsx(RefreshIcon, { className: `h-3.5 w-3.5 ${isRetryingJob ? "animate-spin" : ""}`, strokeWidth: 1 }), label: "Retry translation as new batch", disabled: isRetryingJob, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(job.itemId, job.targetLanguage), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: `Open item in ${job.targetLanguage}`, disabled: isRetryingAll, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] }) })] }, job.targetLanguage));
652
784
  }) })) : (_jsx(SimpleTable, { columns: [
653
785
  {
654
786
  header: "Language",
@@ -659,8 +791,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
659
791
  header: "Status",
660
792
  className: "w-32", // Fixed width for status column
661
793
  body: (job) => {
662
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
794
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
663
795
  const progress = translationProgress.get(progressKey);
796
+ const displayMessage = job.message ||
797
+ (job.status === "Error"
798
+ ? progress?.message || "Translation failed"
799
+ : "");
664
800
  return (_jsxs("div", { children: [_jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium ${job.status === "Completed"
665
801
  ? "bg-green-100 text-green-700"
666
802
  : job.status === "In Progress"
@@ -676,7 +812,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
676
812
  progress &&
677
813
  Math.round(progress.progress || 0) === 0 && (_jsx("div", { className: "mt-1 text-xs text-gray-2", children: "Version created" })), progress && job.status === "In Progress" && (_jsxs("div", { className: "mt-1.5", children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("span", { className: "text-xs text-gray-2 truncate", "data-testid": "translation-progress-text", children: progress.message }), _jsxs("span", { className: "text-xs font-medium text-(--color-gray-1) ml-1", children: [Math.round(progress.progress), "%"] })] }), _jsx(Progress, { value: progress.progress, className: "h-2", indicatorStyle: {
678
814
  backgroundColor: "#9650fb",
679
- }, showValue: false, "data-testid": "translation-progress-bar" })] }))] }));
815
+ }, showValue: false, "data-testid": "translation-progress-bar" })] })), job.status === "Error" && (_jsx("div", { className: "mt-1.5", children: _jsx(Progress, { value: 100, className: "h-2", indicatorClassName: "bg-destructive", showValue: false, "data-testid": "translation-progress-bar" }) }))] }));
680
816
  },
681
817
  },
682
818
  {
@@ -686,8 +822,23 @@ export function BatchTranslationView({ batchId, onBack, }) {
686
822
  },
687
823
  {
688
824
  header: "Message",
689
- className: "w-auto max-w-[300px]", // Use max-width instead of fixed width
690
- body: (job) => (_jsx("div", { className: "text-sm text-gray-2 break-all whitespace-normal leading-relaxed", children: job.message || "—" })),
825
+ className: "w-full min-w-0",
826
+ body: (job) => {
827
+ const message = job.message ||
828
+ (job.status === "Error"
829
+ ? "Translation failed"
830
+ : "—");
831
+ return (_jsxs("div", { className: "flex items-start gap-1.5", children: [_jsx("div", { className: "min-w-0 w-full flex-1 text-sm text-gray-2 break-words [overflow-wrap:anywhere] whitespace-normal leading-relaxed line-clamp-3", title: message, children: message }), message !== "—" && (_jsx(CopyButton, { textToCopy: message, iconOnly: true, className: "mt-0.5 shrink-0 text-gray-2 hover:text-(--color-gray-1)" }))] }));
832
+ },
833
+ },
834
+ {
835
+ header: "Actions",
836
+ className: "w-24",
837
+ body: (job) => {
838
+ const jobRetryKey = getRetryJobKey(job);
839
+ const isRetryingJob = isRetryingAll || retryingJobKeys.has(jobRetryKey);
840
+ return (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [job.status === "Error" && (_jsx(SimpleIconButton, { onClick: () => void handleRetryJobs([job], false), icon: _jsx(RefreshIcon, { className: `h-3.5 w-3.5 ${isRetryingJob ? "animate-spin" : ""}`, strokeWidth: 1 }), label: "Retry translation as new batch", disabled: isRetryingJob, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })), _jsx(SimpleIconButton, { onClick: () => openItemInEditor(job.itemId, job.targetLanguage), icon: _jsx(ExternalLinkIcon, { className: "h-3.5 w-3.5", strokeWidth: 1 }), label: `Open item in ${job.targetLanguage}`, disabled: isRetryingAll, className: "p-0! text-gray-2 hover:text-(--color-gray-1)" })] }));
841
+ },
691
842
  },
692
843
  {
693
844
  header: "Updated",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parhelia/localization",
3
- "version": "0.1.12485",
3
+ "version": "0.1.12515",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"