@saasicat/ui-vue 0.4.0 → 0.6.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 (214) hide show
  1. package/README.md +64 -4
  2. package/dist/chunk-A3W2N26K.js +714 -0
  3. package/dist/chunk-QZOSDCNP.js +98 -0
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3988 -0
  6. package/dist/client/index.d.cts +321 -0
  7. package/dist/client/index.d.ts +321 -0
  8. package/dist/client/index.js +70 -0
  9. package/dist/index.cjs +4446 -1249
  10. package/dist/index.d.cts +297 -888
  11. package/dist/index.d.ts +297 -888
  12. package/dist/index.js +609 -1348
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3340 -0
  16. package/dist/quasar/index.d.cts +95 -0
  17. package/dist/quasar/index.d.ts +95 -0
  18. package/dist/quasar/index.js +96 -0
  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/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
  22. package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
  23. package/package.json +23 -3
  24. package/src/{action-registry.ts → client/action-registry.ts} +4 -4
  25. package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
  26. package/src/{boot-loader.ts → 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 +27 -0
  51. package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
  52. package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
  53. package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/FeatureGate.vue +1 -1
  56. package/src/components/MarketingPromotionsTab.vue +112 -59
  57. package/src/components/MfaPromptDialog.vue +11 -11
  58. package/src/components/TenantActionConfirmDialog.vue +19 -17
  59. package/src/components/VersionDiffPreview.vue +72 -43
  60. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  61. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  62. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  63. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  64. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  65. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  66. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  67. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  68. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  69. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  70. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  71. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  72. package/src/components/dialogs/types.ts +3 -13
  73. package/src/components/plan/PlanCycleToggle.vue +21 -10
  74. package/src/components/plan/PlanGrid.vue +2 -2
  75. package/src/components/plan/PriceSummary.vue +1 -1
  76. package/src/components/plan/PromoCodeInput.vue +1 -1
  77. package/src/components/plan/PublicBundleGrid.vue +1 -1
  78. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  79. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  80. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  81. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  82. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  83. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  84. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  85. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  86. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  87. package/src/components/plan-detail/PlanDetail.vue +21 -23
  88. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  89. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  90. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  91. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  92. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  93. package/src/components/plan-list/PlanList.vue +99 -57
  94. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  95. package/src/components/plan-review/PlanReview.vue +105 -80
  96. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  97. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  98. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  99. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  100. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  101. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  102. package/src/index.ts +82 -79
  103. package/src/pages-standard/AdminLayout.vue +43 -18
  104. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  105. package/src/pages-standard/AuditPage.vue +58 -19
  106. package/src/pages-standard/BundlesPage.vue +29 -18
  107. package/src/pages-standard/DashboardPage.vue +31 -17
  108. package/src/pages-standard/DiscoveryPage.vue +40 -28
  109. package/src/pages-standard/EmailHistoryPage.vue +98 -69
  110. package/src/pages-standard/MarketingCatalogPage.vue +40 -21
  111. package/src/pages-standard/PilotsPage.vue +64 -52
  112. package/src/pages-standard/PlanVersionsPage.vue +9 -5
  113. package/src/pages-standard/PlansPage.vue +91 -59
  114. package/src/pages-standard/PlatformEmailPage.vue +120 -53
  115. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  116. package/src/pages-standard/PromoCodesPage.vue +51 -54
  117. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  118. package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
  119. package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
  120. package/src/pages-standard/TenantDetailPage.vue +54 -40
  121. package/src/pages-standard/TenantsPage.vue +57 -44
  122. package/src/pages-standard/UsersPage.vue +70 -52
  123. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  124. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  125. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  126. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  127. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  128. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  129. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  130. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  131. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  132. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  133. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  134. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  135. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  136. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  137. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  138. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  139. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  140. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  141. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
  142. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
  143. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
  144. package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
  145. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
  146. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
  147. package/src/pages-standard/plan-versions/format.ts +33 -17
  148. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  149. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  150. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  151. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  152. package/src/pages-standard/tenants/format.ts +3 -2
  153. package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
  154. package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
  155. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  156. package/src/pages-tenant/PlanChangeWizard.vue +10 -6
  157. package/src/pages-tenant/TenantPlanSection.vue +20 -16
  158. package/src/pages-tenant/default-i18n.ts +217 -1
  159. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
  160. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
  161. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  162. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
  163. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
  164. package/src/quasar/create-super-admin-app.ts +192 -0
  165. package/src/quasar/index.ts +11 -0
  166. package/src/quasar/notify.ts +30 -0
  167. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  168. package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
  169. package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
  170. package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
  171. package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
  172. package/src/vue/ui-notify.ts +30 -0
  173. package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
  174. package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
  175. package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
  176. package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
  177. package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
  178. package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
  179. package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
  180. package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
  181. package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
  182. package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
  183. package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
  184. package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
  185. package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
  186. package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
  187. package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
  188. package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
  189. package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
  190. package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
  191. package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
  192. package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
  193. package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
  194. package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
  195. package/src/vue/use-super-admin-i18n.ts +82 -0
  196. package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
  197. package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
  198. package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
  199. package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
  200. package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
  201. package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
  202. package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
  203. package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
  204. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  205. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  206. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  207. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  208. package/src/use-business-types.ts +0 -269
  209. /package/src/{http-json.ts → client/http-json.ts} +0 -0
  210. /package/src/{types.ts → client/types.ts} +0 -0
  211. /package/src/{version.ts → client/version.ts} +0 -0
  212. /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
  213. /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
  214. /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
@@ -9,11 +9,10 @@
9
9
  // not required at all). The manifest is Discovery, not security — the server
10
10
  // enforces the routes independently.
11
11
 
12
- import type {
13
- AdminManifest,
14
- StandardPageDef,
15
- StandardPageKey,
16
- } from '@saasicat/types';
12
+ import type { AdminManifest, StandardPageDef, StandardPageKey } from '@saasicat/types';
13
+
14
+ import { DEFAULT_SA_LOCALE, type SaLocale } from './i18n/locale.js';
15
+ import { navMessages } from './i18n/messages/nav.js';
17
16
 
18
17
  /**
19
18
  * Default routes for the platform standard pages. Consumers may override this
@@ -32,7 +31,6 @@ export const DEFAULT_STANDARD_PAGE_ROUTES: Record<StandardPageKey, string> = {
32
31
  pilots: '/admin/pilots',
33
32
  discovery: '/admin/discovery',
34
33
  bundles: '/admin/bundles',
35
- businessTypes: '/admin/business-types',
36
34
  marketingCatalog: '/admin/marketing-catalog',
37
35
  platformEmail: '/admin/platform-email',
38
36
  platformEmailHistory: '/admin/platform-email-history',
@@ -59,13 +57,19 @@ export interface BuildRouteEntry {
59
57
  }
60
58
 
61
59
  export interface NavBuilderOptions {
60
+ /**
61
+ * UI locale for the default labels and section names, default `'de'`.
62
+ * Pass the same locale to `buildSidebar()`'s `sectionOrder` (via
63
+ * `defaultSectionOrder(locale)`) — section names are compared as strings.
64
+ */
65
+ locale?: SaLocale;
62
66
  /**
63
67
  * Optional: overrides the default routes for certain standard pages.
64
68
  * Consumers set this if they want an alternative URL structure.
65
69
  */
66
70
  standardPageRoutes?: Partial<Record<StandardPageKey, string>>;
67
71
  /**
68
- * Default labels for standard pages consumers may localize.
72
+ * Per-page label overrides layered over the locale defaults.
69
73
  */
70
74
  standardPageLabels?: Partial<Record<StandardPageKey, string>>;
71
75
  /** Default icons for standard pages. */
@@ -82,24 +86,6 @@ export interface NavBuilderOptions {
82
86
  availableExtensions?: Set<string>;
83
87
  }
84
88
 
85
- const DEFAULT_LABELS: Record<StandardPageKey, string> = {
86
- dashboard: 'Dashboard',
87
- tenants: 'Mandanten',
88
- subscriptions: 'Abonnements',
89
- promoCodes: 'Promo-Codes',
90
- plans: 'Pläne & Versionen',
91
- planVersions: 'Plan-Versionen',
92
- audit: 'Audit-Log',
93
- users: 'Benutzer',
94
- pilots: 'Piloten',
95
- discovery: 'Discovery',
96
- bundles: 'Bundles',
97
- businessTypes: 'Geschäftstypen',
98
- marketingCatalog: 'Marketing-Catalog',
99
- platformEmail: 'Plattform-E-Mail',
100
- platformEmailHistory: 'E-Mail-Verlauf',
101
- };
102
-
103
89
  const DEFAULT_ICONS: Record<StandardPageKey, string> = {
104
90
  dashboard: 'dashboard',
105
91
  tenants: 'business',
@@ -112,36 +98,39 @@ const DEFAULT_ICONS: Record<StandardPageKey, string> = {
112
98
  pilots: 'flight_takeoff',
113
99
  discovery: 'travel_explore',
114
100
  bundles: 'inventory_2',
115
- businessTypes: 'category',
116
101
  marketingCatalog: 'campaign',
117
102
  platformEmail: 'mail',
118
103
  platformEmailHistory: 'mark_email_read',
119
104
  };
120
105
 
121
- const DEFAULT_NAV_SECTIONS: Partial<Record<StandardPageKey, string>> = {
122
- dashboard: 'Übersicht',
123
- discovery: 'Produktkatalog',
124
- businessTypes: 'Produktkatalog',
125
- bundles: 'Produktkatalog',
126
- plans: 'Produktkatalog',
127
- planVersions: 'Produktkatalog',
128
- marketingCatalog: 'Produktkatalog',
129
- promoCodes: 'Produktkatalog',
130
- tenants: 'Kunden',
131
- subscriptions: 'Kunden',
132
- users: 'Kunden',
133
- pilots: 'Kunden',
134
- audit: 'System',
135
- platformEmail: 'System',
136
- platformEmailHistory: 'System',
106
+ type SectionKey = keyof (typeof navMessages)['de']['sections'];
107
+
108
+ const PAGE_SECTIONS: Partial<Record<StandardPageKey, SectionKey>> = {
109
+ dashboard: 'overview',
110
+ discovery: 'catalog',
111
+ bundles: 'catalog',
112
+ plans: 'catalog',
113
+ planVersions: 'catalog',
114
+ marketingCatalog: 'catalog',
115
+ promoCodes: 'catalog',
116
+ tenants: 'customers',
117
+ subscriptions: 'customers',
118
+ users: 'customers',
119
+ pilots: 'customers',
120
+ audit: 'system',
121
+ platformEmail: 'system',
122
+ platformEmailHistory: 'system',
137
123
  };
138
124
 
139
- export const DEFAULT_SECTION_ORDER: readonly string[] = [
140
- 'Übersicht',
141
- 'Produktkatalog',
142
- 'Kunden',
143
- 'System',
144
- ];
125
+ /**
126
+ * Localized default section names in drawer order (Übersicht → Produktkatalog
127
+ * → Kunden → System). Pass the result to `buildSidebar()` when building routes
128
+ * with a non-default locale.
129
+ */
130
+ export function defaultSectionOrder(locale: SaLocale = DEFAULT_SA_LOCALE): readonly string[] {
131
+ const sections = navMessages[locale].sections;
132
+ return [sections.overview, sections.catalog, sections.customers, sections.system];
133
+ }
145
134
 
146
135
  /**
147
136
  * Returns the list of all routes defined by the current manifest —
@@ -159,10 +148,14 @@ export function buildRoutes(
159
148
 
160
149
  // StandardPages
161
150
  const standard = manifest.navigation?.standardPages ?? {};
151
+ const nav = navMessages[options.locale ?? DEFAULT_SA_LOCALE];
152
+ const defaultNavSections: Partial<Record<StandardPageKey, string>> = Object.fromEntries(
153
+ Object.entries(PAGE_SECTIONS).map(([page, section]) => [page, nav.sections[section]]),
154
+ );
162
155
  const overrideRoutes = { ...DEFAULT_STANDARD_PAGE_ROUTES, ...options.standardPageRoutes };
163
- const overrideLabels = { ...DEFAULT_LABELS, ...options.standardPageLabels };
156
+ const overrideLabels = { ...nav.pages, ...options.standardPageLabels };
164
157
  const overrideIcons = { ...DEFAULT_ICONS, ...options.standardPageIcons };
165
- const overrideNavSection = { ...DEFAULT_NAV_SECTIONS, ...options.standardPageNavSection };
158
+ const overrideNavSection = { ...defaultNavSections, ...options.standardPageNavSection };
166
159
 
167
160
  for (const key of Object.keys(standard) as StandardPageKey[]) {
168
161
  const def = standard[key] as StandardPageDef | undefined;
@@ -230,7 +223,7 @@ export interface SidebarSection {
230
223
  */
231
224
  export function buildSidebar(
232
225
  routes: BuildRouteEntry[],
233
- sectionOrder: readonly string[] = DEFAULT_SECTION_ORDER,
226
+ sectionOrder: readonly string[] = defaultSectionOrder(),
234
227
  ): SidebarSection[] {
235
228
  const bySection = new Map<string | null, SidebarItem[]>();
236
229
  for (const r of routes) {
@@ -17,6 +17,10 @@
17
17
 
18
18
  import type { PlanVersionRow } from '@saasicat/types';
19
19
 
20
+ import { formatMessage } from './i18n/format.js';
21
+ import { DEFAULT_SA_LOCALE, SA_INTL_LOCALES, type SaLocale } from './i18n/locale.js';
22
+ import { planVersionsMessages } from './i18n/messages/plan-versions.js';
23
+
20
24
  export type SnapshotKind = 'drafts' | 'active' | 'historical';
21
25
 
22
26
  export interface CatalogSnapshot<P extends PlanVersionRow = PlanVersionRow> {
@@ -78,6 +82,8 @@ export interface BuildSnapshotsOptions {
78
82
  * end up sorted alphabetically at the back.
79
83
  */
80
84
  planSortOrder?: readonly string[];
85
+ /** UI locale for the generated snapshot labels/titles/descriptions. */
86
+ locale?: SaLocale;
81
87
  }
82
88
 
83
89
  // ─── Helpers ─────────────────────────────────────────────────────────────
@@ -168,8 +174,16 @@ function resolvePlan<P extends PlanVersionRow>(source: P, liveBase: P | null): R
168
174
 
169
175
  // ─── Snapshot constructors ───────────────────────────────────────────────
170
176
 
171
- function dateLabelDe(iso: string): string {
172
- return new Date(iso).toLocaleDateString('de-DE', {
177
+ type CatalogMessages = (typeof planVersionsMessages)['de']['catalog'];
178
+
179
+ function draftsTitle(draftCount: number, msg: CatalogMessages): string {
180
+ if (draftCount === 0) return msg.draftsTitleEmpty;
181
+ const template = draftCount === 1 ? msg.draftsTitleOne : msg.draftsTitleMany;
182
+ return formatMessage(template, { count: draftCount });
183
+ }
184
+
185
+ function dateLabel(iso: string, locale: SaLocale): string {
186
+ return new Date(iso).toLocaleDateString(SA_INTL_LOCALES[locale], {
173
187
  day: '2-digit',
174
188
  month: 'short',
175
189
  year: 'numeric',
@@ -179,7 +193,9 @@ function dateLabelDe(iso: string): string {
179
193
  function buildDraftsSnapshot<P extends PlanVersionRow>(
180
194
  data: RawCatalogData<P>,
181
195
  sortPlansFn: (rows: ResolvedPlan<P>[]) => ResolvedPlan<P>[],
196
+ locale: SaLocale,
182
197
  ): CatalogSnapshot<P> {
198
+ const msg = planVersionsMessages[locale].catalog;
183
199
  const planByKey = groupBy(data.planVersions, (r) => r.planId);
184
200
 
185
201
  const plans: ResolvedPlan<P>[] = [];
@@ -196,15 +212,9 @@ function buildDraftsSnapshot<P extends PlanVersionRow>(
196
212
  id: 'drafts',
197
213
  kind: 'drafts',
198
214
  status: 'DRAFT',
199
- label: 'Arbeitsstand',
200
- title:
201
- draftCount === 0
202
- ? 'Keine offenen Drafts'
203
- : `${draftCount} offene${draftCount === 1 ? 'r' : ''} Draft${draftCount === 1 ? '' : 's'} bereit für Publish`,
204
- description:
205
- draftCount === 0
206
- ? 'Lege einen Draft an, um Limits, Preise oder Features zu ändern. Drafts sind ohne MFA editierbar und werden erst beim Publish wirksam.'
207
- : 'Diese Tabelle zeigt, wie der Catalog nach Publish aller Drafts aussehen würde. Bestehende Mandanten bleiben auf der vorherigen Version, bis sie aktiv migriert werden.',
215
+ label: msg.draftsLabel,
216
+ title: draftsTitle(draftCount, msg),
217
+ description: draftCount === 0 ? msg.draftsDescriptionEmpty : msg.draftsDescription,
208
218
  asOf: null,
209
219
  createdAt: null,
210
220
  publishedAt: null,
@@ -218,7 +228,9 @@ function buildDraftsSnapshot<P extends PlanVersionRow>(
218
228
  function buildActiveSnapshot<P extends PlanVersionRow>(
219
229
  data: RawCatalogData<P>,
220
230
  sortPlansFn: (rows: ResolvedPlan<P>[]) => ResolvedPlan<P>[],
231
+ locale: SaLocale,
221
232
  ): CatalogSnapshot<P> | null {
233
+ const msg = planVersionsMessages[locale].catalog;
222
234
  const planByKey = groupBy(data.planVersions, (r) => r.planId);
223
235
 
224
236
  const plans: ResolvedPlan<P>[] = [];
@@ -238,10 +250,9 @@ function buildActiveSnapshot<P extends PlanVersionRow>(
238
250
  id: 'active',
239
251
  kind: 'active',
240
252
  status: 'ACTIVE',
241
- label: 'Aktuell',
242
- title: 'Live-Catalog',
243
- description:
244
- 'Diese Plan-Versionen sind aktuell für neue Onboardings und für Renewals nach Stichtag aktiv.',
253
+ label: msg.activeLabel,
254
+ title: msg.activeTitle,
255
+ description: msg.activeDescription,
245
256
  asOf: latestPublished ?? null,
246
257
  createdAt: null,
247
258
  publishedAt: latestPublished ?? null,
@@ -255,7 +266,9 @@ function buildActiveSnapshot<P extends PlanVersionRow>(
255
266
  function buildHistoricalSnapshots<P extends PlanVersionRow>(
256
267
  data: RawCatalogData<P>,
257
268
  sortPlansFn: (rows: ResolvedPlan<P>[]) => ResolvedPlan<P>[],
269
+ locale: SaLocale,
258
270
  ): CatalogSnapshot<P>[] {
271
+ const msg = planVersionsMessages[locale].catalog;
259
272
  const allPublishedAt = new Set<string>();
260
273
  for (const r of data.planVersions) if (r.publishedAt) allPublishedAt.add(r.publishedAt);
261
274
  if (allPublishedAt.size === 0) return [];
@@ -293,9 +306,9 @@ function buildHistoricalSnapshots<P extends PlanVersionRow>(
293
306
  id: `hist-${ts}`,
294
307
  kind: 'historical',
295
308
  status: 'ARCHIVED',
296
- label: dateLabelDe(ts),
297
- title: summarizeEvent(publishedAtThisEvent),
298
- description: detailEvent(publishedAtThisEvent),
309
+ label: dateLabel(ts, locale),
310
+ title: summarizeEvent(publishedAtThisEvent, msg),
311
+ description: detailEvent(publishedAtThisEvent, msg),
299
312
  asOf: ts,
300
313
  createdAt: ts,
301
314
  publishedAt: ts,
@@ -308,19 +321,19 @@ function buildHistoricalSnapshots<P extends PlanVersionRow>(
308
321
  return out;
309
322
  }
310
323
 
311
- function summarizeEvent(rows: PlanVersionRow[]): string {
312
- if (rows.length === 0) return 'Publish-Event';
324
+ function summarizeEvent(rows: PlanVersionRow[], msg: CatalogMessages): string {
325
+ if (rows.length === 0) return msg.publishEvent;
313
326
  if (rows.length === 1) {
314
327
  const r = rows[0]!;
315
- return `${r.planId} v${r.version} publiziert`;
328
+ return formatMessage(msg.publishEventSingle, { planId: r.planId, version: r.version });
316
329
  }
317
- return `${rows.length} Versionen publiziert`;
330
+ return formatMessage(msg.publishEventMultiple, { count: rows.length });
318
331
  }
319
332
 
320
- function detailEvent(rows: PlanVersionRow[]): string {
333
+ function detailEvent(rows: PlanVersionRow[], msg: CatalogMessages): string {
321
334
  if (rows.length === 0) return '';
322
335
  const note = rows[0]?.changeNote ?? '';
323
- return note.length > 0 ? note : 'Snapshot rekonstruiert aus per-Entity-Versionen.';
336
+ return note.length > 0 ? note : msg.reconstructedSnapshot;
324
337
  }
325
338
 
326
339
  // ─── Sorting ─────────────────────────────────────────────────────────────
@@ -348,10 +361,11 @@ export function buildSnapshots<P extends PlanVersionRow>(
348
361
  data: RawCatalogData<P>,
349
362
  options: BuildSnapshotsOptions = {},
350
363
  ): CatalogSnapshot<P>[] {
364
+ const locale = options.locale ?? DEFAULT_SA_LOCALE;
351
365
  const sortPlansFn = sortPlansBy<P>(options.planSortOrder);
352
- const drafts = buildDraftsSnapshot(data, sortPlansFn);
353
- const active = buildActiveSnapshot(data, sortPlansFn);
354
- const historical = buildHistoricalSnapshots(data, sortPlansFn);
366
+ const drafts = buildDraftsSnapshot(data, sortPlansFn, locale);
367
+ const active = buildActiveSnapshot(data, sortPlansFn, locale);
368
+ const historical = buildHistoricalSnapshots(data, sortPlansFn, locale);
355
369
 
356
370
  const out: CatalogSnapshot<P>[] = [drafts];
357
371
  if (active) out.push(active);
@@ -359,9 +373,7 @@ export function buildSnapshots<P extends PlanVersionRow>(
359
373
  return out;
360
374
  }
361
375
 
362
- export function listOpenDrafts<P extends PlanVersionRow>(
363
- data: RawCatalogData<P>,
364
- ): { plans: P[] } {
376
+ export function listOpenDrafts<P extends PlanVersionRow>(data: RawCatalogData<P>): { plans: P[] } {
365
377
  return {
366
378
  plans: sortByPublishedDesc(data.planVersions).filter((r) => r.publishedAt === null),
367
379
  };
@@ -2,15 +2,17 @@
2
2
  <q-dialog v-model="open" persistent>
3
3
  <q-card style="min-width: 560px; max-width: 96vw">
4
4
  <q-card-section>
5
- <div class="text-h6">BundleVersion publishen</div>
5
+ <div class="text-h6">{{ msg.publishDialog.title }}</div>
6
6
  <p class="text-caption text-grey-7">
7
- Bundle <code>{{ bundleKey }}</code> · v{{ draft.version }}
7
+ {{ msg.publishDialog.bundleLabel }} <code>{{ bundleKey }}</code> · v{{
8
+ draft.version
9
+ }}
8
10
  </p>
9
11
  </q-card-section>
10
12
 
11
13
  <q-card-section v-if="!previewLoaded" class="bvpd__loading">
12
14
  <q-spinner color="primary" size="32px" />
13
- <span class="q-ml-sm">Diff zur Vorgänger-Version wird geladen …</span>
15
+ <span class="q-ml-sm">{{ msg.publishDialog.loadingDiff }}</span>
14
16
  </q-card-section>
15
17
 
16
18
  <q-card-section v-else class="bvpd__body">
@@ -19,7 +21,7 @@
19
21
  <template #avatar>
20
22
  <q-icon name="warning" color="warning" />
21
23
  </template>
22
- <strong>{{ warnings.length }} Strict-Mode-Warnung(en)</strong>
24
+ <strong>{{ strictWarningsText }}</strong>
23
25
  <ul class="bvpd__list">
24
26
  <li v-for="(w, i) in warnings" :key="i">
25
27
  <code>{{ w.code }}</code>
@@ -32,14 +34,14 @@
32
34
  </q-banner>
33
35
 
34
36
  <div class="bvpd__validity">
35
- <div class="bvpd__label">Gültigkeit</div>
37
+ <div class="bvpd__label">{{ common.validity }}</div>
36
38
  <div class="bvpd__validity-grid">
37
39
  <label class="bvpd__field">
38
- <span>Gültig ab</span>
40
+ <span>{{ msg.fields.validFrom }}</span>
39
41
  <input v-model="validFromInput" class="bvpd__input" type="date" />
40
42
  </label>
41
43
  <label class="bvpd__field">
42
- <span>Gültig bis</span>
44
+ <span>{{ msg.fields.validUntil }}</span>
43
45
  <input v-model="validUntilInput" class="bvpd__input" type="date" />
44
46
  </label>
45
47
  </div>
@@ -47,22 +49,19 @@
47
49
  </div>
48
50
 
49
51
  <div>
50
- <q-toggle
51
- v-model="allowZeroPrice"
52
- label="Preis 0,00 bewusst zulassen (kostenloses Bundle)"
53
- />
52
+ <q-toggle v-model="allowZeroPrice" :label="msg.publishDialog.allowZeroPrice" />
54
53
  <p class="bvpd__label" style="margin-top: 6px">
55
- Standard: Publish mit explizitem Preis 0,00 wird blockiert (Seed-Schutz).
54
+ {{ msg.publishDialog.allowZeroPriceHint }}
56
55
  </p>
57
56
  </div>
58
57
 
59
58
  <!-- Diff against the previous version -->
60
59
  <div>
61
60
  <div class="bvpd__label">
62
- Änderungen gegenüber
61
+ {{ msg.publishDialog.changesVs }}
63
62
  <span v-if="previousVersion"> v{{ previousVersion }} </span>
64
63
  <span v-else class="text-grey-7">
65
- keine Vorgänger-Version (Erst-Veröffentlichung)
64
+ {{ msg.publishDialog.noPrevious }}
66
65
  </span>
67
66
  </div>
68
67
  <q-banner
@@ -74,7 +73,7 @@
74
73
  <template #avatar>
75
74
  <q-icon name="info" color="grey-7" />
76
75
  </template>
77
- Keine Änderungen — die Versionen sind inhaltlich gleich.
76
+ {{ msg.publishDialog.noChanges }}
78
77
  </q-banner>
79
78
  <q-list v-else-if="changes.length > 0" bordered separator>
80
79
  <q-item v-for="(c, i) in changes" :key="i">
@@ -106,14 +105,13 @@
106
105
  <template #avatar>
107
106
  <q-icon name="error" color="negative" />
108
107
  </template>
109
- <strong>Regressive Änderung erkannt.</strong>
110
- Diese Version entfernt Features, senkt Quotas oder erhöht Preise. Vertragsschutz
111
- P3 (SPEC.md §6) verlangt Bestand-Opt-in. Publish erfordert deinen ausdrücklichen
112
- <code>force</code>-Confirm.
108
+ <strong>{{ msg.publishDialog.regressionTitle }}</strong>
109
+ {{ msg.publishDialog.regressionBody }}
110
+ <code>force</code>{{ msg.publishDialog.regressionBodySuffix }}
113
111
  <template #action>
114
112
  <q-toggle
115
113
  v-model="forceRegressive"
116
- label="Trotzdem publishen"
114
+ :label="msg.publishDialog.forceRegressive"
117
115
  color="negative"
118
116
  />
119
117
  </template>
@@ -121,11 +119,15 @@
121
119
  </q-card-section>
122
120
 
123
121
  <q-card-actions align="right">
124
- <q-btn flat label="Abbrechen" @click="close" />
122
+ <q-btn flat :label="common.cancel" @click="close" />
125
123
  <q-btn
126
124
  unelevated
127
125
  :color="isRegression ? 'negative' : 'positive'"
128
- :label="isRegression ? 'Regressiv publishen' : 'Publishen'"
126
+ :label="
127
+ isRegression
128
+ ? msg.publishDialog.confirmRegressive
129
+ : msg.publishDialog.confirm
130
+ "
129
131
  :loading="submitting"
130
132
  :disable="!canSubmit"
131
133
  @click="submit"
@@ -144,6 +146,9 @@ import type {
144
146
  VersionChange,
145
147
  } from '@saasicat/types';
146
148
 
149
+ import { formatMessage } from '../client/i18n/format.js';
150
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
151
+
147
152
  // BundleVersionPublishDialog — confirm modal for publishing a draft version.
148
153
  // Shows the diff against the previous version (publishedChanges) and blocks
149
154
  // regressive versions without a forceRegressive confirm.
@@ -178,6 +183,9 @@ const emit = defineEmits<{
178
183
  (e: 'submitted', result: BundleVersionMutationResult): void;
179
184
  }>();
180
185
 
186
+ const msg = useSaMessages('bundles');
187
+ const common = useSaMessages('common');
188
+
181
189
  const open = computed({
182
190
  get: () => props.modelValue,
183
191
  set: (v) => emit('update:modelValue', v),
@@ -194,10 +202,13 @@ const validUntilInput = ref('');
194
202
 
195
203
  const previousVersion = computed(() => props.previous?.version ?? null);
196
204
  const isRegression = computed(() => previewLoaded.value && !nonRegressive.value);
205
+ const strictWarningsText = computed(() =>
206
+ formatMessage(msg.value.publishDialog.strictWarnings, { count: props.warnings.length }),
207
+ );
197
208
  const validityError = computed<string | null>(() => {
198
- if (!validFromInput.value) return 'Gültig ab ist Pflicht.';
209
+ if (!validFromInput.value) return msg.value.validation.validFromRequired;
199
210
  if (validUntilInput.value && validUntilInput.value <= validFromInput.value) {
200
- return 'Gültig bis muss nach Gültig ab liegen.';
211
+ return msg.value.validation.validUntilAfterValidFrom;
201
212
  }
202
213
  return null;
203
214
  });
@@ -35,7 +35,7 @@
35
35
  // instead of returning `null`. (Strict mode comes later; currently default = lenient.)
36
36
 
37
37
  import { computed } from 'vue';
38
- import { useInjectedEntitlement } from '../entitlement-provider.js';
38
+ import { useInjectedEntitlement } from '../vue/entitlement-provider.js';
39
39
 
40
40
  interface Props {
41
41
  /** A single FeatureKey or several as a logical OR. */