@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,13 +1,13 @@
1
1
  <template>
2
2
  <div class="sa-bd-grid">
3
3
  <div class="sa-bd-col">
4
- <div class="sa-bd-section-label">Stammdaten</div>
4
+ <div class="sa-bd-section-label">{{ msg.fields.masterData }}</div>
5
5
  <div class="sa-bundles__form">
6
6
  <q-input
7
7
  :model-value="editForm.label"
8
8
  outlined
9
9
  dense
10
- label="Label"
10
+ :label="msg.fields.label"
11
11
  @update:model-value="(value) => patchEditForm({ label: String(value ?? '') })"
12
12
  />
13
13
  <q-input
@@ -16,7 +16,7 @@
16
16
  dense
17
17
  type="textarea"
18
18
  autogrow
19
- label="Beschreibung"
19
+ :label="common.description"
20
20
  @update:model-value="
21
21
  (value) => patchEditForm({ description: String(value ?? '') })
22
22
  "
@@ -25,7 +25,7 @@
25
25
  :model-value="editForm.icon"
26
26
  outlined
27
27
  dense
28
- label="Icon"
28
+ :label="common.icon"
29
29
  @update:model-value="(value) => patchEditForm({ icon: String(value ?? '') })"
30
30
  />
31
31
  <q-input
@@ -33,7 +33,7 @@
33
33
  outlined
34
34
  dense
35
35
  type="number"
36
- label="Sortier-Reihenfolge"
36
+ :label="common.sortOrder"
37
37
  @update:model-value="
38
38
  (value) => patchEditForm({ sortOrder: Number(value ?? 0) })
39
39
  "
@@ -41,11 +41,11 @@
41
41
  </div>
42
42
 
43
43
  <div class="sa-bd-section-label sa-bd-section-label--mt">
44
- <span>Übersetzungen</span>
45
- <span class="sa-bd-section-count">{{ translatableLocales.length }} Sprache(n)</span>
44
+ <span>{{ msg.detail.translations }}</span>
45
+ <span class="sa-bd-section-count">{{ languageCount }}</span>
46
46
  </div>
47
47
  <div v-if="translatableLocales.length === 0" class="sa-bd-i18n-hint">
48
- Keine weiteren Sprachen aktiv — werden im Marketing-Catalog aktiviert.
48
+ {{ msg.detail.noTranslatableLocales }}
49
49
  </div>
50
50
  <div v-for="lng in translatableLocales" :key="lng" class="sa-bd-i18n-block">
51
51
  <div class="sa-bd-i18n-head">
@@ -54,14 +54,14 @@
54
54
  v-if="!i18nDraft[lng]?.label || !i18nDraft[lng]?.description"
55
55
  class="sa-bd-i18n-fallback"
56
56
  >
57
- Fallback aus DE
57
+ {{ msg.detail.fallbackFromDe }}
58
58
  </span>
59
59
  </div>
60
60
  <q-input
61
61
  outlined
62
62
  dense
63
63
  :model-value="i18nDraft[lng]?.label || ''"
64
- :placeholder="`Label (Fallback: „${editForm.label}“)`"
64
+ :placeholder="labelPlaceholder"
65
65
  @update:model-value="
66
66
  (value) => emit('setI18n', lng, 'label', String(value ?? ''))
67
67
  "
@@ -72,7 +72,7 @@
72
72
  type="textarea"
73
73
  autogrow
74
74
  :model-value="i18nDraft[lng]?.description || ''"
75
- :placeholder="`Beschreibung (Fallback aus DE)`"
75
+ :placeholder="msg.detail.descriptionPlaceholder"
76
76
  @update:model-value="
77
77
  (value) => emit('setI18n', lng, 'description', String(value ?? ''))
78
78
  "
@@ -83,7 +83,7 @@
83
83
  class="sa-bd-save"
84
84
  unelevated
85
85
  color="primary"
86
- label="Stammdaten & Übersetzungen speichern"
86
+ :label="msg.detail.save"
87
87
  :loading="editSubmitting"
88
88
  @click="emit('submitEdit')"
89
89
  />
@@ -112,10 +112,7 @@
112
112
  @save="(data) => emit('inlineSave', bundle.id, selectedVersion!.id, data)"
113
113
  @discard="emit('discardVersion', bundle.id, selectedVersion!.id)"
114
114
  />
115
- <div v-else class="sa-bd-empty-row">
116
- Noch keine Version. Lege eine neue Version an, um Features, Quotas &amp; Pricing zu
117
- kuratieren.
118
- </div>
115
+ <div v-else class="sa-bd-empty-row">{{ msg.detail.noVersion }}</div>
119
116
 
120
117
  <div class="sa-bd-version-actions">
121
118
  <q-btn
@@ -123,7 +120,7 @@
123
120
  unelevated
124
121
  color="positive"
125
122
  icon="rocket_launch"
126
- label="Diese Version publishen"
123
+ :label="msg.detail.publishVersion"
127
124
  @click="emit('publishVersion', selectedVersion)"
128
125
  />
129
126
  <q-btn
@@ -132,7 +129,7 @@
132
129
  dense
133
130
  color="negative"
134
131
  icon="delete"
135
- label="Bundle soft-deleten"
132
+ :label="msg.detail.softDelete"
136
133
  @click="emit('deleteBundle', bundle)"
137
134
  />
138
135
  </div>
@@ -141,6 +138,7 @@
141
138
  </template>
142
139
 
143
140
  <script setup lang="ts">
141
+ import { computed } from 'vue';
144
142
  import type {
145
143
  BundleRow,
146
144
  BundleVersionRow,
@@ -154,6 +152,8 @@ import BundleVersionInlineEditor from '../../components/bundle-editor/BundleVers
154
152
  import BundleVersionStrip from '../../components/bundle-editor/BundleVersionStrip.vue';
155
153
  import type { FeatureMeta } from '../../components/bundle-editor/BundleFeaturesEditor.vue';
156
154
  import type { QuotaMeta } from '../../components/bundle-editor/catalog-i18n.js';
155
+ import { formatMessage } from '../../client/i18n/format.js';
156
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
157
157
  import type { BundleEditForm } from './types.js';
158
158
 
159
159
  const props = defineProps<{
@@ -187,6 +187,16 @@ const emit = defineEmits<{
187
187
  deleteBundle: [bundle: BundleRow];
188
188
  }>();
189
189
 
190
+ const msg = useSaMessages('bundles');
191
+ const common = useSaMessages('common');
192
+
193
+ const languageCount = computed(() =>
194
+ formatMessage(msg.value.detail.languageCount, { count: props.translatableLocales.length }),
195
+ );
196
+ const labelPlaceholder = computed(() =>
197
+ formatMessage(msg.value.detail.labelPlaceholder, { label: props.editForm.label }),
198
+ );
199
+
190
200
  function patchEditForm(patch: Partial<BundleEditForm>): void {
191
201
  emit('update:editForm', { ...props.editForm, ...patch });
192
202
  }
@@ -5,7 +5,7 @@
5
5
  dense
6
6
  outlined
7
7
  clearable
8
- placeholder="Bundle-Key oder Label suchen …"
8
+ :placeholder="msg.filterBar.searchPlaceholder"
9
9
  class="sa-bundles__search"
10
10
  @update:model-value="emitQuery"
11
11
  >
@@ -26,6 +26,7 @@
26
26
 
27
27
  <script setup lang="ts">
28
28
  import type { BundlesStatusFilter, BundlesStatusFilterOption } from './types.js';
29
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
29
30
 
30
31
  defineProps<{
31
32
  query: string;
@@ -33,6 +34,8 @@ defineProps<{
33
34
  statusFilterOptions: BundlesStatusFilterOption[];
34
35
  }>();
35
36
 
37
+ const msg = useSaMessages('bundles');
38
+
36
39
  const emit = defineEmits<{
37
40
  'update:query': [value: string];
38
41
  'update:statusFilter': [value: BundlesStatusFilter];
@@ -1,11 +1,8 @@
1
1
  <template>
2
2
  <header class="sa-bundles__head">
3
3
  <div>
4
- <h1 class="sa-bundles__title">Bundles</h1>
5
- <p class="sa-bundles__sub">
6
- Produktgruppen aus Features &amp; Quotas — verwendet in Plänen. Bundles werden im
7
- SuperAdmin kuratiert.
8
- </p>
4
+ <h1 class="sa-bundles__title">{{ msg.header.title }}</h1>
5
+ <p class="sa-bundles__sub">{{ msg.header.subtitle }}</p>
9
6
  </div>
10
7
  <div class="sa-bundles__head-actions">
11
8
  <q-select
@@ -17,7 +14,7 @@
17
14
  emit-value
18
15
  map-options
19
16
  class="sa-bundles__locale"
20
- label="Anzeige-Sprache"
17
+ :label="msg.header.displayLocale"
21
18
  @update:model-value="(value) => emit('update:displayLocale', String(value))"
22
19
  >
23
20
  <template #prepend><q-icon name="translate" size="18px" /></template>
@@ -26,11 +23,11 @@
26
23
  unelevated
27
24
  color="primary"
28
25
  icon="add"
29
- label="Neues Bundle"
26
+ :label="msg.header.newBundle"
30
27
  @click="emit('create')"
31
28
  />
32
29
  <q-btn flat icon="refresh" :loading="loading" @click="emit('refresh')">
33
- <q-tooltip>Liste neu laden</q-tooltip>
30
+ <q-tooltip>{{ msg.header.reload }}</q-tooltip>
34
31
  </q-btn>
35
32
  </div>
36
33
  </header>
@@ -39,6 +36,8 @@
39
36
  <script setup lang="ts">
40
37
  import { computed } from 'vue';
41
38
 
39
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
40
+
42
41
  // The language selector controls which locale feature/quota labels are resolved
43
42
  // on the create form and in the detail view (fallback locale → DE → key). The default is the
44
43
  // default locale (DE); further options come from the active project locales.
@@ -55,6 +54,8 @@ const emit = defineEmits<{
55
54
  'update:displayLocale': [locale: string];
56
55
  }>();
57
56
 
57
+ const msg = useSaMessages('bundles');
58
+
58
59
  const localeOptions = computed(() =>
59
60
  props.locales.map((locale) => ({ label: locale.toUpperCase(), value: locale })),
60
61
  );
@@ -1,32 +1,35 @@
1
1
  <template>
2
2
  <div class="sa-bundles__kpis">
3
3
  <div class="sa-bundles__kpi">
4
- <div class="sa-bundles__kpi-label">Bundles gesamt</div>
4
+ <div class="sa-bundles__kpi-label">{{ msg.kpis.total }}</div>
5
5
  <div class="sa-bundles__kpi-value">{{ bundlesTotal }}</div>
6
- <div class="sa-bundles__kpi-sub">
7
- {{ liveCount }} live · {{ scheduledBundlesCount }} mit geplanter Version
8
- </div>
6
+ <div class="sa-bundles__kpi-sub">{{ totalSub }}</div>
9
7
  </div>
10
8
  <div class="sa-bundles__kpi">
11
- <div class="sa-bundles__kpi-label">Geplante Versionen</div>
9
+ <div class="sa-bundles__kpi-label">{{ msg.kpis.scheduled }}</div>
12
10
  <div class="sa-bundles__kpi-value">{{ totalScheduledVersions }}</div>
13
- <div class="sa-bundles__kpi-sub">zukünftig aktiv · noch nicht verkaufbar</div>
11
+ <div class="sa-bundles__kpi-sub">{{ msg.kpis.scheduledSub }}</div>
14
12
  </div>
15
13
  <div class="sa-bundles__kpi">
16
- <div class="sa-bundles__kpi-label">Offene Drafts</div>
14
+ <div class="sa-bundles__kpi-label">{{ msg.kpis.drafts }}</div>
17
15
  <div class="sa-bundles__kpi-value">{{ totalDraftVersions }}</div>
18
- <div class="sa-bundles__kpi-sub">{{ draftBundlesCount }} Bundle(s) ohne Publish</div>
16
+ <div class="sa-bundles__kpi-sub">{{ draftsSub }}</div>
19
17
  </div>
20
18
  <div class="sa-bundles__kpi">
21
- <div class="sa-bundles__kpi-label">Mit Übersetzung</div>
19
+ <div class="sa-bundles__kpi-label">{{ msg.kpis.translated }}</div>
22
20
  <div class="sa-bundles__kpi-value">{{ translatedCount }}</div>
23
- <div class="sa-bundles__kpi-sub">{{ localesCount }} aktive Sprache(n) im Projekt</div>
21
+ <div class="sa-bundles__kpi-sub">{{ translatedSub }}</div>
24
22
  </div>
25
23
  </div>
26
24
  </template>
27
25
 
28
26
  <script setup lang="ts">
29
- defineProps<{
27
+ import { computed } from 'vue';
28
+
29
+ import { formatMessage } from '../../client/i18n/format.js';
30
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
31
+
32
+ const props = defineProps<{
30
33
  bundlesTotal: number;
31
34
  liveCount: number;
32
35
  scheduledBundlesCount: number;
@@ -36,4 +39,19 @@ defineProps<{
36
39
  translatedCount: number;
37
40
  localesCount: number;
38
41
  }>();
42
+
43
+ const msg = useSaMessages('bundles');
44
+
45
+ const totalSub = computed(() =>
46
+ formatMessage(msg.value.kpis.totalSub, {
47
+ live: props.liveCount,
48
+ scheduled: props.scheduledBundlesCount,
49
+ }),
50
+ );
51
+ const draftsSub = computed(() =>
52
+ formatMessage(msg.value.kpis.draftsSub, { count: props.draftBundlesCount }),
53
+ );
54
+ const translatedSub = computed(() =>
55
+ formatMessage(msg.value.kpis.translatedSub, { count: props.localesCount }),
56
+ );
39
57
  </script>
@@ -5,11 +5,11 @@
5
5
  <div class="sa-trans-lang__head">
6
6
  <span class="sa-trans-lang__code">{{ defaultLocale.toUpperCase() }}</span>
7
7
  <span class="sa-trans-lang__name">{{ localeFull(defaultLocale) }}</span>
8
- <span class="sa-trans-lang__badge">Source</span>
8
+ <span class="sa-trans-lang__badge">{{ msg.trans.sourceBadge }}</span>
9
9
  </div>
10
10
  <div class="sa-trans-fields">
11
11
  <div v-for="f in fields" :key="f" class="sa-trans-field">
12
- <label class="sa-trans-field__cap">{{ i18nFieldLabel(f) }}</label>
12
+ <label class="sa-trans-field__cap">{{ i18nFieldLabel(f, uiLocale) }}</label>
13
13
  <q-input
14
14
  v-if="f !== 'unit'"
15
15
  dense
@@ -21,7 +21,7 @@
21
21
  />
22
22
  <div v-else class="sa-trans-locked">
23
23
  {{ entry.unit || '—' }}
24
- <span class="sa-trans-locked__hint">aus Code</span>
24
+ <span class="sa-trans-locked__hint">{{ msg.trans.unitFromCode }}</span>
25
25
  </div>
26
26
  </div>
27
27
  </div>
@@ -38,23 +38,23 @@
38
38
  no-caps
39
39
  size="sm"
40
40
  icon="content_copy"
41
- label="Aus DE"
41
+ :label="msg.trans.copyFromDefault"
42
42
  class="sa-trans-lang__copy"
43
43
  @click="copyFromDefault(lng)"
44
44
  >
45
- <q-tooltip>Leere Felder aus der Default-Locale übernehmen</q-tooltip>
45
+ <q-tooltip>{{ msg.trans.copyFromDefaultHint }}</q-tooltip>
46
46
  </q-btn>
47
47
  </div>
48
48
  <div class="sa-trans-fields">
49
49
  <div v-for="f in fields" :key="f" class="sa-trans-field">
50
- <label class="sa-trans-field__cap">{{ i18nFieldLabel(f) }}</label>
50
+ <label class="sa-trans-field__cap">{{ i18nFieldLabel(f, uiLocale) }}</label>
51
51
  <q-input
52
52
  dense
53
53
  outlined
54
54
  :type="f === 'description' ? 'textarea' : 'text'"
55
55
  :autogrow="f === 'description'"
56
56
  :model-value="localeValue(lng, f)"
57
- :placeholder="baseValue(f) ? `Fallback: „${baseValue(f)}“` : '—'"
57
+ :placeholder="fallbackPlaceholder(f)"
58
58
  @update:model-value="(v) => onLocale(lng, f, String(v ?? ''))"
59
59
  />
60
60
  </div>
@@ -62,7 +62,7 @@
62
62
  </div>
63
63
 
64
64
  <div v-if="targetLocales.length === 0" class="sa-trans__empty">
65
- Keine weiteren Sprachen aktiv — Sprachen werden im Marketing-Catalog aktiviert.
65
+ {{ msg.trans.noOtherLocales }}
66
66
  </div>
67
67
  </div>
68
68
  </template>
@@ -70,6 +70,8 @@
70
70
  <script setup lang="ts">
71
71
  import { computed, reactive } from 'vue';
72
72
  import type { CatalogEntryI18nFields } from '@saasicat/types';
73
+ import { formatMessage } from '../../client/i18n/format.js';
74
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
73
75
  import {
74
76
  DISCOVERY_DEFAULT_LOCALE,
75
77
  i18nFieldLabel,
@@ -103,6 +105,10 @@ const emit = defineEmits<{
103
105
  'update:locale': [locale: string, patch: CatalogEntryI18nFields];
104
106
  }>();
105
107
 
108
+ const msg = useSaMessages('discovery');
109
+ // Renamed: `locale` is the catalog-entry data locale everywhere else in this file.
110
+ const { locale: uiLocale } = useSuperAdminI18n();
111
+
106
112
  const targetLocales = computed(() => props.activeLocales.filter((l) => l !== props.defaultLocale));
107
113
 
108
114
  // Local input buffer: once a field has been edited, the draft wins over
@@ -124,6 +130,12 @@ function localeValue(locale: string, f: I18nField): string {
124
130
  return props.entry.i18n?.[locale]?.[f] ?? '';
125
131
  }
126
132
 
133
+ /** Shows the default-locale value that a blank target field falls back to. */
134
+ function fallbackPlaceholder(f: I18nField): string {
135
+ const base = baseValue(f);
136
+ return base ? formatMessage(msg.value.trans.fallbackPlaceholder, { value: base }) : '—';
137
+ }
138
+
127
139
  function onBase(f: I18nField, value: string): void {
128
140
  if (f === 'unit') return;
129
141
  drafts[`b|${f}`] = value;
@@ -1,8 +1,7 @@
1
1
  <template>
2
2
  <div v-if="capabilities.length === 0" class="sa-caps__empty">
3
3
  <q-icon name="warning" size="14px" />
4
- Feature im Katalog ohne implementierende Capability — blockiert im blocking-Strict-Mode das
5
- Plan-Publish.
4
+ {{ msg.capList.orphanFeature }}
6
5
  </div>
7
6
  <div v-else class="sa-caps">
8
7
  <div
@@ -37,7 +36,7 @@
37
36
  v-if="cap.codeStatus === 'retired'"
38
37
  class="sa-caps-row__flag sa-caps-row__flag--gone"
39
38
  >
40
- aus Code entfernt
39
+ {{ msg.capList.removedFromCode }}
41
40
  </span>
42
41
  </div>
43
42
  <div class="sa-caps-row__meta">
@@ -59,6 +58,7 @@
59
58
  <script setup lang="ts">
60
59
  import type { CapabilityCatalogEntryRow } from '@saasicat/types';
61
60
  import { kindStyle } from './discovery-ui.js';
61
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
62
62
 
63
63
  // Read-only Capability list: code facts from the scan (#20). Embedded in the
64
64
  // master-data subtab of the feature card as well as in the page's orphan bucket.
@@ -73,6 +73,8 @@ const props = defineProps<{
73
73
  newSince?: string | null;
74
74
  }>();
75
75
 
76
+ const msg = useSaMessages('discovery');
77
+
76
78
  function isNew(cap: CapabilityCatalogEntryRow): boolean {
77
79
  return Boolean(props.newSince && cap.createdAt > props.newSince);
78
80
  }
@@ -7,34 +7,38 @@
7
7
  <code class="sa-fc__key">{{ feature.featureKey }}</code>
8
8
  <span class="sa-fc__label">{{ labelValue || feature.featureKey }}</span>
9
9
  <span v-if="newCapsCount > 0" class="sa-fc__flag sa-fc__flag--new">
10
- {{ newCapsCount }} neu
10
+ {{ newCapsLabel }}
11
11
  </span>
12
12
  <span v-if="deprecatedCapsCount > 0" class="sa-fc__flag sa-fc__flag--dep">
13
- {{ deprecatedCapsCount }} deprecated
13
+ {{ deprecatedCapsLabel }}
14
14
  </span>
15
15
  <span
16
16
  v-if="feature.successorKey"
17
17
  class="sa-fc__flag sa-fc__flag--succ"
18
- :title="`ersetzt durch ${feature.successorKey}`"
18
+ :title="replacedByLabel"
19
19
  >
20
- ersetzt durch {{ feature.successorKey }}
20
+ {{ replacedByLabel }}
21
21
  </span>
22
22
  <span
23
23
  v-if="feature.replaces.length"
24
24
  class="sa-fc__flag sa-fc__flag--repl"
25
- :title="`ersetzt: ${feature.replaces.join(', ')}`"
25
+ :title="replacesLabel"
26
26
  >
27
- ersetzt: {{ feature.replaces.join(', ') }}
27
+ {{ replacesLabel }}
28
28
  </span>
29
29
  </div>
30
30
  <div class="sa-fc__sub">
31
31
  <template v-if="owners.length">
32
- <span class="sa-muted">Owner</span>
32
+ <span class="sa-muted">{{ msg.owner }}</span>
33
33
  {{ owners.join(', ') }}
34
34
  <span class="sa-fc__dot">·</span>
35
35
  </template>
36
36
  {{ capabilities.length }}
37
- Capabilit{{ capabilities.length === 1 ? 'y' : 'ies' }}
37
+ {{
38
+ capabilities.length === 1
39
+ ? msg.feature.capabilityOne
40
+ : msg.feature.capabilityMany
41
+ }}
38
42
  <template v-if="tierValue">
39
43
  <span class="sa-fc__dot">·</span>
40
44
  <span class="sa-fc__tier">{{ tierValue }}</span>
@@ -65,34 +69,31 @@
65
69
  <div v-if="feature.discoveryStatus === 'outdated'" class="sa-fc__banner warn">
66
70
  <q-icon name="warning" size="16px" />
67
71
  <span>
68
- Die Implementierung hat sich seit der letzten Freigabe geändert. Bitte
69
- Stammdaten &amp; Capabilities prüfen und <b>erneut freigeben</b>.
72
+ {{ msg.feature.outdatedBanner }} <b>{{ msg.reapproveEmphasis }}</b
73
+ >.
70
74
  </span>
71
75
  </div>
72
76
  <div v-if="feature.discoveryStatus === 'obsolete'" class="sa-fc__banner mute">
73
77
  <q-icon name="info" size="16px" />
74
- <span>
75
- Dieses Feature ist abgekündigt. Es sollte in neuen Plänen nicht mehr verwendet
76
- werden.
77
- </span>
78
+ <span>{{ msg.feature.obsoleteBanner }}</span>
78
79
  </div>
79
80
 
80
81
  <q-tabs v-model="sub" dense align="left" class="sa-fc__subtabs">
81
- <q-tab name="stamm" label="Stammdaten" />
82
- <q-tab name="i18n" label="Übersetzungen" />
82
+ <q-tab name="stamm" :label="msg.feature.tabBaseData" />
83
+ <q-tab name="i18n" :label="msg.feature.tabTranslations" />
83
84
  </q-tabs>
84
85
 
85
86
  <div v-if="sub === 'stamm'" class="sa-fc__split">
86
87
  <div class="sa-fc__split-col">
87
- <div class="sa-fc__split-head">Einstellungen</div>
88
+ <div class="sa-fc__split-head">{{ msg.feature.settings }}</div>
88
89
  <div class="sa-fc__fields">
89
90
  <div class="sa-fc-field">
90
- <label class="sa-fc-field__cap">Icon (Quasar-Icon-Name)</label>
91
+ <label class="sa-fc-field__cap">{{ msg.feature.iconLabel }}</label>
91
92
  <q-input
92
93
  dense
93
94
  outlined
94
95
  :model-value="iconValue"
95
- placeholder="z. B. directions_car"
96
+ :placeholder="msg.feature.iconPlaceholder"
96
97
  @update:model-value="(v) => onField('icon', String(v ?? ''))"
97
98
  >
98
99
  <template #prepend>
@@ -101,7 +102,7 @@
101
102
  </q-input>
102
103
  </div>
103
104
  <div class="sa-fc-field">
104
- <label class="sa-fc-field__cap">Tier</label>
105
+ <label class="sa-fc-field__cap">{{ msg.feature.tierLabel }}</label>
105
106
  <q-select
106
107
  dense
107
108
  outlined
@@ -117,10 +118,8 @@
117
118
  </div>
118
119
  <div class="sa-fc__split-col">
119
120
  <div class="sa-fc__split-head">
120
- Code Capabilities
121
- <span class="sa-fc__split-count">
122
- {{ capabilities.length }} · read-only
123
- </span>
121
+ {{ msg.feature.codeCapabilities }}
122
+ <span class="sa-fc__split-count">{{ codeCapabilitiesCountLabel }}</span>
124
123
  </div>
125
124
  <DiscoveryCapList
126
125
  :capabilities="capabilities"
@@ -156,6 +155,8 @@ import type {
156
155
  import CatalogEntryTransPanel from './CatalogEntryTransPanel.vue';
157
156
  import DiscoveryCapList from './DiscoveryCapList.vue';
158
157
  import DiscoveryStatusControl from './DiscoveryStatusControl.vue';
158
+ import { formatMessage } from '../../client/i18n/format.js';
159
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
159
160
  import {
160
161
  coverageClass,
161
162
  coveragePct,
@@ -189,6 +190,8 @@ const emit = defineEmits<{
189
190
 
190
191
  const TIER_OPTIONS = ['CORE', 'ADVANCED', 'PRO', 'ENTERPRISE'];
191
192
 
193
+ const msg = useSaMessages('discovery');
194
+
192
195
  const sub = ref<'stamm' | 'i18n'>('stamm');
193
196
 
194
197
  // Local input buffer: once a field has been edited, the draft wins over the
@@ -232,6 +235,22 @@ const deprecatedCapsCount = computed(
232
235
  () => props.capabilities.filter((c) => c.codeStatus === 'deprecated').length,
233
236
  );
234
237
 
238
+ const newCapsLabel = computed(() =>
239
+ formatMessage(msg.value.feature.newCaps, { count: newCapsCount.value }),
240
+ );
241
+ const deprecatedCapsLabel = computed(() =>
242
+ formatMessage(msg.value.feature.deprecatedCaps, { count: deprecatedCapsCount.value }),
243
+ );
244
+ const replacedByLabel = computed(() =>
245
+ formatMessage(msg.value.replacedBy, { key: props.feature.successorKey ?? '' }),
246
+ );
247
+ const replacesLabel = computed(() =>
248
+ formatMessage(msg.value.replaces, { keys: props.feature.replaces.join(', ') }),
249
+ );
250
+ const codeCapabilitiesCountLabel = computed(() =>
251
+ formatMessage(msg.value.feature.codeCapabilitiesCount, { count: props.capabilities.length }),
252
+ );
253
+
235
254
  const targetLocales = computed(() =>
236
255
  props.activeLocales.filter((l) => l !== DISCOVERY_DEFAULT_LOCALE),
237
256
  );
@@ -1,10 +1,9 @@
1
1
  <template>
2
2
  <header class="sa-discovery__head">
3
3
  <div>
4
- <h1 class="sa-discovery__title">Discovery</h1>
4
+ <h1 class="sa-discovery__title">{{ msg.title }}</h1>
5
5
  <p class="sa-discovery__sub">
6
- Capabilities, die der Code <b>aktuell</b> implementiert. Discovery ist Ist-Zustand —
7
- die Freigabe für Produkt &amp; Marketing erfolgt im Review.
6
+ {{ msg.subtitleLead }} <b>{{ msg.subtitleEmphasis }}</b> {{ msg.subtitleTail }}
8
7
  </p>
9
8
  </div>
10
9
  <div class="sa-discovery__head-actions">
@@ -12,7 +11,7 @@
12
11
  unelevated
13
12
  color="primary"
14
13
  icon="bolt"
15
- label="Discovery neu ausführen"
14
+ :label="msg.runDiscovery"
16
15
  :loading="loading"
17
16
  @click="emit('runDiscovery')"
18
17
  />
@@ -21,6 +20,8 @@
21
20
  </template>
22
21
 
23
22
  <script setup lang="ts">
23
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
24
+
24
25
  defineProps<{
25
26
  loading: boolean;
26
27
  }>();
@@ -28,4 +29,6 @@ defineProps<{
28
29
  const emit = defineEmits<{
29
30
  (e: 'runDiscovery'): void;
30
31
  }>();
32
+
33
+ const msg = useSaMessages('discovery');
31
34
  </script>