@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.
- package/README.md +13 -10
- package/dist/{messages-BIYw32u1.d.ts → catalog-C4Gv2_KM.d.cts} +126 -39
- package/dist/{messages-BIYw32u1.d.cts → catalog-C4Gv2_KM.d.ts} +126 -39
- package/dist/{chunk-TY2WYKME.js → chunk-247RJPU4.js} +38 -11
- package/dist/{chunk-TBLXKSTR.js → chunk-IZGFS5RS.js} +90 -21
- package/dist/{chunk-66BUSVC5.js → chunk-XPSD5HKG.js} +30 -4
- package/dist/client/index.cjs +122 -22
- package/dist/client/index.d.cts +51 -5
- package/dist/client/index.d.ts +51 -5
- package/dist/client/index.js +14 -2
- package/dist/index.cjs +152 -30
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +17 -3
- package/dist/quasar/index.cjs +110 -14
- package/dist/quasar/index.d.cts +2 -2
- package/dist/quasar/index.d.ts +2 -2
- package/dist/quasar/index.js +2 -2
- package/dist/{use-super-admin-i18n-DL_qI1BY.d.cts → use-super-admin-i18n-BdPqeTHl.d.cts} +51 -5
- package/dist/{use-super-admin-i18n-CWNu_W3u.d.ts → use-super-admin-i18n-uteRCL9q.d.ts} +51 -5
- package/package.json +8 -3
- package/src/client/i18n/catalog.ts +120 -0
- package/src/client/i18n/currency.ts +7 -2
- package/src/client/i18n/define.ts +2 -2
- package/src/client/i18n/index.ts +1 -0
- package/src/client/i18n/locale.ts +52 -8
- package/src/client/i18n/messages/bundles.ts +2 -3
- package/src/client/i18n/messages/marketing.ts +2 -2
- package/src/client/i18n/messages/nav.ts +3 -3
- package/src/client/i18n/messages/shell.ts +2 -0
- package/src/client/i18n/messages.ts +7 -4
- package/src/client/index.ts +1 -0
- package/src/client/login-branding.ts +61 -0
- package/src/client/nav-builder.ts +33 -6
- package/src/client/types.ts +51 -14
- package/src/components/LocaleSwitcher.vue +77 -0
- package/src/components/bundle-editor/bundle-version-status.ts +16 -8
- package/src/pages-standard/AdminLayout.vue +9 -3
- package/src/pages-standard/SuperAdminLoginPage.vue +17 -9
- package/src/pages-standard/SuperAdminSetupWizard.vue +7 -0
- package/src/pages-standard/discovery-page/discovery-ui.ts +8 -8
- package/src/pages-standard/plan-versions/format.ts +2 -2
- package/src/vue/use-super-admin-i18n.ts +116 -19
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<div class="sa-setup-title">{{ msg.setup.title }}</div>
|
|
8
8
|
<div class="sa-setup-sub">{{ subtitleText }}</div>
|
|
9
9
|
</div>
|
|
10
|
+
<LocaleSwitcher class="sa-setup-locale" />
|
|
10
11
|
</div>
|
|
11
12
|
|
|
12
13
|
<q-banner v-if="errorMessage" class="sa-setup-error" rounded>
|
|
@@ -160,6 +161,7 @@ import {
|
|
|
160
161
|
import { HttpJsonError, postJson as httpPostJson } from '../client/http-json.js';
|
|
161
162
|
import { formatMessage } from '../client/i18n/format.js';
|
|
162
163
|
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
164
|
+
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
|
163
165
|
|
|
164
166
|
interface Props {
|
|
165
167
|
/** Display name + badge abbreviation (override the app branding, e.g. from PublicBoot). */
|
|
@@ -288,6 +290,11 @@ async function submitConfirm(): Promise<void> {
|
|
|
288
290
|
gap: 12px;
|
|
289
291
|
margin-bottom: 20px;
|
|
290
292
|
}
|
|
293
|
+
.sa-setup-locale {
|
|
294
|
+
margin-left: auto;
|
|
295
|
+
align-self: flex-start;
|
|
296
|
+
color: #475569;
|
|
297
|
+
}
|
|
291
298
|
.sa-setup-badge {
|
|
292
299
|
width: 44px;
|
|
293
300
|
height: 44px;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// labels/colors/coverage calculations.
|
|
5
5
|
|
|
6
6
|
import type { CatalogEntryI18n, DiscoveryStatus } from '@saasicat/types';
|
|
7
|
-
import { DEFAULT_SA_LOCALE, type SaLocale } from '../../client/i18n/locale.js';
|
|
7
|
+
import { DEFAULT_SA_LOCALE, builtinLocaleOf, type SaLocale } from '../../client/i18n/locale.js';
|
|
8
8
|
import { commonMessages } from '../../client/i18n/messages/common.js';
|
|
9
9
|
import { discoveryMessages } from '../../client/i18n/messages/discovery.js';
|
|
10
10
|
|
|
@@ -43,19 +43,19 @@ export function localeFull(locale: string): string {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
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;
|
|
46
|
+
if (f === 'label') return discoveryMessages[builtinLocaleOf(locale)].trans.fieldLabel;
|
|
47
|
+
if (f === 'description') return commonMessages[builtinLocaleOf(locale)].description;
|
|
48
|
+
return discoveryMessages[builtinLocaleOf(locale)].unit;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// ─── Approval lifecycle (#20): status display + state machine ────────────────
|
|
52
52
|
|
|
53
53
|
export function statusLabel(status: DiscoveryStatus, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
54
|
-
return discoveryMessages[locale].statusLabels[status];
|
|
54
|
+
return discoveryMessages[builtinLocaleOf(locale)].statusLabels[status];
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function statusHint(status: DiscoveryStatus, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
58
|
-
return discoveryMessages[locale].statusHints[status];
|
|
58
|
+
return discoveryMessages[builtinLocaleOf(locale)].statusHints[status];
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export interface ReviewAction {
|
|
@@ -71,7 +71,7 @@ export function primaryReviewAction(
|
|
|
71
71
|
status: DiscoveryStatus,
|
|
72
72
|
locale: SaLocale = DEFAULT_SA_LOCALE,
|
|
73
73
|
): ReviewAction {
|
|
74
|
-
const actions = discoveryMessages[locale].reviewActions;
|
|
74
|
+
const actions = discoveryMessages[builtinLocaleOf(locale)].reviewActions;
|
|
75
75
|
switch (status) {
|
|
76
76
|
case 'pending':
|
|
77
77
|
return { label: actions.approve, to: 'approved', emphasized: true };
|
|
@@ -89,7 +89,7 @@ export function reviewMenuActions(
|
|
|
89
89
|
status: DiscoveryStatus,
|
|
90
90
|
locale: SaLocale = DEFAULT_SA_LOCALE,
|
|
91
91
|
): ReviewAction[] {
|
|
92
|
-
const actions = discoveryMessages[locale].reviewActions;
|
|
92
|
+
const actions = discoveryMessages[builtinLocaleOf(locale)].reviewActions;
|
|
93
93
|
const markObsolete: ReviewAction = {
|
|
94
94
|
label: actions.markObsolete,
|
|
95
95
|
to: 'obsolete',
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Formatting helper retained for the reusable catalog-history timeline.
|
|
2
2
|
|
|
3
3
|
import { formatMessage } from '../../client/i18n/format.js';
|
|
4
|
-
import { DEFAULT_SA_LOCALE, type SaLocale } from '../../client/i18n/locale.js';
|
|
4
|
+
import { DEFAULT_SA_LOCALE, builtinLocaleOf, type SaLocale } from '../../client/i18n/locale.js';
|
|
5
5
|
import { planVersionsMessages } from '../../client/i18n/messages/plan-versions.js';
|
|
6
6
|
|
|
7
7
|
const EMPTY_VALUE = '—';
|
|
8
8
|
|
|
9
9
|
function texts(locale: SaLocale) {
|
|
10
|
-
return planVersionsMessages[locale].format;
|
|
10
|
+
return planVersionsMessages[builtinLocaleOf(locale)].format;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function formatRelative(
|
|
@@ -2,22 +2,35 @@
|
|
|
2
2
|
// provides one instance; pages, components and composables read it via
|
|
3
3
|
// `useSuperAdminI18n()` or the namespace-focused `useSaMessages()`.
|
|
4
4
|
//
|
|
5
|
-
// Design: no vue-i18n dependency. The platform ships complete typed
|
|
6
|
-
// (
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// Design: no vue-i18n dependency. The platform ships two complete typed
|
|
6
|
+
// catalogs (German reference + English translation) and nothing beyond that —
|
|
7
|
+
// which of them an app offers, which languages it adds and what wording it
|
|
8
|
+
// prefers is decided through the bootstrap option and resolved in `SaCatalog`.
|
|
9
|
+
// Without a provider the composables fall back to a shared default instance, so
|
|
9
10
|
// isolated mounts and unit tests need no setup.
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
computed,
|
|
14
|
+
inject,
|
|
15
|
+
isReadonly,
|
|
16
|
+
isRef,
|
|
17
|
+
ref,
|
|
18
|
+
watch,
|
|
19
|
+
type ComputedRef,
|
|
20
|
+
type InjectionKey,
|
|
21
|
+
type Ref,
|
|
22
|
+
} from 'vue';
|
|
12
23
|
|
|
13
24
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
resolveMessages,
|
|
25
|
+
createSaCatalog,
|
|
26
|
+
type SaCatalog,
|
|
17
27
|
type SaLocale,
|
|
28
|
+
type SaLocaleDefinition,
|
|
29
|
+
type SaLocaleOption,
|
|
18
30
|
type SaMessages,
|
|
19
31
|
type SaMessagesOverrides,
|
|
20
32
|
} from '../client/i18n/index.js';
|
|
33
|
+
import { defaultKvStore, type KvStore } from '../client/types.js';
|
|
21
34
|
|
|
22
35
|
export interface SuperAdminI18n {
|
|
23
36
|
/** Active UI locale — mutable; switching re-renders all catalog texts. */
|
|
@@ -26,18 +39,65 @@ export interface SuperAdminI18n {
|
|
|
26
39
|
messages: ComputedRef<SaMessages>;
|
|
27
40
|
/** BCP-47 tag for `Intl`/`toLocaleString` formatting in the active locale. */
|
|
28
41
|
intlLocale: ComputedRef<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the shell renders its language switcher. `false` when the app
|
|
44
|
+
* opted out, when `locale` cannot be written to, or when only one language
|
|
45
|
+
* is on offer — a switcher that cannot change anything is worse than none.
|
|
46
|
+
*/
|
|
47
|
+
switcherEnabled: boolean;
|
|
48
|
+
/** Locales the switcher offers, in order, with their display names. */
|
|
49
|
+
availableLocales: readonly SaLocaleOption[];
|
|
29
50
|
}
|
|
30
51
|
|
|
31
52
|
export interface SuperAdminI18nOptions {
|
|
32
53
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
54
|
+
* Starting UI locale — the shell's switcher lets the user pick another one,
|
|
55
|
+
* and a stored pick outranks this. Defaults to `'de'` when offered,
|
|
56
|
+
* otherwise to the first entry of `locales`. Pass a `Ref` when the app wants
|
|
57
|
+
* to keep control (e.g. a user-profile setting).
|
|
35
58
|
*/
|
|
36
59
|
locale?: SaLocale | Ref<SaLocale>;
|
|
37
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Languages to offer, in switcher order. Default: German, English and
|
|
62
|
+
* everything in `additionalLocales`. Narrow it to ship a single language —
|
|
63
|
+
* the switcher then hides itself.
|
|
64
|
+
*/
|
|
65
|
+
locales?: readonly SaLocale[];
|
|
66
|
+
/**
|
|
67
|
+
* Languages the app adds on top of the two the platform ships, keyed by
|
|
68
|
+
* locale code. Untranslated keys fall back to `basedOn`, so a partial
|
|
69
|
+
* catalog is usable from the first key onwards.
|
|
70
|
+
*/
|
|
71
|
+
additionalLocales?: Readonly<Record<string, SaLocaleDefinition>>;
|
|
72
|
+
/** Per-locale string overrides layered over whatever catalog applies. */
|
|
38
73
|
overrides?: Partial<Record<SaLocale, SaMessagesOverrides>>;
|
|
74
|
+
/**
|
|
75
|
+
* Remembers the locale picked in the header switcher across reloads,
|
|
76
|
+
* default `true`. Ignored when `locale` is a `Ref`: the app owns that value
|
|
77
|
+
* and persists it wherever it belongs (user profile, its own store).
|
|
78
|
+
*/
|
|
79
|
+
persist?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Storage adapter for `persist`. Defaults to `defaultKvStore()`.
|
|
82
|
+
*/
|
|
83
|
+
storage?: KvStore;
|
|
84
|
+
/**
|
|
85
|
+
* Prefix for the persisted key, mirroring `createPlatformLoaders`. Set it
|
|
86
|
+
* when several apps share one origin and must not inherit each other's
|
|
87
|
+
* language pick.
|
|
88
|
+
*/
|
|
89
|
+
storageKeyPrefix?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Renders the language switcher in the shell chrome, default `true`. Set
|
|
92
|
+
* `false` for a deployment that ships one language. A readonly `locale`
|
|
93
|
+
* ref disables it on its own — no need to set this as well.
|
|
94
|
+
*/
|
|
95
|
+
switcher?: boolean;
|
|
39
96
|
}
|
|
40
97
|
|
|
98
|
+
/** `KvStore` key holding the locale the user picked in the switcher. */
|
|
99
|
+
export const SA_LOCALE_STORAGE_KEY = 'sa:locale';
|
|
100
|
+
|
|
41
101
|
// `Symbol.for` — see super-admin-context.ts: dist- and src-imported module
|
|
42
102
|
// instances must resolve to the same injection key.
|
|
43
103
|
export const SUPER_ADMIN_I18N_KEY: InjectionKey<SuperAdminI18n> = Symbol.for(
|
|
@@ -45,14 +105,48 @@ export const SUPER_ADMIN_I18N_KEY: InjectionKey<SuperAdminI18n> = Symbol.for(
|
|
|
45
105
|
);
|
|
46
106
|
|
|
47
107
|
export function createSuperAdminI18n(options: SuperAdminI18nOptions = {}): SuperAdminI18n {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
108
|
+
const catalog = createSaCatalog(options);
|
|
109
|
+
const locale = isRef(options.locale) ? options.locale : createOwnedLocale(options, catalog);
|
|
110
|
+
// An unknown locale would otherwise render an empty shell. Falling back
|
|
111
|
+
// keeps the UI usable and makes the app's mistake visible as "wrong
|
|
112
|
+
// language" rather than "blank page".
|
|
113
|
+
const active = computed(() =>
|
|
114
|
+
catalog.has(locale.value) ? locale.value : catalog.defaultLocale,
|
|
53
115
|
);
|
|
54
|
-
const
|
|
55
|
-
|
|
116
|
+
const messages = computed(() => catalog.messagesFor(active.value));
|
|
117
|
+
const intlLocale = computed(() => catalog.intlLocaleFor(active.value));
|
|
118
|
+
// A readonly ref (typically a `computed` derived from a profile setting)
|
|
119
|
+
// silently swallows writes, so the switcher would be a dead control. The
|
|
120
|
+
// type system permits it — TypeScript ignores `readonly` when checking
|
|
121
|
+
// assignability — so the guard has to be a runtime one.
|
|
122
|
+
const writable = !isReadonly(locale);
|
|
123
|
+
return {
|
|
124
|
+
locale,
|
|
125
|
+
messages,
|
|
126
|
+
intlLocale,
|
|
127
|
+
switcherEnabled: (options.switcher ?? true) && writable && catalog.locales.length > 1,
|
|
128
|
+
availableLocales: catalog.locales,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Builds the locale ref the platform owns. A stored choice outranks the
|
|
134
|
+
* `locale` option, because that option is the app's default while the stored
|
|
135
|
+
* value is the user's explicit pick in the switcher.
|
|
136
|
+
*/
|
|
137
|
+
function createOwnedLocale(options: SuperAdminI18nOptions, catalog: SaCatalog): Ref<SaLocale> {
|
|
138
|
+
const initial = options.locale ?? catalog.defaultLocale;
|
|
139
|
+
if (options.persist === false) return ref(initial);
|
|
140
|
+
|
|
141
|
+
const storage = options.storage ?? defaultKvStore();
|
|
142
|
+
const key = `${options.storageKeyPrefix ?? ''}${SA_LOCALE_STORAGE_KEY}`;
|
|
143
|
+
const stored = storage.get(key);
|
|
144
|
+
// The stored value is checked against what this app offers, not against the
|
|
145
|
+
// built-ins: an app that dropped a language must not be pushed back into it
|
|
146
|
+
// by a pick from before.
|
|
147
|
+
const locale = ref(stored && catalog.has(stored) ? stored : initial);
|
|
148
|
+
watch(locale, (next) => storage.set(key, next));
|
|
149
|
+
return locale;
|
|
56
150
|
}
|
|
57
151
|
|
|
58
152
|
let fallbackI18n: SuperAdminI18n | null = null;
|
|
@@ -65,7 +159,10 @@ let fallbackI18n: SuperAdminI18n | null = null;
|
|
|
65
159
|
export function useSuperAdminI18n(): SuperAdminI18n {
|
|
66
160
|
const injected = inject(SUPER_ADMIN_I18N_KEY, null);
|
|
67
161
|
if (injected) return injected;
|
|
68
|
-
|
|
162
|
+
// Deliberately not persisted: this instance exists for mounts outside a
|
|
163
|
+
// shell, and a shared singleton that reads and writes real storage would
|
|
164
|
+
// make test outcomes depend on their order.
|
|
165
|
+
fallbackI18n ??= createSuperAdminI18n({ persist: false });
|
|
69
166
|
return fallbackI18n;
|
|
70
167
|
}
|
|
71
168
|
|