@saasicat/ui-vue 0.5.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 (186) hide show
  1. package/README.md +17 -0
  2. package/dist/{chunk-EH7TOD4H.js → chunk-A3W2N26K.js} +73 -77
  3. package/dist/{chunk-3YP2ZAOD.js → chunk-QZOSDCNP.js} +36 -1
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3279 -76
  6. package/dist/client/index.d.cts +31 -5
  7. package/dist/client/index.d.ts +31 -5
  8. package/dist/client/index.js +23 -5
  9. package/dist/index.cjs +3709 -465
  10. package/dist/index.d.cts +37 -54
  11. package/dist/index.d.ts +37 -54
  12. package/dist/index.js +347 -311
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3185 -4
  16. package/dist/quasar/index.d.cts +11 -2
  17. package/dist/quasar/index.d.ts +11 -2
  18. package/dist/quasar/index.js +8 -3
  19. package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
  20. package/dist/testing-e2e/admin-pages-suite.js +14 -11
  21. package/dist/{ui-notify-COUzXEQ4.d.ts → use-super-admin-i18n-B3v3-SWZ.d.ts} +35 -3
  22. package/dist/{ui-notify-D_eAAwRh.d.cts → use-super-admin-i18n-DMktRVEt.d.cts} +35 -3
  23. package/package.json +2 -2
  24. package/src/client/action-registry.ts +4 -4
  25. package/src/client/batch-column-fetcher.ts +4 -4
  26. package/src/client/boot-loader.ts +3 -3
  27. package/src/client/i18n/currency.ts +42 -0
  28. package/src/client/i18n/define.ts +59 -0
  29. package/src/client/i18n/format.ts +12 -0
  30. package/src/client/i18n/index.ts +14 -0
  31. package/src/client/i18n/locale.ts +15 -0
  32. package/src/client/i18n/messages/audit.ts +42 -0
  33. package/src/client/i18n/messages/bundles.ts +459 -0
  34. package/src/client/i18n/messages/common.ts +147 -0
  35. package/src/client/i18n/messages/dashboard.ts +16 -0
  36. package/src/client/i18n/messages/discovery.ts +229 -0
  37. package/src/client/i18n/messages/email.ts +125 -0
  38. package/src/client/i18n/messages/marketing.ts +270 -0
  39. package/src/client/i18n/messages/nav.ts +72 -0
  40. package/src/client/i18n/messages/pilots.ts +192 -0
  41. package/src/client/i18n/messages/plan-detail.ts +266 -0
  42. package/src/client/i18n/messages/plan-editor.ts +177 -0
  43. package/src/client/i18n/messages/plan-versions.ts +251 -0
  44. package/src/client/i18n/messages/plans.ts +503 -0
  45. package/src/client/i18n/messages/promos.ts +202 -0
  46. package/src/client/i18n/messages/shell.ts +145 -0
  47. package/src/client/i18n/messages/tenants.ts +104 -0
  48. package/src/client/i18n/messages/users.ts +78 -0
  49. package/src/client/i18n/messages.ts +84 -0
  50. package/src/client/index.ts +2 -0
  51. package/src/client/manifest-loader.ts +4 -4
  52. package/src/client/nav-builder.ts +44 -51
  53. package/src/client/plan-versions-catalog.ts +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/MarketingPromotionsTab.vue +112 -59
  56. package/src/components/MfaPromptDialog.vue +11 -11
  57. package/src/components/TenantActionConfirmDialog.vue +19 -17
  58. package/src/components/VersionDiffPreview.vue +72 -43
  59. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  60. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  61. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  62. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  63. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  64. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  65. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  66. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  67. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  68. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  69. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  70. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  71. package/src/components/dialogs/types.ts +3 -13
  72. package/src/components/plan/PlanCycleToggle.vue +20 -9
  73. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  74. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  75. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  76. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  77. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  78. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  79. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  80. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  81. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  82. package/src/components/plan-detail/PlanDetail.vue +21 -23
  83. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  84. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  85. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  86. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  87. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  88. package/src/components/plan-list/PlanList.vue +99 -57
  89. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  90. package/src/components/plan-review/PlanReview.vue +105 -80
  91. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  92. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  93. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  94. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  95. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  96. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  97. package/src/index.ts +1 -1
  98. package/src/pages-standard/AdminLayout.vue +42 -17
  99. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  100. package/src/pages-standard/AuditPage.vue +58 -19
  101. package/src/pages-standard/BundlesPage.vue +29 -18
  102. package/src/pages-standard/DashboardPage.vue +29 -15
  103. package/src/pages-standard/DiscoveryPage.vue +40 -28
  104. package/src/pages-standard/EmailHistoryPage.vue +92 -59
  105. package/src/pages-standard/MarketingCatalogPage.vue +35 -16
  106. package/src/pages-standard/PilotsPage.vue +57 -41
  107. package/src/pages-standard/PlanVersionsPage.vue +7 -3
  108. package/src/pages-standard/PlansPage.vue +89 -57
  109. package/src/pages-standard/PlatformEmailPage.vue +115 -50
  110. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  111. package/src/pages-standard/PromoCodesPage.vue +46 -35
  112. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  113. package/src/pages-standard/SuperAdminLoginPage.vue +16 -13
  114. package/src/pages-standard/SuperAdminSetupWizard.vue +51 -47
  115. package/src/pages-standard/TenantDetailPage.vue +50 -36
  116. package/src/pages-standard/TenantsPage.vue +54 -41
  117. package/src/pages-standard/UsersPage.vue +66 -42
  118. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  119. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  120. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  121. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  122. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  123. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  124. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  125. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  126. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  127. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  128. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  129. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  130. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  131. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  132. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  133. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  134. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  135. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  136. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +41 -25
  137. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +45 -26
  138. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +7 -5
  139. package/src/pages-standard/plan-versions/PlanVersionsList.vue +39 -16
  140. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +24 -12
  141. package/src/pages-standard/plan-versions/format.ts +33 -17
  142. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  143. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  144. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  145. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  146. package/src/pages-standard/tenants/format.ts +3 -2
  147. package/src/pages-tenant/MySubscriptionBundlesPage.vue +72 -37
  148. package/src/pages-tenant/PlanChangeWizard.vue +8 -4
  149. package/src/pages-tenant/TenantPlanSection.vue +17 -13
  150. package/src/pages-tenant/default-i18n.ts +217 -1
  151. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +8 -6
  152. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +9 -10
  153. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  154. package/src/quasar/create-super-admin-app.ts +19 -0
  155. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  156. package/src/vue/project-page-host.ts +5 -3
  157. package/src/vue/use-audit-entries.ts +3 -3
  158. package/src/vue/use-bulk-publish.ts +6 -4
  159. package/src/vue/use-bundle-versions-map.ts +6 -4
  160. package/src/vue/use-bundles.ts +13 -13
  161. package/src/vue/use-catalog-entries.ts +13 -10
  162. package/src/vue/use-discovery.ts +8 -5
  163. package/src/vue/use-entitlement.ts +3 -3
  164. package/src/vue/use-live-plan-versions.ts +4 -4
  165. package/src/vue/use-marketing-projections.ts +9 -6
  166. package/src/vue/use-nav.ts +8 -2
  167. package/src/vue/use-plan-editor.ts +2 -2
  168. package/src/vue/use-plan-versions.ts +3 -3
  169. package/src/vue/use-plans.ts +12 -12
  170. package/src/vue/use-platform-tenant-actions.ts +9 -6
  171. package/src/vue/use-promotions.ts +9 -10
  172. package/src/vue/use-subscription-draft.ts +2 -3
  173. package/src/vue/use-super-admin-context.ts +3 -3
  174. package/src/vue/use-super-admin-i18n.ts +82 -0
  175. package/src/vue/use-tenant-action-flow.ts +11 -6
  176. package/src/vue/use-tenant-manifest.ts +1 -3
  177. package/src/vue/use-tenant-subscription-bundles.ts +4 -4
  178. package/src/vue/use-tenants.ts +3 -3
  179. package/dist/action-registry-CQ2wL3R-.d.cts +0 -85
  180. package/dist/action-registry-CQ2wL3R-.d.ts +0 -85
  181. package/dist/chunk-LVGUSD3T.js +0 -26
  182. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  183. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  184. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  185. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  186. package/src/vue/use-business-types.ts +0 -269
@@ -1,6 +1,10 @@
1
1
  // Default i18n map for the tenant plan section. Consumers override individual
2
2
  // keys via the `i18n` prop or pass their own map through (build a vue-i18n
3
- // resolver on top of this shape).
3
+ // resolver on top of this shape). The map that a component falls back to is
4
+ // picked from the active SuperAdmin locale via
5
+ // `defaultTenantPlanSectionI18n()`.
6
+
7
+ import type { SaLocale } from '../client/i18n/locale.js';
4
8
 
5
9
  export interface TenantPlanSectionI18n {
6
10
  sectionTitle: string;
@@ -139,6 +143,29 @@ export interface TenantPlanSectionI18n {
139
143
  packageSnapshotNone: string;
140
144
  packageSnapshotShowRaw: string;
141
145
  packageSnapshotHideRaw: string;
146
+ /** Inline error prefix (`Fehler: …`). */
147
+ errorLabel: string;
148
+ /** Tenant self-service page "Meine Bundles" (MySubscriptionBundlesPage). */
149
+ myBundlesTitle: string;
150
+ myBundlesSubtitle: string;
151
+ myBundlesEmptyPrefix: string;
152
+ myBundlesEmptySuffix: string;
153
+ myBundlesBookedSince: string;
154
+ myBundlesCanceledAt: string;
155
+ myBundlesRunsUntil: string;
156
+ myBundlesCancelInProgress: string;
157
+ myBundlesBookInProgress: string;
158
+ myBundlesCancelConfirm: string;
159
+ myBundlesStatusCanceledPending: string;
160
+ myBundlesStatusEnded: string;
161
+ myBundlesAddBundleLabel: string;
162
+ myBundlesAddSelectPlaceholder: string;
163
+ myBundlesPricePerMonthShort: string;
164
+ myBundlesHiddenIncompatible: string;
165
+ myBundlesBundleVersionIdLabel: string;
166
+ myBundlesBundleVersionIdPlaceholder: string;
167
+ myBundlesMinimumTermLabel: string;
168
+ myBundlesMinimumTermPlaceholder: string;
142
169
  }
143
170
 
144
171
  /** German default strings — apps can override them selectively. */
@@ -277,4 +304,193 @@ export const DEFAULT_I18N_DE: TenantPlanSectionI18n = {
277
304
  packageSnapshotNone: 'Diese Subscription wurde nicht über ein Webseiten-Angebot abgeschlossen.',
278
305
  packageSnapshotShowRaw: 'Rohdaten anzeigen',
279
306
  packageSnapshotHideRaw: 'Rohdaten ausblenden',
307
+ errorLabel: 'Fehler',
308
+ myBundlesTitle: 'Meine Bundles',
309
+ myBundlesSubtitle:
310
+ 'Eigenständig gebuchte Add-On-Pakete zu deinem Plan. Mindestlaufzeit + Kündigungs- Termin pro Bundle.',
311
+ myBundlesEmptyPrefix: 'Du hast noch kein Bundle gebucht. Über',
312
+ myBundlesEmptySuffix: 'kannst du dein Paket um zusätzliche Features & Quotas erweitern.',
313
+ myBundlesBookedSince: 'Gebucht seit',
314
+ myBundlesCanceledAt: 'Gekündigt am',
315
+ myBundlesRunsUntil: 'läuft bis',
316
+ myBundlesCancelInProgress: 'Kündige …',
317
+ myBundlesBookInProgress: 'Buche …',
318
+ myBundlesCancelConfirm:
319
+ 'Bundle wirklich kündigen? Die Kündigung wird zum nächstmöglichen Termin wirksam.',
320
+ myBundlesStatusCanceledPending: 'Kündigung wirksam ab …',
321
+ myBundlesStatusEnded: 'Beendet',
322
+ myBundlesAddBundleLabel: 'Bundle',
323
+ myBundlesAddSelectPlaceholder: '— bitte wählen —',
324
+ myBundlesPricePerMonthShort: '€ / Mo',
325
+ myBundlesHiddenIncompatible: 'weitere Bundle(s) ausgeblendet — nicht kompatibel mit Plan',
326
+ myBundlesBundleVersionIdLabel: 'BundleVersion-ID',
327
+ myBundlesBundleVersionIdPlaceholder: 'UUID der gewünschten Bundle-Version',
328
+ myBundlesMinimumTermLabel: 'Mindestlaufzeit (Monate, optional)',
329
+ myBundlesMinimumTermPlaceholder: 'Default: 12',
280
330
  };
331
+
332
+ /** English default strings — mirror of {@link DEFAULT_I18N_DE}. */
333
+ export const DEFAULT_I18N_EN: TenantPlanSectionI18n = {
334
+ sectionTitle: 'Plan & usage',
335
+ sectionSubtitle: 'Current plan, usage and bundles.',
336
+ loading: 'Loading …',
337
+ noSubscription: 'No subscription found for this tenant.',
338
+ activePlan: 'Current plan',
339
+ cycleMonthly: 'Monthly',
340
+ cycleYearly: 'Yearly',
341
+ statusActive: 'Active',
342
+ statusTrial: 'Trial',
343
+ statusPastDue: 'Payment overdue',
344
+ statusCanceled: 'Canceled',
345
+ statusPendingSales: 'Sales review',
346
+ trialEndsAt: 'Trial ends on',
347
+ pilotEndsAt: 'Pilot ends on',
348
+ nextBillingDate: 'Next billing date',
349
+ pendingChange: 'Pending plan change',
350
+ changeFromTo: 'Change to',
351
+ changeEffectiveAt: 'effective as of',
352
+ changePlanButton: 'Change plan',
353
+ cancelSubscriptionButton: 'Cancel subscription',
354
+ usageTitle: 'Usage',
355
+ featuresOverviewTitle: 'Features',
356
+ featuresActive: 'Included',
357
+ featuresLocked: 'Not included',
358
+ bundlesStoreTitle: 'Bundles',
359
+ bundlesBookedTitle: 'Booked bundles',
360
+ bundlesAvailableTitle: 'Available bundles',
361
+ bundlesAvailableEmpty: 'There are currently no additional bundles available.',
362
+ bundlesPerMonth: 'net/month',
363
+ bundleBookAction: 'Book bundle',
364
+ bundleBookInProgress: 'Booking …',
365
+ bundleCancelAction: 'Cancel',
366
+ bundleReactivateAction: 'Reactivate',
367
+ bundleReactivateConfirmTitle: 'Reactivate bundle',
368
+ bundleReactivateConfirmBody:
369
+ 'The cancellation will be reverted. The bundle stays booked, continues to run normally and will be billed again.',
370
+ bundleCanceledAt: 'Canceled effective',
371
+ bundleMinimumTermUntil: 'Minimum term until',
372
+ bundleIncludesLabel: 'Includes',
373
+ bundleAlreadyBooked: 'Already booked',
374
+ bundleIncompatible: 'Not compatible with the current plan',
375
+ bundleMissingRequires: 'Requires',
376
+ bundlePreviewAddTitle: 'Book bundle',
377
+ bundlePreviewCancelTitle: 'Cancel bundle',
378
+ bundlePreviewLoading: 'Calculating preview …',
379
+ bundlePreviewProrationTitle: 'Prorated billing',
380
+ bundlePreviewProratedNow: 'Prorated amount due today',
381
+ bundlePreviewProrationDays: 'days',
382
+ bundlePreviewNextPeriod: 'Regular price from the next period',
383
+ bundlePreviewTrialNote:
384
+ 'Nothing is charged during the trial — billing starts with the first paid period.',
385
+ bundlePreviewNoPrice: 'No list price is configured for the current billing cycle.',
386
+ bundlePreviewMinimumTermLabel: 'Minimum term',
387
+ bundlePreviewMinimumTermMonths: 'months, until',
388
+ bundlePreviewMinimumTermNone: 'No minimum term',
389
+ bundlePreviewRedundantTitle: 'Features already included (would be paid for twice)',
390
+ bundlePreviewRedundantCoveredByPlan: 'already in the plan',
391
+ bundlePreviewRedundantCoveredByBundle: 'already in bundle',
392
+ bundlePreviewMissingRequiresTitle: 'Missing prerequisites',
393
+ bundlePreviewBlockersTitle: 'Booking not possible',
394
+ bundlePreviewWarningsTitle: 'Notes',
395
+ bundlePreviewEffectiveAt: 'Cancellation effective as of',
396
+ bundlePreviewSavings: 'Savings per period once effective',
397
+ bundlePreviewConfirmAdd: 'Book (chargeable)',
398
+ bundlePreviewConfirmCancel: 'Confirm cancellation',
399
+ bundlePreviewInProgress: 'Processing …',
400
+ bundlePreviewClose: 'Cancel',
401
+ pendingVersionTitle: 'Upcoming plan change',
402
+ pendingVersionChipNonRegressive: 'Improvement',
403
+ pendingVersionChipRegressive: 'Confirmation required',
404
+ pendingVersionEffectiveAt: 'Effective as of',
405
+ pendingVersionAcceptAction: 'Accept changes',
406
+ pendingVersionAcceptInProgress: 'Accepting …',
407
+ pendingVersionAcceptedAt: 'Accepted on',
408
+ wizardTitle: 'Change plan',
409
+ wizardClose: 'Close',
410
+ wizardCurrent: 'Current',
411
+ wizardBadgeCurrent: 'active',
412
+ wizardBadgePopular: 'popular',
413
+ wizardPriceUnitMonthly: 'net/month',
414
+ wizardPriceUnitYearly: 'net/year',
415
+ wizardPriceOnRequest: 'on request',
416
+ wizardStepChoose: 'Choose plan',
417
+ wizardStepChooseIntro:
418
+ 'Choose your target plan and the billing cycle. In the next step we show the usage check and the feature diff.',
419
+ wizardStepPreview: 'Preview',
420
+ wizardStepConfirm: 'Confirm',
421
+ wizardNext: 'Next',
422
+ wizardBack: 'Back',
423
+ wizardPreviewLoading: 'Calculating preview …',
424
+ wizardEffectiveAtLabel: 'Effective as of',
425
+ wizardEffectiveImmediate: 'Effective immediately',
426
+ wizardProrationTitle: 'Prorated billing',
427
+ wizardProrationLine: 'Additional amount until the end of the period:',
428
+ wizardProrationDays: 'days',
429
+ wizardLimitsTitle: 'Limit comparison',
430
+ wizardLimitsUsed: 'Usage',
431
+ wizardLimitsCurrent: 'Current',
432
+ wizardLimitsTarget: 'Target',
433
+ wizardFeaturesGained: 'Newly unlocked',
434
+ wizardFeaturesLost: 'Features you lose',
435
+ wizardBlockersTitle: 'Blockers',
436
+ wizardConfirmImmediate: 'The change takes effect immediately.',
437
+ wizardConfirmScheduled: 'The change takes effect on',
438
+ wizardConfirmAction: 'Confirm change',
439
+ wizardConfirmInProgress: 'Changing …',
440
+ wizardConfirmPriceTitle: 'Price overview',
441
+ wizardConfirmProratedNow: 'Prorated amount due today',
442
+ wizardConfirmRecurringNext: 'Regular price from the next period',
443
+ wizardConfirmRecurringFrom: 'Regular price due from',
444
+ wizardConfirmPerCycleMonthly: 'per month',
445
+ wizardConfirmPerCycleYearly: 'per year',
446
+ wizardConfirmTrialNote: 'Nothing is charged during the trial.',
447
+ wizardConfirmRecurringTrialEnd: 'Regular price from the end of the trial',
448
+ wizardChangeTypeUpgrade: 'Upgrade',
449
+ wizardChangeTypeDowngrade: 'Downgrade',
450
+ wizardChangeTypeCycle: 'Cycle change',
451
+ wizardChangeTypeNoop: 'No change',
452
+ packageSnapshotTitle: 'Booked plan (snapshot)',
453
+ packageSnapshotSubtitle:
454
+ 'Read-only copy of the plan as it was marketed at the time of purchase.',
455
+ packageSnapshotCapturedAt: 'Captured on',
456
+ packageSnapshotOfferRef: 'Offer reference',
457
+ packageSnapshotPlanLabel: 'Plan',
458
+ packageSnapshotPlanVersionLabel: 'Plan version',
459
+ packageSnapshotCycleLabel: 'Billing cycle',
460
+ packageSnapshotBundlesLabel: 'Included bundles',
461
+ packageSnapshotBundlesEmpty: 'No bundles in the plan.',
462
+ packageSnapshotPriceMonthly: 'Monthly price',
463
+ packageSnapshotPriceYearly: 'Yearly price',
464
+ packageSnapshotPriceTotal: 'Total price',
465
+ packageSnapshotNone: 'This subscription was not created through a website offer.',
466
+ packageSnapshotShowRaw: 'Show raw data',
467
+ packageSnapshotHideRaw: 'Hide raw data',
468
+ errorLabel: 'Error',
469
+ myBundlesTitle: 'My bundles',
470
+ myBundlesSubtitle:
471
+ 'Add-on packages booked independently on top of your plan. Minimum term + cancellation date per bundle.',
472
+ myBundlesEmptyPrefix: 'You have not booked a bundle yet. Use',
473
+ myBundlesEmptySuffix: 'to extend your plan with additional features & quotas.',
474
+ myBundlesBookedSince: 'Booked since',
475
+ myBundlesCanceledAt: 'Canceled on',
476
+ myBundlesRunsUntil: 'runs until',
477
+ myBundlesCancelInProgress: 'Canceling …',
478
+ myBundlesBookInProgress: 'Booking …',
479
+ myBundlesCancelConfirm:
480
+ 'Really cancel this bundle? The cancellation takes effect on the next possible date.',
481
+ myBundlesStatusCanceledPending: 'Cancellation effective as of …',
482
+ myBundlesStatusEnded: 'Ended',
483
+ myBundlesAddBundleLabel: 'Bundle',
484
+ myBundlesAddSelectPlaceholder: '— please select —',
485
+ myBundlesPricePerMonthShort: '€ / mo',
486
+ myBundlesHiddenIncompatible: 'more bundle(s) hidden — not compatible with plan',
487
+ myBundlesBundleVersionIdLabel: 'BundleVersion ID',
488
+ myBundlesBundleVersionIdPlaceholder: 'UUID of the desired bundle version',
489
+ myBundlesMinimumTermLabel: 'Minimum term (months, optional)',
490
+ myBundlesMinimumTermPlaceholder: 'Default: 12',
491
+ };
492
+
493
+ /** Default map for the given UI locale — fallback layer under the `i18n` prop. */
494
+ export function defaultTenantPlanSectionI18n(locale: SaLocale): TenantPlanSectionI18n {
495
+ return locale === 'en' ? DEFAULT_I18N_EN : DEFAULT_I18N_DE;
496
+ }
@@ -23,7 +23,10 @@
23
23
 
24
24
  <template v-else-if="preview">
25
25
  <!-- Blocker: booking/cancellation not possible -->
26
- <q-card-section v-if="preview.blockers.length > 0" class="sp-bundle-preview__blockers">
26
+ <q-card-section
27
+ v-if="preview.blockers.length > 0"
28
+ class="sp-bundle-preview__blockers"
29
+ >
27
30
  <div class="sp-bundle-preview__block-title">
28
31
  {{ i18n.bundlePreviewBlockersTitle }}
29
32
  </div>
@@ -77,7 +80,9 @@
77
80
  </div>
78
81
  <div class="sp-bundle-preview__price-row">
79
82
  <span>{{ i18n.bundlePreviewMinimumTermLabel }}</span>
80
- <span v-if="addPreview.minimumTermMonths > 0 && addPreview.minimumTermEndsAt">
83
+ <span
84
+ v-if="addPreview.minimumTermMonths > 0 && addPreview.minimumTermEndsAt"
85
+ >
81
86
  {{ addPreview.minimumTermMonths }}
82
87
  {{ i18n.bundlePreviewMinimumTermMonths }}
83
88
  {{ formatDate(addPreview.minimumTermEndsAt) }}
@@ -123,10 +128,7 @@
123
128
  </q-card-section>
124
129
 
125
130
  <!-- Warnings (e.g. MINIMUM_TERM_BINDS) -->
126
- <q-card-section
127
- v-if="otherWarnings.length > 0"
128
- class="sp-bundle-preview__warnings"
129
- >
131
+ <q-card-section v-if="otherWarnings.length > 0" class="sp-bundle-preview__warnings">
130
132
  <div class="sp-bundle-preview__block-title">
131
133
  {{ i18n.bundlePreviewWarningsTitle }}
132
134
  </div>
@@ -9,18 +9,13 @@
9
9
  <li v-for="row in bookedRows" :key="row.id" class="sp-plan-section__item">
10
10
  <div>
11
11
  <span class="sp-plan-section__item-label">{{ row.label }}</span>
12
- <span
13
- v-if="row.canceledAt"
14
- class="sp-plan-section__item-canceled"
15
- >
12
+ <span v-if="row.canceledAt" class="sp-plan-section__item-canceled">
16
13
  {{ i18n.bundleCanceledAt }}
17
14
  {{ formatDate(row.canceledEffectiveAt ?? row.canceledAt) }}
18
15
  </span>
19
- <span
20
- v-else-if="row.minimumTermEndsAt"
21
- class="sp-plan-section__item-price"
22
- >
23
- {{ i18n.bundleMinimumTermUntil }} {{ formatDate(row.minimumTermEndsAt) }}
16
+ <span v-else-if="row.minimumTermEndsAt" class="sp-plan-section__item-price">
17
+ {{ i18n.bundleMinimumTermUntil }}
18
+ {{ formatDate(row.minimumTermEndsAt) }}
24
19
  </span>
25
20
  </div>
26
21
  <div class="sp-bundle-store__booked-actions">
@@ -114,7 +109,11 @@
114
109
  color="primary"
115
110
  unelevated
116
111
  class="sp-bundle-store__card-action"
117
- :label="buyingId === row.bundle.bundleVersionId ? i18n.bundleBookInProgress : i18n.bundleBookAction"
112
+ :label="
113
+ buyingId === row.bundle.bundleVersionId
114
+ ? i18n.bundleBookInProgress
115
+ : i18n.bundleBookAction
116
+ "
118
117
  :loading="buyingId === row.bundle.bundleVersionId"
119
118
  :disable="buyingId !== null"
120
119
  @click="emit('buy', row.bundle.bundleVersionId)"
@@ -38,6 +38,7 @@
38
38
  <script setup lang="ts">
39
39
  import { computed } from 'vue';
40
40
  import type { FeatureUiRegistry } from '@saasicat/types';
41
+ import { useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
41
42
  import type { TenantPlanSectionI18n } from '../default-i18n.js';
42
43
 
43
44
  // TenantFeatureMatrix — complete feature-scope overview (#18):
@@ -54,6 +55,8 @@ const props = defineProps<{
54
55
  i18n: TenantPlanSectionI18n;
55
56
  }>();
56
57
 
58
+ const { intlLocale } = useSuperAdminI18n();
59
+
57
60
  interface FeatureRow {
58
61
  key: string;
59
62
  active: boolean;
@@ -81,7 +84,7 @@ const features = computed<FeatureRow[]>(() => {
81
84
  // Included first, then alphabetically by label.
82
85
  return rows.sort((a, b) => {
83
86
  if (a.active !== b.active) return a.active ? -1 : 1;
84
- return a.label.localeCompare(b.label, 'de');
87
+ return a.label.localeCompare(b.label, intlLocale.value);
85
88
  });
86
89
  });
87
90
  </script>
@@ -43,6 +43,12 @@ import {
43
43
  type SuperAdminLoginAdapter,
44
44
  } from '../vue/super-admin-context.js';
45
45
  import { SUPER_ADMIN_NOTIFY_KEY, type UiNotify } from '../vue/ui-notify.js';
46
+ import {
47
+ SUPER_ADMIN_I18N_KEY,
48
+ createSuperAdminI18n,
49
+ type SuperAdminI18n,
50
+ type SuperAdminI18nOptions,
51
+ } from '../vue/use-super-admin-i18n.js';
46
52
  import { quasarNotify } from './notify.js';
47
53
 
48
54
  export interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
@@ -98,12 +104,21 @@ export interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
98
104
  * including for the setup wizard. Consumed via `useSuperAdminHttp()`.
99
105
  */
100
106
  http?: HttpClient;
107
+ /**
108
+ * Optional: UI locale + string overrides. Default is German. The created
109
+ * context is returned on the handle (`handle.i18n`) — set
110
+ * `handle.i18n.locale.value = 'en'` (or pass a `Ref` here) to switch at
111
+ * runtime. Consumed via `useSuperAdminI18n()` / `useSaMessages()`.
112
+ */
113
+ i18n?: SuperAdminI18nOptions;
101
114
  }
102
115
 
103
116
  export interface SuperAdminAppHandle {
104
117
  app: App;
105
118
  router: Router;
106
119
  pinia: Pinia;
120
+ /** i18n context of the shell — switch locale via `i18n.locale.value`. */
121
+ i18n: SuperAdminI18n;
107
122
  /** Mounts the app on a selector. Returns the root component instance. */
108
123
  mount: (selector: string | Element) => ReturnType<App['mount']>;
109
124
  }
@@ -147,7 +162,10 @@ export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperA
147
162
  options.endpoints.manifestEndpoint ?? `${options.endpoints.apiBase}/manifest`,
148
163
  };
149
164
 
165
+ const i18n = createSuperAdminI18n(options.i18n);
166
+
150
167
  app.provide(SUPER_ADMIN_BRAND_KEY, { tag: 'SuperAdmin', ...options.brand });
168
+ app.provide(SUPER_ADMIN_I18N_KEY, i18n);
151
169
  app.provide(SUPER_ADMIN_ENDPOINTS_KEY, endpoints);
152
170
  app.provide(SUPER_ADMIN_EXTENSIONS_KEY, options.extensions ?? {});
153
171
  app.provide(SUPER_ADMIN_ACTIONS_KEY, options.actions ?? {});
@@ -168,6 +186,7 @@ export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperA
168
186
  app,
169
187
  router,
170
188
  pinia,
189
+ i18n,
171
190
  mount: (selector) => app.mount(selector),
172
191
  };
173
192
  }
@@ -109,7 +109,7 @@ export function runAdminPagesSuite(
109
109
 
110
110
  test.describe(`${config.appName}: SuperAdmin-Pages Smoke`, () => {
111
111
  for (const pageDef of config.pages) {
112
- test(`${pageDef.name} rendert ohne Fehler`, async ({ page }) => {
112
+ test(`${pageDef.name} renders without errors`, async ({ page }) => {
113
113
  const errors = attachListeners(
114
114
  page,
115
115
  config.consoleErrorAllowlist,
@@ -127,19 +127,19 @@ export function runAdminPagesSuite(
127
127
  timeout: 5_000,
128
128
  });
129
129
  }
130
- expect(errors.pageErrors, `pageerror-Events auf ${pageDef.path}`).toHaveLength(0);
130
+ expect(errors.pageErrors, `pageerror events on ${pageDef.path}`).toHaveLength(0);
131
131
  expect(
132
132
  errors.consoleErrors,
133
- `console.error-Events auf ${pageDef.path}`,
133
+ `console.error events on ${pageDef.path}`,
134
134
  ).toHaveLength(0);
135
135
  expect(
136
136
  errors.networkErrors,
137
- `HTTP-Errors auf /api/ während ${pageDef.path}`,
137
+ `HTTP errors on /api/ during ${pageDef.path}`,
138
138
  ).toHaveLength(0);
139
139
  });
140
140
  }
141
141
 
142
- test('Dashboard zeigt erwartete KPIs, Distributions und Shortcuts', async ({ page }) => {
142
+ test('Dashboard shows the expected KPIs, distributions and shortcuts', async ({ page }) => {
143
143
  attachListeners(page, config.consoleErrorAllowlist);
144
144
  await loginIfNeeded(page, config);
145
145
  const origin = new URL(config.loginUrl).origin;
@@ -151,7 +151,9 @@ export function runAdminPagesSuite(
151
151
  const kpiLabels = await page.locator('.sa-kpi__label').allTextContents();
152
152
  const normalizedKpis = kpiLabels.map((s) => s.trim());
153
153
  for (const expected of config.expectedKpiLabels) {
154
- expect(normalizedKpis, `KPI '${expected}' fehlt auf Dashboard`).toContain(expected);
154
+ expect(normalizedKpis, `KPI '${expected}' missing on the dashboard`).toContain(
155
+ expected,
156
+ );
155
157
  }
156
158
 
157
159
  if (config.expectedDistributionTitles?.length) {
@@ -162,7 +164,7 @@ export function runAdminPagesSuite(
162
164
  for (const expected of config.expectedDistributionTitles) {
163
165
  expect(
164
166
  normalizedDist,
165
- `Distribution '${expected}' fehlt auf Dashboard`,
167
+ `Distribution '${expected}' missing on the dashboard`,
166
168
  ).toContain(expected);
167
169
  }
168
170
  }
@@ -172,14 +174,15 @@ export function runAdminPagesSuite(
172
174
  .allTextContents();
173
175
  const normalizedShortcuts = shortcutTitles.map((s) => s.trim());
174
176
  for (const expected of config.expectedShortcutTitles) {
175
- expect(normalizedShortcuts, `Shortcut '${expected}' fehlt auf Dashboard`).toContain(
176
- expected,
177
- );
177
+ expect(
178
+ normalizedShortcuts,
179
+ `Shortcut '${expected}' missing on the dashboard`,
180
+ ).toContain(expected);
178
181
  }
179
182
  for (const forbidden of config.forbiddenShortcutTitles ?? []) {
180
183
  expect(
181
184
  normalizedShortcuts,
182
- `Shortcut '${forbidden}' darf nicht auf Dashboard sein`,
185
+ `Shortcut '${forbidden}' must not be on the dashboard`,
183
186
  ).not.toContain(forbidden);
184
187
  }
185
188
  });
@@ -60,13 +60,15 @@ export const ProjectPageHost = defineComponent({
60
60
  const page = matchingPage.value;
61
61
  const cmp = resolvedComponent.value;
62
62
  if (page && cmp) return h(cmp);
63
+ // Misconfiguration diagnostics — developer-facing, therefore not
64
+ // part of the localized catalog.
63
65
  return h('div', { class: 'sa-project-page-host__missing', role: 'alert' }, [
64
- h('h2', 'Seite nicht verfügbar'),
66
+ h('h2', 'Page not available'),
65
67
  h(
66
68
  'p',
67
69
  page
68
- ? `componentKey "${page.componentKey}" ist nicht in der extensions:-Map registriert. Apps müssen die Komponente in createSuperAdminApp({ extensions }) eintragen.`
69
- : `Keine Manifest-Definition für ${route.path}. Entweder fehlt die ProjectPage im Manifest, oder die Capability ist nicht aktiv.`,
70
+ ? `componentKey "${page.componentKey}" is not registered in the extensions: map. Apps must register the component in createSuperAdminApp({ extensions }).`
71
+ : `No manifest definition for ${route.path}. Either the ProjectPage is missing from the manifest, or the capability is not active.`,
70
72
  ),
71
73
  ]);
72
74
  };
@@ -29,9 +29,9 @@ export interface UseAuditEntriesResult extends UseApiListResult<AuditEntry> {
29
29
  export function useAuditEntries(options: UseAuditEntriesOptions): UseAuditEntriesResult {
30
30
  if (!options?.endpoint) {
31
31
  throw new Error(
32
- 'useAuditEntries: `endpoint` ist Pflicht (z. B. "/api/admin/audit" ' +
33
- 'oder "/api/v1/admin/audit"). Plattform hat keinen Default, weil ' +
34
- 'Apps unterschiedliche globalPrefix-Konventionen haben.',
32
+ 'useAuditEntries: `endpoint` is required (e.g. "/api/admin/audit" ' +
33
+ 'or "/api/v1/admin/audit"). The platform has no default because ' +
34
+ 'apps use different globalPrefix conventions.',
35
35
  );
36
36
  }
37
37
  const filter = options.filter ?? ref<AuditQuery>({});
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { computed, ref, type ComputedRef, type Ref } from 'vue';
14
14
  import { defaultHttpClient, type HttpClient } from '../client/types.js';
15
+ import { useSaMessages } from './use-super-admin-i18n.js';
15
16
 
16
17
  export type BulkItemKind = 'plan';
17
18
 
@@ -62,11 +63,12 @@ export interface UseBulkPublishResult {
62
63
  }
63
64
 
64
65
  export function useBulkPublish(options: UseBulkPublishOptions): UseBulkPublishResult {
66
+ const msg = useSaMessages('planVersions');
65
67
  if (!options?.endpoints) {
66
68
  throw new Error(
67
- 'useBulkPublish: `endpoints` ist Pflicht (Mapping plan → URL-Builder). ' +
68
- 'Plattform hat keinen Default, weil Apps unterschiedliche ' +
69
- 'globalPrefix-Konventionen haben.',
69
+ 'useBulkPublish: `endpoints` is required (mapping plan → URL builder). ' +
70
+ 'The platform has no default because apps use different ' +
71
+ 'globalPrefix conventions.',
70
72
  );
71
73
  }
72
74
  const http = options.http ?? defaultHttpClient();
@@ -128,7 +130,7 @@ export function useBulkPublish(options: UseBulkPublishOptions): UseBulkPublishRe
128
130
  // Mark all items as failed with a clear reason.
129
131
  for (const item of items.value) {
130
132
  item.status = 'failed';
131
- item.error = 'changeNote ist Pflicht beim Publish.';
133
+ item.error = msg.value.bulkPublish.changeNoteRequired;
132
134
  }
133
135
  return;
134
136
  }
@@ -36,10 +36,10 @@ export function useBundleVersionsMap(
36
36
  options: UseBundleVersionsMapOptions,
37
37
  ): UseBundleVersionsMapResult {
38
38
  if (!options?.adminEndpoint) {
39
- throw new Error('useBundleVersionsMap: `adminEndpoint` ist Pflicht.');
39
+ throw new Error('useBundleVersionsMap: `adminEndpoint` is required.');
40
40
  }
41
41
  if (!options?.bundles) {
42
- throw new Error('useBundleVersionsMap: `bundles` ist Pflicht.');
42
+ throw new Error('useBundleVersionsMap: `bundles` is required.');
43
43
  }
44
44
 
45
45
  const http = options.http ?? defaultHttpClient();
@@ -58,7 +58,9 @@ export function useBundleVersionsMap(
58
58
  headers: { 'content-type': 'application/json', ...authHeaders() },
59
59
  });
60
60
  if (res.status >= 400) {
61
- throw new Error(`HTTP ${res.status} beim Laden der Versionen für Bundle '${bundleId}'`);
61
+ throw new Error(
62
+ `HTTP ${res.status} while loading the versions for bundle '${bundleId}'`,
63
+ );
62
64
  }
63
65
  return ((await res.json().catch(() => null)) as BundleVersionRow[] | null) ?? [];
64
66
  }
@@ -74,7 +76,7 @@ export function useBundleVersionsMap(
74
76
  return [b.id, versions] as const;
75
77
  } catch (err) {
76
78
  console.warn(
77
- `useBundleVersionsMap: Bundle '${b.bundleKey}' Versions-Load failed`,
79
+ `useBundleVersionsMap: loading versions for bundle '${b.bundleKey}' failed`,
78
80
  err,
79
81
  );
80
82
  return [b.id, []] as const;
@@ -58,13 +58,13 @@ export interface UseBundlesResult {
58
58
  export function useBundles(options: UseBundlesOptions): UseBundlesResult {
59
59
  if (!options?.adminEndpoint) {
60
60
  throw new Error(
61
- 'useBundles: `adminEndpoint` ist Pflicht (z. B. "/api/admin" oder ' +
62
- '"/api/v1/admin"). Plattform hat keinen Default, weil Apps ' +
63
- 'unterschiedliche globalPrefix-Konventionen haben.',
61
+ 'useBundles: `adminEndpoint` is required (e.g. "/api/admin" or ' +
62
+ '"/api/v1/admin"). The platform has no default, because apps use ' +
63
+ 'different globalPrefix conventions.',
64
64
  );
65
65
  }
66
66
  if (!options?.projectKey) {
67
- throw new Error('useBundles: `projectKey` ist Pflicht.');
67
+ throw new Error('useBundles: `projectKey` is required.');
68
68
  }
69
69
 
70
70
  const http = options.http ?? defaultHttpClient();
@@ -91,7 +91,7 @@ export function useBundles(options: UseBundlesOptions): UseBundlesResult {
91
91
  throw new BundlesApiError(
92
92
  res.status,
93
93
  body,
94
- `Bundles-API antwortete mit HTTP ${res.status}`,
94
+ `Bundles API responded with HTTP ${res.status}`,
95
95
  );
96
96
  }
97
97
  return body as T;
@@ -117,7 +117,7 @@ export function useBundles(options: UseBundlesOptions): UseBundlesResult {
117
117
  method: 'POST',
118
118
  body: JSON.stringify(data),
119
119
  });
120
- if (!created) throw new BundlesApiError(0, null, 'Create gab keinen Body zurück');
120
+ if (!created) throw new BundlesApiError(0, null, 'Create returned no body');
121
121
  bundles.value = [...bundles.value, created];
122
122
  return created;
123
123
  }
@@ -127,7 +127,7 @@ export function useBundles(options: UseBundlesOptions): UseBundlesResult {
127
127
  method: 'PATCH',
128
128
  body: JSON.stringify(data),
129
129
  });
130
- if (!updated) throw new BundlesApiError(0, null, 'Update gab keinen Body zurück');
130
+ if (!updated) throw new BundlesApiError(0, null, 'Update returned no body');
131
131
  bundles.value = bundles.value.map((b) => (b.id === bundleId ? updated : b));
132
132
  return updated;
133
133
  }
@@ -189,10 +189,10 @@ export interface UseBundleVersionsResult {
189
189
 
190
190
  export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleVersionsResult {
191
191
  if (!options?.adminEndpoint) {
192
- throw new Error('useBundleVersions: `adminEndpoint` ist Pflicht.');
192
+ throw new Error('useBundleVersions: `adminEndpoint` is required.');
193
193
  }
194
194
  if (!options?.bundleId) {
195
- throw new Error('useBundleVersions: `bundleId` ist Pflicht.');
195
+ throw new Error('useBundleVersions: `bundleId` is required.');
196
196
  }
197
197
 
198
198
  const http = options.http ?? defaultHttpClient();
@@ -220,7 +220,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
220
220
  throw new BundlesApiError(
221
221
  res.status,
222
222
  body,
223
- `BundleVersions-API antwortete mit HTTP ${res.status}`,
223
+ `BundleVersions API responded with HTTP ${res.status}`,
224
224
  );
225
225
  }
226
226
  return body as T;
@@ -246,7 +246,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
246
246
  method: 'POST',
247
247
  body: JSON.stringify(data),
248
248
  });
249
- if (!result) throw new BundlesApiError(0, null, 'CreateDraft gab keinen Body zurück');
249
+ if (!result) throw new BundlesApiError(0, null, 'CreateDraft returned no body');
250
250
  versions.value = [...versions.value, result.bundleVersion];
251
251
  return result;
252
252
  }
@@ -259,7 +259,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
259
259
  `${versionUrlBase}/${versionId}`,
260
260
  { method: 'PATCH', body: JSON.stringify(data) },
261
261
  );
262
- if (!result) throw new BundlesApiError(0, null, 'UpdateDraft gab keinen Body zurück');
262
+ if (!result) throw new BundlesApiError(0, null, 'UpdateDraft returned no body');
263
263
  versions.value = versions.value.map((v) => (v.id === versionId ? result.bundleVersion : v));
264
264
  return result;
265
265
  }
@@ -277,7 +277,7 @@ export function useBundleVersions(options: UseBundleVersionsOptions): UseBundleV
277
277
  `${versionUrlBase}/${versionId}/publish`,
278
278
  { method: 'POST', body: JSON.stringify(opts) },
279
279
  );
280
- if (!result) throw new BundlesApiError(0, null, 'Publish gab keinen Body zurück');
280
+ if (!result) throw new BundlesApiError(0, null, 'Publish returned no body');
281
281
  // Reload versions, because publishing may mark another version as
282
282
  // superseded — the local cache would otherwise be inconsistent.
283
283
  await load();