@dainamite/cpq 0.6.0 → 0.7.0

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 (164) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
  3. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
  4. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
  5. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
  6. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
  7. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
  8. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
  9. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
  10. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +2 -2
  11. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
  12. package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
  13. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
  14. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +154 -178
  15. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  16. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
  17. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
  18. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
  19. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  20. package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
  21. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
  22. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +69 -66
  23. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  24. package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
  25. package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
  26. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
  27. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
  28. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
  29. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
  30. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
  31. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
  32. package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
  33. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
  34. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
  35. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  36. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
  37. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
  38. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
  39. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
  40. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
  41. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
  42. package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
  43. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
  44. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
  45. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
  46. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +6 -3
  47. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +2 -2
  48. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +129 -94
  49. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +2 -2
  50. package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
  51. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
  52. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
  53. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
  54. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
  55. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  56. package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
  57. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
  58. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
  59. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
  60. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
  61. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
  62. package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
  63. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
  64. package/dist/modules/cpq/components/ArcLineDiff.js +2 -2
  65. package/dist/modules/cpq/components/ArcLineDiff.js.map +2 -2
  66. package/dist/modules/cpq/components/ChargeBreakdownTable.js +4 -10
  67. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +2 -2
  68. package/dist/modules/cpq/components/PriceWaterfall.js +3 -1
  69. package/dist/modules/cpq/components/PriceWaterfall.js.map +2 -2
  70. package/dist/modules/cpq/components/PricingSummaryCard.js +1 -6
  71. package/dist/modules/cpq/components/PricingSummaryCard.js.map +2 -2
  72. package/dist/modules/cpq/components/StatusPath.js +5 -3
  73. package/dist/modules/cpq/components/StatusPath.js.map +2 -2
  74. package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
  75. package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
  76. package/dist/modules/cpq/i18n/de.json +1220 -0
  77. package/dist/modules/cpq/i18n/en.json +1220 -0
  78. package/dist/modules/cpq/i18n/es.json +1220 -0
  79. package/dist/modules/cpq/i18n/pl.json +1220 -0
  80. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
  81. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
  82. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
  83. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
  84. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
  85. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
  86. package/dist/modules/cpq/widgets/injection-table.js +2 -2
  87. package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
  88. package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
  89. package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
  90. package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
  91. package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
  92. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
  93. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
  94. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
  95. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
  96. package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
  97. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
  98. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
  99. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
  100. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
  101. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
  102. package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
  103. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
  104. package/dist/modules/cpq/workflows/steps/index.js +12 -0
  105. package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
  106. package/package.json +1 -1
  107. package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
  108. package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
  109. package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
  110. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
  111. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
  112. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
  113. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
  114. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +2 -2
  115. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
  116. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
  117. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +134 -112
  118. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
  119. package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
  120. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +66 -43
  121. package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
  122. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
  123. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
  124. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
  125. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
  126. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
  127. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
  128. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
  129. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
  130. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  131. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
  132. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +3 -3
  133. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +134 -89
  134. package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
  135. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
  136. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
  137. package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
  138. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
  139. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
  140. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  141. package/src/modules/cpq/components/ArcLineDiff.tsx +2 -2
  142. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +4 -2
  143. package/src/modules/cpq/components/PriceWaterfall.tsx +4 -2
  144. package/src/modules/cpq/components/PricingSummaryCard.tsx +1 -1
  145. package/src/modules/cpq/components/StatusPath.tsx +7 -4
  146. package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
  147. package/src/modules/cpq/i18n/de.json +1220 -0
  148. package/src/modules/cpq/i18n/en.json +1220 -0
  149. package/src/modules/cpq/i18n/es.json +1220 -0
  150. package/src/modules/cpq/i18n/pl.json +1220 -0
  151. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
  152. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
  153. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
  154. package/src/modules/cpq/widgets/injection-table.ts +8 -2
  155. package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
  156. package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
  157. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
  158. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
  159. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
  160. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
  161. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
  162. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
  163. package/src/modules/cpq/workflows/steps/index.ts +12 -0
  164. package/src/modules/cpq/workflows/types.ts +4 -0
@@ -29,12 +29,24 @@ import {
29
29
  function fmt(amount, currency) {
30
30
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
31
31
  }
32
- async function apiJson(url, init) {
32
+ async function apiJson(t, url, init) {
33
33
  const res = await fetch(url, init);
34
34
  const json = await res.json();
35
- if (!res.ok) throw new Error(json?.error ?? `Request failed (${res.status})`);
35
+ if (!res.ok) throw new Error(json?.error ?? t("cpq.common.requestFailed", "Request failed ({status})", { status: res.status }));
36
36
  return json;
37
37
  }
38
+ function quoteTypeLabel(t, quoteType) {
39
+ switch (quoteType) {
40
+ case "amend":
41
+ return t("cpq.quotes.type.amend", "amend");
42
+ case "renew":
43
+ return t("cpq.quotes.type.renew", "renew");
44
+ case "cancel":
45
+ return t("cpq.quotes.type.cancel", "cancel");
46
+ default:
47
+ return quoteType;
48
+ }
49
+ }
38
50
  function MetaCard({ label, children }) {
39
51
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
40
52
  /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: label }),
@@ -42,6 +54,7 @@ function MetaCard({ label, children }) {
42
54
  ] });
43
55
  }
44
56
  function MoreActionsDropdown({ items }) {
57
+ const t = useT();
45
58
  const [open, setOpen] = React.useState(false);
46
59
  const ref = React.useRef(null);
47
60
  React.useEffect(() => {
@@ -55,7 +68,7 @@ function MoreActionsDropdown({ items }) {
55
68
  if (items.length === 0) return null;
56
69
  return /* @__PURE__ */ jsxs("div", { className: "relative", ref, children: [
57
70
  /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: () => setOpen((v) => !v), children: [
58
- "Actions",
71
+ t("cpq.quotes.detail.actions", "Actions"),
59
72
  /* @__PURE__ */ jsx("svg", { className: "h-3 w-3 ml-1", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" }) })
60
73
  ] }),
61
74
  open && /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-full mt-1 z-50 min-w-[200px] rounded-md border bg-popover shadow-md py-1", children: items.map((item) => {
@@ -81,6 +94,7 @@ function MoreActionsDropdown({ items }) {
81
94
  ] });
82
95
  }
83
96
  function buildMoreActions({
97
+ t,
84
98
  recalculate,
85
99
  canRecalculate,
86
100
  submitting,
@@ -92,7 +106,7 @@ function buildMoreActions({
92
106
  if (canRecalculate) {
93
107
  items.push({
94
108
  id: "recalculate",
95
- label: "Recalculate",
109
+ label: t("cpq.quotes.detail.recalculate", "Recalculate"),
96
110
  onSelect: recalculate,
97
111
  loading: submitting,
98
112
  disabled: submitting
@@ -101,7 +115,7 @@ function buildMoreActions({
101
115
  if (deletable) {
102
116
  items.push({
103
117
  id: "delete",
104
- label: "Delete",
118
+ label: t("cpq.common.delete", "Delete"),
105
119
  onSelect: onDelete,
106
120
  disabled: deleting,
107
121
  loading: deleting
@@ -162,11 +176,11 @@ function CpqQuoteDetailPage(props) {
162
176
  }, [cpqConfigId, isNew, loadArcTargets]);
163
177
  const reloadQuote = React.useCallback(async () => {
164
178
  try {
165
- setCpqQuote(await apiJson(`/api/cpq/quotes/${cpqConfigId}`));
179
+ setCpqQuote(await apiJson(t, `/api/cpq/quotes/${cpqConfigId}`));
166
180
  } catch (err) {
167
181
  setError(err.message);
168
182
  }
169
- }, [cpqConfigId]);
183
+ }, [cpqConfigId, t]);
170
184
  const priceAdjustments = usePriceAdjustments({
171
185
  quote: isNew ? null : cpqQuote,
172
186
  quoteType: arcQuoteType,
@@ -179,7 +193,7 @@ function CpqQuoteDetailPage(props) {
179
193
  const quoteId = searchParams.get("quoteId");
180
194
  const customerId = searchParams.get("customerId");
181
195
  if (!customerId) {
182
- setError("customerId is required to create a CPQ quote");
196
+ setError(t("cpq.quotes.detail.customerIdRequired", "customerId is required to create a CPQ quote"));
183
197
  setLoading(false);
184
198
  return;
185
199
  }
@@ -187,7 +201,7 @@ function CpqQuoteDetailPage(props) {
187
201
  try {
188
202
  const body = { customerId };
189
203
  if (quoteId) body.quoteId = quoteId;
190
- const result = await apiJson("/api/cpq/quotes", {
204
+ const result = await apiJson(t, "/api/cpq/quotes", {
191
205
  method: "POST",
192
206
  headers: { "Content-Type": "application/json" },
193
207
  body: JSON.stringify(body)
@@ -204,7 +218,7 @@ function CpqQuoteDetailPage(props) {
204
218
  let cancelled = false;
205
219
  async function load() {
206
220
  try {
207
- const result = await apiJson(`/api/cpq/quotes/${cpqConfigId}`);
221
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqConfigId}`);
208
222
  if (!cancelled) setCpqQuote(result);
209
223
  } catch (err) {
210
224
  if (!cancelled) setError(err.message);
@@ -216,11 +230,11 @@ function CpqQuoteDetailPage(props) {
216
230
  return () => {
217
231
  cancelled = true;
218
232
  };
219
- }, [cpqConfigId, isNew, searchParams, router]);
233
+ }, [cpqConfigId, isNew, searchParams, router, t]);
220
234
  const loadOfferings = async () => {
221
235
  setOfferingsLoading(true);
222
236
  try {
223
- const result = await apiJson("/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
237
+ const result = await apiJson(t, "/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
224
238
  setOfferings(result.items ?? []);
225
239
  } catch {
226
240
  setOfferings([]);
@@ -232,7 +246,7 @@ function CpqQuoteDetailPage(props) {
232
246
  try {
233
247
  const params = new URLSearchParams({ offeringId });
234
248
  if (Object.keys(currentConfig).length > 0) params.set("configuration", JSON.stringify(currentConfig));
235
- const result = await apiJson(`/api/cpq/quotes/attributes?${params}`);
249
+ const result = await apiJson(t, `/api/cpq/quotes/attributes?${params}`);
236
250
  setAttributes(result.attributes ?? []);
237
251
  } catch {
238
252
  setAttributes([]);
@@ -249,7 +263,7 @@ function CpqQuoteDetailPage(props) {
249
263
  const body = { offeringId: selectedOffering.id, configuration: config, quantity };
250
264
  if (addingToParentLineId) body.parentLineId = addingToParentLineId;
251
265
  if (tid) body.targetSubscriptionId = tid;
252
- lastResult = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items`, {
266
+ lastResult = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/items`, {
253
267
  method: "POST",
254
268
  headers: { "Content-Type": "application/json" },
255
269
  body: JSON.stringify(body)
@@ -269,7 +283,7 @@ function CpqQuoteDetailPage(props) {
269
283
  try {
270
284
  setSubmitting(true);
271
285
  setError(null);
272
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, {
286
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, {
273
287
  method: "PUT",
274
288
  headers: { "Content-Type": "application/json" },
275
289
  body: JSON.stringify({ configuration: config, quantity })
@@ -295,7 +309,7 @@ function CpqQuoteDetailPage(props) {
295
309
  try {
296
310
  setSubmitting(true);
297
311
  setError(null);
298
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/recalculate`, {
312
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/recalculate`, {
299
313
  method: "POST",
300
314
  headers: { "Content-Type": "application/json" },
301
315
  body: JSON.stringify({ save: true })
@@ -312,7 +326,7 @@ function CpqQuoteDetailPage(props) {
312
326
  try {
313
327
  setConverting(true);
314
328
  setError(null);
315
- const result = await apiJson("/api/cpq/orders", {
329
+ const result = await apiJson(t, "/api/cpq/orders", {
316
330
  method: "POST",
317
331
  headers: { "Content-Type": "application/json" },
318
332
  body: JSON.stringify({ quoteId: cpqQuote.id })
@@ -329,7 +343,7 @@ function CpqQuoteDetailPage(props) {
329
343
  try {
330
344
  setTransitioning(true);
331
345
  setError(null);
332
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/status`, {
346
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/status`, {
333
347
  method: "POST",
334
348
  headers: { "Content-Type": "application/json" },
335
349
  body: JSON.stringify({ targetStatus })
@@ -347,7 +361,7 @@ function CpqQuoteDetailPage(props) {
347
361
  setDeleting(true);
348
362
  setError(null);
349
363
  setShowDeleteConfirm(false);
350
- await apiJson(`/api/cpq/quotes/${cpqQuote.id}`, { method: "DELETE" });
364
+ await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}`, { method: "DELETE" });
351
365
  router.push("/backend/cpq/quotes");
352
366
  } catch (err) {
353
367
  setError(err.message);
@@ -381,7 +395,7 @@ function CpqQuoteDetailPage(props) {
381
395
  setAddingTargetSubIds(inherited ? [inherited] : []);
382
396
  if (offeringId) {
383
397
  try {
384
- const result = await apiJson("/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
398
+ const result = await apiJson(t, "/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
385
399
  const match = (result.items ?? []).find((o) => o.id === offeringId);
386
400
  if (match) {
387
401
  selectOffering(match);
@@ -417,14 +431,15 @@ function CpqQuoteDetailPage(props) {
417
431
  if (loading) {
418
432
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-12 justify-center text-sm text-muted-foreground", children: [
419
433
  /* @__PURE__ */ jsx(Spinner, {}),
420
- " Loading\u2026"
434
+ " ",
435
+ t("cpq.quotes.detail.loading", "Loading\u2026")
421
436
  ] });
422
437
  }
423
438
  if (!cpqQuote) {
424
439
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
425
- /* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/quotes"), className: "text-sm text-muted-foreground hover:text-foreground", children: "\u2190 Back to Quotes" }),
440
+ /* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/quotes"), className: "text-sm text-muted-foreground hover:text-foreground", children: t("cpq.quotes.detail.backToQuotes", "\u2190 Back to Quotes") }),
426
441
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
427
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "Quote not found." })
442
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.quotes.detail.notFound", "Quote not found.") })
428
443
  ] });
429
444
  }
430
445
  const { pricingSummary: summary, currencyCode: currency } = cpqQuote;
@@ -452,7 +467,7 @@ function CpqQuoteDetailPage(props) {
452
467
  onChanged: async () => {
453
468
  await loadArcTargets();
454
469
  try {
455
- const fresh = await apiJson(`/api/cpq/quotes/${cpqQuote.id}`);
470
+ const fresh = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}`);
456
471
  setCpqQuote(fresh);
457
472
  } catch {
458
473
  }
@@ -464,13 +479,13 @@ function CpqQuoteDetailPage(props) {
464
479
  {
465
480
  mode: "detail",
466
481
  backHref: "/backend/cpq/quotes",
467
- backLabel: "Back to CPQ Quotes",
468
- entityTypeLabel: "CPQ Quote",
469
- title: cpqQuote.quoteNumber || cpqQuote.customerName || "Untitled",
482
+ backLabel: t("cpq.quotes.detail.backLabel", "Back to CPQ Quotes"),
483
+ entityTypeLabel: t("cpq.quotes.detail.entityType", "CPQ Quote"),
484
+ title: cpqQuote.quoteNumber || cpqQuote.customerName || t("cpq.quotes.detail.untitled", "Untitled"),
470
485
  statusBadge: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
471
486
  /* @__PURE__ */ jsx(Tag, { variant: quoteCpqStatusMap[cpqQuote.cpqStatus] ?? "neutral", dot: true, children: formatStatusLabel(cpqQuote.cpqStatus) }),
472
- arcQuoteType !== "new" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "uppercase", children: arcQuoteType }),
473
- cpqQuote.convertedOrderId && /* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: "Converted" })
487
+ arcQuoteType !== "new" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "uppercase", children: quoteTypeLabel(t, arcQuoteType) }),
488
+ cpqQuote.convertedOrderId && /* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: t("cpq.quotes.detail.converted", "Converted") })
474
489
  ] }),
475
490
  actionsContent: /* @__PURE__ */ jsxs(Fragment, { children: [
476
491
  /* @__PURE__ */ jsxs(
@@ -481,7 +496,7 @@ function CpqQuoteDetailPage(props) {
481
496
  onClick: () => router.push(`/backend/sales/quotes/${cpqQuote.quoteId}`),
482
497
  children: [
483
498
  /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5" }),
484
- "View Sales Quote"
499
+ t("cpq.quotes.detail.viewSalesQuote", "View Sales Quote")
485
500
  ]
486
501
  }
487
502
  ),
@@ -492,26 +507,27 @@ function CpqQuoteDetailPage(props) {
492
507
  onClick: () => router.push(`/backend/cpq/orders/${cpqQuote.convertedOrderId}`),
493
508
  children: [
494
509
  /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
495
- "Go to order"
510
+ t("cpq.quotes.detail.goToOrder", "Go to order")
496
511
  ]
497
512
  }
498
513
  ) : view !== "summary" ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
499
514
  resetConfigState();
500
515
  setView("summary");
501
- }, children: "Cancel" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
516
+ }, children: t("cpq.common.cancel", "Cancel") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
502
517
  /* @__PURE__ */ jsxs(Button, { type: "button", onClick: startAddOffering, children: [
503
518
  /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
504
- "Add Offering"
519
+ t("cpq.quotes.detail.addOffering", "Add Offering")
505
520
  ] }),
506
- arcEditable && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setArcDrawerOpen(true), children: "Modify Subscription" }),
521
+ arcEditable && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setArcDrawerOpen(true), children: t("cpq.quotes.detail.modifySubscription", "Modify Subscription") }),
507
522
  cpqQuote.cpqStatus === "accepted" && /* @__PURE__ */ jsxs(Button, { type: "button", onClick: convertToOrder, disabled: converting, children: [
508
523
  converting ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
509
- "Convert to Order"
524
+ t("cpq.quotes.detail.convertToOrder", "Convert to Order")
510
525
  ] }),
511
526
  /* @__PURE__ */ jsx(
512
527
  MoreActionsDropdown,
513
528
  {
514
529
  items: buildMoreActions({
530
+ t,
515
531
  recalculate,
516
532
  canRecalculate: canRecalculateQuote(grantedFeatures, cpqQuote.cpqStatus),
517
533
  submitting,
@@ -535,19 +551,16 @@ function CpqQuoteDetailPage(props) {
535
551
  statusMap: quoteCpqStatusMap,
536
552
  onTransition: transitionStatus,
537
553
  disabled: transitioning,
538
- ariaLabel: "Quote status path"
554
+ ariaLabel: t("cpq.quotes.detail.statusPath", "Quote status path")
539
555
  }
540
556
  ),
541
557
  transitioning && /* @__PURE__ */ jsx(Spinner, {})
542
558
  ] }),
543
559
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error, onDismiss: () => setError(null) }),
544
560
  arcQuoteType !== "new" && arcTargets.length > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-brand-violet/30 bg-brand-violet/10 px-4 py-3 text-sm text-brand-violet", children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
545
- /* @__PURE__ */ jsx("span", { className: "font-medium uppercase", children: arcQuoteType }),
561
+ /* @__PURE__ */ jsx("span", { className: "font-medium uppercase", children: quoteTypeLabel(t, arcQuoteType) }),
546
562
  " \u2022 ",
547
- arcTargets.length,
548
- " target",
549
- arcTargets.length === 1 ? "" : "s",
550
- ":",
563
+ arcTargets.length === 1 ? t("cpq.quotes.detail.arcTargets.one", "{count} target:", { count: arcTargets.length }) : t("cpq.quotes.detail.arcTargets.other", "{count} targets:", { count: arcTargets.length }),
551
564
  " ",
552
565
  arcTargets.map((t2, i) => /* @__PURE__ */ jsxs("span", { children: [
553
566
  i > 0 && ", ",
@@ -560,34 +573,35 @@ function CpqQuoteDetailPage(props) {
560
573
  }
561
574
  )
562
575
  ] }, t2.id)),
563
- isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: "\u2022 Merging into a new contract" })
576
+ isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: t("cpq.quotes.detail.mergingRenew", "\u2022 Merging into a new contract") })
564
577
  ] }) }) }),
565
578
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: [
566
- /* @__PURE__ */ jsx(MetaCard, { label: "Customer", children: cpqQuote.customerName ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.customerName }) : /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
579
+ /* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.customer", "Customer"), children: cpqQuote.customerName ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.customerName }) : /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
567
580
  cpqQuote.customerId.slice(0, 8),
568
581
  "\u2026"
569
582
  ] }) }),
570
- /* @__PURE__ */ jsx(MetaCard, { label: "Version", children: /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
583
+ /* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.version", "Version"), children: /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
571
584
  "v",
572
585
  cpqQuote.version
573
586
  ] }) }),
574
- /* @__PURE__ */ jsx(MetaCard, { label: "Currency", children: /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency }) })
587
+ /* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.currency", "Currency"), children: /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency }) })
575
588
  ] }),
576
589
  showDeleteConfirm && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setShowDeleteConfirm(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-lg border bg-card shadow-xl", onClick: (e) => e.stopPropagation(), children: [
577
- /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Delete Quote" }) }),
578
- /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Are you sure you want to delete this quote? This will soft-delete the quote and all its line items." }) }),
590
+ /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.quotes.detail.deleteConfirm.title", "Delete Quote") }) }),
591
+ /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.quotes.detail.deleteConfirm.text", "Are you sure you want to delete this quote? This will soft-delete the quote and all its line items.") }) }),
579
592
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-4 py-3 border-t", children: [
580
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setShowDeleteConfirm(false), children: "Cancel" }),
593
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setShowDeleteConfirm(false), children: t("cpq.common.cancel", "Cancel") }),
581
594
  /* @__PURE__ */ jsxs(Button, { type: "button", variant: "destructive", onClick: deleteQuote, disabled: deleting, children: [
582
595
  deleting ? /* @__PURE__ */ jsx(Spinner, {}) : null,
583
- " Delete"
596
+ " ",
597
+ t("cpq.common.delete", "Delete")
584
598
  ] })
585
599
  ] })
586
600
  ] }) }),
587
601
  view === "summary" && /* @__PURE__ */ jsxs(Fragment, { children: [
588
602
  /* @__PURE__ */ jsx(PricingSummaryCard, { summary, currency, arcDelta }),
589
603
  !cpqQuote.validationResult.valid && /* @__PURE__ */ jsxs(Alert, { variant: "warning", children: [
590
- /* @__PURE__ */ jsx(AlertTitle, { children: "Validation Issues" }),
604
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("cpq.quotes.detail.validationIssues", "Validation Issues") }),
591
605
  /* @__PURE__ */ jsx(AlertDescription, { children: /* @__PURE__ */ jsx("span", { className: "text-xs space-y-0.5 block", children: cpqQuote.validationResult.errors.map((e, i) => /* @__PURE__ */ jsxs("span", { className: "block", children: [
592
606
  "\u2014 ",
593
607
  e.message
@@ -631,8 +645,9 @@ function CpqQuoteDetailPage(props) {
631
645
  ] }),
632
646
  view === "add-offering" && /* @__PURE__ */ jsxs(Fragment, { children: [
633
647
  addingToParentLineId && /* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
634
- "Adding component to bundle: ",
635
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.lines.find((l) => l.lineId === addingToParentLineId)?.offeringName ?? "Bundle" })
648
+ t("cpq.quotes.detail.addingComponentTo", "Adding component to bundle:"),
649
+ " ",
650
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.lines.find((l) => l.lineId === addingToParentLineId)?.offeringName ?? t("cpq.quotes.detail.bundleFallback", "Bundle") })
636
651
  ] }),
637
652
  /* @__PURE__ */ jsx(
638
653
  OfferingBrowser,
@@ -648,7 +663,7 @@ function CpqQuoteDetailPage(props) {
648
663
  view === "configure" && selectedOffering && /* @__PURE__ */ jsx(
649
664
  ConfigurePanel,
650
665
  {
651
- title: addingToParentLineId ? `Add Component: ${selectedOffering.name}` : `Configure: ${selectedOffering.name}`,
666
+ title: addingToParentLineId ? t("cpq.quotes.detail.configure.addComponentTitle", "Add Component: {name}", { name: selectedOffering.name }) : t("cpq.quotes.detail.configure.configureTitle", "Configure: {name}", { name: selectedOffering.name }),
652
667
  attributes,
653
668
  config,
654
669
  quantity,
@@ -670,14 +685,16 @@ function CpqQuoteDetailPage(props) {
670
685
  },
671
686
  onQuantityChange: setQuantity,
672
687
  onSubmit: addItem,
673
- submitLabel: "Add to Quote",
688
+ submitLabel: t("cpq.quotes.detail.configure.addToQuote", "Add to Quote"),
674
689
  submitting
675
690
  }
676
691
  ),
677
692
  view === "edit" && editingLineId && /* @__PURE__ */ jsx(
678
693
  ConfigurePanel,
679
694
  {
680
- title: `Edit: ${cpqQuote.lines.find((l) => l.lineId === editingLineId)?.offeringName ?? "Line"}`,
695
+ title: t("cpq.quotes.detail.configure.editTitle", "Edit: {name}", {
696
+ name: cpqQuote.lines.find((l) => l.lineId === editingLineId)?.offeringName ?? t("cpq.quotes.detail.configure.lineFallback", "Line")
697
+ }),
681
698
  attributes,
682
699
  config,
683
700
  quantity,
@@ -693,13 +710,14 @@ function CpqQuoteDetailPage(props) {
693
710
  },
694
711
  onQuantityChange: setQuantity,
695
712
  onSubmit: () => updateItem(editingLineId),
696
- submitLabel: "Update Line",
713
+ submitLabel: t("cpq.quotes.detail.configure.updateLine", "Update Line"),
697
714
  submitting
698
715
  }
699
716
  )
700
717
  ] }) }) });
701
718
  }
702
719
  function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
720
+ const t = useT();
703
721
  const [bundleTrees, setBundleTrees] = React.useState(/* @__PURE__ */ new Map());
704
722
  const [loadingTrees, setLoadingTrees] = React.useState(/* @__PURE__ */ new Set());
705
723
  const rootLines = lines.filter((l) => !l.parentLineId);
@@ -715,7 +733,7 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
715
733
  if (bundleTrees.has(offeringId) || loadingTrees.has(offeringId)) return;
716
734
  setLoadingTrees((prev) => new Set(prev).add(offeringId));
717
735
  try {
718
- const result = await apiJson(`/api/cpq/bundle-tree?offeringId=${offeringId}`);
736
+ const result = await apiJson(t, `/api/cpq/bundle-tree?offeringId=${offeringId}`);
719
737
  setBundleTrees((prev) => new Map(prev).set(offeringId, result.tree));
720
738
  } catch {
721
739
  } finally {
@@ -725,10 +743,10 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
725
743
  return n;
726
744
  });
727
745
  }
728
- }, [bundleTrees, loadingTrees]);
746
+ }, [bundleTrees, loadingTrees, t]);
729
747
  return /* @__PURE__ */ jsxs("div", { children: [
730
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: "Line Items" }),
731
- lines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: 'No items configured yet. Click "Add Offering" to start.' }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: rootLines.map((line) => {
748
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: t("cpq.quotes.detail.lineItems", "Line Items") }),
749
+ lines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: t("cpq.quotes.detail.noLines", 'No items configured yet. Click "Add Offering" to start.') }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: rootLines.map((line) => {
732
750
  const lineIdx = lines.indexOf(line);
733
751
  const isBundle = line.offeringType === "bundle";
734
752
  const children = childrenByParent.get(line.lineId) ?? [];
@@ -782,10 +800,12 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
782
800
  ] });
783
801
  }
784
802
  function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency, expandedLines, pricingDetail, parentLineId, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
803
+ const t = useT();
785
804
  if (treeLoading) {
786
805
  return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/5 px-8 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
787
806
  /* @__PURE__ */ jsx(Spinner, {}),
788
- " Loading bundle structure\u2026"
807
+ " ",
808
+ t("cpq.quotes.bundle.loadingStructure", "Loading bundle structure\u2026")
789
809
  ] }) });
790
810
  }
791
811
  if (!bundleTree || bundleTree.slots.length === 0) {
@@ -826,12 +846,13 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
826
846
  return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-b-0", children: [
827
847
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-6 py-2 bg-muted/20", children: [
828
848
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: slot.name }),
829
- isRequired ? /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "text-[10px] px-1.5", children: "required" }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "text-[10px] px-1.5", children: "optional" }),
849
+ isRequired ? /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "text-[10px] px-1.5", children: t("cpq.quotes.bundle.slotRequired", "required") }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "text-[10px] px-1.5", children: t("cpq.quotes.bundle.slotOptional", "optional") }),
830
850
  /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
831
851
  filledCount,
832
852
  "/",
833
853
  slot.cardinalityMax ?? "\u221E",
834
- slot.cardinalityMin > 0 && ` (min ${slot.cardinalityMin})`
854
+ slot.cardinalityMin > 0 && " ",
855
+ slot.cardinalityMin > 0 && t("cpq.quotes.bundle.slotMin", "(min {min})", { min: slot.cardinalityMin })
835
856
  ] }),
836
857
  /* @__PURE__ */ jsx("div", { className: "flex-1" }),
837
858
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: slot.components.map((comp) => {
@@ -842,7 +863,7 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
842
863
  onClick: () => !isFilled && canAdd && onAddComponent(parentLineId, comp.childOffering.offeringId),
843
864
  disabled: isFilled || !canAdd,
844
865
  className: `inline-flex items-center gap-1 rounded border px-2 py-1 text-[11px] font-medium transition-colors ${isFilled ? "border-status-success-border bg-status-success-bg text-status-success-text cursor-default" : canAdd ? "border-border hover:border-primary hover:bg-primary/5 hover:text-primary cursor-pointer" : "border-border text-muted-foreground opacity-50 cursor-not-allowed"}`,
845
- title: isFilled ? "Already added" : canAdd ? `Add ${comp.childOffering.offeringName}` : "Slot is full",
866
+ title: isFilled ? t("cpq.quotes.bundle.alreadyAdded", "Already added") : canAdd ? t("cpq.quotes.bundle.addComponent", "Add {name}", { name: comp.childOffering.offeringName }) : t("cpq.quotes.bundle.slotFull", "Slot is full"),
846
867
  children: [
847
868
  isFilled ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }),
848
869
  comp.childOffering.offeringName
@@ -917,7 +938,7 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
917
938
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
918
939
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
919
940
  indent > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "\u2514" }),
920
- isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: "bundle" }),
941
+ isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: t("cpq.lines.bundle", "bundle") }),
921
942
  /* @__PURE__ */ jsx("span", { className: `font-medium text-sm ${isRemoved ? "text-muted-foreground line-through" : ""}`, children: line.offeringName }),
922
943
  line.quantity > 1 && /* @__PURE__ */ jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs font-medium", children: [
923
944
  "\xD7",
@@ -948,33 +969,38 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
948
969
  {
949
970
  variant: "brand",
950
971
  className: "px-1.5 text-xs",
951
- title: `Acts on subscription ${targetCodes.get(line.targetSubscriptionId)}`,
972
+ title: t("cpq.quotes.detail.actsOnSubscription", "Acts on subscription {code}", { code: targetCodes.get(line.targetSubscriptionId) ?? "" }),
952
973
  children: [
953
974
  "\u2192 ",
954
975
  targetCodes.get(line.targetSubscriptionId)
955
976
  ]
956
977
  }
957
978
  ),
958
- isRemoved ? null : line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: "configured" }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: "incomplete" }),
959
- isBundle && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
960
- childCount,
961
- " component",
962
- childCount !== 1 ? "s" : ""
963
- ] })
979
+ isRemoved ? null : line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: t("cpq.quotes.detail.lineConfigured", "configured") }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: t("cpq.quotes.detail.lineIncomplete", "incomplete") }),
980
+ isBundle && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: childCount !== 1 ? t("cpq.lines.componentCount.other", "{count} components", { count: childCount }) : t("cpq.lines.componentCount.one", "{count} component", { count: childCount }) })
964
981
  ] }),
965
982
  line.validationErrors && line.validationErrors.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-0.5", children: line.validationErrors[0].message })
966
983
  ] }),
967
984
  /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right text-xs space-y-0.5", children: [
968
985
  isRemoved && line.arcSource && line.arcSource.mrcAmount > 0 && /* @__PURE__ */ jsxs("div", { children: [
969
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "MRC " }),
986
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
987
+ t("cpq.lines.mrc", "MRC"),
988
+ " "
989
+ ] }),
970
990
  /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground line-through", children: fmt(line.arcSource.mrcAmount, currency) })
971
991
  ] }),
972
992
  line.nrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
973
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "NRC " }),
993
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
994
+ t("cpq.lines.nrc", "NRC"),
995
+ " "
996
+ ] }),
974
997
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.nrcTotal, currency) })
975
998
  ] }),
976
999
  line.mrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
977
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "MRC " }),
1000
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1001
+ t("cpq.lines.mrc", "MRC"),
1002
+ " "
1003
+ ] }),
978
1004
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.mrcTotal, currency) })
979
1005
  ] })
980
1006
  ] }),
@@ -994,9 +1020,9 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
994
1020
  }
995
1021
  ) }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
996
1022
  /* @__PURE__ */ jsx(DiscountLineAction, { line }),
997
- onAddComponent && /* @__PURE__ */ jsx("button", { onClick: onAddComponent, className: "rounded p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors", title: "Add Component", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }) }),
998
- /* @__PURE__ */ jsx("button", { onClick: onEdit, className: "rounded p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors", title: "Edit", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" }) }) }),
999
- /* @__PURE__ */ jsx("button", { onClick: onRemove, className: "rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors", title: "Remove", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }) })
1023
+ onAddComponent && /* @__PURE__ */ jsx("button", { onClick: onAddComponent, className: "rounded p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors", title: t("cpq.quotes.detail.addComponent", "Add Component"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }) }),
1024
+ /* @__PURE__ */ jsx("button", { onClick: onEdit, className: "rounded p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors", title: t("cpq.common.edit", "Edit"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" }) }) }),
1025
+ /* @__PURE__ */ jsx("button", { onClick: onRemove, className: "rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors", title: t("cpq.quotes.detail.removeLine", "Remove"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }) })
1000
1026
  ] })
1001
1027
  ] }),
1002
1028
  isExpanded && !isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
@@ -1006,20 +1032,24 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
1006
1032
  ] });
1007
1033
  }
1008
1034
  function LineSummaryFallback({ line, currency }) {
1035
+ const t = useT();
1009
1036
  const hasTotals = line.nrcTotal > 0 || line.mrcTotal > 0;
1010
1037
  const mirroredFromItemId = line.configuration?._arcMirroredFromItemId ?? null;
1011
1038
  return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/10 px-12 py-2 text-xs text-muted-foreground space-y-1", children: [
1012
1039
  /* @__PURE__ */ jsxs("div", { children: [
1013
- "No detailed charge breakdown available for this line.",
1014
- mirroredFromItemId && " Mirrored from existing subscription item."
1040
+ t("cpq.quotes.detail.noBreakdown", "No detailed charge breakdown available for this line."),
1041
+ mirroredFromItemId && " ",
1042
+ mirroredFromItemId && t("cpq.orders.detail.mirroredItem", "Mirrored from existing subscription item.")
1015
1043
  ] }),
1016
1044
  hasTotals && /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
1017
1045
  line.nrcTotal > 0 && /* @__PURE__ */ jsxs("span", { children: [
1018
- "NRC ",
1046
+ t("cpq.lines.nrc", "NRC"),
1047
+ " ",
1019
1048
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground", children: fmt(line.nrcTotal, currency) })
1020
1049
  ] }),
1021
1050
  line.mrcTotal > 0 && /* @__PURE__ */ jsxs("span", { children: [
1022
- "MRC ",
1051
+ t("cpq.lines.mrc", "MRC"),
1052
+ " ",
1023
1053
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground", children: fmt(line.mrcTotal, currency) })
1024
1054
  ] })
1025
1055
  ] })
@@ -1043,6 +1073,7 @@ function Spinner() {
1043
1073
  ] });
1044
1074
  }
1045
1075
  function ErrorBanner({ message, onDismiss }) {
1076
+ const t = useT();
1046
1077
  return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", className: "flex items-start gap-2", children: [
1047
1078
  /* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
1048
1079
  onDismiss && /* @__PURE__ */ jsx(
@@ -1050,27 +1081,28 @@ function ErrorBanner({ message, onDismiss }) {
1050
1081
  {
1051
1082
  onClick: onDismiss,
1052
1083
  className: "text-destructive/70 hover:text-destructive text-sm font-bold",
1053
- "aria-label": "Dismiss",
1084
+ "aria-label": t("cpq.common.dismiss", "Dismiss"),
1054
1085
  children: "\xD7"
1055
1086
  }
1056
1087
  )
1057
1088
  ] });
1058
1089
  }
1059
1090
  function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect }) {
1091
+ const t = useT();
1060
1092
  const filtered = offerings.filter((o) => {
1061
1093
  if (!search.trim()) return true;
1062
1094
  const q = search.toLowerCase();
1063
1095
  return o.name.toLowerCase().includes(q) || o.code.toLowerCase().includes(q) || (o.description ?? "").toLowerCase().includes(q);
1064
1096
  });
1065
1097
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1066
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium px-1", children: "Select Product Offering" }),
1098
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium px-1", children: t("cpq.quotes.offeringBrowser.title", "Select Product Offering") }),
1067
1099
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1068
1100
  /* @__PURE__ */ jsx("svg", { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) }),
1069
1101
  /* @__PURE__ */ jsx(
1070
1102
  "input",
1071
1103
  {
1072
1104
  type: "text",
1073
- placeholder: "Search offerings\u2026",
1105
+ placeholder: t("cpq.quotes.offeringBrowser.searchPlaceholder", "Search offerings\u2026"),
1074
1106
  value: search,
1075
1107
  onChange: (e) => onSearchChange(e.target.value),
1076
1108
  className: "w-full rounded-md border bg-background pl-10 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50"
@@ -1079,8 +1111,9 @@ function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect
1079
1111
  ] }),
1080
1112
  loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-8 justify-center text-sm text-muted-foreground", children: [
1081
1113
  /* @__PURE__ */ jsx(Spinner, {}),
1082
- " Loading offerings\u2026"
1083
- ] }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: search ? "No offerings match your search." : "No active offerings available." }) : /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((offering) => /* @__PURE__ */ jsxs(
1114
+ " ",
1115
+ t("cpq.quotes.offeringBrowser.loading", "Loading offerings\u2026")
1116
+ ] }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: search ? t("cpq.quotes.offeringBrowser.noMatch", "No offerings match your search.") : t("cpq.quotes.offeringBrowser.empty", "No active offerings available.") }) : /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((offering) => /* @__PURE__ */ jsxs(
1084
1117
  "button",
1085
1118
  {
1086
1119
  onClick: () => onSelect(offering),
@@ -1096,13 +1129,14 @@ function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect
1096
1129
  ] });
1097
1130
  }
1098
1131
  function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions, arcSelectedTargetIds, onArcTargetToggle, onConfigChange, onQuantityChange, onSubmit, submitLabel, submitting }) {
1132
+ const t = useT();
1099
1133
  const noTargetsPicked = arcTargetOptions !== null && arcTargetOptions.length > 0 && arcSelectedTargetIds.length === 0;
1100
1134
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card", children: [
1101
1135
  /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: title }) }),
1102
1136
  /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4", children: [
1103
1137
  arcTargetOptions && arcTargetOptions.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1104
1138
  /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium mb-1", children: [
1105
- "Apply to subscription(s)",
1139
+ t("cpq.quotes.detail.configure.applyToSubscriptions", "Apply to subscription(s)"),
1106
1140
  /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
1107
1141
  ] }),
1108
1142
  /* @__PURE__ */ jsx("div", { className: "space-y-1.5 rounded-md border bg-background px-3 py-2", children: arcTargetOptions.map((opt) => {
@@ -1131,11 +1165,11 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
1131
1165
  opt.subscriptionId
1132
1166
  );
1133
1167
  }) }),
1134
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Pick one or more \u2014 a separate line will be created on each selected subscription." }),
1135
- noTargetsPicked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1", children: "Select at least one target." })
1168
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t("cpq.quotes.detail.configure.applyToSubscriptionsHint", "Pick one or more \u2014 a separate line will be created on each selected subscription.") }),
1169
+ noTargetsPicked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1", children: t("cpq.quotes.detail.configure.selectTarget", "Select at least one target.") })
1136
1170
  ] }),
1137
1171
  /* @__PURE__ */ jsxs("div", { children: [
1138
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Quantity" }),
1172
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.quotes.detail.configure.quantity", "Quantity") }),
1139
1173
  /* @__PURE__ */ jsx(
1140
1174
  NumberInput,
1141
1175
  {
@@ -1147,7 +1181,7 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
1147
1181
  }
1148
1182
  )
1149
1183
  ] }),
1150
- attributes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No configurable attributes for this offering." }) : attributes.map((attr) => /* @__PURE__ */ jsx(AttributeField, { attribute: attr, value: config[attr.code], onChange: (v) => onConfigChange(attr.code, v) }, attr.code))
1184
+ attributes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.quotes.detail.configure.noAttributes", "No configurable attributes for this offering.") }) : attributes.map((attr) => /* @__PURE__ */ jsx(AttributeField, { attribute: attr, value: config[attr.code], onChange: (v) => onConfigChange(attr.code, v) }, attr.code))
1151
1185
  ] }),
1152
1186
  /* @__PURE__ */ jsx("div", { className: "border-t px-4 py-3 flex justify-end", children: /* @__PURE__ */ jsxs(
1153
1187
  "button",
@@ -1165,6 +1199,7 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
1165
1199
  ] });
1166
1200
  }
1167
1201
  function AttributeField({ attribute, value, onChange }) {
1202
+ const t = useT();
1168
1203
  const { code, name, attributeType, isRequired, options, constraints, helpText, defaultValue } = attribute;
1169
1204
  const currentValue = value ?? defaultValue ?? "";
1170
1205
  return /* @__PURE__ */ jsxs("div", { children: [
@@ -1179,13 +1214,13 @@ function AttributeField({ attribute, value, onChange }) {
1179
1214
  onChange: (e) => onChange(e.target.value),
1180
1215
  className: "w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50",
1181
1216
  children: [
1182
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Select \u2014" }),
1217
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.quotes.detail.configure.selectPlaceholder", "\u2014 Select \u2014") }),
1183
1218
  options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
1184
1219
  ]
1185
1220
  }
1186
1221
  ) : attributeType === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
1187
1222
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: Boolean(currentValue), onChange: (e) => onChange(e.target.checked), className: "rounded border-border" }),
1188
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: helpText ?? "Enable" })
1223
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: helpText ?? t("cpq.quotes.detail.configure.enable", "Enable") })
1189
1224
  ] }) : attributeType === "number" ? /* @__PURE__ */ jsx(
1190
1225
  NumberInput,
1191
1226
  {