@parhelia/localization 0.1.12496 → 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;AAiCD,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
  });
@@ -41,12 +44,47 @@ function normalizeBatchInfo(raw) {
41
44
  };
42
45
  return info.batchId ? info : null;
43
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
+ }
44
77
  export function BatchTranslationView({ batchId, onBack, }) {
45
78
  const editContext = useEditContext();
79
+ const router = useRouter();
80
+ const pathname = usePathname();
81
+ const searchParams = useSearchParams();
46
82
  const [batchJobs, setBatchJobs] = useState([]);
47
83
  const [batchInfo, setBatchInfo] = useState(null);
48
84
  const [providers, setProviders] = useState([]);
49
85
  const [isLoading, setIsLoading] = useState(false);
86
+ const [isRetryingAll, setIsRetryingAll] = useState(false);
87
+ const [retryingJobKeys, setRetryingJobKeys] = useState(new Set());
50
88
  const [translationProgress, setTranslationProgress] = useState(new Map());
51
89
  const [itemNames, setItemNames] = useState(new Map());
52
90
  const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
@@ -64,6 +102,90 @@ export function BatchTranslationView({ batchId, onBack, }) {
64
102
  const provider = providers.find((p) => p.name === serviceName);
65
103
  return provider?.displayName || serviceName;
66
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]);
67
189
  // Parse metadata for display
68
190
  const parsedMetadata = useMemo(() => {
69
191
  if (!batchInfo?.metadata)
@@ -96,43 +218,45 @@ export function BatchTranslationView({ batchId, onBack, }) {
96
218
  try {
97
219
  // Use efficient batch-specific endpoint
98
220
  const res = await listBatchTranslationJobs(batchId);
99
- const apiJobs = (res?.data ??
100
- res ??
101
- []);
221
+ const apiJobsRaw = (res?.data ?? res ?? []);
222
+ const apiJobs = apiJobsRaw.map(normalizeJobRecord);
102
223
  // Merge API response with existing state to prevent flickering
103
224
  setBatchJobs((prevJobs) => {
104
225
  // Create a map of existing jobs keyed by itemId-targetLanguage
105
226
  const existingMap = new Map();
106
227
  for (const job of prevJobs) {
107
- const key = `${job.itemId}-${job.targetLanguage}`;
228
+ const key = getJobKey(job.itemId, job.targetLanguage);
108
229
  existingMap.set(key, job);
109
230
  }
110
231
  // Merge API jobs into the map (API data takes precedence)
111
232
  for (const apiJob of apiJobs) {
112
- const key = `${apiJob.itemId}-${apiJob.targetLanguage}`;
233
+ const normalizedJob = normalizeJobRecord(apiJob);
234
+ const key = getJobKey(normalizedJob.itemId, normalizedJob.targetLanguage);
113
235
  const existingJob = existingMap.get(key);
114
236
  if (existingJob) {
115
237
  // Prefer API data if timestamp is newer or status changed
116
- const apiTimestamp = apiJob.timestamp
117
- ? new Date(apiJob.timestamp).getTime()
238
+ const apiTimestamp = normalizedJob.timestamp
239
+ ? new Date(normalizedJob.timestamp).getTime()
118
240
  : 0;
119
241
  const existingTimestamp = existingJob.timestamp
120
242
  ? new Date(existingJob.timestamp).getTime()
121
243
  : 0;
122
244
  if (apiTimestamp >= existingTimestamp ||
123
- apiJob.status !== existingJob.status) {
245
+ normalizedJob.status !== existingJob.status) {
124
246
  // Preserve sourceLanguage from API if available, otherwise keep existing
125
247
  existingMap.set(key, {
126
- ...apiJob,
127
- sourceLanguage: apiJob.sourceLanguage || existingJob.sourceLanguage || "",
248
+ ...normalizedJob,
249
+ sourceLanguage: normalizedJob.sourceLanguage ||
250
+ existingJob.sourceLanguage ||
251
+ "",
128
252
  });
129
253
  }
130
254
  }
131
255
  else {
132
256
  // New job from API - ensure sourceLanguage is set
133
257
  existingMap.set(key, {
134
- ...apiJob,
135
- sourceLanguage: apiJob.sourceLanguage || "",
258
+ ...normalizedJob,
259
+ sourceLanguage: normalizedJob.sourceLanguage || "",
136
260
  });
137
261
  }
138
262
  }
@@ -243,7 +367,9 @@ export function BatchTranslationView({ batchId, onBack, }) {
243
367
  if (message.payload.batchId === batchId) {
244
368
  // Update the specific job in our local state instead of refetching all jobs
245
369
  setBatchJobs((prevJobs) => {
246
- 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 &&
247
373
  job.targetLanguage === message.payload.language);
248
374
  if (jobIndex >= 0) {
249
375
  // Update existing job
@@ -252,9 +378,10 @@ export function BatchTranslationView({ batchId, onBack, }) {
252
378
  if (existingJob) {
253
379
  updatedJobs[jobIndex] = {
254
380
  ...existingJob,
381
+ itemId: normalizedItemId,
255
382
  status: message.payload.status,
256
383
  timestamp: message.payload.timestamp || existingJob.timestamp,
257
- message: message.payload.message || existingJob.message,
384
+ message: payloadMessage || existingJob.message,
258
385
  // Preserve sourceLanguage from existing job if not provided in message
259
386
  sourceLanguage: message.payload.sourceLanguage ||
260
387
  existingJob.sourceLanguage ||
@@ -263,34 +390,32 @@ export function BatchTranslationView({ batchId, onBack, }) {
263
390
  }
264
391
  return updatedJobs;
265
392
  }
266
- else if (message.type === "translation-started") {
267
- // 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.
268
395
  const newJob = {
269
- itemId: message.payload.itemId,
396
+ itemId: normalizedItemId,
270
397
  targetLanguage: message.payload.language,
271
398
  sourceLanguage: message.payload.sourceLanguage || "", // Keep this, but we'll try to populate from API
272
399
  status: message.payload.status,
273
400
  timestamp: message.payload.timestamp || new Date().toISOString(),
274
401
  batchId: batchId,
275
- message: message.payload.message || "",
402
+ message: payloadMessage,
276
403
  };
277
404
  return [...prevJobs, newJob];
278
405
  }
279
- return prevJobs;
280
406
  });
281
407
  }
282
408
  }
283
409
  if (message.type === "translation-progress") {
284
410
  // Only track progress for jobs in this batch
285
411
  if (message.payload.batchId === batchId) {
286
- const itemId = (message.payload.itemId ?? "")
287
- .toString()
288
- .toLowerCase();
412
+ const itemId = normalizeJobItemId(message.payload.itemId);
289
413
  const language = message.payload.language;
290
- const progressKey = `${itemId}-${language}`;
414
+ const progressKey = getJobKey(itemId, language);
291
415
  // Ensure a row exists; if not, add a placeholder "In Progress" job
292
416
  setBatchJobs((prevJobs) => {
293
- 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);
294
419
  if (idx >= 0)
295
420
  return prevJobs;
296
421
  const newJob = {
@@ -497,7 +622,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
497
622
  let totalActualProgress = completedCount * 100;
498
623
  for (const job of batchJobs) {
499
624
  if (job.status === "In Progress") {
500
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
625
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
501
626
  const jobProgress = translationProgress.get(progressKey);
502
627
  totalActualProgress += jobProgress?.progress || 0;
503
628
  }
@@ -568,7 +693,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
568
693
  ? { backgroundColor: "#f6eeff", color: "#9650fb" }
569
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
570
695
  ? `, ${progressSegments.errorCount} errors`
571
- : ""] }), _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: {
572
697
  width: `${progressSegments.completed}%`,
573
698
  backgroundColor: "#8ae048",
574
699
  } })), progressSegments.inProgress > 0 && (_jsx("div", { className: "h-full transition-all duration-300", style: {
@@ -588,7 +713,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
588
713
  }
589
714
  else if (job.status === "In Progress") {
590
715
  // Get progress for this specific job
591
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
716
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
592
717
  const jobProgress = translationProgress.get(progressKey);
593
718
  totalProgress += jobProgress?.progress || 0; // Use actual progress or 0
594
719
  }
@@ -599,13 +724,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
599
724
  const itemProgress = Math.round(totalProgress / jobs.length);
600
725
  const itemInProgress = jobs.some((j) => j.status === "In Progress");
601
726
  const itemError = jobs.some((j) => j.status === "Error");
727
+ const itemTerminalError = itemError && !itemInProgress;
728
+ const itemVisualProgress = itemTerminalError ? 100 : itemProgress;
602
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: (() => {
603
730
  const itemName = itemNames.get(itemId.toLowerCase());
604
- 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)" })] }));
605
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: {
606
733
  backgroundColor: "#f6eeff",
607
734
  color: "#9650fb",
608
- }, 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
609
736
  ? undefined
610
737
  : itemProgress === 100
611
738
  ? { backgroundColor: "#8ae048" }
@@ -628,9 +755,15 @@ export function BatchTranslationView({ batchId, onBack, }) {
628
755
  return priorityA - priorityB;
629
756
  })
630
757
  .map((job) => {
631
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
758
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
632
759
  const progress = translationProgress.get(progressKey);
633
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);
634
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"
635
768
  ? "bg-green-100 text-green-700"
636
769
  : job.status === "In Progress"
@@ -642,12 +775,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
642
775
  backgroundColor: "#f6eeff",
643
776
  color: "#9650fb",
644
777
  }
645
- : 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: {
646
779
  backgroundColor: "#9650fb",
647
- }, 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([], {
648
781
  hour: "2-digit",
649
782
  minute: "2-digit",
650
- }) })] }, 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));
651
784
  }) })) : (_jsx(SimpleTable, { columns: [
652
785
  {
653
786
  header: "Language",
@@ -658,8 +791,12 @@ export function BatchTranslationView({ batchId, onBack, }) {
658
791
  header: "Status",
659
792
  className: "w-32", // Fixed width for status column
660
793
  body: (job) => {
661
- const progressKey = `${job.itemId}-${job.targetLanguage}`;
794
+ const progressKey = getJobKey(job.itemId, job.targetLanguage);
662
795
  const progress = translationProgress.get(progressKey);
796
+ const displayMessage = job.message ||
797
+ (job.status === "Error"
798
+ ? progress?.message || "Translation failed"
799
+ : "");
663
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"
664
801
  ? "bg-green-100 text-green-700"
665
802
  : job.status === "In Progress"
@@ -675,7 +812,7 @@ export function BatchTranslationView({ batchId, onBack, }) {
675
812
  progress &&
676
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: {
677
814
  backgroundColor: "#9650fb",
678
- }, 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" }) }))] }));
679
816
  },
680
817
  },
681
818
  {
@@ -685,8 +822,23 @@ export function BatchTranslationView({ batchId, onBack, }) {
685
822
  },
686
823
  {
687
824
  header: "Message",
688
- className: "w-auto max-w-[300px]", // Use max-width instead of fixed width
689
- 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
+ },
690
842
  },
691
843
  {
692
844
  header: "Updated",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parhelia/localization",
3
- "version": "0.1.12496",
3
+ "version": "0.1.12515",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"