@vendure/dashboard 3.5.2-master-202511150230 → 3.5.2-master-202511220230
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 +4 -4
- package/src/lib/components/layout/channel-switcher.tsx +6 -2
- package/src/lib/components/layout/content-language-selector.tsx +6 -7
- package/src/lib/components/layout/language-dialog.tsx +26 -13
- package/src/lib/components/layout/manage-languages-dialog.tsx +8 -5
- package/src/lib/components/shared/language-selector.tsx +14 -6
- package/src/lib/hooks/use-sorted-languages.ts +41 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.5.2-master-
|
|
4
|
+
"version": "3.5.2-master-202511220230",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -155,8 +155,8 @@
|
|
|
155
155
|
"@storybook/addon-vitest": "^10.0.0-beta.9",
|
|
156
156
|
"@storybook/react-vite": "^10.0.0-beta.9",
|
|
157
157
|
"@types/node": "^22.13.4",
|
|
158
|
-
"@vendure/common": "^3.5.2-master-
|
|
159
|
-
"@vendure/core": "^3.5.2-master-
|
|
158
|
+
"@vendure/common": "^3.5.2-master-202511220230",
|
|
159
|
+
"@vendure/core": "^3.5.2-master-202511220230",
|
|
160
160
|
"@vitest/browser": "^3.2.4",
|
|
161
161
|
"@vitest/coverage-v8": "^3.2.4",
|
|
162
162
|
"eslint": "^9.19.0",
|
|
@@ -173,5 +173,5 @@
|
|
|
173
173
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
174
174
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
175
175
|
},
|
|
176
|
-
"gitHead": "
|
|
176
|
+
"gitHead": "a7f2e68fea355ab17c8455df426aa754e6525180"
|
|
177
177
|
}
|
|
@@ -17,6 +17,7 @@ import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
|
|
|
17
17
|
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
18
18
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
19
19
|
import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
|
|
20
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
20
21
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
21
22
|
import { cn } from '@/vdb/lib/utils.js';
|
|
22
23
|
import { Trans } from '@lingui/react/macro';
|
|
@@ -65,6 +66,9 @@ export function ChannelSwitcher() {
|
|
|
65
66
|
? [displayChannel, ...channels.filter(ch => ch.id !== displayChannel.id)]
|
|
66
67
|
: channels;
|
|
67
68
|
|
|
69
|
+
// Sort language codes by their formatted names and map to code and label
|
|
70
|
+
const sortedLanguages = useSortedLanguages(displayChannel?.availableLanguageCodes);
|
|
71
|
+
|
|
68
72
|
useEffect(() => {
|
|
69
73
|
if (activeChannel?.availableLanguageCodes) {
|
|
70
74
|
// Ensure the current content language is a valid one for the active
|
|
@@ -150,7 +154,7 @@ export function ChannelSwitcher() {
|
|
|
150
154
|
</div>
|
|
151
155
|
</DropdownMenuSubTrigger>
|
|
152
156
|
<DropdownMenuSubContent>
|
|
153
|
-
{
|
|
157
|
+
{sortedLanguages?.map(({ code: languageCode, label }) => (
|
|
154
158
|
<DropdownMenuItem
|
|
155
159
|
key={`${channel.code}-${languageCode}`}
|
|
156
160
|
onClick={() => setContentLanguage(languageCode)}
|
|
@@ -161,7 +165,7 @@ export function ChannelSwitcher() {
|
|
|
161
165
|
{languageCode.toUpperCase()}
|
|
162
166
|
</span>
|
|
163
167
|
</div>
|
|
164
|
-
<span>{
|
|
168
|
+
<span>{label}</span>
|
|
165
169
|
{contentLanguage === languageCode && (
|
|
166
170
|
<span className="ml-auto text-xs text-muted-foreground">
|
|
167
171
|
<Trans context="active language">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
|
|
2
|
-
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
3
2
|
import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
|
|
3
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
4
4
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
5
5
|
import { cn } from '@/vdb/lib/utils.js';
|
|
6
6
|
|
|
@@ -12,14 +12,13 @@ interface ContentLanguageSelectorProps {
|
|
|
12
12
|
|
|
13
13
|
export function ContentLanguageSelector({ value, onChange, className }: ContentLanguageSelectorProps) {
|
|
14
14
|
const serverConfig = useServerConfig();
|
|
15
|
-
const { formatLanguageName } = useLocalFormat();
|
|
16
15
|
const {
|
|
17
16
|
settings: { contentLanguage },
|
|
18
17
|
setContentLanguage,
|
|
19
18
|
} = useUserSettings();
|
|
20
19
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
20
|
+
// Map languages to code and label, then sort by label
|
|
21
|
+
const sortedLanguages = useSortedLanguages(serverConfig?.availableLanguages);
|
|
23
22
|
|
|
24
23
|
// If no value is provided but languages are available, use the first language
|
|
25
24
|
const currentValue = contentLanguage;
|
|
@@ -36,9 +35,9 @@ export function ContentLanguageSelector({ value, onChange, className }: ContentL
|
|
|
36
35
|
<SelectValue placeholder="Select language" />
|
|
37
36
|
</SelectTrigger>
|
|
38
37
|
<SelectContent>
|
|
39
|
-
{
|
|
40
|
-
<SelectItem key={
|
|
41
|
-
{
|
|
38
|
+
{sortedLanguages.map(({ code, label }) => (
|
|
39
|
+
<SelectItem key={code} value={code}>
|
|
40
|
+
{label}
|
|
42
41
|
</SelectItem>
|
|
43
42
|
))}
|
|
44
43
|
</SelectContent>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CurrencyCode } from '@/vdb/constants.js';
|
|
2
2
|
import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
|
|
3
3
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
4
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
4
5
|
import { useUiLanguageLoader } from '@/vdb/hooks/use-ui-language-loader.js';
|
|
5
6
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
6
7
|
import { Trans } from '@lingui/react/macro';
|
|
7
|
-
import { useState } from 'react';
|
|
8
|
+
import { useMemo, useState } from 'react';
|
|
8
9
|
import { uiConfig } from 'virtual:vendure-ui-config';
|
|
9
10
|
import { Button } from '../ui/button.js';
|
|
10
11
|
import { DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog.js';
|
|
@@ -18,12 +19,24 @@ export function LanguageDialog() {
|
|
|
18
19
|
const { settings, setDisplayLanguage, setDisplayLocale } = useUserSettings();
|
|
19
20
|
const { humanReadableLanguageAndLocale } = useDisplayLocale();
|
|
20
21
|
const availableCurrencyCodes = Object.values(CurrencyCode);
|
|
21
|
-
const { formatCurrency,
|
|
22
|
-
useLocalFormat();
|
|
22
|
+
const { formatCurrency, formatRegionName, formatCurrencyName, formatDate } = useLocalFormat();
|
|
23
23
|
const [selectedCurrency, setSelectedCurrency] = useState<string>('USD');
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
const
|
|
25
|
+
// Map and sort languages by their formatted names
|
|
26
|
+
const sortedLanguages = useSortedLanguages(availableLanguages);
|
|
27
|
+
|
|
28
|
+
// Map and sort locales by their formatted region names
|
|
29
|
+
const sortedLocales = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
availableLocales
|
|
32
|
+
.map(code => ({
|
|
33
|
+
code,
|
|
34
|
+
label: formatRegionName(code),
|
|
35
|
+
}))
|
|
36
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
37
|
+
[availableLocales, formatRegionName],
|
|
38
|
+
);
|
|
39
|
+
|
|
27
40
|
const handleLanguageChange = async (value: string) => {
|
|
28
41
|
setDisplayLanguage(value);
|
|
29
42
|
void loadAndActivateLocale(value);
|
|
@@ -46,10 +59,10 @@ export function LanguageDialog() {
|
|
|
46
59
|
<SelectValue placeholder="Select a language" />
|
|
47
60
|
</SelectTrigger>
|
|
48
61
|
<SelectContent>
|
|
49
|
-
{
|
|
50
|
-
<SelectItem key={
|
|
51
|
-
<span className="uppercase text-muted-foreground">{
|
|
52
|
-
<span>{
|
|
62
|
+
{sortedLanguages.map(({ code, label }) => (
|
|
63
|
+
<SelectItem key={code} value={code} className="flex gap-1">
|
|
64
|
+
<span className="uppercase text-muted-foreground">{code}</span>
|
|
65
|
+
<span>{label}</span>
|
|
53
66
|
</SelectItem>
|
|
54
67
|
))}
|
|
55
68
|
</SelectContent>
|
|
@@ -64,10 +77,10 @@ export function LanguageDialog() {
|
|
|
64
77
|
<SelectValue placeholder="Select a locale" />
|
|
65
78
|
</SelectTrigger>
|
|
66
79
|
<SelectContent>
|
|
67
|
-
{
|
|
68
|
-
<SelectItem key={
|
|
69
|
-
<span className="uppercase text-muted-foreground">{
|
|
70
|
-
<span>{
|
|
80
|
+
{sortedLocales.map(({ code, label }) => (
|
|
81
|
+
<SelectItem key={code} value={code} className="flex gap-1">
|
|
82
|
+
<span className="uppercase text-muted-foreground">{code}</span>
|
|
83
|
+
<span>{label}</span>
|
|
71
84
|
</SelectItem>
|
|
72
85
|
))}
|
|
73
86
|
</SelectContent>
|
|
@@ -17,6 +17,7 @@ import { graphql } from '@/vdb/graphql/graphql.js';
|
|
|
17
17
|
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
18
18
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
19
19
|
import { usePermissions } from '@/vdb/hooks/use-permissions.js';
|
|
20
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
20
21
|
import { Trans } from '@lingui/react/macro';
|
|
21
22
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
22
23
|
import { AlertCircle, Lock } from 'lucide-react';
|
|
@@ -115,6 +116,9 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
115
116
|
const [channelLanguages, setChannelLanguages] = useState<string[]>([]);
|
|
116
117
|
const [channelDefaultLanguage, setChannelDefaultLanguage] = useState<string>('');
|
|
117
118
|
|
|
119
|
+
// Map and sort channel languages by their formatted names
|
|
120
|
+
const sortedChannelLanguages = useSortedLanguages(channelLanguages || []);
|
|
121
|
+
|
|
118
122
|
// Queries
|
|
119
123
|
const {
|
|
120
124
|
data: globalSettingsData,
|
|
@@ -363,7 +367,7 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
363
367
|
)}
|
|
364
368
|
</div>
|
|
365
369
|
|
|
366
|
-
{
|
|
370
|
+
{sortedChannelLanguages.length > 0 && (
|
|
367
371
|
<div>
|
|
368
372
|
<Label className="text-sm font-medium mb-2 block">
|
|
369
373
|
<Trans>Default Language</Trans>
|
|
@@ -377,10 +381,9 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
377
381
|
<SelectValue placeholder="Select default language" />
|
|
378
382
|
</SelectTrigger>
|
|
379
383
|
<SelectContent>
|
|
380
|
-
{
|
|
381
|
-
<SelectItem key={
|
|
382
|
-
{
|
|
383
|
-
{languageCode.toUpperCase()})
|
|
384
|
+
{sortedChannelLanguages.map(({ code, label }) => (
|
|
385
|
+
<SelectItem key={code} value={code}>
|
|
386
|
+
{label} ({code.toUpperCase()})
|
|
384
387
|
</SelectItem>
|
|
385
388
|
))}
|
|
386
389
|
</SelectContent>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { api } from '@/vdb/graphql/api.js';
|
|
2
2
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
3
|
-
import {
|
|
3
|
+
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
4
4
|
import { useLingui } from '@lingui/react/macro';
|
|
5
5
|
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { useMemo } from 'react';
|
|
6
7
|
import { MultiSelect } from './multi-select.js';
|
|
7
8
|
|
|
8
9
|
const availableGlobalLanguages = graphql(`
|
|
@@ -26,14 +27,21 @@ export function LanguageSelector<T extends boolean>(props: LanguageSelectorProps
|
|
|
26
27
|
queryFn: () => api.query(availableGlobalLanguages),
|
|
27
28
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
28
29
|
});
|
|
29
|
-
const { formatLanguageName } = useLocalFormat();
|
|
30
30
|
const { value, onChange, multiple, availableLanguageCodes } = props;
|
|
31
31
|
const { t } = useLingui();
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const sortedLanguages = useSortedLanguages(
|
|
34
|
+
availableLanguageCodes ?? data?.globalSettings.availableLanguages ?? undefined,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const items = useMemo(
|
|
38
|
+
() =>
|
|
39
|
+
sortedLanguages.map(language => ({
|
|
40
|
+
value: language.code,
|
|
41
|
+
label: language.label,
|
|
42
|
+
})),
|
|
43
|
+
[sortedLanguages],
|
|
44
|
+
);
|
|
37
45
|
|
|
38
46
|
return (
|
|
39
47
|
<MultiSelect
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useLocalFormat } from './use-local-format.js';
|
|
4
|
+
|
|
5
|
+
export interface SortedLanguage {
|
|
6
|
+
code: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @description
|
|
12
|
+
* This hook takes an array of language codes and returns a sorted array of language objects
|
|
13
|
+
* with code and localized label, sorted alphabetically by the label.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const sortedLanguages = useSortedLanguages(['en', 'fr', 'de']);
|
|
18
|
+
* // Returns: [{ code: 'de', label: 'German' }, { code: 'en', label: 'English' }, { code: 'fr', label: 'French' }]
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @param availableLanguages - Array of language codes to sort
|
|
22
|
+
* @returns Sorted array of language objects with code and label
|
|
23
|
+
*
|
|
24
|
+
* @docsCategory hooks
|
|
25
|
+
* @docsPage useSortedLanguages
|
|
26
|
+
* @docsWeight 0
|
|
27
|
+
*/
|
|
28
|
+
export function useSortedLanguages(availableLanguages?: string[] | null): SortedLanguage[] {
|
|
29
|
+
const { formatLanguageName } = useLocalFormat();
|
|
30
|
+
|
|
31
|
+
return useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
(availableLanguages ?? [])
|
|
34
|
+
.map(code => ({
|
|
35
|
+
code,
|
|
36
|
+
label: formatLanguageName(code),
|
|
37
|
+
}))
|
|
38
|
+
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
39
|
+
[availableLanguages, formatLanguageName],
|
|
40
|
+
);
|
|
41
|
+
}
|