@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.
- package/README.md +17 -0
- package/dist/{chunk-EH7TOD4H.js → chunk-A3W2N26K.js} +73 -77
- package/dist/{chunk-3YP2ZAOD.js → chunk-QZOSDCNP.js} +36 -1
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3279 -76
- package/dist/client/index.d.cts +31 -5
- package/dist/client/index.d.ts +31 -5
- package/dist/client/index.js +23 -5
- package/dist/index.cjs +3709 -465
- package/dist/index.d.cts +37 -54
- package/dist/index.d.ts +37 -54
- package/dist/index.js +347 -311
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3185 -4
- package/dist/quasar/index.d.cts +11 -2
- package/dist/quasar/index.d.ts +11 -2
- package/dist/quasar/index.js +8 -3
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/{ui-notify-COUzXEQ4.d.ts → use-super-admin-i18n-B3v3-SWZ.d.ts} +35 -3
- package/dist/{ui-notify-D_eAAwRh.d.cts → use-super-admin-i18n-DMktRVEt.d.cts} +35 -3
- package/package.json +2 -2
- package/src/client/action-registry.ts +4 -4
- package/src/client/batch-column-fetcher.ts +4 -4
- package/src/client/boot-loader.ts +3 -3
- package/src/client/i18n/currency.ts +42 -0
- package/src/client/i18n/define.ts +59 -0
- package/src/client/i18n/format.ts +12 -0
- package/src/client/i18n/index.ts +14 -0
- package/src/client/i18n/locale.ts +15 -0
- package/src/client/i18n/messages/audit.ts +42 -0
- package/src/client/i18n/messages/bundles.ts +459 -0
- package/src/client/i18n/messages/common.ts +147 -0
- package/src/client/i18n/messages/dashboard.ts +16 -0
- package/src/client/i18n/messages/discovery.ts +229 -0
- package/src/client/i18n/messages/email.ts +125 -0
- package/src/client/i18n/messages/marketing.ts +270 -0
- package/src/client/i18n/messages/nav.ts +72 -0
- package/src/client/i18n/messages/pilots.ts +192 -0
- package/src/client/i18n/messages/plan-detail.ts +266 -0
- package/src/client/i18n/messages/plan-editor.ts +177 -0
- package/src/client/i18n/messages/plan-versions.ts +251 -0
- package/src/client/i18n/messages/plans.ts +503 -0
- package/src/client/i18n/messages/promos.ts +202 -0
- package/src/client/i18n/messages/shell.ts +145 -0
- package/src/client/i18n/messages/tenants.ts +104 -0
- package/src/client/i18n/messages/users.ts +78 -0
- package/src/client/i18n/messages.ts +84 -0
- package/src/client/index.ts +2 -0
- package/src/client/manifest-loader.ts +4 -4
- package/src/client/nav-builder.ts +44 -51
- package/src/client/plan-versions-catalog.ts +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- package/src/components/MarketingPromotionsTab.vue +112 -59
- package/src/components/MfaPromptDialog.vue +11 -11
- package/src/components/TenantActionConfirmDialog.vue +19 -17
- package/src/components/VersionDiffPreview.vue +72 -43
- package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
- package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
- package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
- package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
- package/src/components/bundle-editor/bundle-version-status.ts +29 -29
- package/src/components/dialogs/PilotCreateDialog.vue +58 -63
- package/src/components/dialogs/PilotEditDialog.vue +40 -34
- package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
- package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
- package/src/components/dialogs/types.ts +3 -13
- package/src/components/plan/PlanCycleToggle.vue +20 -9
- package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
- package/src/components/plan-detail/PlanAuditLog.vue +11 -6
- package/src/components/plan-detail/PlanDetail.vue +21 -23
- package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
- package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
- package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
- package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
- package/src/components/plan-list/PlanList.vue +99 -57
- package/src/components/plan-matrix/PlanMatrix.vue +85 -38
- package/src/components/plan-review/PlanReview.vue +105 -80
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
- package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
- package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
- package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
- package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
- package/src/index.ts +1 -1
- package/src/pages-standard/AdminLayout.vue +42 -17
- package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
- package/src/pages-standard/AuditPage.vue +58 -19
- package/src/pages-standard/BundlesPage.vue +29 -18
- package/src/pages-standard/DashboardPage.vue +29 -15
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +92 -59
- package/src/pages-standard/MarketingCatalogPage.vue +35 -16
- package/src/pages-standard/PilotsPage.vue +57 -41
- package/src/pages-standard/PlanVersionsPage.vue +7 -3
- package/src/pages-standard/PlansPage.vue +89 -57
- package/src/pages-standard/PlatformEmailPage.vue +115 -50
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +46 -35
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +16 -13
- package/src/pages-standard/SuperAdminSetupWizard.vue +51 -47
- package/src/pages-standard/TenantDetailPage.vue +50 -36
- package/src/pages-standard/TenantsPage.vue +54 -41
- package/src/pages-standard/UsersPage.vue +66 -42
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
- package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
- package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
- package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
- package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
- package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
- package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
- package/src/pages-standard/plan-versions/PlanVersionHeader.vue +41 -25
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +45 -26
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +7 -5
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +39 -16
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +24 -12
- package/src/pages-standard/plan-versions/format.ts +33 -17
- package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
- package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
- package/src/pages-standard/tenants/format.ts +3 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +72 -37
- package/src/pages-tenant/PlanChangeWizard.vue +8 -4
- package/src/pages-tenant/TenantPlanSection.vue +17 -13
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +8 -6
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +9 -10
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/quasar/create-super-admin-app.ts +19 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/vue/project-page-host.ts +5 -3
- package/src/vue/use-audit-entries.ts +3 -3
- package/src/vue/use-bulk-publish.ts +6 -4
- package/src/vue/use-bundle-versions-map.ts +6 -4
- package/src/vue/use-bundles.ts +13 -13
- package/src/vue/use-catalog-entries.ts +13 -10
- package/src/vue/use-discovery.ts +8 -5
- package/src/vue/use-entitlement.ts +3 -3
- package/src/vue/use-live-plan-versions.ts +4 -4
- package/src/vue/use-marketing-projections.ts +9 -6
- package/src/vue/use-nav.ts +8 -2
- package/src/vue/use-plan-editor.ts +2 -2
- package/src/vue/use-plan-versions.ts +3 -3
- package/src/vue/use-plans.ts +12 -12
- package/src/vue/use-platform-tenant-actions.ts +9 -6
- package/src/vue/use-promotions.ts +9 -10
- package/src/vue/use-subscription-draft.ts +2 -3
- package/src/vue/use-super-admin-context.ts +3 -3
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/vue/use-tenant-action-flow.ts +11 -6
- package/src/vue/use-tenant-manifest.ts +1 -3
- package/src/vue/use-tenant-subscription-bundles.ts +4 -4
- package/src/vue/use-tenants.ts +3 -3
- package/dist/action-registry-CQ2wL3R-.d.cts +0 -85
- package/dist/action-registry-CQ2wL3R-.d.ts +0 -85
- package/dist/chunk-LVGUSD3T.js +0 -26
- package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
- package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
- package/src/pages-standard/BusinessTypesPage.vue +0 -662
- package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
- package/src/vue/use-business-types.ts +0 -269
package/dist/quasar/index.d.cts
CHANGED
|
@@ -2,8 +2,8 @@ import { Component, App } from 'vue';
|
|
|
2
2
|
import { QuasarPluginOptions } from 'quasar';
|
|
3
3
|
import { Pinia } from 'pinia';
|
|
4
4
|
import { RouteRecordRaw, RouterHistory, Router } from 'vue-router';
|
|
5
|
-
import { H as HttpClient } from '../
|
|
6
|
-
import { S as SuperAdminGuardOptions, a as SuperAdminBrand, b as SuperAdminEndpoints, E as ExtensionsMap, A as ActionsMap, c as SuperAdminLoginAdapter, U as UiNotify, I as InstallPlugin } from '../
|
|
5
|
+
import { H as HttpClient } from '../messages-7b0P8W75.cjs';
|
|
6
|
+
import { S as SuperAdminGuardOptions, a as SuperAdminBrand, b as SuperAdminEndpoints, E as ExtensionsMap, A as ActionsMap, c as SuperAdminLoginAdapter, U as UiNotify, I as InstallPlugin, d as SuperAdminI18nOptions, e as SuperAdminI18n } from '../use-super-admin-i18n-DMktRVEt.cjs';
|
|
7
7
|
import '@saasicat/types';
|
|
8
8
|
|
|
9
9
|
interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
|
|
@@ -59,11 +59,20 @@ interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
|
|
|
59
59
|
* including for the setup wizard. Consumed via `useSuperAdminHttp()`.
|
|
60
60
|
*/
|
|
61
61
|
http?: HttpClient;
|
|
62
|
+
/**
|
|
63
|
+
* Optional: UI locale + string overrides. Default is German. The created
|
|
64
|
+
* context is returned on the handle (`handle.i18n`) — set
|
|
65
|
+
* `handle.i18n.locale.value = 'en'` (or pass a `Ref` here) to switch at
|
|
66
|
+
* runtime. Consumed via `useSuperAdminI18n()` / `useSaMessages()`.
|
|
67
|
+
*/
|
|
68
|
+
i18n?: SuperAdminI18nOptions;
|
|
62
69
|
}
|
|
63
70
|
interface SuperAdminAppHandle {
|
|
64
71
|
app: App;
|
|
65
72
|
router: Router;
|
|
66
73
|
pinia: Pinia;
|
|
74
|
+
/** i18n context of the shell — switch locale via `i18n.locale.value`. */
|
|
75
|
+
i18n: SuperAdminI18n;
|
|
67
76
|
/** Mounts the app on a selector. Returns the root component instance. */
|
|
68
77
|
mount: (selector: string | Element) => ReturnType<App['mount']>;
|
|
69
78
|
}
|
package/dist/quasar/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { Component, App } from 'vue';
|
|
|
2
2
|
import { QuasarPluginOptions } from 'quasar';
|
|
3
3
|
import { Pinia } from 'pinia';
|
|
4
4
|
import { RouteRecordRaw, RouterHistory, Router } from 'vue-router';
|
|
5
|
-
import { H as HttpClient } from '../
|
|
6
|
-
import { S as SuperAdminGuardOptions, a as SuperAdminBrand, b as SuperAdminEndpoints, E as ExtensionsMap, A as ActionsMap, c as SuperAdminLoginAdapter, U as UiNotify, I as InstallPlugin } from '../
|
|
5
|
+
import { H as HttpClient } from '../messages-7b0P8W75.js';
|
|
6
|
+
import { S as SuperAdminGuardOptions, a as SuperAdminBrand, b as SuperAdminEndpoints, E as ExtensionsMap, A as ActionsMap, c as SuperAdminLoginAdapter, U as UiNotify, I as InstallPlugin, d as SuperAdminI18nOptions, e as SuperAdminI18n } from '../use-super-admin-i18n-B3v3-SWZ.js';
|
|
7
7
|
import '@saasicat/types';
|
|
8
8
|
|
|
9
9
|
interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
|
|
@@ -59,11 +59,20 @@ interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
|
|
|
59
59
|
* including for the setup wizard. Consumed via `useSuperAdminHttp()`.
|
|
60
60
|
*/
|
|
61
61
|
http?: HttpClient;
|
|
62
|
+
/**
|
|
63
|
+
* Optional: UI locale + string overrides. Default is German. The created
|
|
64
|
+
* context is returned on the handle (`handle.i18n`) — set
|
|
65
|
+
* `handle.i18n.locale.value = 'en'` (or pass a `Ref` here) to switch at
|
|
66
|
+
* runtime. Consumed via `useSuperAdminI18n()` / `useSaMessages()`.
|
|
67
|
+
*/
|
|
68
|
+
i18n?: SuperAdminI18nOptions;
|
|
62
69
|
}
|
|
63
70
|
interface SuperAdminAppHandle {
|
|
64
71
|
app: App;
|
|
65
72
|
router: Router;
|
|
66
73
|
pinia: Pinia;
|
|
74
|
+
/** i18n context of the shell — switch locale via `i18n.locale.value`. */
|
|
75
|
+
i18n: SuperAdminI18n;
|
|
67
76
|
/** Mounts the app on a selector. Returns the root component instance. */
|
|
68
77
|
mount: (selector: string | Element) => ReturnType<App['mount']>;
|
|
69
78
|
}
|
package/dist/quasar/index.js
CHANGED
|
@@ -4,14 +4,16 @@ import {
|
|
|
4
4
|
SUPER_ADMIN_ENDPOINTS_KEY,
|
|
5
5
|
SUPER_ADMIN_EXTENSIONS_KEY,
|
|
6
6
|
SUPER_ADMIN_HTTP_KEY,
|
|
7
|
+
SUPER_ADMIN_I18N_KEY,
|
|
7
8
|
SUPER_ADMIN_LOGIN_ADAPTER_KEY,
|
|
8
9
|
SUPER_ADMIN_MANIFEST_KEY,
|
|
9
10
|
SUPER_ADMIN_NOTIFY_KEY,
|
|
10
|
-
buildNavigationGuard
|
|
11
|
-
|
|
11
|
+
buildNavigationGuard,
|
|
12
|
+
createSuperAdminI18n
|
|
13
|
+
} from "../chunk-QZOSDCNP.js";
|
|
12
14
|
import {
|
|
13
15
|
defaultHttpClient
|
|
14
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-X5SQ5HMB.js";
|
|
15
17
|
|
|
16
18
|
// src/quasar/create-super-admin-app.ts
|
|
17
19
|
import { createApp } from "vue";
|
|
@@ -62,7 +64,9 @@ function createSuperAdminApp(options) {
|
|
|
62
64
|
publicBootEndpoint: options.endpoints.publicBootEndpoint ?? `${options.endpoints.apiBase}/boot`,
|
|
63
65
|
manifestEndpoint: options.endpoints.manifestEndpoint ?? `${options.endpoints.apiBase}/manifest`
|
|
64
66
|
};
|
|
67
|
+
const i18n = createSuperAdminI18n(options.i18n);
|
|
65
68
|
app.provide(SUPER_ADMIN_BRAND_KEY, { tag: "SuperAdmin", ...options.brand });
|
|
69
|
+
app.provide(SUPER_ADMIN_I18N_KEY, i18n);
|
|
66
70
|
app.provide(SUPER_ADMIN_ENDPOINTS_KEY, endpoints);
|
|
67
71
|
app.provide(SUPER_ADMIN_EXTENSIONS_KEY, options.extensions ?? {});
|
|
68
72
|
app.provide(SUPER_ADMIN_ACTIONS_KEY, options.actions ?? {});
|
|
@@ -81,6 +85,7 @@ function createSuperAdminApp(options) {
|
|
|
81
85
|
app,
|
|
82
86
|
router,
|
|
83
87
|
pinia,
|
|
88
|
+
i18n,
|
|
84
89
|
mount: (selector) => app.mount(selector)
|
|
85
90
|
};
|
|
86
91
|
}
|
|
@@ -60,7 +60,7 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
60
60
|
const dashboardPath = config.dashboardPath ?? "/admin/";
|
|
61
61
|
test.describe(`${config.appName}: SuperAdmin-Pages Smoke`, () => {
|
|
62
62
|
for (const pageDef of config.pages) {
|
|
63
|
-
test(`${pageDef.name}
|
|
63
|
+
test(`${pageDef.name} renders without errors`, async ({ page }) => {
|
|
64
64
|
const errors = attachListeners(
|
|
65
65
|
page,
|
|
66
66
|
config.consoleErrorAllowlist,
|
|
@@ -78,18 +78,18 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
78
78
|
timeout: 5e3
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
-
expect(errors.pageErrors, `pageerror
|
|
81
|
+
expect(errors.pageErrors, `pageerror events on ${pageDef.path}`).toHaveLength(0);
|
|
82
82
|
expect(
|
|
83
83
|
errors.consoleErrors,
|
|
84
|
-
`console.error
|
|
84
|
+
`console.error events on ${pageDef.path}`
|
|
85
85
|
).toHaveLength(0);
|
|
86
86
|
expect(
|
|
87
87
|
errors.networkErrors,
|
|
88
|
-
`HTTP
|
|
88
|
+
`HTTP errors on /api/ during ${pageDef.path}`
|
|
89
89
|
).toHaveLength(0);
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
test("Dashboard
|
|
92
|
+
test("Dashboard shows the expected KPIs, distributions and shortcuts", async ({ page }) => {
|
|
93
93
|
attachListeners(page, config.consoleErrorAllowlist);
|
|
94
94
|
await loginIfNeeded(page, config);
|
|
95
95
|
const origin = new URL(config.loginUrl).origin;
|
|
@@ -100,7 +100,9 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
100
100
|
const kpiLabels = await page.locator(".sa-kpi__label").allTextContents();
|
|
101
101
|
const normalizedKpis = kpiLabels.map((s) => s.trim());
|
|
102
102
|
for (const expected of config.expectedKpiLabels) {
|
|
103
|
-
expect(normalizedKpis, `KPI '${expected}'
|
|
103
|
+
expect(normalizedKpis, `KPI '${expected}' missing on the dashboard`).toContain(
|
|
104
|
+
expected
|
|
105
|
+
);
|
|
104
106
|
}
|
|
105
107
|
if (config.expectedDistributionTitles?.length) {
|
|
106
108
|
const distTitles = await page.locator(".sa-dashboard__row-head h2").allTextContents();
|
|
@@ -108,21 +110,22 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
108
110
|
for (const expected of config.expectedDistributionTitles) {
|
|
109
111
|
expect(
|
|
110
112
|
normalizedDist,
|
|
111
|
-
`Distribution '${expected}'
|
|
113
|
+
`Distribution '${expected}' missing on the dashboard`
|
|
112
114
|
).toContain(expected);
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
const shortcutTitles = await page.locator(".sa-dashboard__shortcut-title").allTextContents();
|
|
116
118
|
const normalizedShortcuts = shortcutTitles.map((s) => s.trim());
|
|
117
119
|
for (const expected of config.expectedShortcutTitles) {
|
|
118
|
-
expect(
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
expect(
|
|
121
|
+
normalizedShortcuts,
|
|
122
|
+
`Shortcut '${expected}' missing on the dashboard`
|
|
123
|
+
).toContain(expected);
|
|
121
124
|
}
|
|
122
125
|
for (const forbidden of config.forbiddenShortcutTitles ?? []) {
|
|
123
126
|
expect(
|
|
124
127
|
normalizedShortcuts,
|
|
125
|
-
`Shortcut '${forbidden}'
|
|
128
|
+
`Shortcut '${forbidden}' must not be on the dashboard`
|
|
126
129
|
).not.toContain(forbidden);
|
|
127
130
|
}
|
|
128
131
|
});
|
|
@@ -36,7 +36,7 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
36
36
|
const dashboardPath = config.dashboardPath ?? "/admin/";
|
|
37
37
|
test.describe(`${config.appName}: SuperAdmin-Pages Smoke`, () => {
|
|
38
38
|
for (const pageDef of config.pages) {
|
|
39
|
-
test(`${pageDef.name}
|
|
39
|
+
test(`${pageDef.name} renders without errors`, async ({ page }) => {
|
|
40
40
|
const errors = attachListeners(
|
|
41
41
|
page,
|
|
42
42
|
config.consoleErrorAllowlist,
|
|
@@ -54,18 +54,18 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
54
54
|
timeout: 5e3
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
-
expect(errors.pageErrors, `pageerror
|
|
57
|
+
expect(errors.pageErrors, `pageerror events on ${pageDef.path}`).toHaveLength(0);
|
|
58
58
|
expect(
|
|
59
59
|
errors.consoleErrors,
|
|
60
|
-
`console.error
|
|
60
|
+
`console.error events on ${pageDef.path}`
|
|
61
61
|
).toHaveLength(0);
|
|
62
62
|
expect(
|
|
63
63
|
errors.networkErrors,
|
|
64
|
-
`HTTP
|
|
64
|
+
`HTTP errors on /api/ during ${pageDef.path}`
|
|
65
65
|
).toHaveLength(0);
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
-
test("Dashboard
|
|
68
|
+
test("Dashboard shows the expected KPIs, distributions and shortcuts", async ({ page }) => {
|
|
69
69
|
attachListeners(page, config.consoleErrorAllowlist);
|
|
70
70
|
await loginIfNeeded(page, config);
|
|
71
71
|
const origin = new URL(config.loginUrl).origin;
|
|
@@ -76,7 +76,9 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
76
76
|
const kpiLabels = await page.locator(".sa-kpi__label").allTextContents();
|
|
77
77
|
const normalizedKpis = kpiLabels.map((s) => s.trim());
|
|
78
78
|
for (const expected of config.expectedKpiLabels) {
|
|
79
|
-
expect(normalizedKpis, `KPI '${expected}'
|
|
79
|
+
expect(normalizedKpis, `KPI '${expected}' missing on the dashboard`).toContain(
|
|
80
|
+
expected
|
|
81
|
+
);
|
|
80
82
|
}
|
|
81
83
|
if (config.expectedDistributionTitles?.length) {
|
|
82
84
|
const distTitles = await page.locator(".sa-dashboard__row-head h2").allTextContents();
|
|
@@ -84,21 +86,22 @@ function runAdminPagesSuite(test, expect, config) {
|
|
|
84
86
|
for (const expected of config.expectedDistributionTitles) {
|
|
85
87
|
expect(
|
|
86
88
|
normalizedDist,
|
|
87
|
-
`Distribution '${expected}'
|
|
89
|
+
`Distribution '${expected}' missing on the dashboard`
|
|
88
90
|
).toContain(expected);
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
const shortcutTitles = await page.locator(".sa-dashboard__shortcut-title").allTextContents();
|
|
92
94
|
const normalizedShortcuts = shortcutTitles.map((s) => s.trim());
|
|
93
95
|
for (const expected of config.expectedShortcutTitles) {
|
|
94
|
-
expect(
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
expect(
|
|
97
|
+
normalizedShortcuts,
|
|
98
|
+
`Shortcut '${expected}' missing on the dashboard`
|
|
99
|
+
).toContain(expected);
|
|
97
100
|
}
|
|
98
101
|
for (const forbidden of config.forbiddenShortcutTitles ?? []) {
|
|
99
102
|
expect(
|
|
100
103
|
normalizedShortcuts,
|
|
101
|
-
`Shortcut '${forbidden}'
|
|
104
|
+
`Shortcut '${forbidden}' must not be on the dashboard`
|
|
102
105
|
).not.toContain(forbidden);
|
|
103
106
|
}
|
|
104
107
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Component, App, InjectionKey } from 'vue';
|
|
1
|
+
import { Component, App, InjectionKey, Ref, ComputedRef } from 'vue';
|
|
2
2
|
import { NavigationGuardWithThis } from 'vue-router';
|
|
3
3
|
import { ActionKey, ComponentKey, AdminManifest } from '@saasicat/types';
|
|
4
|
-
import { A as ActionHandler, H as HttpClient } from './
|
|
4
|
+
import { A as ActionHandler, H as HttpClient, S as SaLocale, a as SaMessages, b as SaMessagesOverrides } from './messages-7b0P8W75.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* App-specific branding data that the platform `AdminLayout` and other
|
|
@@ -175,4 +175,36 @@ type UiNotify = (kind: UiNotifyKind, message: string, options?: UiNotifyOptions)
|
|
|
175
175
|
/** Vue inject key for the notify port (see `Symbol.for` note in super-admin-context.ts). */
|
|
176
176
|
declare const SUPER_ADMIN_NOTIFY_KEY: InjectionKey<UiNotify>;
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
interface SuperAdminI18n {
|
|
179
|
+
/** Active UI locale — mutable; switching re-renders all catalog texts. */
|
|
180
|
+
locale: Ref<SaLocale>;
|
|
181
|
+
/** Resolved catalog for the active locale (platform + app overrides). */
|
|
182
|
+
messages: ComputedRef<SaMessages>;
|
|
183
|
+
/** BCP-47 tag for `Intl`/`toLocaleString` formatting in the active locale. */
|
|
184
|
+
intlLocale: ComputedRef<string>;
|
|
185
|
+
}
|
|
186
|
+
interface SuperAdminI18nOptions {
|
|
187
|
+
/**
|
|
188
|
+
* Initial UI locale, default `'de'`. Pass a `Ref` when the app wants to
|
|
189
|
+
* keep control (e.g. a user-profile setting or a header switcher).
|
|
190
|
+
*/
|
|
191
|
+
locale?: SaLocale | Ref<SaLocale>;
|
|
192
|
+
/** Per-locale string overrides layered over the platform catalog. */
|
|
193
|
+
overrides?: Partial<Record<SaLocale, SaMessagesOverrides>>;
|
|
194
|
+
}
|
|
195
|
+
declare const SUPER_ADMIN_I18N_KEY: InjectionKey<SuperAdminI18n>;
|
|
196
|
+
declare function createSuperAdminI18n(options?: SuperAdminI18nOptions): SuperAdminI18n;
|
|
197
|
+
/**
|
|
198
|
+
* Returns the i18n context provided by `createSuperAdminApp()`. Outside a
|
|
199
|
+
* shell (isolated mounts, tests) a shared German default instance is returned
|
|
200
|
+
* so callers never have to guard for a missing context.
|
|
201
|
+
*/
|
|
202
|
+
declare function useSuperAdminI18n(): SuperAdminI18n;
|
|
203
|
+
/**
|
|
204
|
+
* Focused accessor for one catalog namespace:
|
|
205
|
+
* `const msg = useSaMessages('plans')` → `msg.value.title` /
|
|
206
|
+
* template `msg.title`.
|
|
207
|
+
*/
|
|
208
|
+
declare function useSaMessages<K extends keyof SaMessages>(namespace: K): ComputedRef<SaMessages[K]>;
|
|
209
|
+
|
|
210
|
+
export { type ActionsMap as A, type ExtensionsMap as E, type InstallPlugin as I, type SuperAdminGuardOptions as S, type UiNotify as U, type SuperAdminBrand as a, type SuperAdminEndpoints as b, type SuperAdminLoginAdapter as c, type SuperAdminI18nOptions as d, type SuperAdminI18n as e, type ExtensionLoader as f, SUPER_ADMIN_ACTIONS_KEY as g, SUPER_ADMIN_BRAND_KEY as h, SUPER_ADMIN_ENDPOINTS_KEY as i, SUPER_ADMIN_EXTENSIONS_KEY as j, SUPER_ADMIN_HTTP_KEY as k, SUPER_ADMIN_I18N_KEY as l, SUPER_ADMIN_LOGIN_ADAPTER_KEY as m, SUPER_ADMIN_MANIFEST_KEY as n, SUPER_ADMIN_NOTIFY_KEY as o, type SuperAdminAuthGuardOptions as p, type SuperAdminLoginResult as q, type SuperAdminManifestGuardOptions as r, type UiNotifyKind as s, type UiNotifyOptions as t, buildNavigationGuard as u, createSuperAdminI18n as v, useSaMessages as w, useSuperAdminI18n as x };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Component, App, InjectionKey } from 'vue';
|
|
1
|
+
import { Component, App, InjectionKey, Ref, ComputedRef } from 'vue';
|
|
2
2
|
import { NavigationGuardWithThis } from 'vue-router';
|
|
3
3
|
import { ActionKey, ComponentKey, AdminManifest } from '@saasicat/types';
|
|
4
|
-
import { A as ActionHandler, H as HttpClient } from './
|
|
4
|
+
import { A as ActionHandler, H as HttpClient, S as SaLocale, a as SaMessages, b as SaMessagesOverrides } from './messages-7b0P8W75.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* App-specific branding data that the platform `AdminLayout` and other
|
|
@@ -175,4 +175,36 @@ type UiNotify = (kind: UiNotifyKind, message: string, options?: UiNotifyOptions)
|
|
|
175
175
|
/** Vue inject key for the notify port (see `Symbol.for` note in super-admin-context.ts). */
|
|
176
176
|
declare const SUPER_ADMIN_NOTIFY_KEY: InjectionKey<UiNotify>;
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
interface SuperAdminI18n {
|
|
179
|
+
/** Active UI locale — mutable; switching re-renders all catalog texts. */
|
|
180
|
+
locale: Ref<SaLocale>;
|
|
181
|
+
/** Resolved catalog for the active locale (platform + app overrides). */
|
|
182
|
+
messages: ComputedRef<SaMessages>;
|
|
183
|
+
/** BCP-47 tag for `Intl`/`toLocaleString` formatting in the active locale. */
|
|
184
|
+
intlLocale: ComputedRef<string>;
|
|
185
|
+
}
|
|
186
|
+
interface SuperAdminI18nOptions {
|
|
187
|
+
/**
|
|
188
|
+
* Initial UI locale, default `'de'`. Pass a `Ref` when the app wants to
|
|
189
|
+
* keep control (e.g. a user-profile setting or a header switcher).
|
|
190
|
+
*/
|
|
191
|
+
locale?: SaLocale | Ref<SaLocale>;
|
|
192
|
+
/** Per-locale string overrides layered over the platform catalog. */
|
|
193
|
+
overrides?: Partial<Record<SaLocale, SaMessagesOverrides>>;
|
|
194
|
+
}
|
|
195
|
+
declare const SUPER_ADMIN_I18N_KEY: InjectionKey<SuperAdminI18n>;
|
|
196
|
+
declare function createSuperAdminI18n(options?: SuperAdminI18nOptions): SuperAdminI18n;
|
|
197
|
+
/**
|
|
198
|
+
* Returns the i18n context provided by `createSuperAdminApp()`. Outside a
|
|
199
|
+
* shell (isolated mounts, tests) a shared German default instance is returned
|
|
200
|
+
* so callers never have to guard for a missing context.
|
|
201
|
+
*/
|
|
202
|
+
declare function useSuperAdminI18n(): SuperAdminI18n;
|
|
203
|
+
/**
|
|
204
|
+
* Focused accessor for one catalog namespace:
|
|
205
|
+
* `const msg = useSaMessages('plans')` → `msg.value.title` /
|
|
206
|
+
* template `msg.title`.
|
|
207
|
+
*/
|
|
208
|
+
declare function useSaMessages<K extends keyof SaMessages>(namespace: K): ComputedRef<SaMessages[K]>;
|
|
209
|
+
|
|
210
|
+
export { type ActionsMap as A, type ExtensionsMap as E, type InstallPlugin as I, type SuperAdminGuardOptions as S, type UiNotify as U, type SuperAdminBrand as a, type SuperAdminEndpoints as b, type SuperAdminLoginAdapter as c, type SuperAdminI18nOptions as d, type SuperAdminI18n as e, type ExtensionLoader as f, SUPER_ADMIN_ACTIONS_KEY as g, SUPER_ADMIN_BRAND_KEY as h, SUPER_ADMIN_ENDPOINTS_KEY as i, SUPER_ADMIN_EXTENSIONS_KEY as j, SUPER_ADMIN_HTTP_KEY as k, SUPER_ADMIN_I18N_KEY as l, SUPER_ADMIN_LOGIN_ADAPTER_KEY as m, SUPER_ADMIN_MANIFEST_KEY as n, SUPER_ADMIN_NOTIFY_KEY as o, type SuperAdminAuthGuardOptions as p, type SuperAdminLoginResult as q, type SuperAdminManifestGuardOptions as r, type UiNotifyKind as s, type UiNotifyOptions as t, buildNavigationGuard as u, createSuperAdminI18n as v, useSaMessages as w, useSuperAdminI18n as x };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/ui-vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Vue 3 components + composables for the SuperAdmin UI shell. Provides boot loader, manifest loader (ETag cache), nav builder, action registry and standard pages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"src"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@saasicat/types": "^0.
|
|
56
|
+
"@saasicat/types": "^0.7.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"pinia": "^2.0.0 || ^3.0.0",
|
|
@@ -27,8 +27,8 @@ export interface ResolvedAction<TInput = unknown, TResult = unknown> {
|
|
|
27
27
|
export class MissingHandlerError extends Error {
|
|
28
28
|
constructor(actionKey: string) {
|
|
29
29
|
super(
|
|
30
|
-
`
|
|
31
|
-
`
|
|
30
|
+
`No handler registered for actionKey "${actionKey}". ` +
|
|
31
|
+
`The consumer shell must provide \`actions["${actionKey}"]\`.`,
|
|
32
32
|
);
|
|
33
33
|
this.name = 'MissingHandlerError';
|
|
34
34
|
}
|
|
@@ -37,8 +37,8 @@ export class MissingHandlerError extends Error {
|
|
|
37
37
|
export class ActionDefNotInManifestError extends Error {
|
|
38
38
|
constructor(actionKey: string) {
|
|
39
39
|
super(
|
|
40
|
-
`actionKey "${actionKey}"
|
|
41
|
-
`
|
|
40
|
+
`actionKey "${actionKey}" is not declared in the manifest. ` +
|
|
41
|
+
`Register it as a TenantAction in a ManifestContribution.`,
|
|
42
42
|
);
|
|
43
43
|
this.name = 'ActionDefNotInManifestError';
|
|
44
44
|
}
|
|
@@ -128,14 +128,14 @@ export class BatchColumnFetcher {
|
|
|
128
128
|
|
|
129
129
|
private validateBatchEndpoint(col: TenantColumnDef): void {
|
|
130
130
|
if (!col.endpoint || col.endpoint.trim().length === 0) {
|
|
131
|
-
throw new BatchColumnDriftError(col, 'endpoint
|
|
131
|
+
throw new BatchColumnDriftError(col, 'endpoint is empty');
|
|
132
132
|
}
|
|
133
133
|
if (col.endpoint.includes('{slug}') || col.endpoint.includes('{tenantId}')) {
|
|
134
134
|
throw new BatchColumnDriftError(
|
|
135
135
|
col,
|
|
136
|
-
'endpoint
|
|
137
|
-
'
|
|
138
|
-
'
|
|
136
|
+
'endpoint contains per-tenant placeholders ({slug}/{tenantId}). ' +
|
|
137
|
+
'Column endpoints must be batchable — switch the backend ' +
|
|
138
|
+
'to a `?tenantIds=...` parameter.',
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -41,9 +41,9 @@ export class BootLoader {
|
|
|
41
41
|
constructor(options: BootLoaderOptions) {
|
|
42
42
|
if (!options?.endpoint) {
|
|
43
43
|
throw new Error(
|
|
44
|
-
'BootLoader: `endpoint`
|
|
45
|
-
'
|
|
46
|
-
'
|
|
44
|
+
'BootLoader: `endpoint` is required (e.g. "/api/admin/boot" ' +
|
|
45
|
+
'or "/api/v1/admin/boot"). The platform has no default ' +
|
|
46
|
+
'because apps use different globalPrefix conventions.',
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
this.endpoint = options.endpoint;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Currency formatting for the admin UI. Amounts are net euro values; the
|
|
2
|
+
// grouping and decimal separator follow the active UI locale
|
|
3
|
+
// (de → `12.345,60 €`, en → `€12,345.60`).
|
|
4
|
+
|
|
5
|
+
import { DEFAULT_SA_LOCALE, SA_INTL_LOCALES, type SaLocale } from './locale.js';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CURRENCY = 'EUR';
|
|
8
|
+
|
|
9
|
+
const formatters = new Map<string, Intl.NumberFormat>();
|
|
10
|
+
|
|
11
|
+
function formatterFor(locale: SaLocale, currency: string, decimals: number): Intl.NumberFormat {
|
|
12
|
+
const key = `${locale}:${currency}:${decimals}`;
|
|
13
|
+
let formatter = formatters.get(key);
|
|
14
|
+
if (!formatter) {
|
|
15
|
+
formatter = new Intl.NumberFormat(SA_INTL_LOCALES[locale], {
|
|
16
|
+
style: 'currency',
|
|
17
|
+
currency,
|
|
18
|
+
minimumFractionDigits: decimals,
|
|
19
|
+
maximumFractionDigits: decimals,
|
|
20
|
+
});
|
|
21
|
+
formatters.set(key, formatter);
|
|
22
|
+
}
|
|
23
|
+
return formatter;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Formats a net amount for display. Whole amounts drop the decimals (`29 €`
|
|
28
|
+
* rather than `29,00 €`) — the plan and bundle prices the admin UI shows are
|
|
29
|
+
* mostly round numbers, and the zeros are noise in dense tables.
|
|
30
|
+
*
|
|
31
|
+
* Returns an em dash for null/undefined and for values that are not finite
|
|
32
|
+
* numbers, so callers can pass raw API fields straight through.
|
|
33
|
+
*/
|
|
34
|
+
export function formatCurrency(
|
|
35
|
+
amount: number | string | null | undefined,
|
|
36
|
+
locale: SaLocale = DEFAULT_SA_LOCALE,
|
|
37
|
+
currency: string = DEFAULT_CURRENCY,
|
|
38
|
+
): string {
|
|
39
|
+
const value = typeof amount === 'string' ? Number(amount) : amount;
|
|
40
|
+
if (value === null || value === undefined || !Number.isFinite(value)) return '—';
|
|
41
|
+
return formatterFor(locale, currency, Number.isInteger(value) ? 0 : 2).format(value);
|
|
42
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Typed message-catalog helpers. Every namespace is defined once in German —
|
|
2
|
+
// the reference locale that fixes the key structure — and the English variant
|
|
3
|
+
// must mirror that structure exactly, enforced at compile time via
|
|
4
|
+
// `TranslationOf`.
|
|
5
|
+
|
|
6
|
+
import type { SaLocale } from './locale.js';
|
|
7
|
+
|
|
8
|
+
/** Nested string map — the shape of every message namespace. */
|
|
9
|
+
export interface MessageTree {
|
|
10
|
+
readonly [key: string]: string | MessageTree;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Maps a reference namespace to "same keys, any string values". */
|
|
14
|
+
export type TranslationOf<T> = {
|
|
15
|
+
readonly [K in keyof T]: T[K] extends string ? string : TranslationOf<T[K]>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Deep partial of a namespace — the shape consumers pass as overrides. */
|
|
19
|
+
export type PartialMessages<T> = {
|
|
20
|
+
readonly [K in keyof T]?: T[K] extends string ? string : PartialMessages<T[K]>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Couples the German reference catalog with its English translation. Missing
|
|
25
|
+
* or extra keys in the English object are compile errors.
|
|
26
|
+
*/
|
|
27
|
+
export function defineMessages<T extends MessageTree>(
|
|
28
|
+
de: T,
|
|
29
|
+
en: TranslationOf<T>,
|
|
30
|
+
): Record<SaLocale, T> {
|
|
31
|
+
// `TranslationOf<T>` and `T` are structurally identical for string-valued
|
|
32
|
+
// trees; the cast only erases the mapped-type wrapper.
|
|
33
|
+
return { de, en: en as T };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Recursively overlays `overrides` onto `base` without mutating either side.
|
|
38
|
+
* Only string leaves are replaced; keys not present in `base` are ignored —
|
|
39
|
+
* the catalog structure is fixed by the platform.
|
|
40
|
+
*/
|
|
41
|
+
export function mergeMessages<T extends MessageTree>(
|
|
42
|
+
base: T,
|
|
43
|
+
overrides: PartialMessages<T> | undefined,
|
|
44
|
+
): T {
|
|
45
|
+
if (!overrides) return base;
|
|
46
|
+
const result: Record<string, string | MessageTree> = {};
|
|
47
|
+
for (const [key, baseValue] of Object.entries(base)) {
|
|
48
|
+
const override = (overrides as Record<string, unknown>)[key];
|
|
49
|
+
if (typeof baseValue === 'string') {
|
|
50
|
+
result[key] = typeof override === 'string' ? override : baseValue;
|
|
51
|
+
} else {
|
|
52
|
+
result[key] = mergeMessages(
|
|
53
|
+
baseValue,
|
|
54
|
+
override as PartialMessages<MessageTree> | undefined,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result as T;
|
|
59
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Placeholder interpolation for catalog messages: `{name}` is replaced with
|
|
2
|
+
// `params.name`. Unknown placeholders stay verbatim so a missing param is
|
|
3
|
+
// visible in the UI instead of silently disappearing.
|
|
4
|
+
|
|
5
|
+
export type MessageParams = Record<string, string | number>;
|
|
6
|
+
|
|
7
|
+
export function formatMessage(template: string, params: MessageParams): string {
|
|
8
|
+
return template.replace(/\{(\w+)\}/g, (match, name: string) => {
|
|
9
|
+
const value = params[name];
|
|
10
|
+
return value === undefined ? match : String(value);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Framework-free i18n core: locale registry, typed catalogs, interpolation.
|
|
2
|
+
// The reactive Vue binding lives in `src/vue/use-super-admin-i18n.ts`.
|
|
3
|
+
|
|
4
|
+
export * from './locale.js';
|
|
5
|
+
export {
|
|
6
|
+
defineMessages,
|
|
7
|
+
mergeMessages,
|
|
8
|
+
type MessageTree,
|
|
9
|
+
type PartialMessages,
|
|
10
|
+
type TranslationOf,
|
|
11
|
+
} from './define.js';
|
|
12
|
+
export * from './format.js';
|
|
13
|
+
export * from './currency.js';
|
|
14
|
+
export * from './messages.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Supported display locales of the SuperAdmin UI. The platform ships a German
|
|
2
|
+
// (reference) and an English catalog; consumers pick the locale per app or per
|
|
3
|
+
// user via `createSuperAdminApp({ i18n })`.
|
|
4
|
+
|
|
5
|
+
export type SaLocale = 'de' | 'en';
|
|
6
|
+
|
|
7
|
+
export const SA_LOCALES: readonly SaLocale[] = ['de', 'en'];
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_SA_LOCALE: SaLocale = 'de';
|
|
10
|
+
|
|
11
|
+
/** BCP-47 tags used for `Intl`/`toLocaleString` formatting per UI locale. */
|
|
12
|
+
export const SA_INTL_LOCALES: Record<SaLocale, string> = {
|
|
13
|
+
de: 'de-DE',
|
|
14
|
+
en: 'en-US',
|
|
15
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineMessages } from '../define.js';
|
|
2
|
+
|
|
3
|
+
export const auditMessages = defineMessages(
|
|
4
|
+
{
|
|
5
|
+
title: 'Audit-Trail',
|
|
6
|
+
subtitle: '{count} Einträge · plattformweit.',
|
|
7
|
+
filterButton: 'Filtern',
|
|
8
|
+
detailActorPrefix: 'Actor:',
|
|
9
|
+
filters: {
|
|
10
|
+
actor: 'Actor (E-Mail)',
|
|
11
|
+
action: 'Action',
|
|
12
|
+
entity: 'Entity',
|
|
13
|
+
since: 'Seit',
|
|
14
|
+
},
|
|
15
|
+
columns: {
|
|
16
|
+
time: 'Zeit',
|
|
17
|
+
actor: 'Actor',
|
|
18
|
+
action: 'Action',
|
|
19
|
+
entity: 'Entity',
|
|
20
|
+
id: 'ID',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: 'Audit trail',
|
|
25
|
+
subtitle: '{count} entries · platform-wide.',
|
|
26
|
+
filterButton: 'Filter',
|
|
27
|
+
detailActorPrefix: 'Actor:',
|
|
28
|
+
filters: {
|
|
29
|
+
actor: 'Actor (email)',
|
|
30
|
+
action: 'Action',
|
|
31
|
+
entity: 'Entity',
|
|
32
|
+
since: 'Since',
|
|
33
|
+
},
|
|
34
|
+
columns: {
|
|
35
|
+
time: 'Time',
|
|
36
|
+
actor: 'Actor',
|
|
37
|
+
action: 'Action',
|
|
38
|
+
entity: 'Entity',
|
|
39
|
+
id: 'ID',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
);
|