@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
@@ -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>
@@ -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',