create-mercato-app 0.6.2-develop.3372.1.de9a169973 → 0.6.2-develop.3380.1.bb14509041
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/package.json +1 -1
- package/template/src/app/globals.css +10 -0
- package/template/src/components/BackendHeaderChrome.tsx +101 -7
- package/template/src/components/OrganizationSwitcher.tsx +149 -34
- package/template/src/components/__tests__/BackendHeaderChrome.test.tsx +5 -0
- package/template/src/i18n/de.json +4 -0
- package/template/src/i18n/en.json +4 -0
- package/template/src/i18n/es.json +4 -0
- package/template/src/i18n/pl.json +4 -0
package/package.json
CHANGED
|
@@ -30,6 +30,16 @@ TODO: Fix that latter to have reference by the package names
|
|
|
30
30
|
z-index: var(--z-index-modal-elevated, 55);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/* Explicit text-overline utility. Tailwind 4 sometimes does not generate the
|
|
34
|
+
`text-X` utility from `--font-size-X` in @theme inline if no class literal
|
|
35
|
+
is found in the source scanner ahead of compilation. Declaring the utility
|
|
36
|
+
here guarantees the rule lands in the bundle. Used for tiny section labels
|
|
37
|
+
(workspace switcher TENANT / ORGANIZATION, notification count badge, etc.). */
|
|
38
|
+
@utility text-overline {
|
|
39
|
+
font-size: var(--font-size-overline, 0.6875rem);
|
|
40
|
+
line-height: var(--font-size-overline--line-height, 1rem);
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
@theme inline {
|
|
34
44
|
--color-background: var(--background);
|
|
35
45
|
--color-foreground: var(--foreground);
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import dynamic from 'next/dynamic'
|
|
4
|
+
import Link from 'next/link'
|
|
4
5
|
import * as React from 'react'
|
|
6
|
+
import { MoreHorizontal, PlugZap, Settings, Mail } from 'lucide-react'
|
|
5
7
|
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
8
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
|
|
6
10
|
import { IntegrationsButton } from '@open-mercato/ui/backend/IntegrationsButton'
|
|
7
11
|
import { ProfileDropdown } from '@open-mercato/ui/backend/ProfileDropdown'
|
|
8
12
|
import { SettingsButton } from '@open-mercato/ui/backend/SettingsButton'
|
|
9
13
|
import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
|
|
14
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
10
15
|
import { AiAssistantShellIntegration } from '@/components/AiAssistantShellIntegration'
|
|
11
16
|
|
|
12
17
|
const LazyAiChatHeaderButton = dynamic(
|
|
13
18
|
() => import('@open-mercato/ai-assistant/frontend').then((module) => module.AiChatHeaderButton),
|
|
14
19
|
{ ssr: false, loading: () => null },
|
|
15
20
|
)
|
|
16
|
-
const
|
|
17
|
-
() => import('@open-mercato/search/modules/search/frontend').then((module) => module.
|
|
21
|
+
const LazyTopbarSearchInline = dynamic(
|
|
22
|
+
() => import('@open-mercato/search/modules/search/frontend').then((module) => module.TopbarSearchInline),
|
|
18
23
|
{ ssr: false, loading: () => null },
|
|
19
24
|
)
|
|
20
25
|
const LazyOrganizationSwitcher = dynamic(() => import('@/components/OrganizationSwitcher'), {
|
|
@@ -50,6 +55,51 @@ function hasVisibleRoute(groups: Array<{ items?: Array<{ href: string; hidden?:
|
|
|
50
55
|
return false
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
type MobileMoreItem = {
|
|
59
|
+
id: string
|
|
60
|
+
href: string
|
|
61
|
+
icon: React.ReactNode
|
|
62
|
+
label: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function MobileMoreMenu({ items }: { items: MobileMoreItem[] }) {
|
|
66
|
+
const t = useT()
|
|
67
|
+
const [open, setOpen] = React.useState(false)
|
|
68
|
+
if (items.length === 0) return null
|
|
69
|
+
return (
|
|
70
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
71
|
+
<PopoverTrigger asChild>
|
|
72
|
+
<IconButton
|
|
73
|
+
type="button"
|
|
74
|
+
variant="ghost"
|
|
75
|
+
size="sm"
|
|
76
|
+
aria-label={t('appShell.moreActions', 'More actions')}
|
|
77
|
+
title={t('appShell.moreActions', 'More actions')}
|
|
78
|
+
>
|
|
79
|
+
<MoreHorizontal className="size-4" />
|
|
80
|
+
</IconButton>
|
|
81
|
+
</PopoverTrigger>
|
|
82
|
+
<PopoverContent align="end" className="w-[220px] p-1">
|
|
83
|
+
<div className="flex flex-col">
|
|
84
|
+
{items.map((item) => (
|
|
85
|
+
<Link
|
|
86
|
+
key={item.id}
|
|
87
|
+
href={item.href}
|
|
88
|
+
onClick={() => setOpen(false)}
|
|
89
|
+
className="flex items-center gap-3 rounded-sm px-3 py-2 text-sm text-foreground transition-colors hover:bg-muted/60 focus:outline-none focus-visible:bg-muted/60"
|
|
90
|
+
>
|
|
91
|
+
<span className="inline-flex size-4 shrink-0 items-center justify-center text-muted-foreground">
|
|
92
|
+
{item.icon}
|
|
93
|
+
</span>
|
|
94
|
+
<span className="flex-1 truncate">{item.label}</span>
|
|
95
|
+
</Link>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
</PopoverContent>
|
|
99
|
+
</Popover>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
53
103
|
export function BackendHeaderChrome({
|
|
54
104
|
email,
|
|
55
105
|
embeddingConfigured,
|
|
@@ -57,6 +107,7 @@ export function BackendHeaderChrome({
|
|
|
57
107
|
tenantId,
|
|
58
108
|
organizationId,
|
|
59
109
|
}: BackendHeaderChromeProps) {
|
|
110
|
+
const t = useT()
|
|
60
111
|
const { payload, isReady } = useBackendChrome()
|
|
61
112
|
const grantedFeatures = payload?.grantedFeatures ?? []
|
|
62
113
|
const showIntegrationsButton = React.useMemo(
|
|
@@ -80,6 +131,33 @@ export function BackendHeaderChrome({
|
|
|
80
131
|
[grantedFeatures],
|
|
81
132
|
)
|
|
82
133
|
|
|
134
|
+
const mobileMoreItems = React.useMemo<MobileMoreItem[]>(() => {
|
|
135
|
+
const items: MobileMoreItem[] = []
|
|
136
|
+
if (showIntegrationsButton) {
|
|
137
|
+
items.push({
|
|
138
|
+
id: 'integrations',
|
|
139
|
+
href: '/backend/integrations',
|
|
140
|
+
icon: <PlugZap className="size-4" aria-hidden="true" />,
|
|
141
|
+
label: t('integrations.nav.title', 'Integrations'),
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
items.push({
|
|
145
|
+
id: 'settings',
|
|
146
|
+
href: '/backend/settings',
|
|
147
|
+
icon: <Settings className="size-4" aria-hidden="true" />,
|
|
148
|
+
label: t('backend.nav.settings', 'Settings'),
|
|
149
|
+
})
|
|
150
|
+
if (isReady && showMessages) {
|
|
151
|
+
items.push({
|
|
152
|
+
id: 'messages',
|
|
153
|
+
href: '/backend/messages',
|
|
154
|
+
icon: <Mail className="size-4" aria-hidden="true" />,
|
|
155
|
+
label: t('messages.nav.inbox', 'Messages'),
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
return items
|
|
159
|
+
}, [showIntegrationsButton, isReady, showMessages, t])
|
|
160
|
+
|
|
83
161
|
return (
|
|
84
162
|
<>
|
|
85
163
|
{isReady && showAiAssistant ? (
|
|
@@ -88,17 +166,33 @@ export function BackendHeaderChrome({
|
|
|
88
166
|
</AiAssistantShellIntegration>
|
|
89
167
|
) : null}
|
|
90
168
|
{isReady && showSearch ? (
|
|
91
|
-
<
|
|
169
|
+
<LazyTopbarSearchInline
|
|
92
170
|
embeddingConfigured={embeddingConfigured}
|
|
93
171
|
missingConfigMessage={missingConfigMessage}
|
|
94
172
|
/>
|
|
95
173
|
) : null}
|
|
96
174
|
{isReady ? <LazyOrganizationSwitcher /> : null}
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
175
|
+
|
|
176
|
+
{/* Secondary actions — inline on md+, grouped under a More button on <md */}
|
|
177
|
+
{showIntegrationsButton ? (
|
|
178
|
+
<span className="hidden md:contents">
|
|
179
|
+
<IntegrationsButton />
|
|
180
|
+
</span>
|
|
181
|
+
) : null}
|
|
182
|
+
<span className="hidden md:contents">
|
|
183
|
+
<SettingsButton />
|
|
184
|
+
</span>
|
|
185
|
+
{isReady && showMessages ? (
|
|
186
|
+
<span className="hidden md:contents">
|
|
187
|
+
<LazyMessagesIcon />
|
|
188
|
+
</span>
|
|
189
|
+
) : null}
|
|
190
|
+
<span className="md:hidden">
|
|
191
|
+
<MobileMoreMenu items={mobileMoreItems} />
|
|
192
|
+
</span>
|
|
193
|
+
|
|
100
194
|
{isReady && showNotifications ? <LazyNotificationBellWrapper /> : null}
|
|
101
|
-
{
|
|
195
|
+
<ProfileDropdown email={email} />
|
|
102
196
|
</>
|
|
103
197
|
)
|
|
104
198
|
}
|
|
@@ -7,6 +7,19 @@ import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
|
7
7
|
import { emitOrganizationScopeChanged } from '@open-mercato/shared/lib/frontend/organizationEvents'
|
|
8
8
|
import { OrganizationSelect, type OrganizationTreeNode } from '@open-mercato/core/modules/directory/components/OrganizationSelect'
|
|
9
9
|
import { TenantSelect, type TenantRecord } from '@open-mercato/core/modules/directory/components/TenantSelect'
|
|
10
|
+
import { Building2, Check, ChevronDown, Settings2 } from 'lucide-react'
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
} from '@open-mercato/ui/primitives/select'
|
|
18
|
+
import {
|
|
19
|
+
Popover,
|
|
20
|
+
PopoverContent,
|
|
21
|
+
PopoverTrigger,
|
|
22
|
+
} from '@open-mercato/ui/primitives/popover'
|
|
10
23
|
import { ALL_ORGANIZATIONS_COOKIE_VALUE } from '@open-mercato/core/modules/directory/constants'
|
|
11
24
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
12
25
|
|
|
@@ -372,40 +385,142 @@ export default function OrganizationSwitcher({ compact }: OrganizationSwitcherEx
|
|
|
372
385
|
)
|
|
373
386
|
}
|
|
374
387
|
|
|
388
|
+
const flatOrgOptions = React.useMemo(() => {
|
|
389
|
+
const out: Array<{ id: string; label: string; selectable: boolean; depth: number }> = []
|
|
390
|
+
const walk = (list: OrganizationTreeNode[]) => {
|
|
391
|
+
for (const node of list) {
|
|
392
|
+
const depth = typeof node.depth === 'number' ? node.depth : 0
|
|
393
|
+
const indent = depth > 0 ? `${' '.repeat(depth)}` : ''
|
|
394
|
+
out.push({
|
|
395
|
+
id: node.id,
|
|
396
|
+
label: `${indent}${node.name}`,
|
|
397
|
+
selectable: node.selectable !== false,
|
|
398
|
+
depth,
|
|
399
|
+
})
|
|
400
|
+
if (Array.isArray(node.children) && node.children.length > 0) walk(node.children as OrganizationTreeNode[])
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
walk(nodes)
|
|
404
|
+
return out
|
|
405
|
+
}, [nodes])
|
|
406
|
+
|
|
407
|
+
const ALL_ORGS_SENTINEL = '__all__'
|
|
408
|
+
const orgSelectValue = !value ? (showAllOption ? ALL_ORGS_SENTINEL : '') : value
|
|
409
|
+
const [popoverOpen, setPopoverOpen] = React.useState(false)
|
|
410
|
+
|
|
411
|
+
const activeOrgLabel = React.useMemo(() => {
|
|
412
|
+
if (!value) {
|
|
413
|
+
return showAllOption
|
|
414
|
+
? t('organizationSwitcher.allOrganizations', 'All organizations')
|
|
415
|
+
: t('organizationSwitcher.label', 'Organization')
|
|
416
|
+
}
|
|
417
|
+
return flatOrgOptions.find((opt) => opt.id === value)?.label.trim()
|
|
418
|
+
|| t('organizationSwitcher.label', 'Organization')
|
|
419
|
+
}, [value, showAllOption, flatOrgOptions, t])
|
|
420
|
+
|
|
421
|
+
if (state.status === 'loading') {
|
|
422
|
+
return <span className="hidden md:inline text-xs text-muted-foreground">{t('organizationSwitcher.loading')}</span>
|
|
423
|
+
}
|
|
424
|
+
if (state.status === 'error') {
|
|
425
|
+
return <span className="hidden md:inline text-xs text-destructive">{t('organizationSwitcher.error')}</span>
|
|
426
|
+
}
|
|
427
|
+
if (!hasOptions) {
|
|
428
|
+
return <span className="hidden md:inline text-xs text-muted-foreground">{t('organizationSwitcher.empty')}</span>
|
|
429
|
+
}
|
|
430
|
+
|
|
375
431
|
return (
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
432
|
+
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
|
433
|
+
<PopoverTrigger asChild>
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
aria-label={`${t('organizationSwitcher.label')}: ${activeOrgLabel}`}
|
|
437
|
+
title={activeOrgLabel}
|
|
438
|
+
className="inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md border border-input bg-background px-0 text-sm font-medium shadow-xs transition-colors hover:bg-muted/40 focus:outline-none focus-visible:shadow-focus focus-visible:border-foreground data-[state=open]:bg-muted/40 sm:w-auto sm:justify-start sm:px-3 sm:max-w-[200px] md:max-w-[260px]"
|
|
439
|
+
>
|
|
440
|
+
<Building2 className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
441
|
+
<span className="hidden sm:block truncate flex-1 text-left">{activeOrgLabel}</span>
|
|
442
|
+
<ChevronDown className="hidden sm:block size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
443
|
+
</button>
|
|
444
|
+
</PopoverTrigger>
|
|
445
|
+
<PopoverContent align="end" className="w-[320px] p-0">
|
|
446
|
+
{showTenantSelect ? (
|
|
447
|
+
<div className="border-b p-3 space-y-2">
|
|
448
|
+
<div className="text-overline font-medium uppercase tracking-wider text-muted-foreground/80 leading-none">
|
|
449
|
+
{t('organizationSwitcher.tenantLabel', 'Tenant')}
|
|
450
|
+
</div>
|
|
451
|
+
<Select
|
|
452
|
+
value={tenantSelectValue || undefined}
|
|
453
|
+
onValueChange={(next) => handleTenantChange(next || null)}
|
|
454
|
+
>
|
|
455
|
+
<SelectTrigger
|
|
456
|
+
aria-label={t('organizationSwitcher.tenantLabel', 'Tenant')}
|
|
457
|
+
className="w-full [&>span]:truncate"
|
|
458
|
+
>
|
|
459
|
+
<SelectValue placeholder={t('organizationSwitcher.tenantLabel', 'Tenant')} />
|
|
460
|
+
</SelectTrigger>
|
|
461
|
+
<SelectContent>
|
|
462
|
+
{tenantSelectOptions.map((tenant) => (
|
|
463
|
+
<SelectItem key={tenant.id} value={tenant.id} disabled={!tenant.isActive}>
|
|
464
|
+
{tenant.name}
|
|
465
|
+
</SelectItem>
|
|
466
|
+
))}
|
|
467
|
+
</SelectContent>
|
|
468
|
+
</Select>
|
|
469
|
+
</div>
|
|
470
|
+
) : null}
|
|
471
|
+
<div className="p-2">
|
|
472
|
+
<div className="px-2 py-1.5 text-overline font-medium uppercase tracking-wider text-muted-foreground/80 leading-none">
|
|
473
|
+
{t('organizationSwitcher.label')}
|
|
474
|
+
</div>
|
|
475
|
+
<div className="max-h-[280px] overflow-y-auto">
|
|
476
|
+
{showAllOption ? (
|
|
477
|
+
<button
|
|
478
|
+
type="button"
|
|
479
|
+
onClick={() => {
|
|
480
|
+
handleChange(null)
|
|
481
|
+
setPopoverOpen(false)
|
|
482
|
+
}}
|
|
483
|
+
className="flex w-full items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted/40 focus:outline-none focus-visible:bg-muted/40"
|
|
484
|
+
>
|
|
485
|
+
<span className="truncate min-w-0">{t('organizationSwitcher.allOrganizations', 'All organizations')}</span>
|
|
486
|
+
{!value ? <Check className="size-4 shrink-0 text-accent-indigo" aria-hidden="true" /> : null}
|
|
487
|
+
</button>
|
|
488
|
+
) : null}
|
|
489
|
+
{flatOrgOptions.map((opt) => {
|
|
490
|
+
const isActive = value === opt.id
|
|
491
|
+
return (
|
|
492
|
+
<button
|
|
493
|
+
key={opt.id}
|
|
494
|
+
type="button"
|
|
495
|
+
disabled={!opt.selectable}
|
|
496
|
+
onClick={() => {
|
|
497
|
+
if (!opt.selectable) return
|
|
498
|
+
handleChange(opt.id)
|
|
499
|
+
setPopoverOpen(false)
|
|
500
|
+
}}
|
|
501
|
+
style={{ paddingLeft: `${0.5 + opt.depth * 0.75}rem` }}
|
|
502
|
+
className="flex w-full items-center justify-between gap-2 rounded-sm py-1.5 pr-2 text-left text-sm transition-colors hover:bg-muted/40 disabled:opacity-50 disabled:hover:bg-transparent focus:outline-none focus-visible:bg-muted/40"
|
|
503
|
+
>
|
|
504
|
+
<span className="truncate min-w-0">{opt.label.trim()}</span>
|
|
505
|
+
{isActive ? <Check className="size-4 shrink-0 text-accent-indigo" aria-hidden="true" /> : null}
|
|
506
|
+
</button>
|
|
507
|
+
)
|
|
508
|
+
})}
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
{canManage ? (
|
|
512
|
+
<div className="border-t p-2">
|
|
513
|
+
<Link
|
|
514
|
+
href="/backend/directory/organizations"
|
|
515
|
+
onClick={() => setPopoverOpen(false)}
|
|
516
|
+
className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground focus:outline-none focus-visible:bg-muted/40"
|
|
517
|
+
>
|
|
518
|
+
<Settings2 className="size-4 shrink-0" aria-hidden="true" />
|
|
519
|
+
<span>{t('organizationSwitcher.manage')}</span>
|
|
520
|
+
</Link>
|
|
521
|
+
</div>
|
|
522
|
+
) : null}
|
|
523
|
+
</PopoverContent>
|
|
524
|
+
</Popover>
|
|
410
525
|
)
|
|
411
526
|
}
|
|
@@ -18,6 +18,11 @@ jest.mock('next/dynamic', () => (loader: () => Promise<unknown>) => {
|
|
|
18
18
|
return Lazy
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
jest.mock('@open-mercato/shared/lib/i18n/context', () => ({
|
|
22
|
+
useT: () => (_key: string, fallback?: string) => fallback ?? _key,
|
|
23
|
+
useLocale: () => 'en',
|
|
24
|
+
}))
|
|
25
|
+
|
|
21
26
|
jest.mock('@open-mercato/ui/backend/BackendChromeProvider', () => ({
|
|
22
27
|
useBackendChrome: () => ({ payload: { groups: [], grantedFeatures: [] }, isReady: true }),
|
|
23
28
|
}))
|
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
"app.page.title": "Open Mercato",
|
|
42
42
|
"app.status": "{status}",
|
|
43
43
|
"app.title": "Open Mercato Starter (Next.js + shadcn + MikroORM)",
|
|
44
|
+
"appShell.breadcrumb.collapsed": "{count} ausgeblendete Navigationsschritte anzeigen",
|
|
44
45
|
"appShell.closeMenu": "Menü schließen",
|
|
45
46
|
"appShell.customizeSidebar": "Seitenleiste anpassen",
|
|
46
47
|
"appShell.goToDashboard": "Zum Dashboard",
|
|
48
|
+
"appShell.moreActions": "Weitere Aktionen",
|
|
47
49
|
"appShell.openMenu": "Menü öffnen",
|
|
48
50
|
"appShell.productName": "Open Mercato",
|
|
49
51
|
"appShell.sidebarApplyToRolesDescription": "Wähle Rollen aus, die dieses Seitenleisten-Layout standardmäßig verwenden sollen.",
|
|
@@ -83,6 +85,7 @@
|
|
|
83
85
|
"auth.usersRoles": "Benutzer und Rollen",
|
|
84
86
|
"backend.nav.backToMain": "Zurück zur Übersicht",
|
|
85
87
|
"backend.nav.developers": "Entwickler",
|
|
88
|
+
"backend.nav.main": "Haupt",
|
|
86
89
|
"backend.nav.profile": "Profil",
|
|
87
90
|
"backend.nav.security": "Sicherheit",
|
|
88
91
|
"backend.selectModule": "Wähle ein Modul im Backend aus.",
|
|
@@ -535,6 +538,7 @@
|
|
|
535
538
|
"organizationSelect.error": "Organisationen konnten nicht geladen werden",
|
|
536
539
|
"organizationSelect.inactive": "Inaktiv",
|
|
537
540
|
"organizationSelect.loading": "Organisationen werden geladen…",
|
|
541
|
+
"organizationSwitcher.allOrganizations": "Alle Organisationen",
|
|
538
542
|
"organizationSwitcher.empty": "Keine Organisationen",
|
|
539
543
|
"organizationSwitcher.error": "Laden fehlgeschlagen",
|
|
540
544
|
"organizationSwitcher.label": "Organisation",
|
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
"app.page.title": "Open Mercato",
|
|
42
42
|
"app.status": "{status}",
|
|
43
43
|
"app.title": "Open Mercato starter (Next.js + shadcn + MikroORM)",
|
|
44
|
+
"appShell.breadcrumb.collapsed": "Show {count} hidden navigation steps",
|
|
44
45
|
"appShell.closeMenu": "Close menu",
|
|
45
46
|
"appShell.customizeSidebar": "Customize sidebar",
|
|
46
47
|
"appShell.goToDashboard": "Go to dashboard",
|
|
48
|
+
"appShell.moreActions": "More actions",
|
|
47
49
|
"appShell.openMenu": "Open menu",
|
|
48
50
|
"appShell.productName": "Open Mercato",
|
|
49
51
|
"appShell.sidebarApplyToRolesDescription": "Select roles that should receive this sidebar layout by default.",
|
|
@@ -83,6 +85,7 @@
|
|
|
83
85
|
"auth.usersRoles": "Users & Roles",
|
|
84
86
|
"backend.nav.backToMain": "Back to Main",
|
|
85
87
|
"backend.nav.developers": "Developers",
|
|
88
|
+
"backend.nav.main": "Main",
|
|
86
89
|
"backend.nav.profile": "Profile",
|
|
87
90
|
"backend.nav.security": "Security",
|
|
88
91
|
"backend.selectModule": "Select a module in the backend.",
|
|
@@ -535,6 +538,7 @@
|
|
|
535
538
|
"organizationSelect.error": "Failed to load organizations",
|
|
536
539
|
"organizationSelect.inactive": "Inactive",
|
|
537
540
|
"organizationSelect.loading": "Loading organizations…",
|
|
541
|
+
"organizationSwitcher.allOrganizations": "All organizations",
|
|
538
542
|
"organizationSwitcher.empty": "No organizations",
|
|
539
543
|
"organizationSwitcher.error": "Failed to load",
|
|
540
544
|
"organizationSwitcher.label": "Organization",
|
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
"app.page.title": "Open Mercato",
|
|
42
42
|
"app.status": "{status}",
|
|
43
43
|
"app.title": "Inicio de Open Mercato (Next.js + shadcn + MikroORM)",
|
|
44
|
+
"appShell.breadcrumb.collapsed": "Mostrar {count} pasos de navegación ocultos",
|
|
44
45
|
"appShell.closeMenu": "Cerrar menú",
|
|
45
46
|
"appShell.customizeSidebar": "Personalizar barra lateral",
|
|
46
47
|
"appShell.goToDashboard": "Ir al panel",
|
|
48
|
+
"appShell.moreActions": "Más acciones",
|
|
47
49
|
"appShell.openMenu": "Abrir menú",
|
|
48
50
|
"appShell.productName": "Open Mercato",
|
|
49
51
|
"appShell.sidebarApplyToRolesDescription": "Selecciona los roles que deben usar este diseño de barra lateral de forma predeterminada.",
|
|
@@ -83,6 +85,7 @@
|
|
|
83
85
|
"auth.usersRoles": "Usuarios y roles",
|
|
84
86
|
"backend.nav.backToMain": "Volver al inicio",
|
|
85
87
|
"backend.nav.developers": "Desarrolladores",
|
|
88
|
+
"backend.nav.main": "Principal",
|
|
86
89
|
"backend.nav.profile": "Perfil",
|
|
87
90
|
"backend.nav.security": "Seguridad",
|
|
88
91
|
"backend.selectModule": "Selecciona un módulo en el panel.",
|
|
@@ -535,6 +538,7 @@
|
|
|
535
538
|
"organizationSelect.error": "No se pudieron cargar las organizaciones",
|
|
536
539
|
"organizationSelect.inactive": "Inactiva",
|
|
537
540
|
"organizationSelect.loading": "Cargando organizaciones…",
|
|
541
|
+
"organizationSwitcher.allOrganizations": "Todas las organizaciones",
|
|
538
542
|
"organizationSwitcher.empty": "Sin organizaciones",
|
|
539
543
|
"organizationSwitcher.error": "Error al cargar",
|
|
540
544
|
"organizationSwitcher.label": "Organización",
|
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
"app.page.title": "Open Mercato",
|
|
42
42
|
"app.status": "{status}",
|
|
43
43
|
"app.title": "Open Mercato Starter (Next.js + shadcn + MikroORM)",
|
|
44
|
+
"appShell.breadcrumb.collapsed": "Pokaż {count} ukrytych kroków nawigacji",
|
|
44
45
|
"appShell.closeMenu": "Zamknij menu",
|
|
45
46
|
"appShell.customizeSidebar": "Dostosuj pasek boczny",
|
|
46
47
|
"appShell.goToDashboard": "Przejdź do pulpitu",
|
|
48
|
+
"appShell.moreActions": "Więcej akcji",
|
|
47
49
|
"appShell.openMenu": "Otwórz menu",
|
|
48
50
|
"appShell.productName": "Open Mercato",
|
|
49
51
|
"appShell.sidebarApplyToRolesDescription": "Wybierz role, które powinny domyślnie korzystać z tego układu paska bocznego.",
|
|
@@ -83,6 +85,7 @@
|
|
|
83
85
|
"auth.usersRoles": "Użytkownicy i role",
|
|
84
86
|
"backend.nav.backToMain": "Powrót do strony głównej",
|
|
85
87
|
"backend.nav.developers": "Deweloperzy",
|
|
88
|
+
"backend.nav.main": "Główne",
|
|
86
89
|
"backend.nav.profile": "Profil",
|
|
87
90
|
"backend.nav.security": "Bezpieczeństwo",
|
|
88
91
|
"backend.selectModule": "Wybierz moduł w panelu.",
|
|
@@ -535,6 +538,7 @@
|
|
|
535
538
|
"organizationSelect.error": "Nie udało się załadować organizacji",
|
|
536
539
|
"organizationSelect.inactive": "Nieaktywna",
|
|
537
540
|
"organizationSelect.loading": "Ładowanie organizacji…",
|
|
541
|
+
"organizationSwitcher.allOrganizations": "Wszystkie organizacje",
|
|
538
542
|
"organizationSwitcher.empty": "Brak organizacji",
|
|
539
543
|
"organizationSwitcher.error": "Nie udało się załadować",
|
|
540
544
|
"organizationSwitcher.label": "Organizacja",
|