@parhelia/localization 0.1.12788 → 0.1.12790

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.
Files changed (57) hide show
  1. package/dist/LocalizeItemDialog.d.ts.map +1 -1
  2. package/dist/LocalizeItemDialog.js +92 -34
  3. package/dist/LocalizeItemUtils.d.ts +1 -2
  4. package/dist/LocalizeItemUtils.d.ts.map +1 -1
  5. package/dist/LocalizeItemUtils.js +44 -12
  6. package/dist/api/discovery.d.ts +25 -0
  7. package/dist/api/discovery.d.ts.map +1 -1
  8. package/dist/api/discovery.js +87 -0
  9. package/dist/index.d.ts +8 -17
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +32 -32
  12. package/dist/services/translationService.d.ts +40 -9
  13. package/dist/services/translationService.d.ts.map +1 -1
  14. package/dist/services/translationService.js +30 -4
  15. package/dist/settings/TranslationServicesPanel.d.ts.map +1 -1
  16. package/dist/settings/TranslationServicesPanel.js +18 -36
  17. package/dist/sidebar/TranslationSidebar.d.ts.map +1 -1
  18. package/dist/sidebar/TranslationSidebar.js +4 -1
  19. package/dist/steps/ItemSelectionStep.d.ts +3 -0
  20. package/dist/steps/ItemSelectionStep.d.ts.map +1 -0
  21. package/dist/steps/ItemSelectionStep.js +23 -0
  22. package/dist/steps/ItemSelectionTree.d.ts +13 -0
  23. package/dist/steps/ItemSelectionTree.d.ts.map +1 -0
  24. package/dist/steps/ItemSelectionTree.js +326 -0
  25. package/dist/steps/MetadataInputStep.d.ts.map +1 -1
  26. package/dist/steps/MetadataInputStep.js +8 -1
  27. package/dist/steps/PromptCustomizationStep.d.ts +1 -1
  28. package/dist/steps/PromptCustomizationStep.d.ts.map +1 -1
  29. package/dist/steps/PromptCustomizationStep.js +161 -56
  30. package/dist/steps/ServiceLanguageSelectionStep.d.ts +6 -1
  31. package/dist/steps/ServiceLanguageSelectionStep.d.ts.map +1 -1
  32. package/dist/steps/ServiceLanguageSelectionStep.js +53 -163
  33. package/dist/steps/WizardStepShell.d.ts +17 -0
  34. package/dist/steps/WizardStepShell.d.ts.map +1 -0
  35. package/dist/steps/WizardStepShell.js +11 -0
  36. package/dist/steps/index.d.ts +1 -0
  37. package/dist/steps/index.d.ts.map +1 -1
  38. package/dist/steps/index.js +1 -0
  39. package/dist/steps/types.d.ts +17 -1
  40. package/dist/steps/types.d.ts.map +1 -1
  41. package/dist/translation-center/TranslationBatches.d.ts +2 -0
  42. package/dist/translation-center/TranslationBatches.d.ts.map +1 -0
  43. package/dist/translation-center/TranslationBatches.js +995 -0
  44. package/dist/translation-center/TranslationManagement.d.ts.map +1 -1
  45. package/dist/translation-center/TranslationManagement.js +22 -14
  46. package/dist/translation-center/TranslationsTitlebar.d.ts +7 -0
  47. package/dist/translation-center/TranslationsTitlebar.d.ts.map +1 -0
  48. package/dist/translation-center/TranslationsTitlebar.js +16 -0
  49. package/dist/types.d.ts +1 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/dist/translation-center/BatchTranslationView.d.ts +0 -8
  53. package/dist/translation-center/BatchTranslationView.d.ts.map +0 -1
  54. package/dist/translation-center/BatchTranslationView.js +0 -870
  55. package/dist/translation-center/RecentTranslations.d.ts +0 -2
  56. package/dist/translation-center/RecentTranslations.d.ts.map +0 -1
  57. package/dist/translation-center/RecentTranslations.js +0 -309
@@ -1,63 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
- // Version creation now offered via VersionOnlyTranslationProvider. No custom button here.
4
- function getTranslationStatusBadgeClass(status) {
5
- switch (status) {
6
- case "Completed":
7
- return "text-green-700 bg-green-100";
8
- case "Error":
9
- return "text-red-700 bg-red-100";
10
- case "In Progress":
11
- return "text-purple-800 bg-purple-100";
12
- case "Not started":
13
- return "text-[var(--color-gray-2)] bg-[var(--color-gray-4)]";
14
- default:
15
- return "text-[var(--color-gray-2)] bg-[var(--color-gray-4)]";
16
- }
17
- }
18
- export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data, setData, onStepCompleted, editContext, setFooterActions, requestClose }) {
3
+ import { WizardStepShell } from "./WizardStepShell";
4
+ /**
5
+ * Language-only step. Item selection lives in its own step
6
+ * (see ItemSelectionStep). The legacy name is kept so existing imports
7
+ * keep working.
8
+ */
9
+ export function ServiceLanguageSelectionStep({ isActive = true, data, setData, onStepCompleted, editContext, }) {
19
10
  const [languageSelection, setLanguageSelection] = useState({});
20
11
  const dataRef = useRef(data);
21
- useEffect(() => { dataRef.current = data; }, [data]);
22
- // Check if multi-item translation is enabled
23
- const multiItemEnabled = editContext?.configuration?.localization?.multiItem !== false;
24
- // Use ref to track onStepCompleted to avoid dependency issues
12
+ useEffect(() => {
13
+ dataRef.current = data;
14
+ }, [data]);
25
15
  const onStepCompletedRef = useRef(onStepCompleted);
26
16
  useEffect(() => {
27
17
  onStepCompletedRef.current = onStepCompleted;
28
18
  }, [onStepCompleted]);
29
- // Track last completion state to prevent unnecessary calls
30
19
  const lastCompletionStateRef = useRef(null);
31
- // Call completion check when component mounts or when returning to this step
32
- // Also check when data changes (but only if we're actually the active step)
33
20
  useEffect(() => {
34
- // Only update completion when this step is active
35
21
  if (!isActive)
36
22
  return;
37
- const hasProvider = !!data.translationProvider;
38
- const hasLanguages = data.targetLanguages.length > 0;
39
- const isCompleted = hasProvider && hasLanguages;
40
- // Only call if completion state actually changed
23
+ const isCompleted = data.targetLanguages.length > 0;
41
24
  if (lastCompletionStateRef.current !== isCompleted) {
42
25
  lastCompletionStateRef.current = isCompleted;
43
26
  onStepCompletedRef.current(isCompleted);
44
27
  }
45
- }, [isActive, data.translationProvider, data.targetLanguages.length]);
46
- // 1) Derive provider + available languages (lightweight, in-memory)
47
- const provider = useMemo(() => data.translationProviders.find(p => p.name === data.translationProvider), [data.translationProviders, data.translationProvider]);
48
- // Use editContext.itemLanguages (like TranslationSidebar) to get all languages including source language
49
- // This is not item-specific - it shows all available languages for the site
28
+ }, [isActive, data.targetLanguages.length]);
50
29
  const editContextLanguages = useMemo(() => editContext?.itemLanguages || [], [editContext?.itemLanguages]);
51
- // Get language codes from editContext languages, or fallback to languageData keys
52
30
  const siteLanguageCodes = useMemo(() => {
53
31
  if (editContextLanguages.length > 0) {
54
- return editContextLanguages.map(lang => lang.languageCode);
32
+ return editContextLanguages.map((lang) => lang.languageCode);
55
33
  }
56
34
  return Array.from(data.languageData.keys());
57
35
  }, [editContextLanguages, data.languageData]);
58
- const availableLanguageCodes = useMemo(() => (provider?.supportedLanguages?.length ? provider.supportedLanguages : siteLanguageCodes), [provider?.supportedLanguages, siteLanguageCodes]);
59
- // Determine the source language from the items being translated
60
- // Use the first item's language as the source language (all items should have the same source language)
61
36
  const itemSourceLanguage = useMemo(() => {
62
37
  return (data.items[0]?.descriptor.language ||
63
38
  editContext?.item?.descriptor.language ||
@@ -65,177 +40,92 @@ export function ServiceLanguageSelectionStep({ stepIndex, isActive = true, data,
65
40
  "en");
66
41
  }, [data.items, editContext?.item, editContext?.currentItemDescriptor]);
67
42
  const allLanguages = useMemo(() => {
68
- // Create a map of language codes to Language objects from editContext for quick lookup
69
- const editContextLanguageMap = new Map(editContextLanguages.map(lang => [lang.languageCode, lang]));
70
- const arr = availableLanguageCodes
71
- .map(code => {
72
- // Prefer language info from editContext (like TranslationSidebar), fallback to languageData
43
+ const editContextLanguageMap = new Map(editContextLanguages.map((lang) => [lang.languageCode, lang]));
44
+ const arr = siteLanguageCodes
45
+ .map((code) => {
73
46
  const editContextLang = editContextLanguageMap.get(code);
74
47
  const languageDataLang = data.languageData.get(code);
75
- // Determine source language: prefer from translationStatus, fallback to item's source language
76
- const sourceLanguage = languageDataLang?.translationStatus?.sourceLanguage || itemSourceLanguage;
48
+ const sourceLanguage = languageDataLang?.translationStatus?.sourceLanguage ||
49
+ itemSourceLanguage;
77
50
  return {
78
51
  code,
79
52
  name: editContextLang?.name || languageDataLang?.name || code,
80
- hasVersions: (editContextLang?.versions || 0) > 0 || (languageDataLang?.items.length || 0) > 0,
81
- translationStatus: languageDataLang?.translationStatus,
82
- sourceLanguage, // Add source language to the language object
53
+ icon: editContextLang?.icon,
54
+ sourceLanguage,
83
55
  };
84
56
  })
85
- // Filter out languages where sourceLanguage equals targetLanguage
86
- .filter(lang => lang.sourceLanguage !== lang.code);
57
+ .filter((lang) => lang.sourceLanguage !== lang.code);
87
58
  arr.sort((a, b) => a.name.localeCompare(b.name));
88
59
  return arr;
89
- }, [availableLanguageCodes, editContextLanguages, data.languageData, itemSourceLanguage]);
90
- // Track last processed targetLanguages to prevent unnecessary updates
91
- const lastProcessedTargetLanguagesRef = useRef('');
92
- // Rehydrate UI selection from saved wizard data when returning to this step
60
+ }, [
61
+ siteLanguageCodes,
62
+ editContextLanguages,
63
+ data.languageData,
64
+ itemSourceLanguage,
65
+ ]);
66
+ const lastProcessedTargetLanguagesRef = useRef("");
93
67
  useEffect(() => {
94
68
  if (!allLanguages || allLanguages.length === 0)
95
69
  return;
96
- // Create a stable key from targetLanguages to detect actual changes
97
70
  const targetLanguagesKey = JSON.stringify([...data.targetLanguages].sort());
98
- // Skip if we've already processed this exact set of target languages
99
- if (lastProcessedTargetLanguagesRef.current === targetLanguagesKey) {
71
+ if (lastProcessedTargetLanguagesRef.current === targetLanguagesKey)
100
72
  return;
101
- }
102
73
  lastProcessedTargetLanguagesRef.current = targetLanguagesKey;
103
74
  const initialSelection = {};
104
- // Mark as selected any language that's in our saved targetLanguages array
105
75
  for (const langCode of data.targetLanguages) {
106
- const lang = allLanguages.find(l => l.code === langCode);
107
- if (lang) {
76
+ const lang = allLanguages.find((l) => l.code === langCode);
77
+ if (lang)
108
78
  initialSelection[langCode] = true;
109
- }
110
79
  }
111
80
  setLanguageSelection(initialSelection);
112
81
  }, [allLanguages, data.targetLanguages]);
113
- // Track last set targetLanguages to prevent unnecessary setData calls
114
- const lastSetTargetLanguagesRef = useRef('');
115
- // Update wizard data when language selection changes
82
+ const lastSetTargetLanguagesRef = useRef("");
116
83
  useEffect(() => {
117
84
  const selectedLanguages = Object.entries(languageSelection)
118
85
  .filter(([, isSelected]) => isSelected)
119
86
  .map(([code]) => code);
120
87
  const selectedLanguagesKey = JSON.stringify([...selectedLanguages].sort());
121
88
  const currentTargetLanguagesKey = JSON.stringify([...data.targetLanguages].sort());
122
- // Only update if the selection has actually changed AND we haven't already set this value
123
- if (selectedLanguagesKey === currentTargetLanguagesKey) {
124
- // Selection matches current data, no update needed
89
+ if (selectedLanguagesKey === currentTargetLanguagesKey)
125
90
  return;
126
- }
127
- if (lastSetTargetLanguagesRef.current === selectedLanguagesKey) {
128
- // We've already set this value, skip to prevent loops
91
+ if (lastSetTargetLanguagesRef.current === selectedLanguagesKey)
129
92
  return;
130
- }
131
93
  lastSetTargetLanguagesRef.current = selectedLanguagesKey;
132
94
  const newData = {
133
95
  ...dataRef.current,
134
- targetLanguages: selectedLanguages
96
+ targetLanguages: selectedLanguages,
135
97
  };
136
98
  setData(newData);
137
- // Completion will be updated by the useEffect that watches data.targetLanguages.length
138
99
  }, [languageSelection, setData, data.targetLanguages]);
139
- const handleProviderChange = (e) => {
140
- const newProvider = e.target.value;
141
- // Always create a new Map instance to ensure React detects the change
142
- const newServiceCustomData = new Map();
143
- if (data.serviceCustomData) {
144
- data.serviceCustomData.forEach((value, key) => {
145
- // Only preserve custom data if not switching away from OpenAI
146
- if (key !== "OpenAI" || newProvider === "OpenAI") {
147
- newServiceCustomData.set(key, value);
148
- }
149
- });
150
- }
151
- const newData = {
152
- ...data,
153
- translationProvider: newProvider,
154
- serviceCustomData: newServiceCustomData,
155
- // Clear target languages when provider changes since language availability might change
156
- targetLanguages: []
157
- };
158
- setData(newData);
159
- // Clear UI selection too
160
- setLanguageSelection({});
161
- // Completion will be updated by the useEffect that watches data.translationProvider
162
- };
163
100
  const handleLanguageToggle = (langCode) => {
164
- setLanguageSelection(prev => ({ ...prev, [langCode]: !prev[langCode] }));
165
- };
166
- const handleSubitemsToggle = () => {
167
- const newData = {
168
- ...data,
169
- includeSubitems: !data.includeSubitems
170
- };
171
- setData(newData);
101
+ setLanguageSelection((prev) => ({ ...prev, [langCode]: !prev[langCode] }));
172
102
  };
173
103
  const handleSelectAllLanguages = () => {
174
- const allSelected = allLanguages.every(lang => languageSelection[lang.code]);
104
+ const allSelected = allLanguages.every((lang) => languageSelection[lang.code]);
175
105
  const newSelection = {};
176
- if (allSelected) {
177
- // Deselect all
178
- allLanguages.forEach(lang => {
179
- newSelection[lang.code] = false;
180
- });
181
- }
182
- else {
183
- // Select all
184
- allLanguages.forEach(lang => {
185
- newSelection[lang.code] = true;
186
- });
187
- }
106
+ allLanguages.forEach((lang) => {
107
+ newSelection[lang.code] = !allSelected;
108
+ });
188
109
  setLanguageSelection(newSelection);
189
110
  };
190
111
  const areAllLanguagesSelected = useMemo(() => {
191
112
  if (allLanguages.length === 0)
192
113
  return false;
193
- return allLanguages.every(lang => languageSelection[lang.code]);
114
+ return allLanguages.every((lang) => languageSelection[lang.code]);
194
115
  }, [allLanguages, languageSelection]);
195
116
  const areSomeLanguagesSelected = useMemo(() => {
196
- return allLanguages.some(lang => languageSelection[lang.code]);
117
+ return allLanguages.some((lang) => languageSelection[lang.code]);
197
118
  }, [allLanguages, languageSelection]);
198
- // Compute checkbox state for prompt customization
199
- const isPromptCustomizationEnabled = useMemo(() => {
200
- if (data.translationProvider !== "OpenAI") {
201
- return false;
202
- }
203
- const serviceData = data.serviceCustomData?.get("OpenAI");
204
- return serviceData?.enableCustomPrompt === true;
205
- }, [data.translationProvider, data.serviceCustomData]);
206
- return (_jsx("div", { className: "p-6 space-y-6 h-full flex flex-col", "data-testid": "service-language-selection-step", children: _jsxs("div", { className: "space-y-6 flex-1", children: [_jsxs("div", { className: "bg-background rounded-lg border border-[var(--color-gray-3)] p-4", children: [_jsx("h3", { className: "text-sm font-medium text-[var(--color-dark)] mb-2", children: "Translation Provider" }), _jsx("p", { className: "text-xs text-[var(--color-gray-2)] mb-3", children: "Choose how to translate your content. \"Create Versions\" will create new language versions without automatic translation." }), _jsxs("select", { value: data.translationProvider || "", onChange: handleProviderChange, className: "block w-full px-3 py-2 border border-[var(--color-gray-3)] rounded-md bg-[var(--color-gray-5)] text-[var(--color-dark)] text-sm focus:outline-none focus:ring-2 focus:ring-[#9650fb] focus:border-[#9650fb] transition-colors", "data-testid": "translation-provider-select", children: [_jsx("option", { value: "", disabled: true, children: "Select a provider..." }), data.translationProviders.map((provider) => (_jsx("option", { value: provider.name, children: provider.displayName || provider.name }, provider.name)))] })] }), multiItemEnabled && (_jsxs("div", { className: "bg-background rounded-lg border border-[var(--color-gray-3)] p-4", children: [_jsxs("label", { className: "flex items-center cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: data.includeSubitems, onChange: handleSubitemsToggle, className: "h-4 w-4 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": "include-subitems-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-[var(--color-dark)] font-medium", children: "Include subitems" })] }), _jsx("p", { className: "text-xs text-[var(--color-gray-2)] mt-1.5 ml-6", children: "Also translate any child components and nested content within this item." })] })), data.translationProvider === "OpenAI" && (_jsxs("div", { className: "bg-background rounded-lg border border-[var(--color-gray-3)] p-4", children: [_jsxs("label", { className: "flex items-center cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: isPromptCustomizationEnabled, onChange: (e) => {
207
- const isChecked = e.target.checked;
208
- // Always create a new Map instance to ensure React detects the change
209
- const newServiceCustomData = new Map();
210
- if (data.serviceCustomData) {
211
- data.serviceCustomData.forEach((value, key) => {
212
- newServiceCustomData.set(key, value);
213
- });
214
- }
215
- if (isChecked) {
216
- newServiceCustomData.set("OpenAI", {
217
- enableCustomPrompt: true,
218
- customPrompt: "",
219
- promptCustomizationType: "extend",
220
- });
221
- }
222
- else {
223
- // Remove the service data when unchecked
224
- newServiceCustomData.delete("OpenAI");
119
+ const selectedCount = data.targetLanguages.length;
120
+ return (_jsx(WizardStepShell, { fillHeight: true, testId: "language-selection-step", children: _jsxs("div", { className: "mx-auto flex min-h-0 w-full max-w-3xl flex-1 flex-col overflow-hidden rounded-xl border border-gray-200 bg-white", children: [_jsxs("div", { className: "flex h-[37px] shrink-0 items-center justify-between gap-2 border-b border-gray-100 bg-gray-50/50 px-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "text-xs font-semibold tracking-wider text-gray-600 uppercase", children: "Target Languages" }), selectedCount > 0 && (_jsx("span", { className: "text-muted-foreground text-[11px] font-medium tabular-nums", children: selectedCount }))] }), allLanguages.length > 1 && (_jsxs("label", { className: "flex cursor-pointer items-center gap-1.5 text-[11px] font-medium text-gray-600 transition-colors hover:text-gray-900", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
121
+ if (input) {
122
+ input.indeterminate =
123
+ areSomeLanguagesSelected && !areAllLanguagesSelected;
225
124
  }
226
- const newData = {
227
- ...data,
228
- serviceCustomData: newServiceCustomData
229
- };
230
- setData(newData);
231
- }, className: "h-4 w-4 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": "enable-custom-prompt-checkbox" }), _jsx("span", { className: "ml-2 text-sm text-[var(--color-dark)] font-medium", children: "Customize translation prompt" })] }), _jsx("p", { className: "text-xs text-[var(--color-gray-2)] mt-1.5 ml-6", children: "Enable advanced prompt customization for the translation service." })] })), _jsxs("div", { className: "bg-background rounded-lg border border-[var(--color-gray-3)] p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("h3", { className: "text-sm font-medium text-[var(--color-dark)]", children: "Target Languages" }), allLanguages.length > 1 && (_jsxs("label", { className: "flex items-center cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: areAllLanguagesSelected, ref: (input) => {
232
- if (input) {
233
- input.indeterminate = areSomeLanguagesSelected && !areAllLanguagesSelected;
234
- }
235
- }, onChange: handleSelectAllLanguages, className: "h-4 w-4 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": "select-all-languages-checkbox" }), _jsx("span", { className: "ml-2 text-xs text-[var(--color-gray-2)]", children: "Select All" })] }))] }), _jsx("p", { className: "text-xs text-[var(--color-gray-2)] mb-3", children: "Select the languages you want to translate this content into." }), _jsx("div", { className: "border border-[var(--color-gray-3)] rounded-lg min-h-[200px] max-h-64 overflow-y-auto bg-[var(--color-gray-5)]", children: data.translationProviders.length === 0 || allLanguages.length === 0 ? (
236
- // Loading skeleton
237
- _jsx("div", { className: "p-3 space-y-2", children: [...Array(4)].map((_, i) => (_jsxs("div", { className: "flex items-center animate-pulse", children: [_jsx("div", { className: "h-4 w-4 bg-[var(--color-gray-3)] rounded" }), _jsx("div", { className: "ml-2 h-4 bg-[var(--color-gray-3)] rounded w-32" }), _jsx("div", { className: "ml-auto h-4 bg-[var(--color-gray-3)] rounded w-16" })] }, i))) })) : (_jsx("div", { className: "p-3 grid grid-cols-1 gap-2", children: allLanguages.map((lang) => {
238
- const statusLabel = lang.translationStatus?.status || "Not started";
239
- return (_jsxs("label", { className: "flex items-start gap-2 py-1.5 px-2 rounded-md hover:bg-[var(--color-gray-4)] transition-colors cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: languageSelection[lang.code] || false, onChange: () => handleLanguageToggle(lang.code), className: "mt-0.5 h-4 w-4 shrink-0 text-[#9650fb] focus:ring-[#9650fb] border-[var(--color-gray-3)] rounded accent-[#9650fb]", "data-testid": `language-checkbox-${lang.code}` }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "text-sm text-[var(--color-dark)]", children: [lang.name, " (", lang.code, ")"] }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-[var(--color-gray-2)]", children: [lang.sourceLanguage && _jsxs("span", { children: ["from: ", lang.sourceLanguage] }), !lang.hasVersions && (_jsx("span", { className: "text-2xs text-orange-600 bg-orange-100 px-2 py-1 rounded-full font-medium", children: "No versions" })), lang.hasVersions && (_jsx("span", { title: lang.translationStatus?.message, className: `text-2xs px-2 py-1 rounded-full font-medium ${getTranslationStatusBadgeClass(statusLabel)}`, "data-testid": `translation-status-badge-${lang.code}`, children: statusLabel }))] })] })] }, lang.code));
240
- }) })) })] })] }) }));
125
+ }, onChange: handleSelectAllLanguages, className: "h-3 w-3 rounded border-gray-300 text-[#9650fb] accent-[#9650fb] focus:ring-[#9650fb]", "data-testid": "select-all-languages-checkbox" }), "Select all"] }))] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-2", children: allLanguages.length === 0 ? (_jsx("div", { className: "grid gap-2 p-1 sm:grid-cols-2", children: [...Array(6)].map((_, i) => (_jsxs("div", { className: "flex animate-pulse items-center gap-2 rounded-lg border border-gray-200 px-3 py-2", children: [_jsx("div", { className: "h-3.5 w-3.5 rounded bg-gray-200" }), _jsx("div", { className: "h-4 w-4 rounded-sm bg-gray-200" }), _jsx("div", { className: "h-3.5 w-24 rounded bg-gray-200" }), _jsx("div", { className: "ml-auto h-3 w-10 rounded bg-gray-100" })] }, i))) })) : (_jsx("div", { className: "grid gap-1.5 sm:grid-cols-2", children: allLanguages.map((lang) => {
126
+ const checked = !!languageSelection[lang.code];
127
+ return (_jsxs("label", { className: `group flex cursor-pointer items-center gap-2.5 rounded-lg border px-2.5 py-1.5 transition-all ${checked
128
+ ? "border-primary/30 bg-primary/5 ring-primary/10 ring-1"
129
+ : "border-gray-200 hover:border-gray-300 hover:bg-gray-50"}`, children: [_jsx("input", { type: "checkbox", checked: checked, onChange: () => handleLanguageToggle(lang.code), className: "h-3.5 w-3.5 shrink-0 rounded border-gray-300 text-[#9650fb] accent-[#9650fb] focus:ring-[#9650fb]", "data-testid": `language-checkbox-${lang.code}` }), lang.icon ? (_jsx("img", { src: lang.icon, alt: "", "aria-hidden": "true", className: "h-4 w-5 shrink-0 rounded-sm object-cover" })) : (_jsx("span", { className: "inline-block h-4 w-5 shrink-0 rounded-sm bg-gray-100" })), _jsx("span", { className: `min-w-0 flex-1 truncate text-[13px] ${checked ? "font-medium text-gray-900" : "text-gray-700"}`, children: lang.name }), _jsx("span", { className: "shrink-0 font-mono text-[10px] tracking-wider text-gray-400 uppercase", children: lang.code })] }, lang.code));
130
+ }) })) })] }) }));
241
131
  }
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from "react";
2
+ type WizardStepShellProps = {
3
+ /** Right-aligned content above the body (badges, counts, actions). */
4
+ meta?: ReactNode;
5
+ /** When true, the body section flex-grows and content can overflow internally. */
6
+ fillHeight?: boolean;
7
+ testId?: string;
8
+ children: ReactNode;
9
+ };
10
+ /**
11
+ * Body-only scaffold for translation-wizard steps. Step title/description
12
+ * live in the wizard's stepper bar, so this just gives a consistent
13
+ * padding + optional meta row + flex/scroll behavior for the content.
14
+ */
15
+ export declare function WizardStepShell({ meta, fillHeight, testId, children, }: WizardStepShellProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
17
+ //# sourceMappingURL=WizardStepShell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WizardStepShell.d.ts","sourceRoot":"","sources":["../../src/steps/WizardStepShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,KAAK,oBAAoB,GAAG;IAC1B,sEAAsE;IACtE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kFAAkF;IAClF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,UAAkB,EAClB,MAAM,EACN,QAAQ,GACT,EAAE,oBAAoB,2CAoBtB"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Body-only scaffold for translation-wizard steps. Step title/description
4
+ * live in the wizard's stepper bar, so this just gives a consistent
5
+ * padding + optional meta row + flex/scroll behavior for the content.
6
+ */
7
+ export function WizardStepShell({ meta, fillHeight = false, testId, children, }) {
8
+ return (_jsxs("div", { className: "flex h-full flex-col gap-4 p-6", "data-testid": testId, children: [meta && (_jsx("div", { className: "flex items-center justify-end gap-2", children: meta })), _jsx("div", { className: fillHeight
9
+ ? "flex min-h-0 flex-1 flex-col gap-4"
10
+ : "flex flex-col gap-5", children: children })] }));
11
+ }
@@ -1,4 +1,5 @@
1
1
  export { ServiceLanguageSelectionStep } from "./ServiceLanguageSelectionStep";
2
+ export { ItemSelectionStep } from "./ItemSelectionStep";
2
3
  export { SubitemDiscoveryStep } from "./SubitemDiscoveryStep";
3
4
  export { MetadataInputStep } from "./MetadataInputStep";
4
5
  export * from "./types";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/steps/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/steps/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,cAAc,SAAS,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { ServiceLanguageSelectionStep } from "./ServiceLanguageSelectionStep";
2
+ export { ItemSelectionStep } from "./ItemSelectionStep";
2
3
  export { SubitemDiscoveryStep } from "./SubitemDiscoveryStep";
3
4
  export { MetadataInputStep } from "./MetadataInputStep";
4
5
  export * from "./types";
@@ -1,4 +1,4 @@
1
- import { FullItem, ItemDescriptor, EditContextType } from "@parhelia/core";
1
+ import { FullItem, ItemDescriptor, EditContextType, ItemWithSubtree, SubitemCountState } from "@parhelia/core";
2
2
  export type TranslationProviderInfo = {
3
3
  name: string;
4
4
  displayName: string;
@@ -13,6 +13,7 @@ export type TranslationStatus = {
13
13
  hash?: string;
14
14
  message?: string;
15
15
  batchId?: string;
16
+ totalCost?: number | null;
16
17
  };
17
18
  export type LanguageData = {
18
19
  name: string;
@@ -25,6 +26,8 @@ export type TranslationDialogResult = {
25
26
  targetLanguages: string[];
26
27
  includeSubitems: boolean;
27
28
  discoveredItems: FullItem[];
29
+ selectionTreeItems?: ItemWithSubtree[];
30
+ batchName?: string;
28
31
  metadata?: any;
29
32
  batchId?: string;
30
33
  };
@@ -38,8 +41,21 @@ export type TranslationWizardData = {
38
41
  items: FullItem[];
39
42
  targetLanguages: string[];
40
43
  translationProvider: string;
44
+ /** Legacy flag: true when ANY item in selectionTreeItems has includeSubitems on. */
41
45
  includeSubitems: boolean;
46
+ /** Flattened list of items to translate (root selections + expanded subtrees). */
42
47
  discoveredItems: FullItem[];
48
+ /** User's tree selection with per-item subitem flags. Authoritative for the UI. */
49
+ selectionTreeItems?: ItemWithSubtree[];
50
+ /**
51
+ * Live subitem counts streamed from the backend, keyed by item id.
52
+ * Populated by ItemSelectionTree so later steps (e.g. the Start
53
+ * Translation button) can show a running total with a "+" while
54
+ * streams are still in flight.
55
+ */
56
+ subitemCounts?: Record<string, SubitemCountState>;
57
+ /** User-editable batch name (AI-suggested initially). */
58
+ batchName?: string;
43
59
  languageData: Map<string, LanguageData>;
44
60
  translationProviders: TranslationProviderInfo[];
45
61
  itemMetadata: Map<string, Map<string, 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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"}
@@ -0,0 +1,2 @@
1
+ export declare function TranslationBatches(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=TranslationBatches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TranslationBatches.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationBatches.tsx"],"names":[],"mappings":"AA2NA,wBAAgB,kBAAkB,4CA86BjC"}