@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
@@ -3,12 +3,8 @@
3
3
  <!-- Page header -->
4
4
  <div class="pm-page-head">
5
5
  <div>
6
- <h2 class="pm-h-title">Pläne · Matrix-Vergleich</h2>
7
- <p class="pm-h-sub">
8
- Alle Pläne nebeneinander · {{ plans.length }} Pläne ·
9
- {{ orderedFeatureKeys.length }} Features · {{ orderedQuotaKeys.length }} Quotas
10
- · {{ orderedBundleKeys.length }} Bundles
11
- </p>
6
+ <h2 class="pm-h-title">{{ msg.matrix.title }}</h2>
7
+ <p class="pm-h-sub">{{ summary }}</p>
12
8
  </div>
13
9
  <div class="pm-head-actions">
14
10
  <button class="pm-btn" type="button" @click="$emit('viewCatalog')">
@@ -23,7 +19,7 @@
23
19
  <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
24
20
  <circle cx="12" cy="12" r="3" />
25
21
  </svg>
26
- <span>Catalog-Vorschau</span>
22
+ <span>{{ msg.matrix.catalogPreview }}</span>
27
23
  </button>
28
24
  <button class="pm-btn pm-btn--primary" type="button" @click="$emit('createPlan')">
29
25
  <svg
@@ -36,7 +32,7 @@
36
32
  >
37
33
  <path d="M12 5v14M5 12h14" />
38
34
  </svg>
39
- <span>Plan anlegen</span>
35
+ <span>{{ msg.matrix.createPlan }}</span>
40
36
  </button>
41
37
  </div>
42
38
  </div>
@@ -47,7 +43,9 @@
47
43
  <thead>
48
44
  <tr class="pm-head">
49
45
  <th class="pm-rowhead-cell">
50
- <span class="pm-component-kicker">Komponente</span>
46
+ <span class="pm-component-kicker">{{
47
+ msg.matrix.componentColumn
48
+ }}</span>
51
49
  </th>
52
50
  <th v-for="p in resolvedPlans" :key="p.planKey" class="pm-plan-head">
53
51
  <div
@@ -71,17 +69,19 @@
71
69
  <div class="pm-plan-divider" />
72
70
 
73
71
  <div class="pm-status-row">
74
- <span v-if="p.live" class="pm-chip pm-chip--live pm-chip--dot"
75
- >v{{ p.live.version }} live</span
76
- >
77
- <span v-else class="pm-chip pm-chip--supersed pm-chip--dot"
78
- >keine live</span
72
+ <span
73
+ v-if="p.live"
74
+ class="pm-chip pm-chip--live pm-chip--dot"
75
+ >{{ liveVersionLabel(p.live.version) }}</span
79
76
  >
77
+ <span v-else class="pm-chip pm-chip--supersed pm-chip--dot">{{
78
+ msg.matrix.chipNoLive
79
+ }}</span>
80
80
  <span
81
81
  v-if="p.draft"
82
82
  class="pm-chip pm-chip--draft pm-chip--dot"
83
83
  >
84
- v{{ p.draft.version }} Entwurf
84
+ {{ draftVersionLabel(p.draft.version) }}
85
85
  </span>
86
86
  </div>
87
87
 
@@ -93,34 +93,46 @@
93
93
  Number(p.live.yearlyNet) === 0
94
94
  "
95
95
  >
96
- <span class="pm-price-free">Kostenlos</span>
96
+ <span class="pm-price-free">{{
97
+ msg.matrix.priceFree
98
+ }}</span>
97
99
  </template>
98
100
  <template v-else-if="p.live">
99
101
  <span class="pm-price-big">{{
100
102
  formatMoney(p.live.monthlyNet)
101
103
  }}</span>
102
- <span class="pm-price-unit">/ Mo</span>
104
+ <span class="pm-price-unit">{{
105
+ msg.matrix.perMonthShort
106
+ }}</span>
103
107
  <span class="pm-price-yearly"
104
- {{ formatMoney(p.live.yearlyNet) }}/J</span
108
+
109
+ {{
110
+ formatMoney(p.live.yearlyNet) +
111
+ msg.matrix.perYearShort
112
+ }}</span
105
113
  >
106
114
  </template>
107
115
  <template v-else-if="p.draft">
108
116
  <span class="pm-price-big">{{
109
117
  formatMoney(p.draft.monthlyNet)
110
118
  }}</span>
111
- <span class="pm-price-unit">/ Mo</span>
112
- <span class="pm-price-yearly">(Entwurf)</span>
119
+ <span class="pm-price-unit">{{
120
+ msg.matrix.perMonthShort
121
+ }}</span>
122
+ <span class="pm-price-yearly">{{
123
+ msg.matrix.priceDraftMarker
124
+ }}</span>
113
125
  </template>
114
126
  <template v-else>
115
- <span class="pm-price-free">noch keine Preise</span>
127
+ <span class="pm-price-free">{{ msg.matrix.noPrices }}</span>
116
128
  </template>
117
129
  </div>
118
130
 
119
131
  <div class="pm-plan-meta">
120
- <span>{{ p.tenantCount }} Mandanten</span>
132
+ <span>{{ tenantCountLabel(p.tenantCount) }}</span>
121
133
  <template v-if="p.live?.validFrom">
122
134
  <span>·</span>
123
- <span>ab {{ p.live.validFrom.slice(0, 10) }}</span>
135
+ <span>{{ validFromLabel(p.live.validFrom) }}</span>
124
136
  </template>
125
137
  <span
126
138
  :class="[
@@ -128,7 +140,11 @@
128
140
  p.live?.marketed ? '' : 'pm-chip--supersed',
129
141
  ]"
130
142
  >
131
- {{ p.live?.marketed ? 'im Katalog' : 'privat' }}
143
+ {{
144
+ p.live?.marketed
145
+ ? msg.matrix.inCatalog
146
+ : msg.matrix.private
147
+ }}
132
148
  </span>
133
149
  </div>
134
150
 
@@ -150,13 +166,13 @@
150
166
  d="M12 20h9M16.5 3.5a2.12 2.12 0 113 3L7 19l-4 1 1-4 12.5-12.5z"
151
167
  />
152
168
  </svg>
153
- <span>Öffnen</span>
169
+ <span>{{ common.open }}</span>
154
170
  </button>
155
171
  <button
156
172
  class="pm-btn pm-btn--sm pm-btn--ghost"
157
173
  type="button"
158
174
  @click="$emit('clonePlan', p.plan)"
159
- aria-label="Plan klonen"
175
+ :aria-label="msg.matrix.clonePlan"
160
176
  >
161
177
  <svg
162
178
  width="12"
@@ -187,8 +203,8 @@
187
203
  <path d="M12 5v14M5 12h14" />
188
204
  </svg>
189
205
  </div>
190
- <div class="pm-add-title">Plan anlegen</div>
191
- <div class="pm-add-sub">oder Klon erstellen</div>
206
+ <div class="pm-add-title">{{ msg.matrix.createPlan }}</div>
207
+ <div class="pm-add-sub">{{ msg.matrix.createPlanSub }}</div>
192
208
  </button>
193
209
  </th>
194
210
  </tr>
@@ -200,7 +216,7 @@
200
216
  <td :colspan="resolvedPlans.length + 2">
201
217
  <div class="pm-group-inner">
202
218
  <span class="pm-group-dot pm-group-dot--quota" />
203
- <span>Quotas</span>
219
+ <span>{{ msg.matrix.groupQuotas }}</span>
204
220
  <span class="pm-group-count">{{ orderedQuotaKeys.length }}</span>
205
221
  </div>
206
222
  </td>
@@ -239,7 +255,7 @@
239
255
  <td :colspan="resolvedPlans.length + 2">
240
256
  <div class="pm-group-inner">
241
257
  <span class="pm-group-dot pm-group-dot--feature" />
242
- <span>Features</span>
258
+ <span>{{ msg.matrix.groupFeatures }}</span>
243
259
  <span class="pm-group-count">{{ orderedFeatureKeys.length }}</span>
244
260
  </div>
245
261
  </td>
@@ -270,7 +286,7 @@
270
286
  >
271
287
  <path d="M5 13l4 4L19 7" />
272
288
  </svg>
273
- Basis · immer enthalten
289
+ {{ msg.matrix.baseBadge }}
274
290
  </span>
275
291
  </td>
276
292
  <template v-else>
@@ -313,7 +329,7 @@
313
329
  <td :colspan="resolvedPlans.length + 2">
314
330
  <div class="pm-group-inner">
315
331
  <span class="pm-group-dot pm-group-dot--bundle" />
316
- <span>Bundles</span>
332
+ <span>{{ msg.matrix.groupBundles }}</span>
317
333
  <span class="pm-group-count">{{ orderedBundleKeys.length }}</span>
318
334
  </div>
319
335
  </td>
@@ -375,13 +391,13 @@
375
391
  >
376
392
  <path d="M5 13l4 4L19 7" /></svg
377
393
  ></span>
378
- enthalten
394
+ {{ msg.matrix.legendIncluded }}
379
395
  </span>
380
396
  <span class="pm-legend-item">
381
397
  <span class="pm-legend-dash">—</span>
382
- nicht enthalten
398
+ {{ msg.matrix.legendNotIncluded }}
383
399
  </span>
384
- <span v-if="loading" class="pm-legend-loading">lade Versionen…</span>
400
+ <span v-if="loading" class="pm-legend-loading">{{ msg.matrix.legendLoading }}</span>
385
401
  </div>
386
402
  </div>
387
403
  </template>
@@ -389,6 +405,9 @@
389
405
  <script setup lang="ts">
390
406
  import { computed } from 'vue';
391
407
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
408
+ import { formatMessage } from '../../client/i18n/format.js';
409
+ import { formatCurrency } from '../../client/i18n/currency.js';
410
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
392
411
 
393
412
  // PlanMatrix — V1 matrix overview. Plans as columns, quotas/features/
394
413
  // bundles as rows. Expects the plan master list plus, per plan, the
@@ -460,6 +479,26 @@ interface ResolvedPlan {
460
479
  tenantCount: number;
461
480
  }
462
481
 
482
+ const msg = useSaMessages('plans');
483
+ const { locale, intlLocale } = useSuperAdminI18n();
484
+ const common = useSaMessages('common');
485
+
486
+ function liveVersionLabel(version: number): string {
487
+ return formatMessage(msg.value.matrix.chipLiveVersion, { version });
488
+ }
489
+
490
+ function draftVersionLabel(version: number): string {
491
+ return formatMessage(msg.value.matrix.chipDraftVersion, { version });
492
+ }
493
+
494
+ function tenantCountLabel(count: number): string {
495
+ return formatMessage(msg.value.matrix.tenantCount, { count });
496
+ }
497
+
498
+ function validFromLabel(validFrom: string): string {
499
+ return formatMessage(msg.value.matrix.validFrom, { date: validFrom.slice(0, 10) });
500
+ }
501
+
463
502
  const DEFAULT_ACCENTS: Record<string, string> = {
464
503
  STARTER: '#64748b',
465
504
  STANDARD: '#2563eb',
@@ -587,12 +626,21 @@ const orderedFeatureKeys = computed<string[]>(() => {
587
626
  const pb = presence.get(b)!;
588
627
  if (pa.count !== pb.count) return pb.count - pa.count;
589
628
  if (pa.mask !== pb.mask) return pb.mask.localeCompare(pa.mask);
590
- return featureLabel(a).localeCompare(featureLabel(b), 'de-DE');
629
+ return featureLabel(a).localeCompare(featureLabel(b), intlLocale.value);
591
630
  });
592
631
  });
593
632
 
594
633
  const orderedBundleKeys = computed<string[]>(() => props.availableBundles.map((b) => b.bundleKey));
595
634
 
635
+ const summary = computed(() =>
636
+ formatMessage(msg.value.matrix.summary, {
637
+ plans: props.plans.length,
638
+ features: orderedFeatureKeys.value.length,
639
+ quotas: orderedQuotaKeys.value.length,
640
+ bundles: orderedBundleKeys.value.length,
641
+ }),
642
+ );
643
+
596
644
  function quotaLabel(key: string): string {
597
645
  return props.availableQuotas.find((q) => q.quotaKey === key)?.label || key;
598
646
  }
@@ -627,8 +675,7 @@ function hasBundle(p: ResolvedPlan, bKey: string): boolean {
627
675
  function formatMoney(raw: string | number): string {
628
676
  const num = typeof raw === 'string' ? Number(raw) : raw;
629
677
  if (!Number.isFinite(num)) return String(raw);
630
- if (Number.isInteger(num)) return `${num} €`;
631
- return `${num.toFixed(2).replace('.', ',')} €`;
678
+ return formatCurrency(num, locale.value);
632
679
  }
633
680
 
634
681
  function formatQuota(v: number | undefined): string {