@saasicat/ui-vue 0.15.1 → 0.17.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 (43) hide show
  1. package/README.md +13 -10
  2. package/dist/{messages-BIYw32u1.d.ts → catalog-C4Gv2_KM.d.cts} +126 -39
  3. package/dist/{messages-BIYw32u1.d.cts → catalog-C4Gv2_KM.d.ts} +126 -39
  4. package/dist/{chunk-TY2WYKME.js → chunk-247RJPU4.js} +38 -11
  5. package/dist/{chunk-TBLXKSTR.js → chunk-IZGFS5RS.js} +90 -21
  6. package/dist/{chunk-66BUSVC5.js → chunk-XPSD5HKG.js} +30 -4
  7. package/dist/client/index.cjs +122 -22
  8. package/dist/client/index.d.cts +51 -5
  9. package/dist/client/index.d.ts +51 -5
  10. package/dist/client/index.js +14 -2
  11. package/dist/index.cjs +152 -30
  12. package/dist/index.d.cts +5 -5
  13. package/dist/index.d.ts +5 -5
  14. package/dist/index.js +17 -3
  15. package/dist/quasar/index.cjs +110 -14
  16. package/dist/quasar/index.d.cts +2 -2
  17. package/dist/quasar/index.d.ts +2 -2
  18. package/dist/quasar/index.js +2 -2
  19. package/dist/{use-super-admin-i18n-DL_qI1BY.d.cts → use-super-admin-i18n-BdPqeTHl.d.cts} +51 -5
  20. package/dist/{use-super-admin-i18n-CWNu_W3u.d.ts → use-super-admin-i18n-uteRCL9q.d.ts} +51 -5
  21. package/package.json +8 -3
  22. package/src/client/i18n/catalog.ts +120 -0
  23. package/src/client/i18n/currency.ts +7 -2
  24. package/src/client/i18n/define.ts +2 -2
  25. package/src/client/i18n/index.ts +1 -0
  26. package/src/client/i18n/locale.ts +52 -8
  27. package/src/client/i18n/messages/bundles.ts +2 -3
  28. package/src/client/i18n/messages/marketing.ts +2 -2
  29. package/src/client/i18n/messages/nav.ts +3 -3
  30. package/src/client/i18n/messages/shell.ts +2 -0
  31. package/src/client/i18n/messages.ts +7 -4
  32. package/src/client/index.ts +1 -0
  33. package/src/client/login-branding.ts +61 -0
  34. package/src/client/nav-builder.ts +33 -6
  35. package/src/client/types.ts +51 -14
  36. package/src/components/LocaleSwitcher.vue +77 -0
  37. package/src/components/bundle-editor/bundle-version-status.ts +16 -8
  38. package/src/pages-standard/AdminLayout.vue +9 -3
  39. package/src/pages-standard/SuperAdminLoginPage.vue +17 -9
  40. package/src/pages-standard/SuperAdminSetupWizard.vue +7 -0
  41. package/src/pages-standard/discovery-page/discovery-ui.ts +8 -8
  42. package/src/pages-standard/plan-versions/format.ts +2 -2
  43. package/src/vue/use-super-admin-i18n.ts +116 -19
@@ -1,8 +1,7 @@
1
1
  import {
2
- DEFAULT_SA_LOCALE,
3
- SA_INTL_LOCALES,
4
- resolveMessages
5
- } from "./chunk-TBLXKSTR.js";
2
+ createSaCatalog,
3
+ defaultKvStore
4
+ } from "./chunk-IZGFS5RS.js";
6
5
 
7
6
  // src/vue/super-admin-context.ts
8
7
  var SUPER_ADMIN_BRAND_KEY = /* @__PURE__ */ Symbol.for(
@@ -57,23 +56,50 @@ var SUPER_ADMIN_NOTIFY_KEY = /* @__PURE__ */ Symbol.for(
57
56
  );
58
57
 
59
58
  // src/vue/use-super-admin-i18n.ts
60
- import { computed, inject, isRef, ref } from "vue";
59
+ import {
60
+ computed,
61
+ inject,
62
+ isReadonly,
63
+ isRef,
64
+ ref,
65
+ watch
66
+ } from "vue";
67
+ var SA_LOCALE_STORAGE_KEY = "sa:locale";
61
68
  var SUPER_ADMIN_I18N_KEY = /* @__PURE__ */ Symbol.for(
62
69
  "@saasicat/ui-vue/SUPER_ADMIN_I18N"
63
70
  );
64
71
  function createSuperAdminI18n(options = {}) {
65
- const locale = isRef(options.locale) ? options.locale : ref(options.locale ?? DEFAULT_SA_LOCALE);
66
- const messages = computed(
67
- () => resolveMessages(locale.value, options.overrides?.[locale.value])
72
+ const catalog = createSaCatalog(options);
73
+ const locale = isRef(options.locale) ? options.locale : createOwnedLocale(options, catalog);
74
+ const active = computed(
75
+ () => catalog.has(locale.value) ? locale.value : catalog.defaultLocale
68
76
  );
69
- const intlLocale = computed(() => SA_INTL_LOCALES[locale.value]);
70
- return { locale, messages, intlLocale };
77
+ const messages = computed(() => catalog.messagesFor(active.value));
78
+ const intlLocale = computed(() => catalog.intlLocaleFor(active.value));
79
+ const writable = !isReadonly(locale);
80
+ return {
81
+ locale,
82
+ messages,
83
+ intlLocale,
84
+ switcherEnabled: (options.switcher ?? true) && writable && catalog.locales.length > 1,
85
+ availableLocales: catalog.locales
86
+ };
87
+ }
88
+ function createOwnedLocale(options, catalog) {
89
+ const initial = options.locale ?? catalog.defaultLocale;
90
+ if (options.persist === false) return ref(initial);
91
+ const storage = options.storage ?? defaultKvStore();
92
+ const key = `${options.storageKeyPrefix ?? ""}${SA_LOCALE_STORAGE_KEY}`;
93
+ const stored = storage.get(key);
94
+ const locale = ref(stored && catalog.has(stored) ? stored : initial);
95
+ watch(locale, (next) => storage.set(key, next));
96
+ return locale;
71
97
  }
72
98
  var fallbackI18n = null;
73
99
  function useSuperAdminI18n() {
74
100
  const injected = inject(SUPER_ADMIN_I18N_KEY, null);
75
101
  if (injected) return injected;
76
- fallbackI18n ??= createSuperAdminI18n();
102
+ fallbackI18n ??= createSuperAdminI18n({ persist: false });
77
103
  return fallbackI18n;
78
104
  }
79
105
  function useSaMessages(namespace) {
@@ -91,6 +117,7 @@ export {
91
117
  SUPER_ADMIN_HTTP_KEY,
92
118
  buildNavigationGuard,
93
119
  SUPER_ADMIN_NOTIFY_KEY,
120
+ SA_LOCALE_STORAGE_KEY,
94
121
  SUPER_ADMIN_I18N_KEY,
95
122
  createSuperAdminI18n,
96
123
  useSuperAdminI18n,
@@ -1,21 +1,43 @@
1
1
  // src/client/types.ts
2
- function defaultKvStore() {
3
- if (typeof globalThis.localStorage === "undefined") {
4
- return {
5
- get: () => null,
6
- set: () => {
7
- },
8
- remove: () => {
9
- }
10
- };
2
+ var NOOP_KV_STORE = {
3
+ get: () => null,
4
+ set: () => {
5
+ },
6
+ remove: () => {
11
7
  }
12
- const ls = globalThis.localStorage;
8
+ };
9
+ function defaultKvStore() {
10
+ const ls = resolveLocalStorage();
11
+ if (!ls) return NOOP_KV_STORE;
13
12
  return {
14
- get: (k) => ls.getItem(k),
15
- set: (k, v) => ls.setItem(k, v),
16
- remove: (k) => ls.removeItem(k)
13
+ get: (k) => {
14
+ try {
15
+ return ls.getItem(k);
16
+ } catch {
17
+ return null;
18
+ }
19
+ },
20
+ set: (k, v) => {
21
+ try {
22
+ ls.setItem(k, v);
23
+ } catch {
24
+ }
25
+ },
26
+ remove: (k) => {
27
+ try {
28
+ ls.removeItem(k);
29
+ } catch {
30
+ }
31
+ }
17
32
  };
18
33
  }
34
+ function resolveLocalStorage() {
35
+ try {
36
+ return globalThis.localStorage ?? null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
19
41
  function defaultHttpClient() {
20
42
  return (url, init) => fetch(url, init);
21
43
  }
@@ -27,6 +49,16 @@ var SA_INTL_LOCALES = {
27
49
  de: "de-DE",
28
50
  en: "en-US"
29
51
  };
52
+ var SA_LOCALE_LABELS = {
53
+ de: "Deutsch",
54
+ en: "English"
55
+ };
56
+ function isSaBuiltinLocale(value) {
57
+ return typeof value === "string" && SA_LOCALES.includes(value);
58
+ }
59
+ function builtinLocaleOf(locale, fallback = DEFAULT_SA_LOCALE) {
60
+ return isSaBuiltinLocale(locale) ? locale : fallback;
61
+ }
30
62
 
31
63
  // src/client/i18n/define.ts
32
64
  function defineMessages(de, en) {
@@ -64,7 +96,7 @@ function formatterFor(locale, currency, decimals) {
64
96
  const key = `${locale}:${currency}:${decimals}`;
65
97
  let formatter = formatters.get(key);
66
98
  if (!formatter) {
67
- formatter = new Intl.NumberFormat(SA_INTL_LOCALES[locale], {
99
+ formatter = new Intl.NumberFormat(SA_INTL_LOCALES[locale] ?? locale, {
68
100
  style: "currency",
69
101
  currency,
70
102
  minimumFractionDigits: decimals,
@@ -230,7 +262,7 @@ var bundlesMessages = defineMessages(
230
262
  bundleKeyHint: "API-stabil \xB7 wird aus dem Label erzeugt",
231
263
  errorKeyFormat: "Nur A-Z, 0-9, Underscore; muss mit Buchstabe beginnen.",
232
264
  errorKeyExists: "Dieser Bundle-Key existiert bereits.",
233
- descriptionPlaceholder: "z. B. Kampagnen, WhatsApp und Korrespondenz f\xFCr aktive Vereine.",
265
+ descriptionPlaceholder: "z. B. Kampagnen, WhatsApp und Korrespondenz.",
234
266
  sectionPricing: "v1 \xB7 Pricing & G\xFCltigkeit",
235
267
  pricingHint: "Monats- & Jahrespreis sowie Datum, ab dem das Bundle verkaufbar wird.",
236
268
  validFromImmediate: "\u2713 Bundle ist nach Anlage sofort live und verkaufbar.",
@@ -442,7 +474,7 @@ var bundlesMessages = defineMessages(
442
474
  bundleKeyHint: "API-stable \xB7 derived from the label",
443
475
  errorKeyFormat: "Only A-Z, 0-9, underscore; must start with a letter.",
444
476
  errorKeyExists: "This bundle key already exists.",
445
- descriptionPlaceholder: "e.g. Campaigns, WhatsApp and correspondence for active clubs.",
477
+ descriptionPlaceholder: "e.g. Campaigns, WhatsApp and correspondence.",
446
478
  sectionPricing: "v1 \xB7 pricing & validity",
447
479
  pricingHint: "Monthly & yearly price plus the date from which the bundle becomes sellable.",
448
480
  validFromImmediate: "\u2713 The bundle is live and sellable right after creation.",
@@ -1116,7 +1148,7 @@ var marketingMessages = defineMessages(
1116
1148
  ctaOverrideLabel: "CTA-Text \xFCberschreiben (optional)",
1117
1149
  ctaOverrideHint: "leer lassen f\xFCr Auto-Text",
1118
1150
  topFeatureCount: "{count} Eintr\xE4ge",
1119
- featureLabelPlaceholder: "z. B. Beitragsverwaltung",
1151
+ featureLabelPlaceholder: "z. B. Erweiterte Berichte",
1120
1152
  featureStrongPlaceholder: "Highlight, z. B. bis 100",
1121
1153
  moveUp: "Nach oben",
1122
1154
  moveDown: "Nach unten",
@@ -1244,7 +1276,7 @@ var marketingMessages = defineMessages(
1244
1276
  ctaOverrideLabel: "Override CTA text (optional)",
1245
1277
  ctaOverrideHint: "leave empty for the auto text",
1246
1278
  topFeatureCount: "{count} entries",
1247
- featureLabelPlaceholder: "e.g. Membership management",
1279
+ featureLabelPlaceholder: "e.g. Advanced reporting",
1248
1280
  featureStrongPlaceholder: "Highlight, e.g. up to 100",
1249
1281
  moveUp: "Move up",
1250
1282
  moveDown: "Move down",
@@ -2737,7 +2769,8 @@ var shellMessages = defineMessages(
2737
2769
  productionWarning: "Aktionen wirken sich sofort auf alle Mandanten aus.",
2738
2770
  subtitle: "SuperAdmin \xB7 Plattform-Verwaltung",
2739
2771
  roleBadge: "SUPER ADMIN",
2740
- logout: "Abmelden"
2772
+ logout: "Abmelden",
2773
+ language: "Sprache"
2741
2774
  },
2742
2775
  drawer: {
2743
2776
  docs: "Doku \xF6ffnen"
@@ -2805,7 +2838,8 @@ var shellMessages = defineMessages(
2805
2838
  productionWarning: "Actions take effect immediately for all tenants.",
2806
2839
  subtitle: "SuperAdmin \xB7 Platform administration",
2807
2840
  roleBadge: "SUPER ADMIN",
2808
- logout: "Sign out"
2841
+ logout: "Sign out",
2842
+ language: "Language"
2809
2843
  },
2810
2844
  drawer: {
2811
2845
  docs: "Open documentation"
@@ -3078,17 +3112,52 @@ function resolveMessages(locale, overrides) {
3078
3112
  return mergeMessages(SA_MESSAGES[locale], overrides);
3079
3113
  }
3080
3114
 
3115
+ // src/client/i18n/catalog.ts
3116
+ function createSaCatalog(options = {}) {
3117
+ const additional = options.additionalLocales ?? {};
3118
+ const known = /* @__PURE__ */ new Set([...SA_LOCALES, ...Object.keys(additional)]);
3119
+ const requested = options.locales?.filter((code) => known.has(code));
3120
+ const offered = requested && requested.length > 0 ? requested : [...SA_LOCALES, ...Object.keys(additional)];
3121
+ const labelFor = (locale) => additional[locale]?.label ?? (isSaBuiltinLocale(locale) ? SA_LOCALE_LABELS[locale] : locale);
3122
+ const cache = /* @__PURE__ */ new Map();
3123
+ const messagesFor = (locale) => {
3124
+ const hit = cache.get(locale);
3125
+ if (hit) return hit;
3126
+ const definition = additional[locale];
3127
+ const base = SA_MESSAGES[definition?.basedOn ?? (isSaBuiltinLocale(locale) ? locale : "en")];
3128
+ const translated = definition ? mergeMessages(base, definition.messages) : base;
3129
+ const resolved = mergeMessages(translated, options.overrides?.[locale]);
3130
+ cache.set(locale, resolved);
3131
+ return resolved;
3132
+ };
3133
+ const offeredSet = new Set(offered);
3134
+ return {
3135
+ locales: offered.map((code) => ({ code, label: labelFor(code) })),
3136
+ has: (locale) => offeredSet.has(locale),
3137
+ // The app's first offered locale beats the platform default: an app
3138
+ // shipping English only must not start in German.
3139
+ defaultLocale: offered.includes(DEFAULT_SA_LOCALE) ? DEFAULT_SA_LOCALE : offered[0],
3140
+ messagesFor,
3141
+ intlLocaleFor: (locale) => additional[locale]?.intlLocale ?? (isSaBuiltinLocale(locale) ? SA_INTL_LOCALES[locale] : locale),
3142
+ labelFor
3143
+ };
3144
+ }
3145
+
3081
3146
  export {
3082
3147
  defaultKvStore,
3083
3148
  defaultHttpClient,
3084
3149
  SA_LOCALES,
3085
3150
  DEFAULT_SA_LOCALE,
3086
3151
  SA_INTL_LOCALES,
3152
+ SA_LOCALE_LABELS,
3153
+ isSaBuiltinLocale,
3154
+ builtinLocaleOf,
3087
3155
  defineMessages,
3088
3156
  mergeMessages,
3089
3157
  navMessages,
3090
3158
  formatMessage,
3091
3159
  formatCurrency,
3092
3160
  SA_MESSAGES,
3093
- resolveMessages
3161
+ resolveMessages,
3162
+ createSaCatalog
3094
3163
  };
@@ -2,8 +2,9 @@ import {
2
2
  DEFAULT_SA_LOCALE,
3
3
  defaultHttpClient,
4
4
  defaultKvStore,
5
+ isSaBuiltinLocale,
5
6
  navMessages
6
- } from "./chunk-TBLXKSTR.js";
7
+ } from "./chunk-IZGFS5RS.js";
7
8
 
8
9
  // src/client/version.ts
9
10
  var ADMIN_UI_VERSION = "1.2.0";
@@ -213,15 +214,21 @@ var PAGE_SECTIONS = {
213
214
  platformEmail: "system",
214
215
  platformEmailHistory: "system"
215
216
  };
217
+ function resolveNav(options) {
218
+ return options.nav ?? builtinNav(options.locale);
219
+ }
220
+ function builtinNav(locale) {
221
+ return navMessages[isSaBuiltinLocale(locale) ? locale : DEFAULT_SA_LOCALE];
222
+ }
216
223
  function defaultSectionOrder(locale = DEFAULT_SA_LOCALE) {
217
- const sections = navMessages[locale].sections;
224
+ const sections = typeof locale === "string" ? builtinNav(locale).sections : locale.sections;
218
225
  return [sections.overview, sections.catalog, sections.customers, sections.system];
219
226
  }
220
227
  function buildRoutes(manifest, options = {}) {
221
228
  const routes = [];
222
229
  const capabilities = manifest.capabilities ?? {};
223
230
  const standard = manifest.navigation?.standardPages ?? {};
224
- const nav = navMessages[options.locale ?? DEFAULT_SA_LOCALE];
231
+ const nav = resolveNav(options);
225
232
  const defaultNavSections = Object.fromEntries(
226
233
  Object.entries(PAGE_SECTIONS).map(([page, section]) => [page, nav.sections[section]])
227
234
  );
@@ -541,6 +548,23 @@ function queryString(params) {
541
548
  return value ? `?${value}` : "";
542
549
  }
543
550
 
551
+ // src/client/login-branding.ts
552
+ var DEFAULT_TAG = "SuperAdmin";
553
+ function resolveLoginBranding(boot, fallback = {}) {
554
+ const project = boot?.project ?? null;
555
+ const environment = project?.environment ?? null;
556
+ return {
557
+ name: project?.displayName || fallback.name || "",
558
+ tag: project?.label || fallback.tag || DEFAULT_TAG,
559
+ icon: project?.icon || fallback.logoText || "",
560
+ logoUrl: project?.logoUrl || null,
561
+ environment: environment && environment !== "production" ? environment : null
562
+ };
563
+ }
564
+ function isProductionBoot(boot) {
565
+ return boot?.project?.environment === "production";
566
+ }
567
+
544
568
  export {
545
569
  ADMIN_UI_VERSION,
546
570
  HttpJsonError,
@@ -561,5 +585,7 @@ export {
561
585
  ActionRegistry,
562
586
  BatchColumnDriftError,
563
587
  BatchColumnFetcher,
564
- createAdminResourceClient
588
+ createAdminResourceClient,
589
+ resolveLoginBranding,
590
+ isProductionBoot
565
591
  };
@@ -35,10 +35,13 @@ __export(client_exports, {
35
35
  MissingHandlerError: () => MissingHandlerError,
36
36
  SA_INTL_LOCALES: () => SA_INTL_LOCALES,
37
37
  SA_LOCALES: () => SA_LOCALES,
38
+ SA_LOCALE_LABELS: () => SA_LOCALE_LABELS,
38
39
  SA_MESSAGES: () => SA_MESSAGES,
39
40
  buildRoutes: () => buildRoutes,
40
41
  buildSidebar: () => buildSidebar,
42
+ builtinLocaleOf: () => builtinLocaleOf,
41
43
  createAdminResourceClient: () => createAdminResourceClient,
44
+ createSaCatalog: () => createSaCatalog,
42
45
  defaultHttpClient: () => defaultHttpClient,
43
46
  defaultKvStore: () => defaultKvStore,
44
47
  defaultSectionOrder: () => defaultSectionOrder,
@@ -46,9 +49,12 @@ __export(client_exports, {
46
49
  formatCurrency: () => formatCurrency,
47
50
  formatMessage: () => formatMessage,
48
51
  getJson: () => getJson,
52
+ isProductionBoot: () => isProductionBoot,
53
+ isSaBuiltinLocale: () => isSaBuiltinLocale,
49
54
  mergeMessages: () => mergeMessages,
50
55
  postJson: () => postJson,
51
56
  resolveExtension: () => resolveExtension,
57
+ resolveLoginBranding: () => resolveLoginBranding,
52
58
  resolveMessages: () => resolveMessages,
53
59
  trimTrailingSlashes: () => trimTrailingSlashes
54
60
  });
@@ -58,23 +64,45 @@ module.exports = __toCommonJS(client_exports);
58
64
  var ADMIN_UI_VERSION = "1.2.0";
59
65
 
60
66
  // src/client/types.ts
61
- function defaultKvStore() {
62
- if (typeof globalThis.localStorage === "undefined") {
63
- return {
64
- get: () => null,
65
- set: () => {
66
- },
67
- remove: () => {
68
- }
69
- };
67
+ var NOOP_KV_STORE = {
68
+ get: () => null,
69
+ set: () => {
70
+ },
71
+ remove: () => {
70
72
  }
71
- const ls = globalThis.localStorage;
73
+ };
74
+ function defaultKvStore() {
75
+ const ls = resolveLocalStorage();
76
+ if (!ls) return NOOP_KV_STORE;
72
77
  return {
73
- get: (k) => ls.getItem(k),
74
- set: (k, v) => ls.setItem(k, v),
75
- remove: (k) => ls.removeItem(k)
78
+ get: (k) => {
79
+ try {
80
+ return ls.getItem(k);
81
+ } catch {
82
+ return null;
83
+ }
84
+ },
85
+ set: (k, v) => {
86
+ try {
87
+ ls.setItem(k, v);
88
+ } catch {
89
+ }
90
+ },
91
+ remove: (k) => {
92
+ try {
93
+ ls.removeItem(k);
94
+ } catch {
95
+ }
96
+ }
76
97
  };
77
98
  }
99
+ function resolveLocalStorage() {
100
+ try {
101
+ return globalThis.localStorage ?? null;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
78
106
  function defaultHttpClient() {
79
107
  return (url, init) => fetch(url, init);
80
108
  }
@@ -242,6 +270,16 @@ var SA_INTL_LOCALES = {
242
270
  de: "de-DE",
243
271
  en: "en-US"
244
272
  };
273
+ var SA_LOCALE_LABELS = {
274
+ de: "Deutsch",
275
+ en: "English"
276
+ };
277
+ function isSaBuiltinLocale(value) {
278
+ return typeof value === "string" && SA_LOCALES.includes(value);
279
+ }
280
+ function builtinLocaleOf(locale, fallback = DEFAULT_SA_LOCALE) {
281
+ return isSaBuiltinLocale(locale) ? locale : fallback;
282
+ }
245
283
 
246
284
  // src/client/i18n/define.ts
247
285
  function defineMessages(de, en) {
@@ -363,15 +401,21 @@ var PAGE_SECTIONS = {
363
401
  platformEmail: "system",
364
402
  platformEmailHistory: "system"
365
403
  };
404
+ function resolveNav(options) {
405
+ return options.nav ?? builtinNav(options.locale);
406
+ }
407
+ function builtinNav(locale) {
408
+ return navMessages[isSaBuiltinLocale(locale) ? locale : DEFAULT_SA_LOCALE];
409
+ }
366
410
  function defaultSectionOrder(locale = DEFAULT_SA_LOCALE) {
367
- const sections = navMessages[locale].sections;
411
+ const sections = typeof locale === "string" ? builtinNav(locale).sections : locale.sections;
368
412
  return [sections.overview, sections.catalog, sections.customers, sections.system];
369
413
  }
370
414
  function buildRoutes(manifest, options = {}) {
371
415
  const routes = [];
372
416
  const capabilities = manifest.capabilities ?? {};
373
417
  const standard = manifest.navigation?.standardPages ?? {};
374
- const nav = navMessages[options.locale ?? DEFAULT_SA_LOCALE];
418
+ const nav = resolveNav(options);
375
419
  const defaultNavSections = Object.fromEntries(
376
420
  Object.entries(PAGE_SECTIONS).map(([page, section]) => [page, nav.sections[section]])
377
421
  );
@@ -706,7 +750,7 @@ function formatterFor(locale, currency, decimals) {
706
750
  const key = `${locale}:${currency}:${decimals}`;
707
751
  let formatter = formatters.get(key);
708
752
  if (!formatter) {
709
- formatter = new Intl.NumberFormat(SA_INTL_LOCALES[locale], {
753
+ formatter = new Intl.NumberFormat(SA_INTL_LOCALES[locale] ?? locale, {
710
754
  style: "currency",
711
755
  currency,
712
756
  minimumFractionDigits: decimals,
@@ -872,7 +916,7 @@ var bundlesMessages = defineMessages(
872
916
  bundleKeyHint: "API-stabil \xB7 wird aus dem Label erzeugt",
873
917
  errorKeyFormat: "Nur A-Z, 0-9, Underscore; muss mit Buchstabe beginnen.",
874
918
  errorKeyExists: "Dieser Bundle-Key existiert bereits.",
875
- descriptionPlaceholder: "z. B. Kampagnen, WhatsApp und Korrespondenz f\xFCr aktive Vereine.",
919
+ descriptionPlaceholder: "z. B. Kampagnen, WhatsApp und Korrespondenz.",
876
920
  sectionPricing: "v1 \xB7 Pricing & G\xFCltigkeit",
877
921
  pricingHint: "Monats- & Jahrespreis sowie Datum, ab dem das Bundle verkaufbar wird.",
878
922
  validFromImmediate: "\u2713 Bundle ist nach Anlage sofort live und verkaufbar.",
@@ -1084,7 +1128,7 @@ var bundlesMessages = defineMessages(
1084
1128
  bundleKeyHint: "API-stable \xB7 derived from the label",
1085
1129
  errorKeyFormat: "Only A-Z, 0-9, underscore; must start with a letter.",
1086
1130
  errorKeyExists: "This bundle key already exists.",
1087
- descriptionPlaceholder: "e.g. Campaigns, WhatsApp and correspondence for active clubs.",
1131
+ descriptionPlaceholder: "e.g. Campaigns, WhatsApp and correspondence.",
1088
1132
  sectionPricing: "v1 \xB7 pricing & validity",
1089
1133
  pricingHint: "Monthly & yearly price plus the date from which the bundle becomes sellable.",
1090
1134
  validFromImmediate: "\u2713 The bundle is live and sellable right after creation.",
@@ -1758,7 +1802,7 @@ var marketingMessages = defineMessages(
1758
1802
  ctaOverrideLabel: "CTA-Text \xFCberschreiben (optional)",
1759
1803
  ctaOverrideHint: "leer lassen f\xFCr Auto-Text",
1760
1804
  topFeatureCount: "{count} Eintr\xE4ge",
1761
- featureLabelPlaceholder: "z. B. Beitragsverwaltung",
1805
+ featureLabelPlaceholder: "z. B. Erweiterte Berichte",
1762
1806
  featureStrongPlaceholder: "Highlight, z. B. bis 100",
1763
1807
  moveUp: "Nach oben",
1764
1808
  moveDown: "Nach unten",
@@ -1886,7 +1930,7 @@ var marketingMessages = defineMessages(
1886
1930
  ctaOverrideLabel: "Override CTA text (optional)",
1887
1931
  ctaOverrideHint: "leave empty for the auto text",
1888
1932
  topFeatureCount: "{count} entries",
1889
- featureLabelPlaceholder: "e.g. Membership management",
1933
+ featureLabelPlaceholder: "e.g. Advanced reporting",
1890
1934
  featureStrongPlaceholder: "Highlight, e.g. up to 100",
1891
1935
  moveUp: "Move up",
1892
1936
  moveDown: "Move down",
@@ -3329,7 +3373,8 @@ var shellMessages = defineMessages(
3329
3373
  productionWarning: "Aktionen wirken sich sofort auf alle Mandanten aus.",
3330
3374
  subtitle: "SuperAdmin \xB7 Plattform-Verwaltung",
3331
3375
  roleBadge: "SUPER ADMIN",
3332
- logout: "Abmelden"
3376
+ logout: "Abmelden",
3377
+ language: "Sprache"
3333
3378
  },
3334
3379
  drawer: {
3335
3380
  docs: "Doku \xF6ffnen"
@@ -3397,7 +3442,8 @@ var shellMessages = defineMessages(
3397
3442
  productionWarning: "Actions take effect immediately for all tenants.",
3398
3443
  subtitle: "SuperAdmin \xB7 Platform administration",
3399
3444
  roleBadge: "SUPER ADMIN",
3400
- logout: "Sign out"
3445
+ logout: "Sign out",
3446
+ language: "Language"
3401
3447
  },
3402
3448
  drawer: {
3403
3449
  docs: "Open documentation"
@@ -3669,6 +3715,54 @@ function resolveMessages(locale, overrides) {
3669
3715
  if (!overrides) return SA_MESSAGES[locale];
3670
3716
  return mergeMessages(SA_MESSAGES[locale], overrides);
3671
3717
  }
3718
+
3719
+ // src/client/i18n/catalog.ts
3720
+ function createSaCatalog(options = {}) {
3721
+ const additional = options.additionalLocales ?? {};
3722
+ const known = /* @__PURE__ */ new Set([...SA_LOCALES, ...Object.keys(additional)]);
3723
+ const requested = options.locales?.filter((code) => known.has(code));
3724
+ const offered = requested && requested.length > 0 ? requested : [...SA_LOCALES, ...Object.keys(additional)];
3725
+ const labelFor = (locale) => additional[locale]?.label ?? (isSaBuiltinLocale(locale) ? SA_LOCALE_LABELS[locale] : locale);
3726
+ const cache = /* @__PURE__ */ new Map();
3727
+ const messagesFor = (locale) => {
3728
+ const hit = cache.get(locale);
3729
+ if (hit) return hit;
3730
+ const definition = additional[locale];
3731
+ const base = SA_MESSAGES[definition?.basedOn ?? (isSaBuiltinLocale(locale) ? locale : "en")];
3732
+ const translated = definition ? mergeMessages(base, definition.messages) : base;
3733
+ const resolved = mergeMessages(translated, options.overrides?.[locale]);
3734
+ cache.set(locale, resolved);
3735
+ return resolved;
3736
+ };
3737
+ const offeredSet = new Set(offered);
3738
+ return {
3739
+ locales: offered.map((code) => ({ code, label: labelFor(code) })),
3740
+ has: (locale) => offeredSet.has(locale),
3741
+ // The app's first offered locale beats the platform default: an app
3742
+ // shipping English only must not start in German.
3743
+ defaultLocale: offered.includes(DEFAULT_SA_LOCALE) ? DEFAULT_SA_LOCALE : offered[0],
3744
+ messagesFor,
3745
+ intlLocaleFor: (locale) => additional[locale]?.intlLocale ?? (isSaBuiltinLocale(locale) ? SA_INTL_LOCALES[locale] : locale),
3746
+ labelFor
3747
+ };
3748
+ }
3749
+
3750
+ // src/client/login-branding.ts
3751
+ var DEFAULT_TAG = "SuperAdmin";
3752
+ function resolveLoginBranding(boot, fallback = {}) {
3753
+ const project = boot?.project ?? null;
3754
+ const environment = project?.environment ?? null;
3755
+ return {
3756
+ name: project?.displayName || fallback.name || "",
3757
+ tag: project?.label || fallback.tag || DEFAULT_TAG,
3758
+ icon: project?.icon || fallback.logoText || "",
3759
+ logoUrl: project?.logoUrl || null,
3760
+ environment: environment && environment !== "production" ? environment : null
3761
+ };
3762
+ }
3763
+ function isProductionBoot(boot) {
3764
+ return boot?.project?.environment === "production";
3765
+ }
3672
3766
  // Annotate the CommonJS export names for ESM import in node:
3673
3767
  0 && (module.exports = {
3674
3768
  ADMIN_UI_VERSION,
@@ -3686,10 +3780,13 @@ function resolveMessages(locale, overrides) {
3686
3780
  MissingHandlerError,
3687
3781
  SA_INTL_LOCALES,
3688
3782
  SA_LOCALES,
3783
+ SA_LOCALE_LABELS,
3689
3784
  SA_MESSAGES,
3690
3785
  buildRoutes,
3691
3786
  buildSidebar,
3787
+ builtinLocaleOf,
3692
3788
  createAdminResourceClient,
3789
+ createSaCatalog,
3693
3790
  defaultHttpClient,
3694
3791
  defaultKvStore,
3695
3792
  defaultSectionOrder,
@@ -3697,9 +3794,12 @@ function resolveMessages(locale, overrides) {
3697
3794
  formatCurrency,
3698
3795
  formatMessage,
3699
3796
  getJson,
3797
+ isProductionBoot,
3798
+ isSaBuiltinLocale,
3700
3799
  mergeMessages,
3701
3800
  postJson,
3702
3801
  resolveExtension,
3802
+ resolveLoginBranding,
3703
3803
  resolveMessages,
3704
3804
  trimTrailingSlashes
3705
3805
  });