@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
@@ -1,41 +1,42 @@
1
1
  <template>
2
2
  <div class="sa-discovery__kpis">
3
3
  <div class="sa-discovery__kpi">
4
- <div class="sa-discovery__kpi-label">Features gesamt</div>
4
+ <div class="sa-discovery__kpi-label">{{ msg.kpi.featuresTotal }}</div>
5
5
  <div class="sa-discovery__kpi-value">{{ featuresCount }}</div>
6
- <div class="sa-discovery__kpi-sub">
7
- aggregiert aus {{ capabilitiesCount }} Capabilities
8
- </div>
6
+ <div class="sa-discovery__kpi-sub">{{ featuresTotalSub }}</div>
9
7
  </div>
10
8
  <div class="sa-discovery__kpi good">
11
- <div class="sa-discovery__kpi-label">Freigegeben</div>
9
+ <div class="sa-discovery__kpi-label">{{ msg.kpi.approved }}</div>
12
10
  <div class="sa-discovery__kpi-value">{{ approvedCount }}</div>
13
- <div class="sa-discovery__kpi-sub">planungsbereit</div>
11
+ <div class="sa-discovery__kpi-sub">{{ msg.kpi.approvedSub }}</div>
14
12
  </div>
15
13
  <div class="sa-discovery__kpi" :class="{ warn: pendingCount > 0 }">
16
- <div class="sa-discovery__kpi-label">Ausstehend</div>
14
+ <div class="sa-discovery__kpi-label">{{ msg.kpi.pending }}</div>
17
15
  <div class="sa-discovery__kpi-value">{{ pendingCount }}</div>
18
- <div class="sa-discovery__kpi-sub">noch nicht freigegeben</div>
16
+ <div class="sa-discovery__kpi-sub">{{ msg.kpi.pendingSub }}</div>
19
17
  </div>
20
18
  <div class="sa-discovery__kpi">
21
- <div class="sa-discovery__kpi-label">Veraltet / Obsolet</div>
19
+ <div class="sa-discovery__kpi-label">{{ msg.kpi.outdated }}</div>
22
20
  <div class="sa-discovery__kpi-value">{{ outdatedCount + obsoleteCount }}</div>
23
- <div class="sa-discovery__kpi-sub">
24
- {{ outdatedCount }} outdated · {{ obsoleteCount }} obsolete
25
- </div>
21
+ <div class="sa-discovery__kpi-sub">{{ outdatedSub }}</div>
26
22
  </div>
27
23
  <div class="sa-discovery__kpi" :class="{ bad: orphanCount > 0 }">
28
- <div class="sa-discovery__kpi-label">Orphans</div>
24
+ <div class="sa-discovery__kpi-label">{{ msg.kpi.orphans }}</div>
29
25
  <div class="sa-discovery__kpi-value">{{ orphanCount }}</div>
30
- <div class="sa-discovery__kpi-sub">Capabilities ohne Feature</div>
26
+ <div class="sa-discovery__kpi-sub">{{ msg.orphansTitle }}</div>
31
27
  </div>
32
28
  </div>
33
29
  </template>
34
30
 
35
31
  <script setup lang="ts">
32
+ import { computed } from 'vue';
33
+
34
+ import { formatMessage } from '../../client/i18n/format.js';
35
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
36
+
36
37
  // Feature-centric KPI bar (#20 Slice 1, sim layout): the approval
37
38
  // counts refer to features, no longer to capability reviews.
38
- defineProps<{
39
+ const props = defineProps<{
39
40
  featuresCount: number;
40
41
  capabilitiesCount: number;
41
42
  approvedCount: number;
@@ -44,4 +45,16 @@ defineProps<{
44
45
  obsoleteCount: number;
45
46
  orphanCount: number;
46
47
  }>();
48
+
49
+ const msg = useSaMessages('discovery');
50
+
51
+ const featuresTotalSub = computed(() =>
52
+ formatMessage(msg.value.kpi.featuresTotalSub, { count: props.capabilitiesCount }),
53
+ );
54
+ const outdatedSub = computed(() =>
55
+ formatMessage(msg.value.kpi.outdatedSub, {
56
+ outdated: props.outdatedCount,
57
+ obsolete: props.obsoleteCount,
58
+ }),
59
+ );
47
60
  </script>
@@ -15,26 +15,25 @@
15
15
  <span
16
16
  v-if="quota.successorKey"
17
17
  class="sa-qc__flag sa-qc__flag--succ"
18
- :title="`ersetzt durch ${quota.successorKey}`"
18
+ :title="replacedByLabel"
19
19
  >
20
- ersetzt durch {{ quota.successorKey }}
20
+ {{ replacedByLabel }}
21
21
  </span>
22
22
  <span
23
23
  v-if="quota.replaces.length"
24
24
  class="sa-qc__flag sa-qc__flag--repl"
25
- :title="`ersetzt: ${quota.replaces.join(', ')}`"
25
+ :title="replacesLabel"
26
26
  >
27
- ersetzt: {{ quota.replaces.join(', ') }}
27
+ {{ replacesLabel }}
28
28
  </span>
29
29
  </div>
30
30
  <div class="sa-qc__sub">
31
- Einheit <code>{{ quota.unit }}</code> · UsageProvider
31
+ {{ msg.unit }} <code>{{ quota.unit }}</code> · {{ msg.quota.usageProvider }}
32
32
  <code v-if="quota.usageProvider">{{ quota.usageProvider }}</code>
33
- <span v-else class="sa-qc__missing">fehlt</span>
33
+ <span v-else class="sa-qc__missing">{{ msg.quota.usageProviderMissing }}</span>
34
34
  </div>
35
35
  <div v-if="!quota.usageProvider" class="sa-qc__warning">
36
- Eine harte Quota ohne UsageProvider ist nicht deploy-fähig (Preflight, SPEC_V2
37
- §6.3).
36
+ {{ msg.quota.noUsageProviderWarning }}
38
37
  </div>
39
38
  </div>
40
39
 
@@ -61,8 +60,8 @@
61
60
  <div v-if="quota.discoveryStatus === 'outdated'" class="sa-qc__banner">
62
61
  <q-icon name="warning" size="16px" />
63
62
  <span>
64
- Die code-abgeleiteten Quota-Fakten (Einheit/Enforcement/Provider) haben sich
65
- seit der letzten Freigabe geändert — bitte prüfen und <b>erneut freigeben</b>.
63
+ {{ msg.quota.outdatedBanner }} <b>{{ msg.reapproveEmphasis }}</b
64
+ >.
66
65
  </span>
67
66
  </div>
68
67
  <CatalogEntryTransPanel
@@ -88,6 +87,8 @@ import type {
88
87
  } from '@saasicat/types';
89
88
  import CatalogEntryTransPanel from './CatalogEntryTransPanel.vue';
90
89
  import DiscoveryStatusControl from './DiscoveryStatusControl.vue';
90
+ import { formatMessage } from '../../client/i18n/format.js';
91
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
91
92
  import {
92
93
  coverageClass,
93
94
  coveragePct,
@@ -114,11 +115,20 @@ const emit = defineEmits<{
114
115
  'quota-locale': [key: string, locale: string, patch: CatalogEntryI18nFields];
115
116
  }>();
116
117
 
118
+ const msg = useSaMessages('discovery');
119
+
117
120
  // Draft buffer for the header label — the fields themselves are buffered by
118
121
  // the CatalogEntryTransPanel; here only so the title follows along while typing.
119
122
  const drafts = reactive<{ label?: string }>({});
120
123
  const labelValue = computed(() => drafts.label ?? props.quota.label ?? '');
121
124
 
125
+ const replacedByLabel = computed(() =>
126
+ formatMessage(msg.value.replacedBy, { key: props.quota.successorKey ?? '' }),
127
+ );
128
+ const replacesLabel = computed(() =>
129
+ formatMessage(msg.value.replaces, { keys: props.quota.replaces.join(', ') }),
130
+ );
131
+
122
132
  /** Mirror base edits from the translation panel + bubble them up. */
123
133
  function onTransBase(patch: { label?: string; description?: string }): void {
124
134
  if (patch.label !== undefined) drafts.label = patch.label;
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div class="sa-stc" @click.stop>
3
3
  <span class="sa-review" :class="`sa-review--${status}`">
4
- {{ STATUS_META[status].label }}
5
- <q-tooltip>{{ STATUS_META[status].hint }}</q-tooltip>
4
+ {{ statusLabel(status, locale) }}
5
+ <q-tooltip>{{ statusHint(status, locale) }}</q-tooltip>
6
6
  </span>
7
7
  <q-btn
8
8
  dense
@@ -15,7 +15,7 @@
15
15
  :loading="busy"
16
16
  @click="emit('set-status', primary.to)"
17
17
  >
18
- <q-tooltip>{{ STATUS_META[primary.to].hint }}</q-tooltip>
18
+ <q-tooltip>{{ statusHint(primary.to, locale) }}</q-tooltip>
19
19
  </q-btn>
20
20
  <q-btn v-if="menu.length" dense flat round size="sm" icon="more_horiz">
21
21
  <q-menu auto-close anchor="bottom right" self="top right">
@@ -39,7 +39,8 @@
39
39
  <script setup lang="ts">
40
40
  import { computed } from 'vue';
41
41
  import type { DiscoveryStatus } from '@saasicat/types';
42
- import { primaryReviewAction, reviewMenuActions, STATUS_META } from './discovery-ui.js';
42
+ import { useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
43
+ import { primaryReviewAction, reviewMenuActions, statusHint, statusLabel } from './discovery-ui.js';
43
44
 
44
45
  // StatusControl (#20, design sim): status chip + contextual primary action
45
46
  // + kebab menu for the remaining lifecycle transitions. Shared by the feature
@@ -56,8 +57,10 @@ const emit = defineEmits<{
56
57
  'set-status': [target: DiscoveryStatus];
57
58
  }>();
58
59
 
59
- const primary = computed(() => primaryReviewAction(props.status));
60
- const menu = computed(() => reviewMenuActions(props.status));
60
+ const { locale } = useSuperAdminI18n();
61
+
62
+ const primary = computed(() => primaryReviewAction(props.status, locale.value));
63
+ const menu = computed(() => reviewMenuActions(props.status, locale.value));
61
64
  </script>
62
65
 
63
66
  <style scoped>
@@ -3,11 +3,10 @@
3
3
  // `<script setup>` allows no named exports and the cards share the same
4
4
  // labels/colors/coverage calculations.
5
5
 
6
- import type {
7
- CapabilityCodeStatus,
8
- CatalogEntryI18n,
9
- DiscoveryStatus,
10
- } from '@saasicat/types';
6
+ import type { CatalogEntryI18n, DiscoveryStatus } from '@saasicat/types';
7
+ import { DEFAULT_SA_LOCALE, type SaLocale } from '../../client/i18n/locale.js';
8
+ import { commonMessages } from '../../client/i18n/messages/common.js';
9
+ import { discoveryMessages } from '../../client/i18n/messages/discovery.js';
11
10
 
12
11
  /** Translatable fields of a catalog entry. */
13
12
  export type I18nField = 'label' | 'description' | 'unit';
@@ -43,30 +42,21 @@ export function localeFull(locale: string): string {
43
42
  return LOCALE_NAMES[locale]?.full ?? locale;
44
43
  }
45
44
 
46
- export function i18nFieldLabel(f: I18nField): string {
47
- return f === 'label' ? 'Label' : f === 'description' ? 'Beschreibung' : 'Einheit';
45
+ export function i18nFieldLabel(f: I18nField, locale: SaLocale = DEFAULT_SA_LOCALE): string {
46
+ if (f === 'label') return discoveryMessages[locale].trans.fieldLabel;
47
+ if (f === 'description') return commonMessages[locale].description;
48
+ return discoveryMessages[locale].unit;
48
49
  }
49
50
 
50
51
  // ─── Approval lifecycle (#20): status display + state machine ────────────────
51
52
 
52
- export const STATUS_META: Record<DiscoveryStatus, { label: string; hint: string }> = {
53
- pending: {
54
- label: 'Pending',
55
- hint: 'Im Code gefunden, noch nicht freigegeben — steht der Planung nicht zur Verfügung.',
56
- },
57
- approved: {
58
- label: 'Approved',
59
- hint: 'Für Pläne, Bundles & Marketing freigegeben.',
60
- },
61
- outdated: {
62
- label: 'Outdated',
63
- hint: 'Code hat sich seit der Freigabe geändert — bitte erneut prüfen und freigeben.',
64
- },
65
- obsolete: {
66
- label: 'Obsolete',
67
- hint: 'Abgekündigt — nicht mehr verwenden, in neuen Plänen ausblenden.',
68
- },
69
- };
53
+ export function statusLabel(status: DiscoveryStatus, locale: SaLocale = DEFAULT_SA_LOCALE): string {
54
+ return discoveryMessages[locale].statusLabels[status];
55
+ }
56
+
57
+ export function statusHint(status: DiscoveryStatus, locale: SaLocale = DEFAULT_SA_LOCALE): string {
58
+ return discoveryMessages[locale].statusHints[status];
59
+ }
70
60
 
71
61
  export interface ReviewAction {
72
62
  label: string;
@@ -77,53 +67,51 @@ export interface ReviewAction {
77
67
  }
78
68
 
79
69
  /** Primary action per status (design sim `StatusControl`, #20). */
80
- export function primaryReviewAction(status: DiscoveryStatus): ReviewAction {
70
+ export function primaryReviewAction(
71
+ status: DiscoveryStatus,
72
+ locale: SaLocale = DEFAULT_SA_LOCALE,
73
+ ): ReviewAction {
74
+ const actions = discoveryMessages[locale].reviewActions;
81
75
  switch (status) {
82
76
  case 'pending':
83
- return { label: 'Freigeben', to: 'approved', emphasized: true };
77
+ return { label: actions.approve, to: 'approved', emphasized: true };
84
78
  case 'approved':
85
- return { label: 'Freigabe entziehen', to: 'pending', emphasized: false };
79
+ return { label: actions.revoke, to: 'pending', emphasized: false };
86
80
  case 'outdated':
87
- return { label: 'Erneut freigeben', to: 'approved', emphasized: true };
81
+ return { label: actions.reapprove, to: 'approved', emphasized: true };
88
82
  case 'obsolete':
89
- return { label: 'Reaktivieren', to: 'pending', emphasized: false };
83
+ return { label: actions.reactivate, to: 'pending', emphasized: false };
90
84
  }
91
85
  }
92
86
 
93
87
  /** Kebab menu actions per status (design sim `StatusControl`, #20). */
94
- export function reviewMenuActions(status: DiscoveryStatus): ReviewAction[] {
88
+ export function reviewMenuActions(
89
+ status: DiscoveryStatus,
90
+ locale: SaLocale = DEFAULT_SA_LOCALE,
91
+ ): ReviewAction[] {
92
+ const actions = discoveryMessages[locale].reviewActions;
93
+ const markObsolete: ReviewAction = {
94
+ label: actions.markObsolete,
95
+ to: 'obsolete',
96
+ emphasized: false,
97
+ danger: true,
98
+ };
95
99
  switch (status) {
96
100
  case 'pending':
97
- return [
98
- { label: 'Als obsolet markieren', to: 'obsolete', emphasized: false, danger: true },
99
- ];
101
+ return [markObsolete];
100
102
  case 'approved':
101
103
  return [
102
- { label: 'Als veraltet markieren', to: 'outdated', emphasized: false },
103
- { label: 'Als obsolet markieren', to: 'obsolete', emphasized: false, danger: true },
104
+ { label: actions.markOutdated, to: 'outdated', emphasized: false },
105
+ markObsolete,
104
106
  ];
105
107
  case 'outdated':
106
- return [
107
- { label: 'Freigabe entziehen', to: 'pending', emphasized: false },
108
- { label: 'Als obsolet markieren', to: 'obsolete', emphasized: false, danger: true },
109
- ];
108
+ return [{ label: actions.revoke, to: 'pending', emphasized: false }, markObsolete];
110
109
  case 'obsolete':
111
110
  return [];
112
111
  }
113
112
  }
114
113
 
115
- // ─── Capability code status (read-only code facts, #20) ──────────────────────
116
-
117
- const CODE_STATUS_LABELS: Record<CapabilityCodeStatus, string> = {
118
- active: 'Aktiv',
119
- experimental: 'Experimental',
120
- deprecated: 'Deprecated',
121
- retired: 'Retired',
122
- };
123
-
124
- export function codeStatusLabel(status: CapabilityCodeStatus): string {
125
- return CODE_STATUS_LABELS[status] ?? status;
126
- }
114
+ // ─── Capability kind styling (read-only code facts, #20) ─────────────────────
127
115
 
128
116
  const KIND_COLORS: Record<string, string> = {
129
117
  endpoint: '#2563eb',
@@ -2,21 +2,18 @@
2
2
  <div class="mc-admin">
3
3
  <div class="mc-admin-head">
4
4
  <div style="flex: 1">
5
- <div class="mc-admin-title">Marketing-Verwaltung</div>
6
- <div class="mc-admin-sub">
7
- Sichtbarkeit, Reihenfolge &amp; Highlight-Badges pro Plan · Edits gehen direkt
8
- live
9
- </div>
5
+ <div class="mc-admin-title">{{ msg.tabs.admin }}</div>
6
+ <div class="mc-admin-sub">{{ msg.admin.subtitle }}</div>
10
7
  </div>
11
8
  </div>
12
9
 
13
10
  <div class="mc-admin-grid">
14
11
  <div class="mc-admin-thead">
15
- <div>Plan</div>
16
- <div>Sichtbar</div>
17
- <div>Badge</div>
18
- <div>Priorität</div>
19
- <div>Highlight</div>
12
+ <div>{{ msg.admin.colPlan }}</div>
13
+ <div>{{ msg.admin.colVisible }}</div>
14
+ <div>{{ msg.admin.colBadge }}</div>
15
+ <div>{{ msg.admin.colPriority }}</div>
16
+ <div>{{ msg.admin.colHighlight }}</div>
20
17
  <div></div>
21
18
  </div>
22
19
 
@@ -45,7 +42,9 @@
45
42
  v-if="row.publishedVersions.length > 1"
46
43
  class="mc-version-tabs"
47
44
  role="tablist"
48
- :aria-label="`Plan-Versionen für ${row.plan.label}`"
45
+ :aria-label="
46
+ formatMessage(msg.admin.versionTabsLabel, { plan: row.plan.label })
47
+ "
49
48
  >
50
49
  <button
51
50
  v-for="v in row.publishedVersions"
@@ -107,23 +106,23 @@
107
106
  </div>
108
107
  <div class="mc-admin-row-end">
109
108
  <span v-if="!row.liveVersion" class="mc-chip mc-chip--muted">
110
- keine Live-Version
109
+ {{ msg.admin.noLiveVersion }}
111
110
  </span>
112
111
  <span v-else-if="!row.m.visible" class="mc-chip mc-chip--muted">
113
- versteckt
112
+ {{ msg.admin.hidden }}
114
113
  </span>
115
114
  <span v-else-if="row.m.highlight" class="mc-chip mc-chip--featured">
116
- Featured
115
+ {{ msg.admin.featured }}
117
116
  </span>
118
- <span v-else class="mc-chip mc-chip--live">live</span>
117
+ <span v-else class="mc-chip mc-chip--live">{{ msg.admin.live }}</span>
119
118
  <button
120
119
  v-if="row.liveVersion"
121
120
  type="button"
122
121
  class="mc-expand-btn"
123
122
  :title="
124
123
  expandedKey === row.plan.planKey
125
- ? 'Schließen'
126
- : 'Teaser, Trial & Top-Features bearbeiten'
124
+ ? common.close
125
+ : msg.admin.expandTitle
127
126
  "
128
127
  @click="$emit('toggle-expand', row)"
129
128
  >
@@ -151,19 +150,27 @@
151
150
  <div class="mc-expand-col">
152
151
  <div class="mc-expand-sec">
153
152
  <div class="mc-field-head">
154
- <label class="mc-expand-label">Plan-Name</label>
153
+ <label class="mc-expand-label">
154
+ {{ msg.admin.planNameLabel }}
155
+ </label>
155
156
  <span
156
157
  v-if="activeLocale !== defaultLocale"
157
158
  class="mc-source-hint"
158
159
  >
159
160
  {{ defaultLocale.toUpperCase() }}:
160
- <em>„{{ row.plan.label }}"</em>
161
+ <em>
162
+ {{
163
+ formatMessage(msg.admin.planNameSourceValue, {
164
+ label: row.plan.label,
165
+ })
166
+ }}
167
+ </em>
161
168
  </span>
162
169
  </div>
163
170
  <div v-if="activeLocale === defaultLocale" class="mc-locked-value">
164
171
  <span>{{ row.plan.label }}</span>
165
172
  <span class="mc-locked-hint">
166
- wird in den Plan-Stammdaten gepflegt
173
+ {{ msg.admin.planNameLocked }}
167
174
  </span>
168
175
  </div>
169
176
  <input
@@ -178,29 +185,31 @@
178
185
  v-if="activeLocale !== defaultLocale && !row.m.displayLabel"
179
186
  class="mc-expand-hint"
180
187
  >
181
- leer · Fallback auf „{{ row.plan.label }}" wird verwendet
188
+ {{
189
+ formatMessage(msg.admin.planNameFallbackHint, {
190
+ label: row.plan.label,
191
+ })
192
+ }}
182
193
  </div>
183
194
  </div>
184
195
 
185
196
  <div class="mc-expand-sec">
186
- <label class="mc-expand-label">Teaser-Text</label>
197
+ <label class="mc-expand-label">{{ msg.admin.teaserLabel }}</label>
187
198
  <textarea
188
199
  class="mc-field mc-field--area"
189
200
  rows="2"
190
- placeholder="Kurzbeschreibung für die Pricing-Page …"
201
+ :placeholder="msg.admin.teaserPlaceholder"
191
202
  :value="row.m.description"
192
203
  :disabled="busy"
193
204
  @change="
194
205
  $emit('patch', row, { description: textValue($event) })
195
206
  "
196
207
  />
197
- <div class="mc-expand-hint">
198
- erscheint unter dem Plannamen in der Vorschau
199
- </div>
208
+ <div class="mc-expand-hint">{{ msg.admin.teaserHint }}</div>
200
209
  </div>
201
210
 
202
211
  <div class="mc-expand-sec">
203
- <label class="mc-expand-label">Kostenlose Testphase</label>
212
+ <label class="mc-expand-label">{{ msg.admin.trialLabel }}</label>
204
213
  <div class="mc-trial-row">
205
214
  <label class="mc-toggle">
206
215
  <input
@@ -216,7 +225,11 @@
216
225
  <span />
217
226
  </label>
218
227
  <span class="mc-trial-label">
219
- {{ row.m.trialEnabled ? 'Trial aktiv' : 'Kein Trial' }}
228
+ {{
229
+ row.m.trialEnabled
230
+ ? msg.admin.trialOn
231
+ : msg.admin.trialOff
232
+ }}
220
233
  </span>
221
234
  <span class="mc-trial-days">
222
235
  <input
@@ -233,19 +246,18 @@
233
246
  })
234
247
  "
235
248
  />
236
- <span class="mc-trial-unit">Tage</span>
249
+ <span class="mc-trial-unit">
250
+ {{ msg.admin.trialDaysUnit }}
251
+ </span>
237
252
  </span>
238
253
  </div>
239
- <div class="mc-expand-hint">
240
- CTA-Text generiert sich automatisch („Jetzt
241
- {{ row.m.trialDays }} Tage testen“).
242
- </div>
254
+ <div class="mc-expand-hint">{{ trialCtaHint(row) }}</div>
243
255
  </div>
244
256
 
245
257
  <div class="mc-expand-sec">
246
- <label class="mc-expand-label"
247
- >CTA-Text überschreiben (optional)</label
248
- >
258
+ <label class="mc-expand-label">
259
+ {{ msg.admin.ctaOverrideLabel }}
260
+ </label>
249
261
  <input
250
262
  class="mc-field"
251
263
  :placeholder="autoCtaText(row)"
@@ -257,7 +269,7 @@
257
269
  })
258
270
  "
259
271
  />
260
- <div class="mc-expand-hint">leer lassen für Auto-Text</div>
272
+ <div class="mc-expand-hint">{{ msg.admin.ctaOverrideHint }}</div>
261
273
  </div>
262
274
  </div>
263
275
 
@@ -265,10 +277,14 @@
265
277
  <div class="mc-expand-sec">
266
278
  <div class="mc-tf-head">
267
279
  <label class="mc-expand-label" style="margin: 0">
268
- Top-Features
280
+ {{ msg.topFeatures }}
269
281
  </label>
270
282
  <span class="mc-expand-hint" style="margin-left: auto">
271
- {{ editFeatures.length }} Einträge
283
+ {{
284
+ formatMessage(msg.admin.topFeatureCount, {
285
+ count: editFeatures.length,
286
+ })
287
+ }}
272
288
  </span>
273
289
  </div>
274
290
 
@@ -280,7 +296,7 @@
280
296
  :placeholder="
281
297
  f.key
282
298
  ? resolveComponentLabel(f.key)
283
- : 'z. B. Beitragsverwaltung'
299
+ : msg.admin.featureLabelPlaceholder
284
300
  "
285
301
  :value="f.label"
286
302
  :disabled="busy"
@@ -291,7 +307,7 @@
291
307
  />
292
308
  <input
293
309
  class="mc-field mc-tf-strong"
294
- placeholder="Highlight, z. B. bis 100"
310
+ :placeholder="msg.admin.featureStrongPlaceholder"
295
311
  :value="f.strong"
296
312
  :disabled="busy"
297
313
  @input="
@@ -303,7 +319,7 @@
303
319
  <button
304
320
  type="button"
305
321
  class="mc-iconbtn"
306
- title="Nach oben"
322
+ :title="msg.admin.moveUp"
307
323
  :disabled="i === 0 || busy"
308
324
  @click="$emit('move-feature', row, i, -1)"
309
325
  >
@@ -312,7 +328,7 @@
312
328
  <button
313
329
  type="button"
314
330
  class="mc-iconbtn"
315
- title="Nach unten"
331
+ :title="msg.admin.moveDown"
316
332
  :disabled="i === editFeatures.length - 1 || busy"
317
333
  @click="$emit('move-feature', row, i, 1)"
318
334
  >
@@ -321,7 +337,7 @@
321
337
  <button
322
338
  type="button"
323
339
  class="mc-iconbtn mc-iconbtn--danger"
324
- title="Entfernen"
340
+ :title="common.remove"
325
341
  :disabled="busy"
326
342
  @click="$emit('remove-feature', row, i)"
327
343
  >
@@ -339,8 +355,7 @@
339
355
  </div>
340
356
  </div>
341
357
  <div v-if="editFeatures.length === 0" class="mc-tf-empty">
342
- Noch keine Top-Features — füge Plan-Komponenten oder freien
343
- Text hinzu.
358
+ {{ msg.admin.topFeaturesEmpty }}
344
359
  </div>
345
360
  </div>
346
361
 
@@ -361,20 +376,26 @@
361
376
  >
362
377
  <path d="M12 5v14M5 12h14" />
363
378
  </svg>
364
- <span>Eigener Eintrag</span>
379
+ <span>{{ msg.admin.addCustomFeature }}</span>
365
380
  </button>
366
381
  <div
367
382
  v-if="suggestionsFor(row).length > 0"
368
383
  class="mc-tf-suggestions"
369
384
  >
370
- <span class="mc-expand-hint">aus Plan-Komponenten:</span>
385
+ <span class="mc-expand-hint">
386
+ {{ msg.admin.suggestionsLabel }}
387
+ </span>
371
388
  <button
372
389
  v-for="(s, i) in suggestionsFor(row)"
373
390
  :key="i"
374
391
  type="button"
375
392
  class="mc-tf-chip"
376
393
  :disabled="busy"
377
- :title="`„${s.label}“ als Top-Feature übernehmen`"
394
+ :title="
395
+ formatMessage(msg.admin.addSuggestionTitle, {
396
+ label: s.label,
397
+ })
398
+ "
378
399
  @click="$emit('add-suggestion', row, s)"
379
400
  >
380
401
  <svg
@@ -403,6 +424,8 @@
403
424
 
404
425
  <script setup lang="ts">
405
426
  import type { MarketingTopFeature, PlanRow, PlanVersionRow } from '@saasicat/types';
427
+ import { formatMessage } from '../../client/i18n/format.js';
428
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
406
429
  import type { FeatureSuggestion, MarketingRow, ResolvedMarketing } from './types.js';
407
430
 
408
431
  defineProps<{
@@ -434,6 +457,15 @@ defineEmits<{
434
457
  (e: 'add-suggestion', row: MarketingRow, suggestion: FeatureSuggestion): void;
435
458
  }>();
436
459
 
460
+ const msg = useSaMessages('marketing');
461
+ const common = useSaMessages('common');
462
+
463
+ /** Hint below the trial toggle — shows the auto CTA text for the set trial days. */
464
+ function trialCtaHint(row: MarketingRow): string {
465
+ const cta = formatMessage(msg.value.cta.trial, { days: row.m.trialDays });
466
+ return formatMessage(msg.value.admin.ctaAutoHint, { cta });
467
+ }
468
+
437
469
  function textValue(event: Event): string {
438
470
  return (event.target as HTMLInputElement | HTMLTextAreaElement | null)?.value ?? '';
439
471
  }