@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
@@ -15,7 +15,7 @@
15
15
  class="sa-pv-header__cmp-btn"
16
16
  @click="emit('clearCompare')"
17
17
  >
18
- <q-icon name="close" size="14px" /> Vergleich beenden
18
+ <q-icon name="close" size="14px" /> {{ msg.compareEnd }}
19
19
  </button>
20
20
  </template>
21
21
  </div>
@@ -28,23 +28,21 @@
28
28
  class="sa-pv-header__meta-item sa-pv-header__meta-item--ok"
29
29
  >
30
30
  <q-icon name="rocket_launch" size="14px" />
31
- Publiziert {{ formatDateDe(snapshot.publishedAt) }}
31
+ {{ publishedOnLabel }}
32
32
  </span>
33
33
  <span
34
34
  v-if="snapshot.kind === 'drafts'"
35
35
  class="sa-pv-header__meta-item sa-pv-header__meta-item--warn"
36
36
  >
37
37
  <q-icon name="edit_note" size="14px" />
38
- <strong>{{ snapshot.draftCount }}</strong> offene Drafts
38
+ <strong>{{ snapshot.draftCount }}</strong> {{ msg.header.openDrafts }}
39
39
  </span>
40
40
  <span
41
41
  v-if="snapshot.regressionCount > 0"
42
42
  class="sa-pv-header__meta-item sa-pv-header__meta-item--bad"
43
43
  >
44
44
  <q-icon name="trending_down" size="14px" />
45
- <strong>{{ snapshot.regressionCount }}</strong> Regression{{
46
- snapshot.regressionCount === 1 ? '' : 'en'
47
- }}
45
+ <strong>{{ snapshot.regressionCount }}</strong> {{ regressionLabel }}
48
46
  </span>
49
47
  </div>
50
48
  </div>
@@ -57,7 +55,7 @@
57
55
  @click="emit('publish')"
58
56
  >
59
57
  <q-icon name="rocket_launch" size="16px" />
60
- Publish-Flow starten
58
+ {{ msg.header.startPublishFlow }}
61
59
  </button>
62
60
  </div>
63
61
  </div>
@@ -68,7 +66,7 @@
68
66
  :class="{ 'sa-pv-header__switcher--disabled': !!compareSnapshot }"
69
67
  >
70
68
  <button
71
- v-for="m in modes"
69
+ v-for="m in resolvedModes"
72
70
  :key="m.id"
73
71
  type="button"
74
72
  class="sa-pv-header__mode-btn"
@@ -81,7 +79,7 @@
81
79
  </div>
82
80
  <span v-if="compareSnapshot" class="sa-pv-header__cmp-hint">
83
81
  <q-icon name="compare_arrows" size="13px" />
84
- Vergleichsmodus aktiv — andere Ansichten deaktiviert
82
+ {{ msg.header.compareModeHint }}
85
83
  </span>
86
84
  </div>
87
85
  </header>
@@ -90,25 +88,19 @@
90
88
  <script setup lang="ts">
91
89
  import { computed } from 'vue';
92
90
  import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
93
- import { formatDateDe } from './format.js';
91
+ import { formatMessage } from '../../client/i18n/format.js';
92
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
93
+ import { formatDate } from './format.js';
94
94
  import PlanVersionStatusBadge from './PlanVersionStatusBadge.vue';
95
95
  import type { PlanVersionViewMode } from './types.js';
96
96
 
97
- const props = withDefaults(
98
- defineProps<{
99
- snapshot: CatalogSnapshot;
100
- viewMode: PlanVersionViewMode;
101
- compareSnapshot: CatalogSnapshot | null;
102
- modes?: ReadonlyArray<{ id: PlanVersionViewMode; label: string; icon: string }>;
103
- }>(),
104
- {
105
- modes: () => [
106
- { id: 'list' as const, label: 'Plan-Liste', icon: 'list' },
107
- { id: 'matrix' as const, label: 'Feature-Matrix', icon: 'grid_view' },
108
- { id: 'audit' as const, label: 'Aktivität', icon: 'history' },
109
- ],
110
- },
111
- );
97
+ const props = defineProps<{
98
+ snapshot: CatalogSnapshot;
99
+ viewMode: PlanVersionViewMode;
100
+ compareSnapshot: CatalogSnapshot | null;
101
+ /** Overrides the localized default view switcher. */
102
+ modes?: ReadonlyArray<{ id: PlanVersionViewMode; label: string; icon: string }>;
103
+ }>();
112
104
 
113
105
  const emit = defineEmits<{
114
106
  (e: 'viewChange', mode: PlanVersionViewMode): void;
@@ -116,9 +108,33 @@ const emit = defineEmits<{
116
108
  (e: 'clearCompare'): void;
117
109
  }>();
118
110
 
111
+ const msg = useSaMessages('planVersions');
112
+ const { locale } = useSuperAdminI18n();
113
+
119
114
  const canPublish = computed(
120
115
  () => props.snapshot.kind === 'drafts' && props.snapshot.draftCount > 0,
121
116
  );
117
+
118
+ const publishedOnLabel = computed(() =>
119
+ formatMessage(msg.value.header.publishedOn, {
120
+ date: formatDate(props.snapshot.publishedAt ?? undefined, locale.value),
121
+ }),
122
+ );
123
+
124
+ const regressionLabel = computed(() =>
125
+ props.snapshot.regressionCount === 1
126
+ ? msg.value.header.regressionOne
127
+ : msg.value.header.regressionMany,
128
+ );
129
+
130
+ const resolvedModes = computed(
131
+ () =>
132
+ props.modes ?? [
133
+ { id: 'list' as const, label: msg.value.header.modeList, icon: 'list' },
134
+ { id: 'matrix' as const, label: msg.value.header.modeMatrix, icon: 'grid_view' },
135
+ { id: 'audit' as const, label: msg.value.header.modeAudit, icon: 'history' },
136
+ ],
137
+ );
122
138
  </script>
123
139
 
124
140
  <style scoped>
@@ -3,18 +3,18 @@
3
3
  <div class="sa-pv-audit__card">
4
4
  <header class="sa-pv-audit__head">
5
5
  <q-icon name="history" size="18px" class="sa-pv-audit__head-icon" />
6
- <h3>Aktivität</h3>
7
- <span class="sa-pv-audit__count">{{ events.length }} Events</span>
6
+ <h3>{{ msg.audit.title }}</h3>
7
+ <span class="sa-pv-audit__count">{{ eventCountLabel }}</span>
8
8
  <q-space />
9
9
  <button v-if="!loading" type="button" class="sa-pv-audit__refresh" @click="reload">
10
- <q-icon name="refresh" size="14px" /> Neu laden
10
+ <q-icon name="refresh" size="14px" /> {{ common.reload }}
11
11
  </button>
12
12
  </header>
13
13
  <div v-if="loading" class="sa-pv-audit__loading">
14
- <q-spinner size="20px" /> Audit-Trail wird geladen…
14
+ <q-spinner size="20px" /> {{ msg.audit.loading }}
15
15
  </div>
16
16
  <div v-else-if="events.length === 0" class="sa-pv-audit__empty">
17
- Keine Plan-/Add-on-Events im Audit-Log gefunden.
17
+ {{ msg.audit.empty }}
18
18
  </div>
19
19
  <div v-else class="sa-pv-audit__list">
20
20
  <div
@@ -42,7 +42,7 @@
42
42
  <div class="sa-pv-audit__meta">
43
43
  <span
44
44
  ><q-icon name="schedule" size="12px" />
45
- {{ formatTsDe(e.createdAt) }}</span
45
+ {{ formatTimestamp(e.createdAt, locale) }}</span
46
46
  >
47
47
  <span v-if="actorEmail(e)">
48
48
  <q-icon name="person" size="12px" /> {{ actorEmail(e) }}
@@ -56,9 +56,11 @@
56
56
  </template>
57
57
 
58
58
  <script setup lang="ts">
59
- import { onMounted, ref, watch } from 'vue';
59
+ import { computed, onMounted, ref, watch } from 'vue';
60
60
  import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
61
- import { formatTsDe } from './format.js';
61
+ import { formatMessage } from '../../client/i18n/format.js';
62
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
63
+ import { formatTimestamp } from './format.js';
62
64
 
63
65
  // PlanVersionsAudit — data-agnostic. Consumers pass in a `loadAudit` loader
64
66
  // that supplies events; the component filters them to match the current
@@ -100,34 +102,49 @@ const props = withDefaults(
100
102
  const loading = ref(false);
101
103
  const events = ref<AuditRow[]>([]);
102
104
 
103
- const DEFAULT_ACTION_META: Record<string, ActionMeta> = {
105
+ const msg = useSaMessages('planVersions');
106
+ const common = useSaMessages('common');
107
+ const { locale } = useSuperAdminI18n();
108
+
109
+ const eventCountLabel = computed(() =>
110
+ formatMessage(msg.value.audit.eventCount, { count: events.value.length }),
111
+ );
112
+
113
+ const ACTION_ICONS: Record<string, { icon: string; color: string }> = {
114
+ PLAN_VERSION_PUBLISH: { icon: 'rocket_launch', color: '#047857' },
115
+ PLAN_VERSION_DRAFT_CREATE: { icon: 'edit_note', color: '#b45309' },
116
+ PLAN_VERSION_DRAFT_UPDATE: { icon: 'edit', color: '#3f6bff' },
117
+ PLAN_VERSION_DRAFT_DELETE: { icon: 'delete', color: '#dc2626' },
118
+ PLAN_CATALOG_UPDATE: { icon: 'rocket_launch', color: '#047857' },
119
+ };
120
+
121
+ const defaultActionMeta = computed<Record<string, ActionMeta>>(() => ({
104
122
  PLAN_VERSION_PUBLISH: {
105
- icon: 'rocket_launch',
106
- color: '#047857',
107
- label: 'Plan publiziert',
123
+ ...ACTION_ICONS.PLAN_VERSION_PUBLISH,
124
+ label: msg.value.audit.actionPlanPublished,
108
125
  },
109
126
  PLAN_VERSION_DRAFT_CREATE: {
110
- icon: 'edit_note',
111
- color: '#b45309',
112
- label: 'Plan-Draft angelegt',
127
+ ...ACTION_ICONS.PLAN_VERSION_DRAFT_CREATE,
128
+ label: msg.value.audit.actionPlanDraftCreated,
129
+ },
130
+ PLAN_VERSION_DRAFT_UPDATE: {
131
+ ...ACTION_ICONS.PLAN_VERSION_DRAFT_UPDATE,
132
+ label: msg.value.audit.actionPlanDraftUpdated,
113
133
  },
114
- PLAN_VERSION_DRAFT_UPDATE: { icon: 'edit', color: '#3f6bff', label: 'Plan-Draft geändert' },
115
134
  PLAN_VERSION_DRAFT_DELETE: {
116
- icon: 'delete',
117
- color: '#dc2626',
118
- label: 'Plan-Draft gelöscht',
135
+ ...ACTION_ICONS.PLAN_VERSION_DRAFT_DELETE,
136
+ label: msg.value.audit.actionPlanDraftDeleted,
119
137
  },
120
138
  PLAN_CATALOG_UPDATE: {
121
- icon: 'rocket_launch',
122
- color: '#047857',
123
- label: 'Catalog publiziert',
139
+ ...ACTION_ICONS.PLAN_CATALOG_UPDATE,
140
+ label: msg.value.audit.actionCatalogPublished,
124
141
  },
125
- };
142
+ }));
126
143
 
127
144
  function meta(action: string): ActionMeta {
128
145
  return (
129
146
  props.actionMeta[action] ??
130
- DEFAULT_ACTION_META[action] ?? {
147
+ defaultActionMeta.value[action] ?? {
131
148
  icon: 'circle',
132
149
  color: '#64748b',
133
150
  label: action,
@@ -156,8 +173,10 @@ function detail(e: AuditRow): string {
156
173
  changes?: unknown;
157
174
  };
158
175
  if (c.changeNote) return c.changeNote;
159
- if (c.nonRegressive === false) return 'Mit mindestens einer Verschlechterung publiziert.';
160
- if (Array.isArray(c.changes)) return `${c.changes.length} Felder geändert.`;
176
+ if (c.nonRegressive === false) return msg.value.audit.detailRegression;
177
+ if (Array.isArray(c.changes)) {
178
+ return formatMessage(msg.value.audit.detailFieldsChanged, { count: c.changes.length });
179
+ }
161
180
  return e.action;
162
181
  }
163
182
 
@@ -5,7 +5,7 @@
5
5
  <q-icon name="compare_arrows" size="22px" color="white" />
6
6
  </div>
7
7
  <div>
8
- <div class="sa-pv-diff__legend">Vergleich</div>
8
+ <div class="sa-pv-diff__legend">{{ msg.diff.legend }}</div>
9
9
  <div class="sa-pv-diff__title">
10
10
  {{ from.label }} <span class="sa-pv-diff__sep">→</span> {{ to.label }}
11
11
  </div>
@@ -13,13 +13,13 @@
13
13
  <div class="sa-pv-diff__spacer" />
14
14
  <div class="sa-pv-diff__stats">
15
15
  <div class="sa-pv-diff__stat">
16
- <div class="sa-pv-diff__stat-label">Pakete geändert</div>
16
+ <div class="sa-pv-diff__stat-label">{{ msg.diff.plansChanged }}</div>
17
17
  <div class="sa-pv-diff__stat-value sa-pv-diff__stat-value--warn">
18
18
  {{ planChangedCount }}
19
19
  </div>
20
20
  </div>
21
21
  <div v-if="regressionCount > 0" class="sa-pv-diff__stat">
22
- <div class="sa-pv-diff__stat-label">Regressionen</div>
22
+ <div class="sa-pv-diff__stat-label">{{ msg.diff.regressions }}</div>
23
23
  <div class="sa-pv-diff__stat-value sa-pv-diff__stat-value--bad">
24
24
  {{ regressionCount }}
25
25
  </div>
@@ -27,7 +27,7 @@
27
27
  </div>
28
28
  </div>
29
29
 
30
- <h2 class="sa-pv-diff__section">Pakete</h2>
30
+ <h2 class="sa-pv-diff__section">{{ msg.diff.sectionPlans }}</h2>
31
31
  <PlanDiffCard
32
32
  v-for="d in planDiffs"
33
33
  :key="d.id"
@@ -38,7 +38,6 @@
38
38
  :changes="d.changes"
39
39
  :field-labels="fieldLabels"
40
40
  />
41
-
42
41
  </div>
43
42
  </template>
44
43
 
@@ -46,6 +45,7 @@
46
45
  import { computed } from 'vue';
47
46
  import type { VersionChange } from '@saasicat/types';
48
47
  import type { CatalogSnapshot, ResolvedPlan } from '../../client/plan-versions-catalog.js';
48
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
49
49
  import PlanDiffCard from './PlanDiffCard.vue';
50
50
 
51
51
  interface SnapshotEntityDiff {
@@ -64,6 +64,8 @@ const props = defineProps<{
64
64
  fieldLabels?: Record<string, string>;
65
65
  }>();
66
66
 
67
+ const msg = useSaMessages('planVersions');
68
+
67
69
  function planAccent(planId: string): string {
68
70
  return props.planAccents?.[planId] ?? '#3f6bff';
69
71
  }
@@ -1,21 +1,29 @@
1
1
  <template>
2
2
  <div class="sa-pv-list">
3
3
  <div class="sa-pv-list__kpis">
4
- <PlanVersionsKpi label="Pakete" :value="snapshot.plans.length" icon="inventory_2" />
5
4
  <PlanVersionsKpi
6
- label="Self-Service"
5
+ :label="msg.list.kpiPlans"
6
+ :value="snapshot.plans.length"
7
+ icon="inventory_2"
8
+ />
9
+ <PlanVersionsKpi
10
+ :label="msg.list.kpiSelfService"
7
11
  :value="marketedCount"
8
12
  icon="storefront"
9
- :sub="`von ${snapshot.plans.length}`"
13
+ :sub="totalPlansLabel"
14
+ />
15
+ <PlanVersionsKpi
16
+ :label="msg.list.kpiFeaturesTotal"
17
+ :value="totalFeatures"
18
+ icon="extension"
10
19
  />
11
- <PlanVersionsKpi label="Features gesamt" :value="totalFeatures" icon="extension" />
12
20
  </div>
13
21
 
14
22
  <div v-if="missingPlans.length > 0" class="sa-pv-list__missing">
15
23
  <q-icon name="info" size="16px" />
16
24
  <div>
17
- <strong>Fehlende Pakete:</strong> {{ missingPlans.join(', ') }} — keine Live-Version
18
- in diesem Snapshot.
25
+ <strong>{{ msg.list.missingPlansLabel }}</strong> {{ missingPlans.join(', ') }}
26
+ {{ msg.list.missingPlansHint }}
19
27
  <template v-if="canEdit">
20
28
  <button
21
29
  v-for="planId in missingPlans"
@@ -24,7 +32,7 @@
24
32
  class="sa-pv-list__missing-btn"
25
33
  @click="emit('createPlanDraft', planId)"
26
34
  >
27
- Draft für {{ planId }}
35
+ {{ createDraftLabel(planId) }}
28
36
  </button>
29
37
  </template>
30
38
  </div>
@@ -36,8 +44,8 @@
36
44
  class="sa-pv-list__head sa-pv-list__row sa-pv-list__row--default"
37
45
  :style="rowGridStyle"
38
46
  >
39
- <div>Paket</div>
40
- <div class="sa-pv-list__th--num">Preis (netto)</div>
47
+ <div>{{ msg.list.colPlan }}</div>
48
+ <div class="sa-pv-list__th--num">{{ msg.list.colPriceNet }}</div>
41
49
  <div
42
50
  v-for="col in quotaColumns"
43
51
  :key="`hd-${col.key}`"
@@ -45,7 +53,7 @@
45
53
  >
46
54
  {{ col.label }}
47
55
  </div>
48
- <div class="sa-pv-list__th--num">Features</div>
56
+ <div class="sa-pv-list__th--num">{{ msg.list.colFeatures }}</div>
49
57
  </div>
50
58
  </slot>
51
59
 
@@ -65,7 +73,9 @@
65
73
  <div class="sa-pv-list__default-name">
66
74
  <strong>{{ plan.planId }}</strong>
67
75
  <span class="sa-pv-list__default-version">v{{ plan.version }}</span>
68
- <span v-if="plan.isDraft" class="sa-pv-list__default-chip">DRAFT</span>
76
+ <span v-if="plan.isDraft" class="sa-pv-list__default-chip">{{
77
+ msg.status.draft
78
+ }}</span>
69
79
  </div>
70
80
  <div class="sa-pv-list__th--num">
71
81
  {{ formatEuro(plan.monthlyNet) }}
@@ -86,10 +96,10 @@
86
96
  <div v-if="snapshot.kind === 'drafts'" class="sa-pv-list__draft-hint">
87
97
  <q-icon name="info" size="18px" />
88
98
  <div>
89
- <strong>Arbeitsstand:</strong> Drafts oben in der Liste. Limits, Preise und Features
90
- per Klick auf <em>„Bearbeiten"</em> ändern (ohne MFA). Erst
91
- <strong>Publish-Flow</strong> (Validate → Diff-Review → MFA → Apply) macht sie für
92
- künftige Renewals wirksam.
99
+ <strong>{{ msg.list.draftHintLabel }}</strong> {{ msg.list.draftHintBefore }}
100
+ <em>{{ msg.list.draftHintEditButton }}</em> {{ msg.list.draftHintMiddle }}
101
+ <strong>{{ msg.list.draftHintPublishFlow }}</strong>
102
+ {{ msg.list.draftHintAfter }}
93
103
  </div>
94
104
  </div>
95
105
  </div>
@@ -98,6 +108,8 @@
98
108
  <script setup lang="ts">
99
109
  import { computed, ref } from 'vue';
100
110
  import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
111
+ import { formatMessage } from '../../client/i18n/format.js';
112
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
101
113
  import PlanVersionsKpi from './PlanVersionsKpi.vue';
102
114
  import { fmtEuro } from './format.js';
103
115
 
@@ -136,9 +148,16 @@ const emit = defineEmits<{
136
148
  (e: 'createPlanDraft', planId: string): void;
137
149
  }>();
138
150
 
151
+ const msg = useSaMessages('planVersions');
152
+ const { locale } = useSuperAdminI18n();
153
+
139
154
  const openId = ref<string | null>(null);
140
155
  const canEdit = computed(() => props.snapshot.kind === 'drafts');
141
156
 
157
+ const totalPlansLabel = computed(() =>
158
+ formatMessage(msg.value.list.kpiOfTotal, { count: props.snapshot.plans.length }),
159
+ );
160
+
142
161
  const marketedCount = computed(() => props.snapshot.plans.filter((p) => p.marketed).length);
143
162
  const totalFeatures = computed(() => new Set(props.snapshot.plans.flatMap((p) => p.features)).size);
144
163
 
@@ -163,8 +182,12 @@ function onToggle(id: string): void {
163
182
  openId.value = openId.value === id ? null : id;
164
183
  }
165
184
 
185
+ function createDraftLabel(planId: string): string {
186
+ return formatMessage(msg.value.list.createDraftFor, { planId });
187
+ }
188
+
166
189
  function formatEuro(n: number): string {
167
- return fmtEuro(n);
190
+ return fmtEuro(n, locale.value);
168
191
  }
169
192
 
170
193
  function formatQuotaValue(value: number | undefined, col: QuotaColumnConfig): string {
@@ -2,9 +2,9 @@
2
2
  <aside class="sa-pv-timeline">
3
3
  <div class="sa-pv-timeline__head">
4
4
  <div class="sa-pv-timeline__row">
5
- <div class="sa-pv-timeline__title">{{ title ?? 'Plan-Catalog' }}</div>
5
+ <div class="sa-pv-timeline__title">{{ title ?? msg.timeline.title }}</div>
6
6
  </div>
7
- <div class="sa-pv-timeline__count">{{ snapshots.length }} Snapshots</div>
7
+ <div class="sa-pv-timeline__count">{{ snapshotCountLabel }}</div>
8
8
  </div>
9
9
 
10
10
  <div class="sa-pv-timeline__list">
@@ -56,7 +56,7 @@
56
56
  <div class="sa-pv-timeline__foot">
57
57
  <div class="sa-pv-timeline__hint">
58
58
  <q-icon name="info" size="13px" />
59
- <span>Rechtsklick auf Snapshot → vergleichen</span>
59
+ <span>{{ msg.timeline.compareHint }}</span>
60
60
  </div>
61
61
  <button
62
62
  v-if="compareId"
@@ -64,17 +64,20 @@
64
64
  class="sa-pv-timeline__clear-btn"
65
65
  @click="emit('clearCompare')"
66
66
  >
67
- Vergleich beenden
67
+ {{ msg.compareEnd }}
68
68
  </button>
69
69
  </div>
70
70
  </aside>
71
71
  </template>
72
72
 
73
73
  <script setup lang="ts">
74
+ import { computed } from 'vue';
74
75
  import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
75
- import { formatRelativeDe } from './format.js';
76
+ import { formatMessage } from '../../client/i18n/format.js';
77
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
78
+ import { formatRelative } from './format.js';
76
79
 
77
- defineProps<{
80
+ const props = defineProps<{
78
81
  snapshots: CatalogSnapshot[];
79
82
  selectedId: string;
80
83
  compareId: string | null;
@@ -87,18 +90,27 @@ const emit = defineEmits<{
87
90
  (e: 'clearCompare'): void;
88
91
  }>();
89
92
 
93
+ const msg = useSaMessages('planVersions');
94
+ const { locale } = useSuperAdminI18n();
95
+
96
+ const snapshotCountLabel = computed(() =>
97
+ formatMessage(msg.value.timeline.snapshotCount, { count: props.snapshots.length }),
98
+ );
99
+
90
100
  function statusLabel(status: CatalogSnapshot['status']): string {
91
- if (status === 'DRAFT') return 'DRAFT';
92
- if (status === 'ACTIVE') return 'AKTIV';
93
- return 'ARCHIV';
101
+ if (status === 'DRAFT') return msg.value.status.draft;
102
+ if (status === 'ACTIVE') return msg.value.status.active;
103
+ return msg.value.status.archived;
94
104
  }
95
105
 
96
106
  function metaText(s: CatalogSnapshot): string {
97
107
  if (s.kind === 'drafts') {
98
- return s.draftCount === 0 ? 'keine offenen Drafts' : `${s.draftCount} offen`;
108
+ return s.draftCount === 0
109
+ ? msg.value.timeline.noOpenDrafts
110
+ : formatMessage(msg.value.timeline.openCount, { count: s.draftCount });
99
111
  }
100
- if (s.publishedAt) return formatRelativeDe(s.publishedAt);
101
- return 'unbekannt';
112
+ if (s.publishedAt) return formatRelative(s.publishedAt, locale.value);
113
+ return msg.value.timeline.unknown;
102
114
  }
103
115
  </script>
104
116
 
@@ -1,13 +1,24 @@
1
1
  // Formatting helpers for the plan-versions views.
2
2
  //
3
3
  // Phase 2c: Ported from a consumer admin.
4
- // The time reference point for `formatRelativeDe` is the moment of the call,
4
+ // The time reference point for `formatRelative` is the moment of the call,
5
5
  // not a hard-coded demo date (the ported template had `2026-05-04` —
6
6
  // the platform must work in production).
7
7
 
8
- export function fmtEuro(n: number): string {
9
- if (n === 0) return 'individuell';
10
- return n.toFixed(2).replace('.', ',') + ' ';
8
+ import { formatCurrency } from '../../client/i18n/currency.js';
9
+ import { formatMessage } from '../../client/i18n/format.js';
10
+ import { DEFAULT_SA_LOCALE, SA_INTL_LOCALES, type SaLocale } from '../../client/i18n/locale.js';
11
+ import { planVersionsMessages } from '../../client/i18n/messages/plan-versions.js';
12
+
13
+ const EMPTY_VALUE = '—';
14
+
15
+ function texts(locale: SaLocale) {
16
+ return planVersionsMessages[locale].format;
17
+ }
18
+
19
+ export function fmtEuro(n: number, locale: SaLocale = DEFAULT_SA_LOCALE): string {
20
+ if (n === 0) return texts(locale).priceIndividual;
21
+ return formatCurrency(n, locale);
11
22
  }
12
23
 
13
24
  export function fmtStorage(gb: number): string {
@@ -16,34 +27,39 @@ export function fmtStorage(gb: number): string {
16
27
  return `${gb} GB`;
17
28
  }
18
29
 
19
- export function formatRelativeDe(iso: string | undefined, now: Date = new Date()): string {
20
- if (!iso) return '—';
30
+ export function formatRelative(
31
+ iso: string | undefined,
32
+ locale: SaLocale = DEFAULT_SA_LOCALE,
33
+ now: Date = new Date(),
34
+ ): string {
35
+ if (!iso) return EMPTY_VALUE;
36
+ const msg = texts(locale);
21
37
  const d = new Date(iso);
22
38
  const diffMs = now.getTime() - d.getTime();
23
39
  const days = Math.floor(diffMs / 86_400_000);
24
- if (days === 0) return 'heute';
25
- if (days === 1) return 'gestern';
26
- if (days < 7) return `vor ${days} Tagen`;
40
+ if (days === 0) return msg.today;
41
+ if (days === 1) return msg.yesterday;
42
+ if (days < 7) return formatMessage(msg.daysAgo, { count: days });
27
43
  if (days < 30) {
28
44
  const w = Math.floor(days / 7);
29
- return `vor ${w} Woche${w > 1 ? 'n' : ''}`;
45
+ return formatMessage(w > 1 ? msg.weeksAgoMany : msg.weeksAgoOne, { count: w });
30
46
  }
31
- if (days < 365) return `vor ${Math.floor(days / 30)} Monaten`;
47
+ if (days < 365) return formatMessage(msg.monthsAgo, { count: Math.floor(days / 30) });
32
48
  const y = Math.floor(days / 365);
33
- return `vor ${y} Jahr${y > 1 ? 'en' : ''}`;
49
+ return formatMessage(y > 1 ? msg.yearsAgoMany : msg.yearsAgoOne, { count: y });
34
50
  }
35
51
 
36
- export function formatDateDe(iso: string | undefined): string {
37
- if (!iso) return '—';
38
- return new Date(iso).toLocaleDateString('de-DE', {
52
+ export function formatDate(iso: string | undefined, locale: SaLocale = DEFAULT_SA_LOCALE): string {
53
+ if (!iso) return EMPTY_VALUE;
54
+ return new Date(iso).toLocaleDateString(SA_INTL_LOCALES[locale], {
39
55
  day: '2-digit',
40
56
  month: 'short',
41
57
  year: 'numeric',
42
58
  });
43
59
  }
44
60
 
45
- export function formatTsDe(iso: string): string {
46
- return new Date(iso).toLocaleString('de-DE', {
61
+ export function formatTimestamp(iso: string, locale: SaLocale = DEFAULT_SA_LOCALE): string {
62
+ return new Date(iso).toLocaleString(SA_INTL_LOCALES[locale], {
47
63
  day: '2-digit',
48
64
  month: 'short',
49
65
  year: 'numeric',
@@ -6,25 +6,25 @@
6
6
  >
7
7
  <q-card style="min-width: 440px">
8
8
  <q-card-section>
9
- <div class="text-h6">Plan komplett löschen?</div>
9
+ <div class="text-h6">{{ msg.archiveDialog.title }}</div>
10
10
  <div class="text-body2 q-mt-sm">
11
- Plan <code>{{ target?.plan.planKey }}</code> wird
12
- <b>unwiderruflich aus der DB entfernt</b>. Da der Plan keine published Version
13
- hat, sind keine Subscriptions betroffen.
11
+ {{ msg.archiveDialog.bodyLead }} <code>{{ target?.plan.planKey }}</code>
12
+ {{ msg.archiveDialog.bodyVerb }}
13
+ <b>{{ msg.archiveDialog.bodyEmphasis }}</b
14
+ >{{ msg.archiveDialog.bodyTail }}
14
15
  </div>
15
16
  <div class="text-caption text-grey-7 q-mt-sm">
16
- Vertragsschutz P1: Pläne mit jemals veröffentlichten Versionen können nicht
17
- gelöscht werden — das Backend würde 422 antworten.
17
+ {{ msg.archiveDialog.contractProtectionNote }}
18
18
  </div>
19
19
  </q-card-section>
20
20
  <q-banner v-if="error" class="bg-warning q-mx-md q-mb-md" rounded>
21
21
  {{ error }}
22
22
  </q-banner>
23
23
  <q-card-actions align="right">
24
- <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
24
+ <q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
25
25
  <q-btn
26
26
  color="negative"
27
- label="Löschen"
27
+ :label="common.delete"
28
28
  :loading="archiving"
29
29
  @click="$emit('execute')"
30
30
  />
@@ -34,8 +34,12 @@
34
34
  </template>
35
35
 
36
36
  <script setup lang="ts">
37
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
37
38
  import type { PlanArchiveTarget } from './types.js';
38
39
 
40
+ const msg = useSaMessages('plans');
41
+ const common = useSaMessages('common');
42
+
39
43
  defineProps<{
40
44
  modelValue: boolean;
41
45
  target: PlanArchiveTarget | null;