@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
@@ -135,7 +135,7 @@ function SpecificationDetailPage(props) {
135
135
  }
136
136
  const res = await fetch(`/api/cpq/product-specifications?id=${encodeURIComponent(specId)}`);
137
137
  if (!res.ok) {
138
- if (!cancelled) setError("Failed to load specification");
138
+ if (!cancelled) setError(t("cpq.specifications.detail.error.load", "Failed to load specification"));
139
139
  return;
140
140
  }
141
141
  const spec = await res.json();
@@ -159,7 +159,7 @@ function SpecificationDetailPage(props) {
159
159
  }
160
160
  } catch (err) {
161
161
  console.error("Failed to load", err);
162
- if (!cancelled) setError("Failed to load");
162
+ if (!cancelled) setError(t("cpq.specifications.detail.error.loadGeneric", "Failed to load"));
163
163
  } finally {
164
164
  if (!cancelled) setLoading(false);
165
165
  }
@@ -182,7 +182,7 @@ function SpecificationDetailPage(props) {
182
182
  });
183
183
  if (!res.ok) {
184
184
  const d = await res.json();
185
- setError(d.error ?? "Failed to save");
185
+ setError(d.error ?? t("cpq.specifications.detail.error.save", "Failed to save"));
186
186
  return;
187
187
  }
188
188
  const saved = await res.json();
@@ -205,7 +205,7 @@ function SpecificationDetailPage(props) {
205
205
  setGeneralMode("view");
206
206
  }
207
207
  } catch {
208
- setError("Failed to save");
208
+ setError(t("cpq.specifications.detail.error.save", "Failed to save"));
209
209
  } finally {
210
210
  setSaving(false);
211
211
  }
@@ -222,10 +222,10 @@ function SpecificationDetailPage(props) {
222
222
  router.push("/backend/cpq/specifications");
223
223
  } else {
224
224
  const d = await res.json();
225
- setError(d.error ?? "Failed to delete");
225
+ setError(d.error ?? t("cpq.specifications.detail.error.delete", "Failed to delete"));
226
226
  }
227
227
  } catch {
228
- setError("Failed to delete");
228
+ setError(t("cpq.specifications.detail.error.delete", "Failed to delete"));
229
229
  }
230
230
  };
231
231
  const saveAttribute = async () => {
@@ -253,7 +253,7 @@ function SpecificationDetailPage(props) {
253
253
  });
254
254
  if (!res.ok) {
255
255
  const d = await res.json();
256
- setError(d.error ?? "Failed to save attribute");
256
+ setError(d.error ?? t("cpq.specifications.attributes.error.save", "Failed to save attribute"));
257
257
  return;
258
258
  }
259
259
  const saved = await res.json();
@@ -264,13 +264,13 @@ function SpecificationDetailPage(props) {
264
264
  }
265
265
  setEditingAttr(null);
266
266
  } catch {
267
- setError("Failed to save attribute");
267
+ setError(t("cpq.specifications.attributes.error.save", "Failed to save attribute"));
268
268
  } finally {
269
269
  setSaving(false);
270
270
  }
271
271
  };
272
272
  const deleteAttribute = async (attrId) => {
273
- if (!confirm("Delete this attribute?")) return;
273
+ if (!confirm(t("cpq.specifications.attributes.confirmDelete", "Delete this attribute?"))) return;
274
274
  try {
275
275
  const res = await fetch("/api/cpq/product-attributes", {
276
276
  method: "DELETE",
@@ -279,7 +279,7 @@ function SpecificationDetailPage(props) {
279
279
  });
280
280
  if (res.ok) setAttributes((prev) => prev.filter((a) => a.id !== attrId));
281
281
  } catch {
282
- setError("Failed to delete attribute");
282
+ setError(t("cpq.specifications.attributes.error.delete", "Failed to delete attribute"));
283
283
  }
284
284
  };
285
285
  const startEditAttr = (attr) => {
@@ -311,7 +311,7 @@ function SpecificationDetailPage(props) {
311
311
  });
312
312
  if (!res.ok) {
313
313
  const d = await res.json();
314
- setError(d.error ?? "Failed to save offering");
314
+ setError(d.error ?? t("cpq.specifications.offerings.error.save", "Failed to save offering"));
315
315
  return;
316
316
  }
317
317
  const saved = await res.json();
@@ -322,13 +322,13 @@ function SpecificationDetailPage(props) {
322
322
  }
323
323
  setEditingOffering(null);
324
324
  } catch {
325
- setError("Failed to save offering");
325
+ setError(t("cpq.specifications.offerings.error.save", "Failed to save offering"));
326
326
  } finally {
327
327
  setSaving(false);
328
328
  }
329
329
  };
330
330
  const deleteOffering = async (offeringId) => {
331
- if (!confirm("Delete this offering?")) return;
331
+ if (!confirm(t("cpq.specifications.offerings.confirmDelete", "Delete this offering?"))) return;
332
332
  try {
333
333
  const res = await fetch("/api/cpq/product-offerings", {
334
334
  method: "DELETE",
@@ -337,7 +337,7 @@ function SpecificationDetailPage(props) {
337
337
  });
338
338
  if (res.ok) setOfferings((prev) => prev.filter((o) => o.id !== offeringId));
339
339
  } catch {
340
- setError("Failed to delete offering");
340
+ setError(t("cpq.specifications.offerings.error.delete", "Failed to delete offering"));
341
341
  }
342
342
  };
343
343
  const startEditOffering = (offering) => {
@@ -423,20 +423,20 @@ function SpecificationDetailPage(props) {
423
423
  });
424
424
  if (!res.ok) {
425
425
  const d = await res.json();
426
- setError(d.error ?? `Failed to save charge for ${offering.name}`);
426
+ setError(d.error ?? t("cpq.specifications.charges.error.saveFor", "Failed to save charge for {name}", { name: offering.name }));
427
427
  break;
428
428
  }
429
429
  }
430
430
  setChargeFormOpen(false);
431
431
  await loadAllOfferingCharges();
432
432
  } catch {
433
- setError("Failed to save charges");
433
+ setError(t("cpq.specifications.charges.error.saveAll", "Failed to save charges"));
434
434
  } finally {
435
435
  setSaving(false);
436
436
  }
437
437
  };
438
438
  const deleteCharge = async (offeringId, chargeId) => {
439
- if (!confirm("Delete this charge?")) return;
439
+ if (!confirm(t("cpq.specifications.charges.confirmDelete", "Delete this charge?"))) return;
440
440
  try {
441
441
  const res = await fetch("/api/cpq/product-charges", {
442
442
  method: "DELETE",
@@ -450,7 +450,7 @@ function SpecificationDetailPage(props) {
450
450
  }));
451
451
  }
452
452
  } catch {
453
- setError("Failed to delete charge");
453
+ setError(t("cpq.specifications.charges.error.delete", "Failed to delete charge"));
454
454
  }
455
455
  };
456
456
  const selectedPricingTableRef = chargePricingTableId ? pricingTables.find((pt) => pt.id === chargePricingTableId) : null;
@@ -477,7 +477,7 @@ function SpecificationDetailPage(props) {
477
477
  });
478
478
  if (!res.ok) {
479
479
  const d = await res.json();
480
- setError(d.error ?? "Failed to save relationship");
480
+ setError(d.error ?? t("cpq.specifications.relationships.error.save", "Failed to save relationship"));
481
481
  return;
482
482
  }
483
483
  const saved = await res.json();
@@ -488,13 +488,13 @@ function SpecificationDetailPage(props) {
488
488
  }
489
489
  setEditingRel(null);
490
490
  } catch {
491
- setError("Failed to save relationship");
491
+ setError(t("cpq.specifications.relationships.error.save", "Failed to save relationship"));
492
492
  } finally {
493
493
  setSaving(false);
494
494
  }
495
495
  };
496
496
  const deleteRelationship = async (relId) => {
497
- if (!confirm("Delete this relationship?")) return;
497
+ if (!confirm(t("cpq.specifications.relationships.confirmDelete", "Delete this relationship?"))) return;
498
498
  try {
499
499
  const res = await fetch("/api/cpq/product-relationships", {
500
500
  method: "DELETE",
@@ -503,7 +503,7 @@ function SpecificationDetailPage(props) {
503
503
  });
504
504
  if (res.ok) setRelationships((prev) => prev.filter((r) => r.id !== relId));
505
505
  } catch {
506
- setError("Failed to delete relationship");
506
+ setError(t("cpq.specifications.relationships.error.delete", "Failed to delete relationship"));
507
507
  }
508
508
  };
509
509
  const startEditRel = (rel) => {
@@ -513,7 +513,7 @@ function SpecificationDetailPage(props) {
513
513
  };
514
514
  const getSpecName = (id) => allSpecs.find((s) => s.id === id)?.name ?? id.slice(0, 8);
515
515
  if (loading) {
516
- return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("common.loading", "Loading...") });
516
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("cpq.common.loading", "Loading...") });
517
517
  }
518
518
  const designTimeAttrs = attributes.filter((a) => a.resolutionTime === "design_time");
519
519
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
@@ -526,7 +526,7 @@ function SpecificationDetailPage(props) {
526
526
  className: "text-sm text-muted-foreground hover:text-foreground",
527
527
  children: [
528
528
  "\u2190 ",
529
- t("common.back", "Back")
529
+ t("cpq.common.back", "Back")
530
530
  ]
531
531
  }
532
532
  ),
@@ -542,10 +542,10 @@ function SpecificationDetailPage(props) {
542
542
  setFormSnapshot(form);
543
543
  setGeneralMode("edit");
544
544
  },
545
- children: t("common.edit", "Edit")
545
+ children: t("cpq.common.edit", "Edit")
546
546
  }
547
547
  ),
548
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: deleteSpec, children: t("common.delete", "Delete") })
548
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: deleteSpec, children: t("cpq.common.delete", "Delete") })
549
549
  ] })
550
550
  ] }),
551
551
  error && /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: error }),
@@ -564,11 +564,11 @@ function SpecificationDetailPage(props) {
564
564
  },
565
565
  className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === t_ ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
566
566
  children: [
567
- t_ === "general" && "General",
568
- t_ === "attributes" && `Attributes (${attributes.length})`,
569
- t_ === "offerings" && `Offerings (${offerings.length})`,
570
- t_ === "relationships" && `Relationships (${relationships.length})`,
571
- t_ === "slots" && `Bundle Slots (${bundleSlots.length})`
567
+ t_ === "general" && t("cpq.specifications.detail.tabs.general", "General"),
568
+ t_ === "attributes" && t("cpq.specifications.detail.tabs.attributes", "Attributes ({count})", { count: attributes.length }),
569
+ t_ === "offerings" && t("cpq.specifications.detail.tabs.offerings", "Offerings ({count})", { count: offerings.length }),
570
+ t_ === "relationships" && t("cpq.specifications.detail.tabs.relationships", "Relationships ({count})", { count: relationships.length }),
571
+ t_ === "slots" && t("cpq.specifications.detail.tabs.slots", "Bundle Slots ({count})", { count: bundleSlots.length })
572
572
  ]
573
573
  },
574
574
  t_
@@ -586,7 +586,7 @@ function SpecificationDetailPage(props) {
586
586
  type: "text",
587
587
  value: form.code,
588
588
  onChange: (e) => setForm({ ...form, code: e.target.value }),
589
- placeholder: "e.g. gix-access-port",
589
+ placeholder: t("cpq.specifications.detail.codePlaceholder", "e.g. gix-access-port"),
590
590
  className: "w-full rounded-md border px-3 py-2 text-sm"
591
591
  }
592
592
  )
@@ -612,16 +612,16 @@ function SpecificationDetailPage(props) {
612
612
  onChange: (e) => setForm({ ...form, lifecycleStatus: e.target.value }),
613
613
  className: "w-full rounded-md border px-3 py-2 text-sm",
614
614
  children: [
615
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
616
- /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
617
- /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" })
615
+ /* @__PURE__ */ jsx("option", { value: "draft", children: t("cpq.specifications.lifecycle.draft", "Draft") }),
616
+ /* @__PURE__ */ jsx("option", { value: "active", children: t("cpq.specifications.lifecycle.active", "Active") }),
617
+ /* @__PURE__ */ jsx("option", { value: "deprecated", children: t("cpq.specifications.lifecycle.deprecated", "Deprecated") })
618
618
  ]
619
619
  }
620
620
  )
621
621
  ] }),
622
622
  /* @__PURE__ */ jsxs("div", { children: [
623
623
  /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.isActive", "Is Active?") }),
624
- /* @__PURE__ */ jsx("div", { className: "flex items-center h-[38px]", children: isViewing ? /* @__PURE__ */ jsx(Tag, { variant: form.isActive ? "success" : "neutral", dot: true, children: form.isActive ? t("common.yes", "Yes") : t("common.no", "No") }) : /* @__PURE__ */ jsx(
624
+ /* @__PURE__ */ jsx("div", { className: "flex items-center h-[38px]", children: isViewing ? /* @__PURE__ */ jsx(Tag, { variant: form.isActive ? "success" : "neutral", dot: true, children: form.isActive ? t("cpq.common.yes", "Yes") : t("cpq.common.no", "No") }) : /* @__PURE__ */ jsx(
625
625
  "input",
626
626
  {
627
627
  type: "checkbox",
@@ -632,8 +632,8 @@ function SpecificationDetailPage(props) {
632
632
  ) })
633
633
  ] }),
634
634
  /* @__PURE__ */ jsxs("div", { children: [
635
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Spec Type" }),
636
- isViewing ? /* @__PURE__ */ jsx("p", { className: "text-sm py-2", children: form.specType === "bundle" ? "Bundle" : "Simple" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
635
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.detail.specType", "Spec Type") }),
636
+ isViewing ? /* @__PURE__ */ jsx("p", { className: "text-sm py-2", children: form.specType === "bundle" ? t("cpq.specifications.type.bundle", "Bundle") : t("cpq.specifications.type.simple", "Simple") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
637
637
  /* @__PURE__ */ jsxs(
638
638
  "select",
639
639
  {
@@ -642,17 +642,17 @@ function SpecificationDetailPage(props) {
642
642
  disabled: specTypeLocked,
643
643
  className: "w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50",
644
644
  children: [
645
- /* @__PURE__ */ jsx("option", { value: "simple", children: "Simple" }),
646
- /* @__PURE__ */ jsx("option", { value: "bundle", children: "Bundle" })
645
+ /* @__PURE__ */ jsx("option", { value: "simple", children: t("cpq.specifications.type.simple", "Simple") }),
646
+ /* @__PURE__ */ jsx("option", { value: "bundle", children: t("cpq.specifications.type.bundle", "Bundle") })
647
647
  ]
648
648
  }
649
649
  ),
650
- specTypeLocked && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Cannot change after offerings or slots exist" })
650
+ specTypeLocked && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t("cpq.specifications.detail.specTypeLocked", "Cannot change after offerings or slots exist") })
651
651
  ] })
652
652
  ] }),
653
653
  /* @__PURE__ */ jsxs("div", { children: [
654
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Is Assetizable?" }),
655
- /* @__PURE__ */ jsx("div", { className: "flex items-center h-[38px]", children: isViewing ? /* @__PURE__ */ jsx(Tag, { variant: form.isAssetizable ? "warning" : "neutral", dot: true, children: form.isAssetizable ? t("common.yes", "Yes") : t("common.no", "No") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
654
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.detail.isAssetizable", "Is Assetizable?") }),
655
+ /* @__PURE__ */ jsx("div", { className: "flex items-center h-[38px]", children: isViewing ? /* @__PURE__ */ jsx(Tag, { variant: form.isAssetizable ? "warning" : "neutral", dot: true, children: form.isAssetizable ? t("cpq.common.yes", "Yes") : t("cpq.common.no", "No") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
656
656
  /* @__PURE__ */ jsx(
657
657
  "input",
658
658
  {
@@ -662,7 +662,7 @@ function SpecificationDetailPage(props) {
662
662
  className: "rounded border"
663
663
  }
664
664
  ),
665
- /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: "Creates asset records during fulfilment" })
665
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: t("cpq.specifications.detail.isAssetizableHelp", "Creates asset records during fulfilment") })
666
666
  ] }) })
667
667
  ] })
668
668
  ] }),
@@ -678,10 +678,7 @@ function SpecificationDetailPage(props) {
678
678
  }
679
679
  )
680
680
  ] }),
681
- !isNew && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
682
- "Version ",
683
- form.version
684
- ] }),
681
+ !isNew && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("cpq.specifications.detail.versionLabel", "Version {version}", { version: form.version }) }),
685
682
  !isViewing && /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
686
683
  /* @__PURE__ */ jsx(
687
684
  Button,
@@ -689,7 +686,7 @@ function SpecificationDetailPage(props) {
689
686
  type: "button",
690
687
  onClick: saveSpec,
691
688
  disabled: saving || !form.code || !form.name,
692
- children: saving ? t("common.saving", "Saving...") : t("common.save", "Save")
689
+ children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.common.save", "Save")
693
690
  }
694
691
  ),
695
692
  /* @__PURE__ */ jsx(
@@ -706,7 +703,7 @@ function SpecificationDetailPage(props) {
706
703
  setFormSnapshot(null);
707
704
  setGeneralMode("view");
708
705
  },
709
- children: t("common.cancel", "Cancel")
706
+ children: t("cpq.common.cancel", "Cancel")
710
707
  }
711
708
  )
712
709
  ] })
@@ -722,36 +719,36 @@ function SpecificationDetailPage(props) {
722
719
  }
723
720
  ) }),
724
721
  editingAttr && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
725
- /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingAttr.id ? "Edit Attribute" : "New Attribute" }),
722
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingAttr.id ? t("cpq.specifications.attributes.form.editTitle", "Edit Attribute") : t("cpq.specifications.attributes.form.newTitle", "New Attribute") }),
726
723
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3", children: [
727
724
  /* @__PURE__ */ jsxs("div", { children: [
728
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Code" }),
725
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.code", "Code") }),
729
726
  /* @__PURE__ */ jsx("input", { type: "text", value: editingAttr.code, onChange: (e) => setEditingAttr({ ...editingAttr, code: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
730
727
  ] }),
731
728
  /* @__PURE__ */ jsxs("div", { children: [
732
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Name" }),
729
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.name", "Name") }),
733
730
  /* @__PURE__ */ jsx("input", { type: "text", value: editingAttr.name, onChange: (e) => setEditingAttr({ ...editingAttr, name: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
734
731
  ] }),
735
732
  /* @__PURE__ */ jsxs("div", { children: [
736
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Type" }),
733
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.type", "Type") }),
737
734
  /* @__PURE__ */ jsxs("select", { value: editingAttr.attributeType, onChange: (e) => setEditingAttr({ ...editingAttr, attributeType: e.target.value, defaultValue: null }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
738
- /* @__PURE__ */ jsx("option", { value: "text", children: "Text" }),
739
- /* @__PURE__ */ jsx("option", { value: "number", children: "Number" }),
740
- /* @__PURE__ */ jsx("option", { value: "boolean", children: "Boolean" }),
741
- /* @__PURE__ */ jsx("option", { value: "select", children: "Select" }),
742
- /* @__PURE__ */ jsx("option", { value: "multi-select", children: "Multi-Select" }),
743
- /* @__PURE__ */ jsx("option", { value: "reference", children: "Reference" })
735
+ /* @__PURE__ */ jsx("option", { value: "text", children: t("cpq.specifications.attributeType.text", "Text") }),
736
+ /* @__PURE__ */ jsx("option", { value: "number", children: t("cpq.specifications.attributeType.number", "Number") }),
737
+ /* @__PURE__ */ jsx("option", { value: "boolean", children: t("cpq.specifications.attributeType.boolean", "Boolean") }),
738
+ /* @__PURE__ */ jsx("option", { value: "select", children: t("cpq.specifications.attributeType.select", "Select") }),
739
+ /* @__PURE__ */ jsx("option", { value: "multi-select", children: t("cpq.specifications.attributeType.multiSelect", "Multi-Select") }),
740
+ /* @__PURE__ */ jsx("option", { value: "reference", children: t("cpq.specifications.attributeType.reference", "Reference") })
744
741
  ] })
745
742
  ] }),
746
743
  /* @__PURE__ */ jsxs("div", { children: [
747
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Resolution Time" }),
744
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.resolutionTime", "Resolution Time") }),
748
745
  /* @__PURE__ */ jsxs("select", { value: editingAttr.resolutionTime, onChange: (e) => setEditingAttr({ ...editingAttr, resolutionTime: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
749
- /* @__PURE__ */ jsx("option", { value: "run_time", children: "Run-time (quoting)" }),
750
- /* @__PURE__ */ jsx("option", { value: "design_time", children: "Design-time (offering)" })
746
+ /* @__PURE__ */ jsx("option", { value: "run_time", children: t("cpq.specifications.resolutionTime.runTime", "Run-time (quoting)") }),
747
+ /* @__PURE__ */ jsx("option", { value: "design_time", children: t("cpq.specifications.resolutionTime.designTime", "Design-time (offering)") })
751
748
  ] })
752
749
  ] }),
753
750
  /* @__PURE__ */ jsxs("div", { children: [
754
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Sort Order" }),
751
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.sortOrder", "Sort Order") }),
755
752
  /* @__PURE__ */ jsx(
756
753
  NumberInput,
757
754
  {
@@ -762,7 +759,7 @@ function SpecificationDetailPage(props) {
762
759
  )
763
760
  ] }),
764
761
  /* @__PURE__ */ jsxs("div", { children: [
765
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Help Text" }),
762
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.helpText", "Help Text") }),
766
763
  /* @__PURE__ */ jsx("input", { type: "text", value: editingAttr.helpText ?? "", onChange: (e) => setEditingAttr({ ...editingAttr, helpText: e.target.value || null }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
767
764
  ] }),
768
765
  editingAttr.attributeType !== "reference" && /* @__PURE__ */ jsxs("div", { children: [
@@ -820,23 +817,23 @@ function SpecificationDetailPage(props) {
820
817
  )
821
818
  ] }),
822
819
  editingAttr.attributeType === "reference" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { children: [
823
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Reference Entity" }),
824
- /* @__PURE__ */ jsx("input", { type: "text", value: editingAttr.referenceEntity ?? "", onChange: (e) => setEditingAttr({ ...editingAttr, referenceEntity: e.target.value || null }), placeholder: "e.g. CatalogProduct", className: "w-full rounded-md border px-2 py-1.5 text-sm" })
820
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.fields.referenceEntity", "Reference Entity") }),
821
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingAttr.referenceEntity ?? "", onChange: (e) => setEditingAttr({ ...editingAttr, referenceEntity: e.target.value || null }), placeholder: t("cpq.specifications.attributes.fields.referenceEntityPlaceholder", "e.g. CatalogProduct"), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
825
822
  ] }) }),
826
823
  (editingAttr.attributeType === "select" || editingAttr.attributeType === "multi-select") && /* @__PURE__ */ jsxs("div", { className: "col-span-3 space-y-2", children: [
827
824
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
828
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium", children: "Options" }),
825
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium", children: t("cpq.specifications.attributes.fields.options", "Options") }),
829
826
  /* @__PURE__ */ jsx(
830
827
  "button",
831
828
  {
832
829
  type: "button",
833
830
  onClick: () => setEditingOptions([...editingOptions, { value: "", label: "" }]),
834
831
  className: "text-xs text-primary hover:underline",
835
- children: "+ Add option"
832
+ children: t("cpq.specifications.attributes.addOption", "+ Add option")
836
833
  }
837
834
  )
838
835
  ] }),
839
- editingOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground py-2", children: 'No options defined. Click "Add option" to start.' }) : /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: editingOptions.map((opt, idx) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
836
+ editingOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground py-2", children: t("cpq.specifications.attributes.noOptions", 'No options defined. Click "Add option" to start.') }) : /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: editingOptions.map((opt, idx) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
840
837
  /* @__PURE__ */ jsx(
841
838
  "input",
842
839
  {
@@ -847,7 +844,7 @@ function SpecificationDetailPage(props) {
847
844
  next[idx] = { ...next[idx], value: e.target.value };
848
845
  setEditingOptions(next);
849
846
  },
850
- placeholder: "Value (e.g. aws)",
847
+ placeholder: t("cpq.specifications.attributes.optionValuePlaceholder", "Value (e.g. aws)"),
851
848
  className: "flex-1 rounded-md border px-2 py-1.5 text-sm"
852
849
  }
853
850
  ),
@@ -861,7 +858,7 @@ function SpecificationDetailPage(props) {
861
858
  next[idx] = { ...next[idx], label: e.target.value };
862
859
  setEditingOptions(next);
863
860
  },
864
- placeholder: "Label (e.g. Amazon Web Services)",
861
+ placeholder: t("cpq.specifications.attributes.optionLabelPlaceholder", "Label (e.g. Amazon Web Services)"),
865
862
  className: "flex-1 rounded-md border px-2 py-1.5 text-sm"
866
863
  }
867
864
  ),
@@ -871,7 +868,7 @@ function SpecificationDetailPage(props) {
871
868
  type: "button",
872
869
  onClick: () => setEditingOptions(editingOptions.filter((_, i) => i !== idx)),
873
870
  className: "text-destructive hover:text-destructive/80 text-sm px-1",
874
- title: "Remove",
871
+ title: t("cpq.specifications.attributes.removeOption", "Remove"),
875
872
  children: "\xD7"
876
873
  }
877
874
  )
@@ -888,14 +885,14 @@ function SpecificationDetailPage(props) {
888
885
  className: "rounded border"
889
886
  }
890
887
  ),
891
- "Depends on another attribute"
888
+ t("cpq.specifications.attributes.dependsOn.enable", "Depends on another attribute")
892
889
  ] }),
893
890
  dependsOnEnabled && (() => {
894
891
  const parentAttr = attributes.find((a) => a.code === dependsOnAttrCode);
895
892
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
896
893
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
897
894
  /* @__PURE__ */ jsxs("div", { children: [
898
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Parent Attribute Code" }),
895
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.dependsOn.parentAttribute", "Parent Attribute Code") }),
899
896
  /* @__PURE__ */ jsxs(
900
897
  "select",
901
898
  {
@@ -905,7 +902,7 @@ function SpecificationDetailPage(props) {
905
902
  },
906
903
  className: "w-full rounded-md border px-2 py-1.5 text-sm",
907
904
  children: [
908
- /* @__PURE__ */ jsx("option", { value: "", children: "Select attribute..." }),
905
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.specifications.attributes.dependsOn.selectAttribute", "Select attribute...") }),
909
906
  attributes.filter((a) => editingAttr && a.code !== editingAttr.code).map((a) => /* @__PURE__ */ jsxs("option", { value: a.code, children: [
910
907
  a.name,
911
908
  " (",
@@ -917,7 +914,7 @@ function SpecificationDetailPage(props) {
917
914
  )
918
915
  ] }),
919
916
  /* @__PURE__ */ jsxs("div", { children: [
920
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Effect" }),
917
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.attributes.dependsOn.effect", "Effect") }),
921
918
  /* @__PURE__ */ jsxs(
922
919
  "select",
923
920
  {
@@ -929,16 +926,16 @@ function SpecificationDetailPage(props) {
929
926
  },
930
927
  className: "w-full rounded-md border px-2 py-1.5 text-sm",
931
928
  children: [
932
- /* @__PURE__ */ jsx("option", { value: "filter_options", children: "Filter options" }),
933
- /* @__PURE__ */ jsx("option", { value: "set_value", children: "Set value" }),
934
- /* @__PURE__ */ jsx("option", { value: "toggle_required", children: "Toggle required" })
929
+ /* @__PURE__ */ jsx("option", { value: "filter_options", children: t("cpq.specifications.attributes.dependsOn.effects.filterOptions", "Filter options") }),
930
+ /* @__PURE__ */ jsx("option", { value: "set_value", children: t("cpq.specifications.attributes.dependsOn.effects.setValue", "Set value") }),
931
+ /* @__PURE__ */ jsx("option", { value: "toggle_required", children: t("cpq.specifications.attributes.dependsOn.effects.toggleRequired", "Toggle required") })
935
932
  ]
936
933
  }
937
934
  )
938
935
  ] })
939
936
  ] }),
940
937
  /* @__PURE__ */ jsxs("div", { children: [
941
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-2", children: "Rules" }),
938
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-2", children: t("cpq.specifications.attributes.dependsOn.rules", "Rules") }),
942
939
  /* @__PURE__ */ jsx(
943
940
  DependsOnRulesEditor,
944
941
  {
@@ -956,39 +953,39 @@ function SpecificationDetailPage(props) {
956
953
  /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-4", children: [
957
954
  /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-sm", children: [
958
955
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: editingAttr.isRequired, onChange: (e) => setEditingAttr({ ...editingAttr, isRequired: e.target.checked }), className: "rounded border" }),
959
- "Required"
956
+ t("cpq.specifications.attributes.fields.isRequired", "Required")
960
957
  ] }),
961
958
  /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-sm", children: [
962
959
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: editingAttr.isActive, onChange: (e) => setEditingAttr({ ...editingAttr, isActive: e.target.checked }), className: "rounded border" }),
963
- "Active"
960
+ t("cpq.common.active", "Active")
964
961
  ] })
965
962
  ] })
966
963
  ] }),
967
964
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
968
- /* @__PURE__ */ jsx("button", { onClick: saveAttribute, disabled: saving, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50", children: saving ? "Saving..." : "Save" }),
969
- /* @__PURE__ */ jsx("button", { onClick: () => setEditingAttr(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: "Cancel" })
965
+ /* @__PURE__ */ jsx("button", { onClick: saveAttribute, disabled: saving, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50", children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.common.save", "Save") }),
966
+ /* @__PURE__ */ jsx("button", { onClick: () => setEditingAttr(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: t("cpq.common.cancel", "Cancel") })
970
967
  ] })
971
968
  ] }),
972
- attributes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No attributes defined. Add design-time and run-time attributes for this specification." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
969
+ attributes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.specifications.attributes.empty", "No attributes defined. Add design-time and run-time attributes for this specification.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
973
970
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
974
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Code" }),
975
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Name" }),
976
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Type" }),
977
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Resolution" }),
978
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Options" }),
979
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Required" }),
980
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: "Actions" })
971
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.attributes.columns.code", "Code") }),
972
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.attributes.columns.name", "Name") }),
973
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.attributes.columns.type", "Type") }),
974
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.attributes.columns.resolution", "Resolution") }),
975
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.attributes.columns.options", "Options") }),
976
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.attributes.columns.required", "Required") }),
977
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: t("cpq.specifications.attributes.columns.actions", "Actions") })
981
978
  ] }) }),
982
979
  /* @__PURE__ */ jsx("tbody", { children: attributes.map((attr) => /* @__PURE__ */ jsxs("tr", { className: "border-b", children: [
983
980
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3 font-mono text-xs", children: attr.code }),
984
981
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: attr.name }),
985
982
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: attr.attributeType }),
986
- /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Tag, { variant: attr.resolutionTime === "design_time" ? "info" : "neutral", className: "px-2 text-xs", children: attr.resolutionTime === "design_time" ? "design" : "run" }) }),
987
- /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-muted-foreground", children: attr.options ? `${attr.options.length} options` : "\u2014" }),
988
- /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: attr.isRequired ? "Yes" : "No" }),
983
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Tag, { variant: attr.resolutionTime === "design_time" ? "info" : "neutral", className: "px-2 text-xs", children: attr.resolutionTime === "design_time" ? t("cpq.specifications.attributes.resolutionShort.design", "design") : t("cpq.specifications.attributes.resolutionShort.run", "run") }) }),
984
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-muted-foreground", children: attr.options ? t("cpq.specifications.attributes.optionsCount", "{count} options", { count: attr.options.length }) : "\u2014" }),
985
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: attr.isRequired ? t("cpq.common.yes", "Yes") : t("cpq.common.no", "No") }),
989
986
  /* @__PURE__ */ jsxs("td", { className: "px-4 py-3 text-right", children: [
990
- /* @__PURE__ */ jsx("button", { onClick: () => startEditAttr(attr), className: "text-xs text-primary hover:underline mr-2", children: "Edit" }),
991
- /* @__PURE__ */ jsx("button", { onClick: () => deleteAttribute(attr.id), className: "text-xs text-destructive hover:underline", children: "Delete" })
987
+ /* @__PURE__ */ jsx("button", { onClick: () => startEditAttr(attr), className: "text-xs text-primary hover:underline mr-2", children: t("cpq.common.edit", "Edit") }),
988
+ /* @__PURE__ */ jsx("button", { onClick: () => deleteAttribute(attr.id), className: "text-xs text-destructive hover:underline", children: t("cpq.common.delete", "Delete") })
992
989
  ] })
993
990
  ] }, attr.id)) })
994
991
  ] }) })
@@ -1003,35 +1000,35 @@ function SpecificationDetailPage(props) {
1003
1000
  }
1004
1001
  ) }),
1005
1002
  editingOffering && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
1006
- /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingOffering.id ? "Edit Offering" : "New Offering" }),
1003
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingOffering.id ? t("cpq.specifications.offerings.form.editTitle", "Edit Offering") : t("cpq.specifications.offerings.form.newTitle", "New Offering") }),
1007
1004
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-4 gap-3", children: [
1008
1005
  /* @__PURE__ */ jsxs("div", { children: [
1009
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Code" }),
1010
- /* @__PURE__ */ jsx("input", { type: "text", value: editingOffering.code, onChange: (e) => setEditingOffering({ ...editingOffering, code: e.target.value }), placeholder: "e.g. gix-cloud-connect-aws", className: "w-full rounded-md border px-2 py-1.5 text-sm" })
1006
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.offerings.fields.code", "Code") }),
1007
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingOffering.code, onChange: (e) => setEditingOffering({ ...editingOffering, code: e.target.value }), placeholder: t("cpq.specifications.offerings.fields.codePlaceholder", "e.g. gix-cloud-connect-aws"), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
1011
1008
  ] }),
1012
1009
  /* @__PURE__ */ jsxs("div", { children: [
1013
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Name" }),
1010
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.offerings.fields.name", "Name") }),
1014
1011
  /* @__PURE__ */ jsx("input", { type: "text", value: editingOffering.name, onChange: (e) => setEditingOffering({ ...editingOffering, name: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
1015
1012
  ] }),
1016
1013
  /* @__PURE__ */ jsxs("div", { children: [
1017
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Lifecycle Status" }),
1014
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.offerings.fields.lifecycleStatus", "Lifecycle Status") }),
1018
1015
  /* @__PURE__ */ jsxs("select", { value: editingOffering.lifecycleStatus, onChange: (e) => setEditingOffering({ ...editingOffering, lifecycleStatus: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
1019
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
1020
- /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
1021
- /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" })
1016
+ /* @__PURE__ */ jsx("option", { value: "draft", children: t("cpq.offerings.lifecycle.draft", "Draft") }),
1017
+ /* @__PURE__ */ jsx("option", { value: "active", children: t("cpq.offerings.lifecycle.active", "Active") }),
1018
+ /* @__PURE__ */ jsx("option", { value: "deprecated", children: t("cpq.offerings.lifecycle.deprecated", "Deprecated") })
1022
1019
  ] })
1023
1020
  ] }),
1024
1021
  /* @__PURE__ */ jsxs("div", { children: [
1025
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Is Active?" }),
1022
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.offerings.fields.isActive", "Is Active?") }),
1026
1023
  /* @__PURE__ */ jsx("div", { className: "flex items-center h-[30px]", children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: editingOffering.isActive, onChange: (e) => setEditingOffering({ ...editingOffering, isActive: e.target.checked }), className: "rounded border" }) })
1027
1024
  ] })
1028
1025
  ] }),
1029
1026
  /* @__PURE__ */ jsxs("div", { children: [
1030
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Description" }),
1027
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.offerings.fields.description", "Description") }),
1031
1028
  /* @__PURE__ */ jsx("input", { type: "text", value: editingOffering.description ?? "", onChange: (e) => setEditingOffering({ ...editingOffering, description: e.target.value || null }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
1032
1029
  ] }),
1033
1030
  designTimeAttrs.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1034
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium", children: "Design-Time Values" }),
1031
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium", children: t("cpq.specifications.offerings.fields.designTimeValues", "Design-Time Values") }),
1035
1032
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: designTimeAttrs.map((attr) => /* @__PURE__ */ jsxs("div", { children: [
1036
1033
  /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
1037
1034
  attr.name,
@@ -1044,11 +1041,7 @@ function SpecificationDetailPage(props) {
1044
1041
  onChange: (e) => setEditingDtValues({ ...editingDtValues, [attr.code]: e.target.value || null }),
1045
1042
  className: "w-full rounded-md border px-2 py-1.5 text-sm",
1046
1043
  children: [
1047
- /* @__PURE__ */ jsxs("option", { value: "", children: [
1048
- "Select ",
1049
- attr.name,
1050
- "..."
1051
- ] }),
1044
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.specifications.offerings.fields.selectAttribute", "Select {name}...", { name: attr.name }) }),
1052
1045
  attr.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
1053
1046
  ]
1054
1047
  }
@@ -1081,19 +1074,19 @@ function SpecificationDetailPage(props) {
1081
1074
  ),
1082
1075
  attr.helpText && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: attr.helpText })
1083
1076
  ] }, attr.code)) })
1084
- ] }) : /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground py-1", children: "No design-time attributes defined on this specification. All attributes are run-time." }),
1077
+ ] }) : /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground py-1", children: t("cpq.specifications.offerings.noDesignTimeAttributes", "No design-time attributes defined on this specification. All attributes are run-time.") }),
1085
1078
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1086
- /* @__PURE__ */ jsx("button", { onClick: saveOffering, disabled: saving, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50", children: saving ? "Saving..." : "Save" }),
1087
- /* @__PURE__ */ jsx("button", { onClick: () => setEditingOffering(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: "Cancel" })
1079
+ /* @__PURE__ */ jsx("button", { onClick: saveOffering, disabled: saving, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50", children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.common.save", "Save") }),
1080
+ /* @__PURE__ */ jsx("button", { onClick: () => setEditingOffering(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: t("cpq.common.cancel", "Cancel") })
1088
1081
  ] })
1089
1082
  ] }),
1090
- offerings.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No offerings created. An offering is a sellable instance of this specification with fixed design-time values and attached charges." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1083
+ offerings.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.specifications.offerings.empty", "No offerings created. An offering is a sellable instance of this specification with fixed design-time values and attached charges.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1091
1084
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
1092
- /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: "Code" }),
1093
- /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: "Name" }),
1094
- /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: "Status" }),
1095
- /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: "Design-Time Values" }),
1096
- /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-right font-medium", children: "Actions" })
1085
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: t("cpq.specifications.offerings.columns.code", "Code") }),
1086
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: t("cpq.specifications.offerings.columns.name", "Name") }),
1087
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: t("cpq.specifications.offerings.columns.status", "Status") }),
1088
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: t("cpq.specifications.offerings.columns.designTimeValues", "Design-Time Values") }),
1089
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-right font-medium", children: t("cpq.specifications.offerings.columns.actions", "Actions") })
1097
1090
  ] }) }),
1098
1091
  /* @__PURE__ */ jsx("tbody", { children: offerings.map((o) => /* @__PURE__ */ jsxs(
1099
1092
  "tr",
@@ -1109,11 +1102,11 @@ function SpecificationDetailPage(props) {
1109
1102
  /* @__PURE__ */ jsx("button", { onClick: (e) => {
1110
1103
  e.stopPropagation();
1111
1104
  startEditOffering(o);
1112
- }, className: "text-xs text-primary hover:underline mr-2", children: "Edit" }),
1105
+ }, className: "text-xs text-primary hover:underline mr-2", children: t("cpq.common.edit", "Edit") }),
1113
1106
  /* @__PURE__ */ jsx("button", { onClick: (e) => {
1114
1107
  e.stopPropagation();
1115
1108
  deleteOffering(o.id);
1116
- }, className: "text-xs text-destructive hover:underline", children: "Delete" })
1109
+ }, className: "text-xs text-destructive hover:underline", children: t("cpq.common.delete", "Delete") })
1117
1110
  ] })
1118
1111
  ]
1119
1112
  },
@@ -1131,20 +1124,20 @@ function SpecificationDetailPage(props) {
1131
1124
  }
1132
1125
  ) }),
1133
1126
  editingRel && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
1134
- /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingRel.id ? "Edit Relationship" : "New Relationship" }),
1127
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingRel.id ? t("cpq.specifications.relationships.form.editTitle", "Edit Relationship") : t("cpq.specifications.relationships.form.newTitle", "New Relationship") }),
1135
1128
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3", children: [
1136
1129
  /* @__PURE__ */ jsxs("div", { children: [
1137
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Type" }),
1130
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.relationships.fields.type", "Type") }),
1138
1131
  /* @__PURE__ */ jsxs("select", { value: editingRel.relationshipType, onChange: (e) => setEditingRel({ ...editingRel, relationshipType: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
1139
- /* @__PURE__ */ jsx("option", { value: "parent_child", children: "Parent / Child" }),
1140
- /* @__PURE__ */ jsx("option", { value: "requires", children: "Requires" }),
1141
- /* @__PURE__ */ jsx("option", { value: "excludes", children: "Excludes" })
1132
+ /* @__PURE__ */ jsx("option", { value: "parent_child", children: t("cpq.specifications.relationships.type.parentChild", "Parent / Child") }),
1133
+ /* @__PURE__ */ jsx("option", { value: "requires", children: t("cpq.specifications.relationships.type.requires", "Requires") }),
1134
+ /* @__PURE__ */ jsx("option", { value: "excludes", children: t("cpq.specifications.relationships.type.excludes", "Excludes") })
1142
1135
  ] })
1143
1136
  ] }),
1144
1137
  /* @__PURE__ */ jsxs("div", { children: [
1145
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Target Specification" }),
1138
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.relationships.fields.targetSpecification", "Target Specification") }),
1146
1139
  /* @__PURE__ */ jsxs("select", { value: editingRel.targetSpecId, onChange: (e) => setEditingRel({ ...editingRel, targetSpecId: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
1147
- /* @__PURE__ */ jsx("option", { value: "", children: "Select..." }),
1140
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.specifications.relationships.fields.selectTarget", "Select...") }),
1148
1141
  allSpecs.filter((s) => s.id !== specId).map((s) => /* @__PURE__ */ jsxs("option", { value: s.id, children: [
1149
1142
  s.name,
1150
1143
  " (",
@@ -1155,7 +1148,7 @@ function SpecificationDetailPage(props) {
1155
1148
  ] }),
1156
1149
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1157
1150
  /* @__PURE__ */ jsxs("div", { children: [
1158
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Min" }),
1151
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.relationships.fields.min", "Min") }),
1159
1152
  /* @__PURE__ */ jsx(
1160
1153
  NumberInput,
1161
1154
  {
@@ -1167,7 +1160,7 @@ function SpecificationDetailPage(props) {
1167
1160
  )
1168
1161
  ] }),
1169
1162
  /* @__PURE__ */ jsxs("div", { children: [
1170
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Max" }),
1163
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.relationships.fields.max", "Max") }),
1171
1164
  /* @__PURE__ */ jsx(
1172
1165
  NumberInput,
1173
1166
  {
@@ -1182,22 +1175,22 @@ function SpecificationDetailPage(props) {
1182
1175
  ] })
1183
1176
  ] }),
1184
1177
  /* @__PURE__ */ jsxs("div", { children: [
1185
- /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Condition (JSON)" }),
1186
- /* @__PURE__ */ jsx("textarea", { value: conditionJson, onChange: (e) => setConditionJson(e.target.value), rows: 3, className: "w-full rounded-md border px-2 py-1.5 text-sm font-mono", placeholder: 'null or {"attribute":"direction","operator":"in","value":["inbound"]}' })
1178
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: t("cpq.specifications.relationships.fields.condition", "Condition (JSON)") }),
1179
+ /* @__PURE__ */ jsx("textarea", { value: conditionJson, onChange: (e) => setConditionJson(e.target.value), rows: 3, className: "w-full rounded-md border px-2 py-1.5 text-sm font-mono", placeholder: t("cpq.specifications.relationships.fields.conditionPlaceholder", 'null or {"attribute":"direction","operator":"in","value":["inbound"]}') })
1187
1180
  ] }),
1188
1181
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1189
- /* @__PURE__ */ jsx("button", { onClick: saveRelationship, disabled: saving, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50", children: saving ? "Saving..." : "Save" }),
1190
- /* @__PURE__ */ jsx("button", { onClick: () => setEditingRel(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: "Cancel" })
1182
+ /* @__PURE__ */ jsx("button", { onClick: saveRelationship, disabled: saving, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50", children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.common.save", "Save") }),
1183
+ /* @__PURE__ */ jsx("button", { onClick: () => setEditingRel(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: t("cpq.common.cancel", "Cancel") })
1191
1184
  ] })
1192
1185
  ] }),
1193
- relationships.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No relationships defined. Declare parent/child, requires, or excludes relationships with other specifications." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1186
+ relationships.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.specifications.relationships.empty", "No relationships defined. Declare parent/child, requires, or excludes relationships with other specifications.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1194
1187
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
1195
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Type" }),
1196
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Source" }),
1197
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Target" }),
1198
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Cardinality" }),
1199
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Condition" }),
1200
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: "Actions" })
1188
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.relationships.columns.type", "Type") }),
1189
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.relationships.columns.source", "Source") }),
1190
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.relationships.columns.target", "Target") }),
1191
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.relationships.columns.cardinality", "Cardinality") }),
1192
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.relationships.columns.condition", "Condition") }),
1193
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: t("cpq.specifications.relationships.columns.actions", "Actions") })
1201
1194
  ] }) }),
1202
1195
  /* @__PURE__ */ jsx("tbody", { children: relationships.map((rel) => /* @__PURE__ */ jsxs("tr", { className: "border-b", children: [
1203
1196
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(
@@ -1216,8 +1209,8 @@ function SpecificationDetailPage(props) {
1216
1209
  ] }),
1217
1210
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-muted-foreground text-xs", children: rel.condition ? JSON.stringify(rel.condition) : "\u2014" }),
1218
1211
  /* @__PURE__ */ jsxs("td", { className: "px-4 py-3 text-right", children: [
1219
- /* @__PURE__ */ jsx("button", { onClick: () => startEditRel(rel), className: "text-xs text-primary hover:underline mr-2", children: "Edit" }),
1220
- /* @__PURE__ */ jsx("button", { onClick: () => deleteRelationship(rel.id), className: "text-xs text-destructive hover:underline", children: "Delete" })
1212
+ /* @__PURE__ */ jsx("button", { onClick: () => startEditRel(rel), className: "text-xs text-primary hover:underline mr-2", children: t("cpq.common.edit", "Edit") }),
1213
+ /* @__PURE__ */ jsx("button", { onClick: () => deleteRelationship(rel.id), className: "text-xs text-destructive hover:underline", children: t("cpq.common.delete", "Delete") })
1221
1214
  ] })
1222
1215
  ] }, rel.id)) })
1223
1216
  ] }) })
@@ -1228,40 +1221,40 @@ function SpecificationDetailPage(props) {
1228
1221
  {
1229
1222
  onClick: () => setEditingSlot({ ...EMPTY_SLOT }),
1230
1223
  className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90",
1231
- children: "+ Add Slot"
1224
+ children: t("cpq.specifications.slots.add", "+ Add Slot")
1232
1225
  }
1233
1226
  ) }),
1234
1227
  editingSlot && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6 space-y-4", children: [
1235
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: editingSlot.id ? "Edit Bundle Slot" : "New Bundle Slot" }),
1228
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: editingSlot.id ? t("cpq.specifications.slots.form.editTitle", "Edit Bundle Slot") : t("cpq.specifications.slots.form.newTitle", "New Bundle Slot") }),
1236
1229
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1237
1230
  /* @__PURE__ */ jsxs("div", { children: [
1238
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Name" }),
1231
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.name", "Name") }),
1239
1232
  /* @__PURE__ */ jsx(
1240
1233
  "input",
1241
1234
  {
1242
1235
  type: "text",
1243
1236
  value: editingSlot.name,
1244
1237
  onChange: (e) => setEditingSlot({ ...editingSlot, name: e.target.value }),
1245
- placeholder: "e.g. Internet Component",
1238
+ placeholder: t("cpq.specifications.slots.fields.namePlaceholder", "e.g. Internet Component"),
1246
1239
  className: "w-full rounded-md border px-3 py-2 text-sm"
1247
1240
  }
1248
1241
  )
1249
1242
  ] }),
1250
1243
  /* @__PURE__ */ jsxs("div", { children: [
1251
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Component Group" }),
1244
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.componentGroup", "Component Group") }),
1252
1245
  /* @__PURE__ */ jsx(
1253
1246
  "input",
1254
1247
  {
1255
1248
  type: "text",
1256
1249
  value: editingSlot.componentGroup,
1257
1250
  onChange: (e) => setEditingSlot({ ...editingSlot, componentGroup: e.target.value }),
1258
- placeholder: "e.g. internet, tv, phone",
1251
+ placeholder: t("cpq.specifications.slots.fields.componentGroupPlaceholder", "e.g. internet, tv, phone"),
1259
1252
  className: "w-full rounded-md border px-3 py-2 text-sm"
1260
1253
  }
1261
1254
  )
1262
1255
  ] }),
1263
1256
  /* @__PURE__ */ jsxs("div", { children: [
1264
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Target Specification" }),
1257
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.targetSpecification", "Target Specification") }),
1265
1258
  /* @__PURE__ */ jsxs(
1266
1259
  "select",
1267
1260
  {
@@ -1269,7 +1262,7 @@ function SpecificationDetailPage(props) {
1269
1262
  onChange: (e) => setEditingSlot({ ...editingSlot, targetSpecId: e.target.value }),
1270
1263
  className: "w-full rounded-md border px-3 py-2 text-sm",
1271
1264
  children: [
1272
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Select specification \u2014" }),
1265
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.specifications.slots.fields.selectSpecification", "\u2014 Select specification \u2014") }),
1273
1266
  allSpecs.filter((s) => s.id !== specId).map((s) => /* @__PURE__ */ jsxs("option", { value: s.id, children: [
1274
1267
  s.name,
1275
1268
  " (",
@@ -1281,7 +1274,7 @@ function SpecificationDetailPage(props) {
1281
1274
  )
1282
1275
  ] }),
1283
1276
  /* @__PURE__ */ jsxs("div", { children: [
1284
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Sort Order" }),
1277
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.sortOrder", "Sort Order") }),
1285
1278
  /* @__PURE__ */ jsx(
1286
1279
  NumberInput,
1287
1280
  {
@@ -1292,7 +1285,7 @@ function SpecificationDetailPage(props) {
1292
1285
  )
1293
1286
  ] }),
1294
1287
  /* @__PURE__ */ jsxs("div", { children: [
1295
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Cardinality Min" }),
1288
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.cardinalityMin", "Cardinality Min") }),
1296
1289
  /* @__PURE__ */ jsx(
1297
1290
  NumberInput,
1298
1291
  {
@@ -1304,13 +1297,13 @@ function SpecificationDetailPage(props) {
1304
1297
  )
1305
1298
  ] }),
1306
1299
  /* @__PURE__ */ jsxs("div", { children: [
1307
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Cardinality Max" }),
1300
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.cardinalityMax", "Cardinality Max") }),
1308
1301
  /* @__PURE__ */ jsx(
1309
1302
  NumberInput,
1310
1303
  {
1311
1304
  integer: true,
1312
1305
  min: 0,
1313
- placeholder: "\u221E (unlimited)",
1306
+ placeholder: t("cpq.specifications.slots.fields.cardinalityMaxPlaceholder", "\u221E (unlimited)"),
1314
1307
  value: editingSlot.cardinalityMax,
1315
1308
  onChange: (n) => setEditingSlot({ ...editingSlot, cardinalityMax: n })
1316
1309
  }
@@ -1318,7 +1311,7 @@ function SpecificationDetailPage(props) {
1318
1311
  ] })
1319
1312
  ] }),
1320
1313
  /* @__PURE__ */ jsxs("div", { children: [
1321
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Description" }),
1314
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.specifications.slots.fields.description", "Description") }),
1322
1315
  /* @__PURE__ */ jsx(
1323
1316
  "textarea",
1324
1317
  {
@@ -1346,7 +1339,7 @@ function SpecificationDetailPage(props) {
1346
1339
  });
1347
1340
  if (!res.ok) {
1348
1341
  const d = await res.json();
1349
- setError(d.error ?? "Failed to save slot");
1342
+ setError(d.error ?? t("cpq.specifications.slots.error.save", "Failed to save slot"));
1350
1343
  return;
1351
1344
  }
1352
1345
  setEditingSlot(null);
@@ -1356,14 +1349,14 @@ function SpecificationDetailPage(props) {
1356
1349
  setBundleSlots(d.items ?? []);
1357
1350
  }
1358
1351
  } catch {
1359
- setError("Failed to save slot");
1352
+ setError(t("cpq.specifications.slots.error.save", "Failed to save slot"));
1360
1353
  } finally {
1361
1354
  setSaving(false);
1362
1355
  }
1363
1356
  },
1364
1357
  disabled: saving || !editingSlot.name || !editingSlot.componentGroup || !editingSlot.targetSpecId,
1365
1358
  className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
1366
- children: saving ? "Saving..." : editingSlot.id ? "Update" : "Create"
1359
+ children: saving ? t("cpq.common.saving", "Saving...") : editingSlot.id ? t("cpq.specifications.slots.form.update", "Update") : t("cpq.specifications.slots.form.create", "Create")
1367
1360
  }
1368
1361
  ),
1369
1362
  /* @__PURE__ */ jsx(
@@ -1371,27 +1364,27 @@ function SpecificationDetailPage(props) {
1371
1364
  {
1372
1365
  onClick: () => setEditingSlot(null),
1373
1366
  className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted",
1374
- children: "Cancel"
1367
+ children: t("cpq.common.cancel", "Cancel")
1375
1368
  }
1376
1369
  )
1377
1370
  ] })
1378
1371
  ] }),
1379
- bundleSlots.length === 0 && !editingSlot ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No bundle slots defined yet. Add slots to define what component specifications can fill this bundle." }) : bundleSlots.length > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1372
+ bundleSlots.length === 0 && !editingSlot ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.specifications.slots.empty", "No bundle slots defined yet. Add slots to define what component specifications can fill this bundle.") }) : bundleSlots.length > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1380
1373
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
1381
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Name" }),
1382
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Group" }),
1383
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Target Spec" }),
1384
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Cardinality" }),
1385
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Order" }),
1386
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: "Actions" })
1374
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.slots.columns.name", "Name") }),
1375
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.slots.columns.group", "Group") }),
1376
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.slots.columns.targetSpec", "Target Spec") }),
1377
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.slots.columns.cardinality", "Cardinality") }),
1378
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.specifications.slots.columns.order", "Order") }),
1379
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: t("cpq.specifications.slots.columns.actions", "Actions") })
1387
1380
  ] }) }),
1388
1381
  /* @__PURE__ */ jsx("tbody", { children: bundleSlots.map((slot) => /* @__PURE__ */ jsxs("tr", { className: "border-b", children: [
1389
1382
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3 font-medium", children: slot.name }),
1390
1383
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Tag, { variant: "info", children: slot.componentGroup }) }),
1391
1384
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-sm", children: slot.targetSpec ? /* @__PURE__ */ jsxs("span", { children: [
1392
1385
  slot.targetSpec.name,
1393
- slot.targetSpec.specType === "bundle" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "ml-1 px-1.5 text-[10px]", children: "bundle" })
1394
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Unknown" }) }),
1386
+ slot.targetSpec.specType === "bundle" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "ml-1 px-1.5 text-[10px]", children: t("cpq.specifications.slots.tag.bundle", "bundle") })
1387
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("cpq.specifications.slots.unknown", "Unknown") }) }),
1395
1388
  /* @__PURE__ */ jsxs("td", { className: "px-4 py-3 text-muted-foreground", children: [
1396
1389
  slot.cardinalityMin,
1397
1390
  "..",
@@ -1413,14 +1406,14 @@ function SpecificationDetailPage(props) {
1413
1406
  sortOrder: slot.sortOrder
1414
1407
  }),
1415
1408
  className: "text-xs text-primary hover:underline mr-2",
1416
- children: "Edit"
1409
+ children: t("cpq.common.edit", "Edit")
1417
1410
  }
1418
1411
  ),
1419
1412
  /* @__PURE__ */ jsx(
1420
1413
  "button",
1421
1414
  {
1422
1415
  onClick: async () => {
1423
- if (!confirm("Delete this bundle slot?")) return;
1416
+ if (!confirm(t("cpq.specifications.slots.confirmDelete", "Delete this bundle slot?"))) return;
1424
1417
  try {
1425
1418
  const res = await fetch("/api/cpq/bundle-slots", {
1426
1419
  method: "DELETE",
@@ -1431,14 +1424,14 @@ function SpecificationDetailPage(props) {
1431
1424
  setBundleSlots((prev) => prev.filter((s) => s.id !== slot.id));
1432
1425
  } else {
1433
1426
  const d = await res.json();
1434
- setError(d.error ?? "Failed to delete slot");
1427
+ setError(d.error ?? t("cpq.specifications.slots.error.delete", "Failed to delete slot"));
1435
1428
  }
1436
1429
  } catch {
1437
- setError("Failed to delete slot");
1430
+ setError(t("cpq.specifications.slots.error.delete", "Failed to delete slot"));
1438
1431
  }
1439
1432
  },
1440
1433
  className: "text-xs text-destructive hover:underline",
1441
- children: "Delete"
1434
+ children: t("cpq.common.delete", "Delete")
1442
1435
  }
1443
1436
  )
1444
1437
  ] })