@open-mercato/core 0.4.5-develop-4849712ccb → 0.4.5-develop-7f44fcf045
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/dist/modules/auth/api/admin/nav.js +10 -7
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/translations.js +9 -0
- package/dist/modules/staff/translations.js.map +7 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
- package/dist/modules/translations/lib/extract-record-id.js +31 -2
- package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +3 -0
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/dist/modules/translations/widgets/injection-table.js +18 -29
- package/dist/modules/translations/widgets/injection-table.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/auth/api/admin/nav.ts +13 -7
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
- package/src/modules/staff/translations.ts +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
- package/src/modules/translations/lib/extract-record-id.ts +47 -3
- package/src/modules/translations/lib/resolve-field-list.ts +4 -0
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
- package/src/modules/translations/widgets/injection-table.ts +19 -33
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import Link from 'next/link'
|
|
5
4
|
import { useParams } from 'next/navigation'
|
|
6
|
-
import
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { ExternalLink, Languages, X } from 'lucide-react'
|
|
7
7
|
import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
|
|
8
8
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
9
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
10
|
import { TranslationManager } from '../../../components/TranslationManager'
|
|
10
11
|
import { extractRecordId } from '../../../lib/extract-record-id'
|
|
11
12
|
|
|
12
|
-
type WidgetContext = { entityId?: string }
|
|
13
|
+
type WidgetContext = { entityId?: string; recordId?: string }
|
|
13
14
|
type WidgetData = Record<string, unknown> & { id?: string | number }
|
|
14
15
|
|
|
15
16
|
function useTranslationAccess(): boolean {
|
|
@@ -32,43 +33,114 @@ export default function TranslationWidget({ context, data }: InjectionWidgetComp
|
|
|
32
33
|
const entityType = context?.entityId
|
|
33
34
|
const params = useParams()
|
|
34
35
|
const t = useT()
|
|
36
|
+
const [open, setOpen] = React.useState(false)
|
|
35
37
|
const hasAccess = useTranslationAccess()
|
|
36
38
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const contextRecordId = typeof context?.recordId === 'string' && context.recordId.trim().length > 0
|
|
40
|
+
? context.recordId.trim()
|
|
41
|
+
: undefined
|
|
42
|
+
const dataRecordId = data?.id === undefined || data.id === null ? undefined : String(data.id)
|
|
43
|
+
const routeRecordId = params ? extractRecordId(params as Record<string, string | string[]>) : undefined
|
|
44
|
+
const recordId = contextRecordId ?? dataRecordId ?? routeRecordId
|
|
45
|
+
const canRender = Boolean(entityType && recordId && hasAccess)
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
if (!open || !canRender) return
|
|
49
|
+
const prev = document.body.style.overflow
|
|
50
|
+
document.body.style.overflow = 'hidden'
|
|
51
|
+
return () => {
|
|
52
|
+
document.body.style.overflow = prev
|
|
53
|
+
}
|
|
54
|
+
}, [canRender, open])
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (!open || !canRender) return
|
|
58
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
59
|
+
if (event.key === 'Escape') {
|
|
60
|
+
setOpen(false)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
64
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
65
|
+
}, [canRender, open])
|
|
42
66
|
|
|
43
|
-
if (!
|
|
67
|
+
if (!canRender) return null
|
|
44
68
|
|
|
45
69
|
return (
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
<>
|
|
71
|
+
<Button
|
|
72
|
+
type="button"
|
|
73
|
+
variant="ghost"
|
|
74
|
+
size="icon"
|
|
75
|
+
onClick={() => setOpen(true)}
|
|
76
|
+
aria-label={t('translations.widgets.translationManager.fullManager', 'Translation manager')}
|
|
77
|
+
title={t('translations.widgets.translationManager.fullManager', 'Translation manager')}
|
|
78
|
+
>
|
|
79
|
+
<Languages className="size-4" />
|
|
80
|
+
</Button>
|
|
81
|
+
{open ? (
|
|
82
|
+
<>
|
|
83
|
+
<div
|
|
84
|
+
className="fixed inset-0 z-40 bg-black/20"
|
|
85
|
+
onClick={() => setOpen(false)}
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
/>
|
|
88
|
+
<div
|
|
89
|
+
className="fixed right-0 top-0 z-50 h-full w-full max-w-4xl border-l bg-background shadow-lg"
|
|
90
|
+
role="dialog"
|
|
91
|
+
aria-modal="true"
|
|
92
|
+
aria-label={t('translations.widgets.translationManager.groupLabel', 'Translations')}
|
|
93
|
+
>
|
|
94
|
+
<div className="flex h-full flex-col">
|
|
95
|
+
<div className="flex items-start justify-between gap-3 border-b px-4 py-3">
|
|
96
|
+
<div className="space-y-1">
|
|
97
|
+
<h2 className="font-semibold">
|
|
98
|
+
{t('translations.widgets.translationManager.groupLabel', 'Translations')}
|
|
99
|
+
</h2>
|
|
100
|
+
<p className="text-sm text-muted-foreground">
|
|
101
|
+
{t('translations.widgets.translationManager.groupDescription', 'Manage translations for this record across supported locales.')}
|
|
102
|
+
</p>
|
|
103
|
+
</div>
|
|
104
|
+
<Button
|
|
105
|
+
variant="ghost"
|
|
106
|
+
size="icon"
|
|
107
|
+
onClick={() => setOpen(false)}
|
|
108
|
+
aria-label={t('ui.dialog.close.ariaLabel', 'Close')}
|
|
109
|
+
>
|
|
110
|
+
<X className="size-4" />
|
|
111
|
+
</Button>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="flex-1 overflow-y-auto px-4 py-4">
|
|
114
|
+
<TranslationManager
|
|
115
|
+
mode="embedded"
|
|
116
|
+
compact
|
|
117
|
+
entityType={entityType}
|
|
118
|
+
recordId={recordId}
|
|
119
|
+
baseValues={data}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 border-t px-4 py-3">
|
|
123
|
+
<Link
|
|
124
|
+
href={`/backend/entities/system/${encodeURIComponent(entityType!)}`}
|
|
125
|
+
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
126
|
+
>
|
|
127
|
+
<Languages className="size-3" />
|
|
128
|
+
{t('translations.widgets.translationManager.customFieldLabels', 'Custom fields translations')}
|
|
129
|
+
<ExternalLink className="size-2.5" />
|
|
130
|
+
</Link>
|
|
131
|
+
<Link
|
|
132
|
+
href="/backend/config/translations"
|
|
133
|
+
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
134
|
+
>
|
|
135
|
+
<Languages className="size-3" />
|
|
136
|
+
{t('translations.widgets.translationManager.fullManager', 'Translation manager')}
|
|
137
|
+
<ExternalLink className="size-2.5" />
|
|
138
|
+
</Link>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</>
|
|
143
|
+
) : null}
|
|
144
|
+
</>
|
|
73
145
|
)
|
|
74
146
|
}
|
|
@@ -1,45 +1,31 @@
|
|
|
1
1
|
import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
|
|
2
|
-
import {
|
|
3
|
-
import { translatableFields as dictionaryFields } from '../../dictionaries/translations'
|
|
4
|
-
import { translatableFields as entitiesFields } from '../../entities/translations'
|
|
5
|
-
import { translatableFields as resourcesFields } from '../../resources/translations'
|
|
2
|
+
import { getTranslatableFieldsRegistry } from '@open-mercato/shared/lib/localization/translatable-fields'
|
|
6
3
|
|
|
7
4
|
const WIDGET_ID = 'translations.injection.translation-manager'
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
...catalogFields,
|
|
11
|
-
...dictionaryFields,
|
|
12
|
-
...entitiesFields,
|
|
13
|
-
...resourcesFields,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const ENTRY_TEMPLATE = {
|
|
17
|
-
widgetId: WIDGET_ID,
|
|
18
|
-
kind: 'group' as const,
|
|
19
|
-
column: 2 as const,
|
|
20
|
-
groupLabel: 'translations.widgets.translationManager.groupLabel',
|
|
21
|
-
groupDescription: 'translations.widgets.translationManager.groupDescription',
|
|
22
|
-
priority: 40,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const table: ModuleInjectionTable = {}
|
|
26
|
-
for (const entityType of Object.keys(allFields)) {
|
|
6
|
+
function addEntitySpots(table: ModuleInjectionTable, entityType: string): void {
|
|
27
7
|
const [module, entitySlug] = entityType.split(':')
|
|
28
|
-
if (!module || !entitySlug)
|
|
8
|
+
if (!module || !entitySlug) return
|
|
29
9
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
table[fullSpot] = [{ ...ENTRY_TEMPLATE }]
|
|
10
|
+
const fullSpot = `crud-form:${module}.${entitySlug}:header`
|
|
11
|
+
table[fullSpot] = WIDGET_ID
|
|
33
12
|
|
|
34
|
-
// Short form: crud-form:catalog.product (hardcoded in some pages)
|
|
35
13
|
const prefix = `${module}_`
|
|
36
|
-
if (entitySlug.startsWith(prefix))
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
14
|
+
if (!entitySlug.startsWith(prefix)) return
|
|
15
|
+
|
|
16
|
+
const shortSpot = `crud-form:${module}.${entitySlug.slice(prefix.length)}:header`
|
|
17
|
+
if (shortSpot !== fullSpot) {
|
|
18
|
+
table[shortSpot] = WIDGET_ID
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildInjectionTable(allFields: Record<string, string[]> = getTranslatableFieldsRegistry()): ModuleInjectionTable {
|
|
23
|
+
const table: ModuleInjectionTable = {}
|
|
24
|
+
for (const entityType of Object.keys(allFields)) {
|
|
25
|
+
addEntitySpots(table, entityType)
|
|
41
26
|
}
|
|
27
|
+
return table
|
|
42
28
|
}
|
|
43
29
|
|
|
44
|
-
export const injectionTable: ModuleInjectionTable =
|
|
30
|
+
export const injectionTable: ModuleInjectionTable = buildInjectionTable()
|
|
45
31
|
export default injectionTable
|