@parhelia/localization 0.4.12800 → 0.4.12825

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"LocalizeItemCommand.d.ts","sourceRoot":"","sources":["../src/LocalizeItemCommand.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EACP,cAAc,EACd,WAAW,EACX,eAAe,EACf,QAAQ,EACT,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAEL,uBAAuB,EACxB,MAAM,eAAe,CAAC;AAQvB,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAClD,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GACpC,cAAc,CAAC,uBAAuB,CAAC,CAAC;AAC1C,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEnE,wBAAsB,sBAAsB,CAAC,EAC3C,WAAW,EACX,UAAmC,EACnC,KAAK,GACN,EAAE;IACD,WAAW,EAAE,eAAe,CAAC;IAC7B,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC3C,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,2CAiCA;AAED,eAAO,MAAM,mBAAmB,EAAE,mBA6BjC,CAAC"}
1
+ {"version":3,"file":"LocalizeItemCommand.d.ts","sourceRoot":"","sources":["../src/LocalizeItemCommand.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EACP,cAAc,EACd,WAAW,EACX,eAAe,EACf,QAAQ,EACT,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAEL,uBAAuB,EACxB,MAAM,eAAe,CAAC;AAQvB,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAClD,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GACpC,cAAc,CAAC,uBAAuB,CAAC,CAAC;AAC1C,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEnE,wBAAsB,sBAAsB,CAAC,EAC3C,WAAW,EACX,UAAmC,EACnC,KAAK,GACN,EAAE;IACD,WAAW,EAAE,eAAe,CAAC;IAC7B,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC3C,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,2CAiCA;AAED,eAAO,MAAM,mBAAmB,EAAE,mBAkCjC,CAAC"}
@@ -39,6 +39,11 @@ export const localizeItemCommand = {
39
39
  if (!context.data?.items || context.data.items.length === 0) {
40
40
  return true;
41
41
  }
42
+ // Translation writes the target versions, so a selection where the user
43
+ // cannot write any item has nothing the wizard could do.
44
+ if (context.data.items.every((item) => item.canWriteItem === false)) {
45
+ return true;
46
+ }
42
47
  // Check if multi-item is disabled and multiple items are selected
43
48
  const multiItemEnabled = context.editContext?.configuration?.localization?.multiItem !==
44
49
  false;
@@ -1 +1 @@
1
- {"version":3,"file":"useTranslationWizard.d.ts","sourceRoot":"","sources":["../../src/hooks/useTranslationWizard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAUhF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,uBAAuB;;;EAsGlE"}
1
+ {"version":3,"file":"useTranslationWizard.d.ts","sourceRoot":"","sources":["../../src/hooks/useTranslationWizard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAUhF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,uBAAuB;;;EAuGlE"}
@@ -45,6 +45,7 @@ export function useTranslationWizard(props) {
45
45
  name: language.name,
46
46
  languageCode: language.languageCode,
47
47
  items: [],
48
+ canWrite: language.canWrite,
48
49
  };
49
50
  if (language.versions > 0 && itemDescriptors[itemIndex]) {
50
51
  languageData.items.push(itemDescriptors[itemIndex]);
@@ -1 +1 @@
1
- {"version":3,"file":"ItemSelectionTree.d.ts","sourceRoot":"","sources":["../../src/steps/ItemSelectionTree.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EAMrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAGrD,KAAK,sBAAsB,GAAG;IAC5B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC/C,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B,CAAC;AAsEF,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,OAAO,EACP,WAAW,EACX,QAAe,EACf,sBAAsB,EACtB,MAAY,GACb,EAAE,sBAAsB,+BAiWxB"}
1
+ {"version":3,"file":"ItemSelectionTree.d.ts","sourceRoot":"","sources":["../../src/steps/ItemSelectionTree.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EAMrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAGrD,KAAK,sBAAsB,GAAG;IAC5B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC/C,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B,CAAC;AAuFF,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,OAAO,EACP,WAAW,EACX,QAAe,EACf,sBAAsB,EACtB,MAAY,GACb,EAAE,sBAAsB,+BA0WxB"}
@@ -2,7 +2,20 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { ItemCollectionEditor, } from "@parhelia/core";
4
4
  import { streamSubitemCount } from "../api/discovery";
5
- const SITECORE_ROOT = "11111111-1111-1111-1111-111111111111";
5
+ const TRANSLATION_ROOT_ITEM_IDS = [
6
+ "0de95ae4-41ab-4d01-9eb0-67441b7c2450", // Content
7
+ "b701850a-cb8a-4943-b2bc-dddb1238c103", // Forms
8
+ "3d6658d8-a0bf-4e75-b3e2-d050fabcf4e1", // Media Library
9
+ ];
10
+ const NO_WRITE_ACCESS_TITLE = "You don't have write access to this item, so it can't be translated.";
11
+ /**
12
+ * Only items the user may write can receive a translation (the backend
13
+ * rejects the rest at start time). Sources that don't report the flag are
14
+ * treated as allowed and left to the backend check.
15
+ */
16
+ function isWritable(item) {
17
+ return item.canWrite !== false && item.canWriteItem !== false;
18
+ }
6
19
  function fullItemFromDescriptor(descriptor) {
7
20
  return {
8
21
  id: descriptor.id,
@@ -24,7 +37,7 @@ function fullItemFromDescriptor(descriptor) {
24
37
  thumbnail: "",
25
38
  path: descriptor.path ?? "",
26
39
  fields: [],
27
- canWriteItem: false,
40
+ canWriteItem: descriptor.canWrite !== false,
28
41
  canWriteLanguage: false,
29
42
  canWriteWorkflow: false,
30
43
  canRename: false,
@@ -68,7 +81,11 @@ export function ItemSelectionTree({ data, setData, editContext, isActive = true,
68
81
  const seed = data.discoveredItems && data.discoveredItems.length > 0
69
82
  ? data.discoveredItems
70
83
  : data.items;
71
- return seed.map((item) => fullItemToWithSubtree(item, !!data.includeSubitems));
84
+ // The command can be invoked on a read-only item (context menu, sidebar);
85
+ // such seeds are dropped so the list never holds an untranslatable item.
86
+ return seed
87
+ .filter(isWritable)
88
+ .map((item) => fullItemToWithSubtree(item, !!data.includeSubitems));
72
89
  // First mount only — once the user edits, wizard data is downstream.
73
90
  // eslint-disable-next-line react-hooks/exhaustive-deps
74
91
  }, []);
@@ -254,6 +271,7 @@ export function ItemSelectionTree({ data, setData, editContext, isActive = true,
254
271
  icon: anyNode.icon,
255
272
  templateId: anyNode.templateId,
256
273
  hasChildren: anyNode.hasChildren,
274
+ canWrite: anyNode.canWrite,
257
275
  }));
258
276
  }, [language]);
259
277
  const handleAddToList = useCallback(async (itemsToAdd) => {
@@ -265,6 +283,8 @@ export function ItemSelectionTree({ data, setData, editContext, isActive = true,
265
283
  for (const node of source) {
266
284
  if (!node.id || existing.has(node.id))
267
285
  continue;
286
+ if (!isWritable(node))
287
+ continue;
268
288
  const nodeWithIcon = node;
269
289
  cacheTreeNode(node);
270
290
  next.push({
@@ -290,6 +310,8 @@ export function ItemSelectionTree({ data, setData, editContext, isActive = true,
290
310
  const id = item.id;
291
311
  if (!id || items.some((i) => i.descriptor.id === id))
292
312
  return;
313
+ if (!isWritable(item))
314
+ return;
293
315
  cacheTreeNode(item);
294
316
  const next = [
295
317
  ...items,
@@ -328,5 +350,5 @@ export function ItemSelectionTree({ data, setData, editContext, isActive = true,
328
350
  setItems(next);
329
351
  commitToWizard(next);
330
352
  }, [items, commitToWizard]);
331
- return (_jsx(ItemCollectionEditor, { items: items, selectedInTree: selectedInTree, onSelectedInTreeChange: setSelectedInTree, selectedFromList: selectedFromList, onSelectedFromListChange: setSelectedFromList, onAddToList: handleAddToList, onRemoveFromList: handleRemoveFromList, onAddItem: handleAddItem, onRemoveItem: handleRemoveItem, onToggleSubitems: handleToggleSubitems, language: language, rootItemIds: [SITECORE_ROOT], selectedItemsLabel: "Items to Translate", emptyMessage: "No items selected", emptyHint: "Browse or search to add items", height: height, subitemCounts: subitemCounts, expandIdPath: expandIdPath, localStorageKey: "translation-wizard.itemCollectionSplitter", treeSearchContainerClassName: "border-b-0 p-0 pb-3", frameless: true }));
353
+ return (_jsx(ItemCollectionEditor, { items: items, selectedInTree: selectedInTree, onSelectedInTreeChange: setSelectedInTree, selectedFromList: selectedFromList, onSelectedFromListChange: setSelectedFromList, onAddToList: handleAddToList, onRemoveFromList: handleRemoveFromList, onAddItem: handleAddItem, onRemoveItem: handleRemoveItem, onToggleSubitems: handleToggleSubitems, language: language, rootItemIds: TRANSLATION_ROOT_ITEM_IDS, hideRootNodes: false, isItemSelectable: isWritable, unselectableItemTitle: NO_WRITE_ACCESS_TITLE, selectedItemsLabel: "Items to Translate", emptyMessage: "No items selected", emptyHint: "Browse or search to add items", height: height, subitemCounts: subitemCounts, expandIdPath: expandIdPath, localStorageKey: "translation-wizard.itemCollectionSplitter", treeSearchContainerClassName: "border-b-0 p-0 pb-3", frameless: true }));
332
354
  }
@@ -228,5 +228,5 @@ export function ProviderStep({ isActive = true, data, setData, onStepCompleted,
228
228
  }), options: data.translationProviders.map((provider) => ({
229
229
  value: provider.name,
230
230
  label: provider.displayName || provider.name,
231
- })), placeholder: "Select a provider...", size: "sm", className: "mt-2 w-full", "data-testid": "translation-provider-select" }), isCreateLanguageVersionsOnlyProvider && (_jsx("p", { className: "text-muted-foreground mt-2 text-xs", children: "\"Create Language Versions Only\" will create new language versions without automatic translation." }))] }), supportsPromptCustomization && (_jsxs("div", { children: [_jsxs(Label, { className: "flex cursor-pointer items-start gap-2.5", children: [_jsx("input", { type: "checkbox", checked: enableCustomPrompt, onChange: (e) => handleCustomPromptToggle(e.target.checked), className: "mt-0.5 h-3.5 w-3.5 rounded border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)]", "data-testid": "enable-custom-prompt-checkbox" }), _jsxs("div", { className: "min-w-0", children: [_jsx("span", { className: "block text-[13px] font-medium text-neutral-grey-100", children: "Customize translation prompt (optional)" }), _jsx("span", { className: "text-muted-foreground mt-0.5 block text-xs", children: "Edit the provider prompt before starting translation." })] })] }), enableCustomPrompt && (_jsxs("div", { className: "mt-4 space-y-2", children: [_jsx(Textarea, { id: "custom-prompt-textarea", value: customPrompt, onChange: (e) => setCustomPrompt(e.target.value), className: "text-xs", rows: 8, placeholder: "Enter prompt instructions here...", "data-testid": "custom-prompt-textarea" }), hasDefaultPrompt && (_jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleResetPrompt, disabled: customPrompt === (defaultPrompt || ""), className: "gap-1.5", title: "Reset to the default prompt", "data-testid": "reset-custom-prompt-button", children: [_jsx(ResetIcon, { className: "size-3.5", strokeWidth: 1, "aria-hidden": "true" }), "Reset to default"] }))] }))] }))] })] }) }) }));
231
+ })), placeholder: "Select a provider...", size: "sm", className: "mt-2 w-full", "data-testid": "translation-provider-select" }), isCreateLanguageVersionsOnlyProvider && (_jsx("p", { className: "text-muted-foreground mt-2 text-xs", children: "\"Create Language Versions Only\" will create new language versions without automatic translation." }))] }), supportsPromptCustomization && (_jsxs("div", { children: [_jsxs(Label, { className: "flex cursor-pointer items-start gap-2.5", children: [_jsx("input", { type: "checkbox", checked: enableCustomPrompt, onChange: (e) => handleCustomPromptToggle(e.target.checked), className: "mt-0.5 h-3.5 w-3.5 rounded border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)]", "data-testid": "enable-custom-prompt-checkbox" }), _jsxs("div", { className: "min-w-0", children: [_jsx("span", { className: "block text-[13px] font-medium text-neutral-grey-100", children: "Customize translation prompt (optional)" }), _jsx("span", { className: "text-muted-foreground mt-0.5 block text-xs", children: "Edit the provider prompt before starting translation." })] })] }), enableCustomPrompt && (_jsxs("div", { className: "mt-4 space-y-2", children: [_jsx(Textarea, { id: "custom-prompt-textarea", value: customPrompt, onChange: (e) => setCustomPrompt(e.target.value), className: "text-xs", rows: 8, placeholder: "Add instructions for the translator, e.g. tone of voice, terms to keep untranslated, or formality.", "data-testid": "custom-prompt-textarea" }), hasDefaultPrompt && (_jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleResetPrompt, disabled: customPrompt === (defaultPrompt || ""), className: "gap-1.5", title: "Reset to the default prompt", "data-testid": "reset-custom-prompt-button", children: [_jsx(ResetIcon, { className: "size-3.5", strokeWidth: 1, "aria-hidden": "true" }), "Reset to default"] }))] }))] }))] })] }) }) }));
232
232
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ServiceLanguageSelectionStep.d.ts","sourceRoot":"","sources":["../../src/steps/ServiceLanguageSelectionStep.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAGtE;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,QAAe,EACf,IAAI,EACJ,OAAO,EACP,eAAe,EACf,WAAW,GACZ,EAAE,oBAAoB,+BA+RtB"}
1
+ {"version":3,"file":"ServiceLanguageSelectionStep.d.ts","sourceRoot":"","sources":["../../src/steps/ServiceLanguageSelectionStep.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAGtE;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,QAAe,EACf,IAAI,EACJ,OAAO,EACP,eAAe,EACf,WAAW,GACZ,EAAE,oBAAoB,+BAkUtB"}
@@ -82,6 +82,12 @@ export function ServiceLanguageSelectionStep({ isActive = true, data, setData, o
82
82
  data.languageData.get(sourceLanguage)?.name ||
83
83
  siteLanguageMap.get(sourceLanguage)?.name ||
84
84
  sourceLanguage;
85
+ // Language write ACL (language:write). Every source reports it for
86
+ // the same language item, so the first defined value wins; unknown
87
+ // is treated as allowed and left to the backend check.
88
+ const canWrite = editContextLang?.canWrite ??
89
+ languageDataLang?.canWrite ??
90
+ siteLanguage?.canWrite;
85
91
  return {
86
92
  code,
87
93
  name: editContextLang?.name ||
@@ -91,6 +97,7 @@ export function ServiceLanguageSelectionStep({ isActive = true, data, setData, o
91
97
  icon: editContextLang?.icon || siteLanguage?.icon,
92
98
  sourceLanguage,
93
99
  sourceLanguageName,
100
+ canWrite: canWrite !== false,
94
101
  };
95
102
  })
96
103
  .filter((lang) => lang.sourceLanguage !== lang.code);
@@ -114,7 +121,8 @@ export function ServiceLanguageSelectionStep({ isActive = true, data, setData, o
114
121
  const initialSelection = {};
115
122
  for (const langCode of data.targetLanguages) {
116
123
  const lang = allLanguages.find((l) => l.code === langCode);
117
- if (lang)
124
+ // Predefined targets the user cannot write are dropped silently.
125
+ if (lang?.canWrite)
118
126
  initialSelection[langCode] = true;
119
127
  }
120
128
  setLanguageSelection(initialSelection);
@@ -137,37 +145,47 @@ export function ServiceLanguageSelectionStep({ isActive = true, data, setData, o
137
145
  };
138
146
  setData(newData);
139
147
  }, [languageSelection, setData, data.targetLanguages]);
148
+ // Only languages the user may write take part in toggling / select-all;
149
+ // the others render disabled below.
150
+ const writableLanguages = useMemo(() => allLanguages.filter((lang) => lang.canWrite), [allLanguages]);
140
151
  const handleLanguageToggle = (langCode) => {
152
+ if (!writableLanguages.some((lang) => lang.code === langCode))
153
+ return;
141
154
  setLanguageSelection((prev) => ({ ...prev, [langCode]: !prev[langCode] }));
142
155
  };
143
156
  const handleSelectAllLanguages = () => {
144
- const allSelected = allLanguages.every((lang) => languageSelection[lang.code]);
157
+ const allSelected = writableLanguages.every((lang) => languageSelection[lang.code]);
145
158
  const newSelection = {};
146
- allLanguages.forEach((lang) => {
159
+ writableLanguages.forEach((lang) => {
147
160
  newSelection[lang.code] = !allSelected;
148
161
  });
149
162
  setLanguageSelection(newSelection);
150
163
  };
151
164
  const areAllLanguagesSelected = useMemo(() => {
152
- if (allLanguages.length === 0)
165
+ if (writableLanguages.length === 0)
153
166
  return false;
154
- return allLanguages.every((lang) => languageSelection[lang.code]);
155
- }, [allLanguages, languageSelection]);
167
+ return writableLanguages.every((lang) => languageSelection[lang.code]);
168
+ }, [writableLanguages, languageSelection]);
156
169
  const areSomeLanguagesSelected = useMemo(() => {
157
- return allLanguages.some((lang) => languageSelection[lang.code]);
158
- }, [allLanguages, languageSelection]);
170
+ return writableLanguages.some((lang) => languageSelection[lang.code]);
171
+ }, [writableLanguages, languageSelection]);
159
172
  const selectedCount = data.targetLanguages.length;
160
- return (_jsx(WizardStepShell, { fillHeight: true, noPadding: true, testId: "language-selection-step", children: _jsxs("div", { className: "flex min-h-0 w-full flex-1 flex-col overflow-hidden p-6", children: [_jsxs("div", { className: "flex shrink-0 items-start justify-between gap-2 pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "text-xs font-medium tracking-wider text-neutral-grey-50", children: "Select target languages" }), selectedCount > 0 && _jsx(CountBadge, { children: selectedCount })] }), allLanguages.length > 1 && (_jsxs("label", { className: "flex cursor-pointer items-center gap-1.5 text-[11px] font-medium text-neutral-grey-50 transition-colors hover:text-neutral-grey-100", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
173
+ return (_jsx(WizardStepShell, { fillHeight: true, noPadding: true, testId: "language-selection-step", children: _jsxs("div", { className: "flex min-h-0 w-full flex-1 flex-col overflow-hidden p-6", children: [_jsxs("div", { className: "flex shrink-0 items-start justify-between gap-2 pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "text-xs font-medium tracking-wider text-neutral-grey-50", children: "Select target languages" }), selectedCount > 0 && _jsx(CountBadge, { children: selectedCount })] }), writableLanguages.length > 1 && (_jsxs("label", { className: "flex cursor-pointer items-center gap-1.5 text-[11px] font-medium text-neutral-grey-50 transition-colors hover:text-neutral-grey-100", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
161
174
  if (input) {
162
175
  input.indeterminate =
163
176
  areSomeLanguagesSelected && !areAllLanguagesSelected;
164
177
  }
165
178
  }, onChange: handleSelectAllLanguages, className: "h-3 w-3 rounded border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)]", "data-testid": "select-all-languages-checkbox" }), "Select all"] }))] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: allLanguages.length === 0 ? (_jsx("div", { className: "grid gap-2.5 sm:grid-cols-2", children: [...Array(6)].map((_, i) => (_jsxs("div", { className: "flex animate-pulse items-center gap-2 rounded-[8px] border border-border-default px-3 py-2", children: [_jsx("div", { className: "h-3.5 w-3.5 rounded bg-neutral-grey-10" }), _jsx("div", { className: "h-4 w-4 bg-neutral-grey-10" }), _jsx("div", { className: "h-3.5 w-24 rounded bg-neutral-grey-10" }), _jsx("div", { className: "ml-auto h-3 w-10 rounded bg-neutral-grey-5" })] }, i))) })) : (_jsx("div", { className: "grid gap-2.5 sm:grid-cols-2", children: allLanguages.map((lang) => {
166
179
  const checked = !!languageSelection[lang.code];
167
- return (_jsxs("label", { className: `group flex cursor-pointer items-center gap-2.5 rounded-[8px] border px-2 py-1.5 transition-all ${checked
168
- ? "border-neutral-grey-100"
169
- : "border-border-default hover:border-border-default hover:bg-neutral-grey-5"}`, children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => handleLanguageToggle(lang.code), className: "h-3.5 w-3.5 shrink-0 rounded border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)]", "data-testid": `language-checkbox-${lang.code}` }), lang.icon ? (_jsx("img", { src: lang.icon, alt: "", "aria-hidden": "true", className: "h-4 w-5 shrink-0 object-cover" })) : (_jsx("span", { className: "inline-block h-4 w-5 shrink-0 bg-neutral-grey-5" })), _jsx("span", { className: `min-w-0 flex-1 truncate text-[13px] ${checked
180
+ const readOnly = !lang.canWrite;
181
+ return (_jsxs("label", { title: readOnly
182
+ ? "You don't have write access to this language."
183
+ : undefined, "data-testid": `language-option-${lang.code}`, "data-readonly": readOnly || undefined, className: `group flex items-center gap-2.5 rounded-[8px] border px-2 py-1.5 transition-all ${readOnly
184
+ ? "cursor-not-allowed border-border-default opacity-50"
185
+ : checked
186
+ ? "cursor-pointer border-neutral-grey-100"
187
+ : "cursor-pointer border-border-default hover:border-border-default hover:bg-neutral-grey-5"}`, children: [_jsx("input", { type: "checkbox", checked: checked, disabled: readOnly, onChange: () => handleLanguageToggle(lang.code), className: "h-3.5 w-3.5 shrink-0 rounded border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)] disabled:cursor-not-allowed", "data-testid": `language-checkbox-${lang.code}` }), lang.icon ? (_jsx("img", { src: lang.icon, alt: "", "aria-hidden": "true", className: "h-4 w-5 shrink-0 object-cover" })) : (_jsx("span", { className: "inline-block h-4 w-5 shrink-0 bg-neutral-grey-5" })), _jsx("span", { className: `min-w-0 flex-1 truncate text-[13px] ${checked
170
188
  ? "font-medium text-neutral-grey-100"
171
- : "text-neutral-grey-100"}`, children: lang.name }), _jsxs("span", { className: "ml-auto flex max-w-[9rem] shrink-0 flex-col items-end leading-tight", children: [_jsx("span", { className: "font-mono text-[10px] tracking-wider text-neutral-grey-50", children: lang.code }), _jsxs("span", { className: "max-w-full truncate text-[10px] text-neutral-grey-50", title: `Source language: ${lang.sourceLanguageName}`, children: ["from ", lang.sourceLanguageName] })] })] }, lang.code));
189
+ : "text-neutral-grey-100"}`, children: lang.name }), _jsxs("span", { className: "ml-auto flex max-w-[9rem] shrink-0 flex-col items-end leading-tight", children: [_jsx("span", { className: "font-mono text-[10px] tracking-wider text-neutral-grey-50", children: lang.code }), readOnly ? (_jsx("span", { className: "max-w-full truncate text-[10px] text-neutral-grey-50", children: "no write access" })) : (_jsxs("span", { className: "max-w-full truncate text-[10px] text-neutral-grey-50", title: `Source language: ${lang.sourceLanguageName}`, children: ["from ", lang.sourceLanguageName] }))] })] }, lang.code));
172
190
  }) })) })] }) }));
173
191
  }
@@ -20,6 +20,8 @@ export type LanguageData = {
20
20
  languageCode: string;
21
21
  items: ItemDescriptor[];
22
22
  translationStatus?: TranslationStatus;
23
+ /** Language write ACL (language:write) for the current user. Undefined when not reported. */
24
+ canWrite?: boolean;
23
25
  };
24
26
  export type TranslationDialogResult = {
25
27
  translationProvider: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/steps/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/steps/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TranslationBatches.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationBatches.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,OAAO,CAAC;AAyV1B,wBAAgB,kBAAkB,sBAspCjC;AAED,eAAO,MAAM,oCAAoC,8BAA8B,CAAC;AAEhF,wBAAgB,8BAA8B,sBA0Y7C"}
1
+ {"version":3,"file":"TranslationBatches.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationBatches.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,OAAO,CAAC;AA0V1B,wBAAgB,kBAAkB,sBA6qCjC;AAED,eAAO,MAAM,oCAAoC,8BAA8B,CAAC;AAEhF,wBAAgB,8BAA8B,sBA+Y7C"}
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState, useMemo, useSyncExternalStore, } from "react";
3
3
  import React from "react";
4
- import { Button, Badge, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipContent, TooltipTrigger, ContentTree, ContentInspectorPopover, PageHeader, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, WorkspaceHomeView, formatDateTime, } from "@parhelia/core";
4
+ import { Button, Badge, Select, Input, UserPicker, Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipContent, TooltipTrigger, ContentTree, ContentInspectorPopover, PageHeader, fetchItemStubs, searchUsers as searchBackendUsers, useEditContext, getLanguages, SimpleIconButton, WorkspaceHomeView, capitalizeFirstLetter, formatDateTime, } from "@parhelia/core";
5
5
  import { listBatches, getTranslationProviders, getTranslationJobLastPayload, listBatchTranslationJobs, retryBatchTranslation, abortJob, abortBatch, subscribeToBatch, unsubscribeFromBatch, } from "../services/translationService";
6
6
  import { TRANSLATION_BATCH_STARTED_EVENT, } from "../translationEvents";
7
7
  import { useDebouncedCallback } from "use-debounce";
@@ -191,6 +191,11 @@ export function TranslationBatches() {
191
191
  const [batches, setBatches] = useState([]);
192
192
  const [sitecoreLanguages, setSitecoreLanguages] = useState([]);
193
193
  const [isLoading, setIsLoading] = useState(false);
194
+ // False until the first list response lands. Later reloads (the user filter
195
+ // defaulting in, socket refreshes, the reconciliation poll) update in place
196
+ // instead of swapping the view for a spinner: the home view picks its slogan
197
+ // per mount, so every swap re-rolled the greeting.
198
+ const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
194
199
  const [isLoadingMore, setIsLoadingMore] = useState(false);
195
200
  const [hasMore, setHasMore] = useState(true);
196
201
  const [currentOffset, setCurrentOffset] = useState(0);
@@ -209,7 +214,11 @@ export function TranslationBatches() {
209
214
  // (e.g. when the user changes filters quickly).
210
215
  const requestIdRef = useRef(0);
211
216
  const currentUserName = editContext?.user?.name || "all";
212
- const isLimitedPreviewUser = editContext?.user?.isLimitedPreviewUser ?? false;
217
+ // Limited-preview sessions and accounts confined to their own batches (see
218
+ // userInfo.canViewAllTranslationBatches) get the user filter pinned to themselves;
219
+ // the server enforces the same scope on every batch endpoint.
220
+ const userFilterLocked = (editContext?.user?.isLimitedPreviewUser ?? false) ||
221
+ editContext?.userInfo?.canViewAllTranslationBatches === false;
213
222
  const isMobile = editContext?.isMobile ?? false;
214
223
  const rootRef = useRef(null);
215
224
  const [isCompactBatches, setIsCompactBatches] = useState(isMobile);
@@ -253,7 +262,7 @@ export function TranslationBatches() {
253
262
  }, [currentUserName]);
254
263
  // Enforce current user filter for limited preview users
255
264
  useEffect(() => {
256
- if (isLimitedPreviewUser &&
265
+ if (userFilterLocked &&
257
266
  currentUserName !== "all" &&
258
267
  filters.user !== currentUserName) {
259
268
  setTranslationBatchFilters((prev) => ({
@@ -261,9 +270,9 @@ export function TranslationBatches() {
261
270
  user: currentUserName,
262
271
  }));
263
272
  }
264
- }, [isLimitedPreviewUser, currentUserName, filters.user]);
273
+ }, [userFilterLocked, currentUserName, filters.user]);
265
274
  const effectiveFilters = useMemo(() => {
266
- const userFilter = isLimitedPreviewUser ? currentUserName : filters.user;
275
+ const userFilter = userFilterLocked ? currentUserName : filters.user;
267
276
  const trimmedItem = filters.itemIdOrPath.trim();
268
277
  return {
269
278
  fromUtc: dateRangeToFromUtc(filters.dateRange),
@@ -276,7 +285,7 @@ export function TranslationBatches() {
276
285
  itemIdOrPath: trimmedItem ? trimmedItem : undefined,
277
286
  itemIncludeSubitems: trimmedItem && filters.itemIncludeSubitems ? true : undefined,
278
287
  };
279
- }, [filters, isLimitedPreviewUser, currentUserName]);
288
+ }, [filters, userFilterLocked, currentUserName]);
280
289
  const loadRecentBatches = useCallback(async (offset, append, filtersToUse) => {
281
290
  const myRequestId = ++requestIdRef.current;
282
291
  if (offset === 0) {
@@ -323,6 +332,7 @@ export function TranslationBatches() {
323
332
  if (myRequestId === requestIdRef.current) {
324
333
  setIsLoading(false);
325
334
  setIsLoadingMore(false);
335
+ setHasLoadedOnce(true);
326
336
  }
327
337
  }
328
338
  }, []);
@@ -777,12 +787,18 @@ export function TranslationBatches() {
777
787
  editContext?.setShowAgentsWorkspaceEditor(true);
778
788
  await editContext?.loadItem(language ? { id: normalized, language, version: 0 } : normalized);
779
789
  }, [editContext]);
780
- const showHomeView = !(isLoading && batches.length === 0) &&
781
- groupedBatches.length === 0 &&
782
- !hasActiveEmptyStateFilters;
790
+ // The blank-slate home view (no header, grey full-height greeting) also
791
+ // covers the initial load, so the greeting mounts exactly once.
792
+ const showHomeView = groupedBatches.length === 0 && !hasActiveEmptyStateFilters;
793
+ // Only the very first load, or a filtered reload with nothing to show,
794
+ // replaces the content with a spinner.
795
+ const showLoadingPlaceholder = !hasLoadedOnce ||
796
+ (isLoading && batches.length === 0 && hasActiveEmptyStateFilters);
783
797
  return (_jsxs("div", { ref: rootRef, className: "flex h-full min-h-0 flex-col bg-neutral-grey-5", style: { padding: isCompactBatches ? 12 : 40 }, "data-testid": "translation-batches", children: [!showHomeView && (_jsx(PageHeader, { title: "Translation Batches", description: isMobile
784
798
  ? undefined
785
- : "Review translation history and manage batch progress", variant: "workspace", className: isCompactBatches ? "mb-4" : "mb-5" })), _jsx("main", { className: "min-h-0 min-w-0 flex-1 overflow-hidden rounded-[12px] bg-white", children: _jsx("div", { className: `h-full min-h-0 overflow-auto ${showHomeView ? "" : isCompactBatches ? "p-3" : "p-6"}`, children: isLoading && batches.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-neutral-grey-50", children: [_jsx(LoaderIcon, { className: "h-5 w-5 animate-spin text-highlight-100" }), "Loading recent translations..."] }) })) : groupedBatches.length === 0 ? (hasActiveEmptyStateFilters ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-neutral-grey-50", children: [_jsx(LanguagesIcon, { className: "h-8 w-8 block mx-auto mb-2 text-neutral-grey-15" }), _jsx("p", { className: "font-medium text-neutral-grey-100", children: emptyStateSlogan }), _jsx("p", { className: "text-sm mt-1", children: "Try adjusting your filters or start a translation to see it appear here" })] }) })) : (_jsx(WorkspaceHomeView, { slogans: EMPTY_STATE_SLOGANS, actionLabel: "Start your first translation", onAction: editContext ? handleStartFirstTranslation : undefined, testId: "translation-batches-home-view", actionTestId: "translation-batches-start-translation-card" }))) : (_jsxs("div", { className: isCompactBatches ? "space-y-6" : "space-y-10", children: [groupedBatches.map((dateGroup) => (_jsxs("div", { children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-2", children: [_jsx("h2", { className: "text-[11px] font-medium tracking-[0.08em] text-neutral-grey-50", children: dateGroup.label }), _jsx("span", { className: "flex-1 ml-2 border-t border-border-default" })] }), _jsx("div", { className: "divide-border-default divide-y overflow-hidden rounded-lg bg-white", children: dateGroup.batches.map((b) => {
799
+ : "Review translation history and manage batch progress", variant: "workspace", className: isCompactBatches ? "mb-4" : "mb-5" })), _jsx("main", { className: "min-h-0 min-w-0 flex-1 overflow-hidden rounded-[12px] bg-white", children: _jsx("div", { className: `h-full min-h-0 overflow-auto ${showHomeView ? "" : isCompactBatches ? "p-3" : "p-6"}`, children: showLoadingPlaceholder ? (_jsx("div", { className: showHomeView
800
+ ? "bg-neutral-grey-5 flex h-full min-h-[420px] w-full items-center justify-center"
801
+ : "flex items-center justify-center h-32", "data-testid": "translation-batches-loading", children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-neutral-grey-50", children: [_jsx(LoaderIcon, { className: "h-5 w-5 animate-spin text-highlight-100" }), "Loading recent translations..."] }) })) : groupedBatches.length === 0 ? (hasActiveEmptyStateFilters ? (_jsx("div", { className: "flex items-center justify-center h-32", children: _jsxs("div", { className: "text-center text-neutral-grey-50", children: [_jsx(LanguagesIcon, { className: "h-8 w-8 block mx-auto mb-2 text-neutral-grey-15" }), _jsx("p", { className: "font-medium text-neutral-grey-100", children: emptyStateSlogan }), _jsx("p", { className: "text-sm mt-1", children: "Try adjusting your filters or start a translation to see it appear here" })] }) })) : (_jsx(WorkspaceHomeView, { slogans: EMPTY_STATE_SLOGANS, actionLabel: "Start your first translation", onAction: editContext ? handleStartFirstTranslation : undefined, testId: "translation-batches-home-view", actionTestId: "translation-batches-start-translation-card" }))) : (_jsxs("div", { className: isCompactBatches ? "space-y-6" : "space-y-10", children: [groupedBatches.map((dateGroup) => (_jsxs("div", { children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-2", children: [_jsx("h2", { className: "text-[11px] font-medium tracking-[0.08em] text-neutral-grey-50", children: dateGroup.label }), _jsx("span", { className: "flex-1 ml-2 border-t border-border-default" })] }), _jsx("div", { className: "divide-border-default divide-y overflow-hidden rounded-lg bg-white", children: dateGroup.batches.map((b) => {
786
802
  const batchDate = new Date(b.timestamp);
787
803
  const formattedBatchDate = formatDateTime(batchDate);
788
804
  const info = b.info;
@@ -906,12 +922,16 @@ export function TranslationBatchFiltersSidebar() {
906
922
  const [sitecoreLanguages, setSitecoreLanguages] = useState([]);
907
923
  const [itemPickerOpen, setItemPickerOpen] = useState(false);
908
924
  const currentUserName = editContext?.user?.name || "all";
909
- const isLimitedPreviewUser = editContext?.user?.isLimitedPreviewUser ?? false;
925
+ // Limited-preview sessions and accounts confined to their own batches (see
926
+ // userInfo.canViewAllTranslationBatches) get the user filter pinned to themselves;
927
+ // the server enforces the same scope on every batch endpoint.
928
+ const userFilterLocked = (editContext?.user?.isLimitedPreviewUser ?? false) ||
929
+ editContext?.userInfo?.canViewAllTranslationBatches === false;
910
930
  useEffect(() => {
911
931
  ensureDefaultTranslationBatchUser(currentUserName);
912
932
  }, [currentUserName]);
913
933
  useEffect(() => {
914
- if (isLimitedPreviewUser &&
934
+ if (userFilterLocked &&
915
935
  currentUserName !== "all" &&
916
936
  filters.user !== currentUserName) {
917
937
  setTranslationBatchFilters((prev) => ({
@@ -919,7 +939,7 @@ export function TranslationBatchFiltersSidebar() {
919
939
  user: currentUserName,
920
940
  }));
921
941
  }
922
- }, [isLimitedPreviewUser, currentUserName, filters.user]);
942
+ }, [userFilterLocked, currentUserName, filters.user]);
923
943
  const searchTranslationUsers = useCallback((query, limit) => searchBackendUsers(query, limit), []);
924
944
  const debouncedSetItemFilter = useDebouncedCallback((value) => setTranslationBatchFilters((prev) => ({
925
945
  ...prev,
@@ -996,20 +1016,20 @@ export function TranslationBatchFiltersSidebar() {
996
1016
  })), placeholder: "Select date range" })] }), _jsxs("div", { className: filterFieldClass, children: [_jsx("label", { className: filterLabelClass, children: "Status" }), _jsx(Select, { className: "w-full bg-white", options: STATUS_OPTIONS, value: filters.status, onValueChange: (value) => setTranslationBatchFilters((prev) => ({
997
1017
  ...prev,
998
1018
  status: value,
999
- })), placeholder: "Select status" })] }), _jsxs("div", { className: filterFieldClass, children: [_jsx("label", { className: filterLabelClass, children: "User" }), _jsx(UserPicker, { value: isLimitedPreviewUser
1019
+ })), placeholder: "Select status" })] }), _jsxs("div", { className: filterFieldClass, children: [_jsx("label", { className: filterLabelClass, children: "User" }), _jsx(UserPicker, { value: userFilterLocked
1000
1020
  ? currentUserName
1001
1021
  : filters.user === "all"
1002
1022
  ? null
1003
- : filters.user, displayValue: isLimitedPreviewUser || filters.user === currentUserName
1023
+ : filters.user, displayValue: userFilterLocked || filters.user === currentUserName
1004
1024
  ? `${editContext?.user?.fullName?.trim() || currentUserName} (You)`
1005
- : undefined, displayAvatarName: isLimitedPreviewUser || filters.user === currentUserName
1025
+ : undefined, displayAvatarName: userFilterLocked || filters.user === currentUserName
1006
1026
  ? editContext?.user?.fullName?.trim() || currentUserName
1007
- : undefined, displayAvatarUrl: isLimitedPreviewUser || filters.user === currentUserName
1027
+ : undefined, displayAvatarUrl: userFilterLocked || filters.user === currentUserName
1008
1028
  ? editContext?.user?.avatarUrl
1009
1029
  : undefined, onChange: (user) => setTranslationBatchFilters((prev) => ({
1010
1030
  ...prev,
1011
1031
  user: user?.userName ?? "all",
1012
- })), disabled: isLimitedPreviewUser, placeholder: "All Users", buttonClassName: "h-8 min-h-0 w-full justify-start rounded-[4px] border bg-white px-2 py-0 text-xs font-medium", allowClear: true, clearLabel: "All Users", showIcon: isLimitedPreviewUser || filters.user !== "all", searchUsers: searchTranslationUsers })] }), _jsxs("div", { className: filterFieldClass, children: [_jsx("label", { className: filterLabelClass, children: "Provider" }), _jsx(Select, { className: "w-full bg-white", options: [
1032
+ })), disabled: userFilterLocked, placeholder: "All Users", buttonClassName: "h-8 min-h-0 w-full justify-start rounded-[4px] border bg-white px-2 py-0 text-xs font-medium", allowClear: true, clearLabel: "All Users", showIcon: userFilterLocked || filters.user !== "all", searchUsers: searchTranslationUsers })] }), _jsxs("div", { className: filterFieldClass, children: [_jsx("label", { className: filterLabelClass, children: "Provider" }), _jsx(Select, { className: "w-full bg-white", options: [
1013
1033
  { value: "all", label: "All Providers" },
1014
1034
  ...providers.map((p) => ({
1015
1035
  value: p.name,
@@ -1703,10 +1723,10 @@ function LanguageJobChip({ job, language, provider, batchStartedAtUtc, onOpen, i
1703
1723
  const emptyFieldText = statistics && statistics.emptyFieldCount > 0
1704
1724
  ? `${formatCount(statistics.emptyFieldCount)} empty`
1705
1725
  : null;
1706
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: `group inline-flex items-center gap-1.5 rounded border badge-pad-sm text-[12px] transition-colors cursor-pointer ${chipCls}`, children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-3.5 shrink-0" })) : null, _jsx("span", { className: "font-mono tracking-tight", children: job.targetLanguage }), jobInProgress && (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin" })), jobPending && (_jsx(QueuedIcon, { className: "h-3 w-3 shrink-0", strokeWidth: 1, "aria-label": "Queued" })), jobError && _jsx(AlertCircleIcon, { className: "h-3 w-3 shrink-0" }), !jobInProgress && !jobError && !jobPending && !jobAborted && (_jsx(CheckIcon, { className: "h-3 w-3 shrink-0 text-feedback-green" }))] }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 6, className: "w-[22rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border-default px-3 py-2", children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-4 shrink-0" })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-[13px] font-medium text-neutral-grey-100 truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-neutral-grey-50 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs(Badge, { size: "sm", className: statusBadgeCls, children: [jobInProgress && (_jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" })), jobStatus || "Unknown"] })] }), _jsxs("dl", { className: "divide-y divide-border-default/60", children: [claimedAtValid && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: formatDateTime(claimedAt) })] })), timestampValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: formatDateTime(timestamp) })] })), durationMs != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", title: durationApproximated
1726
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: `group inline-flex items-center gap-1.5 rounded border badge-pad-sm text-[12px] transition-colors cursor-pointer ${chipCls}`, children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-3.5 shrink-0" })) : null, _jsx("span", { className: "font-mono tracking-tight", children: job.targetLanguage }), jobInProgress && (_jsx(LoaderIcon, { className: "h-3 w-3 shrink-0 animate-spin" })), jobPending && (_jsx(QueuedIcon, { className: "h-3 w-3 shrink-0", strokeWidth: 1, "aria-label": "Queued" })), jobError && _jsx(AlertCircleIcon, { className: "h-3 w-3 shrink-0" }), !jobInProgress && !jobError && !jobPending && !jobAborted && (_jsx(CheckIcon, { className: "h-3 w-3 shrink-0 text-feedback-green" }))] }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 6, className: "w-[26rem] max-w-[calc(100vw-2rem)] p-0", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border-default px-3 py-2", children: [language?.icon ? (_jsx("img", { src: language.icon, alt: langName, className: "h-4 shrink-0" })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-[13px] font-medium text-neutral-grey-100 truncate", children: langName }), _jsxs("div", { className: "text-[11px] text-neutral-grey-50 font-mono truncate", children: [job.targetLanguage, job.sourceLanguage ? ` ← ${job.sourceLanguage}` : ""] })] }), _jsxs(Badge, { size: "sm", className: statusBadgeCls, children: [jobInProgress && (_jsx(LoaderIcon, { className: "h-2.5 w-2.5 animate-spin" })), capitalizeFirstLetter(jobStatus || "Unknown")] })] }), _jsxs("dl", { className: "divide-y divide-border-default/60", children: [claimedAtValid && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Claimed" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: formatDateTime(claimedAt) })] })), timestampValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Updated" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: formatDateTime(timestamp) })] })), durationMs != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: jobInProgress || jobPending ? "Running" : "Duration" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", title: durationApproximated
1707
1727
  ? "Approximate — measured from batch start"
1708
1728
  : undefined, children: [durationApproximated ? "~" : "", formatDuration(durationMs)] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Fields" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: [fieldText, emptyFieldText ? (_jsxs("span", { className: "ml-1.5 text-neutral-grey-50", children: ["(", emptyFieldText, ")"] })) : null] })] })), statistics && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Text" }), _jsxs("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: [formatCount(statistics.sourceCharacterCount), " chars"] })] })), job.totalCost != null && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Cost" }), _jsx("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: formatUsdCost(job.totalCost) })] })), lastHeartbeatValid && (jobInProgress || jobPending) && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Heartbeat" }), _jsx("dd", { className: "text-[12px] text-neutral-grey-100", children: formatDateTime(lastHeartbeat) })] })), attemptCount > 1 && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Attempts" }), _jsx("dd", { className: "text-[12px] tabular-nums text-neutral-grey-100", children: attemptCount })] })), job.hash && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Hash" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-100 break-all", children: job.hash })] })), job.message &&
1709
- (jobError || jobAborted ? (_jsxs("div", { className: "flex flex-col gap-1 px-3 py-2", children: [_jsx("dt", { className: "text-[10px] tracking-wide text-neutral-grey-50", children: jobError ? "Error" : "Aborted" }), _jsx("dd", { className: `max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-sm bg-neutral-grey-5 p-2 font-mono text-[11px] leading-snug ${jobError ? "text-feedback-red" : "text-feedback-orange"}`, children: job.message })] })) : (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Progress" }), _jsx("dd", { className: "text-[12px] break-words text-neutral-grey-100", children: job.message })] }))), job.batchId && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-50 break-all", children: job.batchId })] }))] }), _jsxs("div", { className: "flex justify-end gap-2 border-t border-border-default px-3 py-2", children: [canInspectRawResponse && (_jsx(ContentInspectorPopover, { title: "Raw AI response", emptyMessage: "Nothing captured for this job yet. Captures are kept in memory only and exist for AI provider jobs that ran since the last restart.", downloadFileName: `translation-job-${job.id}-raw-response.txt`, className: "w-[min(64rem,calc(100vw-2rem))]", side: "left", align: "start", loadSections: async () => {
1729
+ (jobError || jobAborted ? (_jsxs("div", { className: "flex flex-col gap-1 px-3 py-2", children: [_jsx("dt", { className: "text-[10px] tracking-wide text-neutral-grey-50", children: jobError ? "Error" : "Aborted" }), _jsx("dd", { className: `max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-sm bg-neutral-grey-5 p-2 font-mono text-[11px] leading-snug ${jobError ? "text-feedback-red" : "text-feedback-orange"}`, children: job.message })] })) : (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Progress" }), _jsx("dd", { className: "text-[12px] break-words text-neutral-grey-100", children: job.message })] }))), job.batchId && (_jsxs("div", { className: "flex items-baseline gap-3 px-3 py-2", children: [_jsx("dt", { className: "w-20 shrink-0 text-[10px] tracking-wide text-neutral-grey-50", children: "Batch" }), _jsx("dd", { className: "text-[11px] font-mono text-neutral-grey-50 break-all", children: job.batchId })] }))] }), _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 border-t border-border-default px-3 py-2", children: [canInspectRawResponse && (_jsx(ContentInspectorPopover, { title: "Raw AI response", emptyMessage: "Nothing captured for this job yet. Captures are kept in memory only and exist for AI provider jobs that ran since the last restart.", downloadFileName: `translation-job-${job.id}-raw-response.txt`, className: "w-[min(64rem,calc(100vw-2rem))]", side: "bottom", align: "end", loadSections: async () => {
1710
1730
  const { payload, rawResponse } = await getTranslationJobLastPayload(job.id);
1711
1731
  const sections = [];
1712
1732
  if (rawResponse) {
@@ -1716,14 +1736,17 @@ function LanguageJobChip({ job, language, provider, batchStartedAtUtc, onOpen, i
1716
1736
  });
1717
1737
  }
1718
1738
  if (payload) {
1719
- sections.push({ title: "Last request payload", content: payload });
1739
+ sections.push({
1740
+ title: "Last request payload",
1741
+ content: payload,
1742
+ });
1720
1743
  }
1721
1744
  return sections;
1722
1745
  }, trigger: _jsxs(Button, { size: "sm", variant: "outline", "data-testid": "translation-job-raw-response-button", onClick: (e) => e.stopPropagation(), className: "mr-auto gap-1.5 text-neutral-grey-50", title: "Show the raw AI request and responses for this job", children: [_jsx(BugIcon, { className: "h-3.5 w-3.5" }), "Raw response"] }) })), canAbort && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isAborting, onClick: (e) => {
1723
1746
  e.stopPropagation();
1724
1747
  void onAbort?.();
1725
1748
  setOpen(false);
1726
- }, className: "gap-1.5 text-feedback-orange hover:text-feedback-orange", children: [isAborting ? (_jsx(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(AbortIcon, { className: "h-3.5 w-3.5" })), "Abort"] })), jobError && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isRetrying, onClick: (e) => {
1749
+ }, className: "gap-1.5 text-neutral-grey-50 hover:text-neutral-grey-100", children: [isAborting ? (_jsx(LoaderIcon, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(AbortIcon, { className: "h-3.5 w-3.5" })), "Abort"] })), jobError && onRetry && (_jsxs(Button, { size: "sm", variant: "outline", disabled: isRetrying, onClick: (e) => {
1727
1750
  e.stopPropagation();
1728
1751
  void onRetry();
1729
1752
  setOpen(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parhelia/localization",
3
- "version": "0.4.12800",
3
+ "version": "0.4.12825",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"