1
0
Эх сурвалжийг харах

fix(dashboard): Sort languages by their label instead of language label (#3981)

Bibiana Sebestianova 1 сар өмнө
parent
commit
e503f3c026

+ 6 - 2
packages/dashboard/src/lib/components/layout/channel-switcher.tsx

@@ -17,6 +17,7 @@ import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
 import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
+import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
 import { cn } from '@/vdb/lib/utils.js';
 import { Trans } from '@lingui/react/macro';
@@ -65,6 +66,9 @@ export function ChannelSwitcher() {
         ? [displayChannel, ...channels.filter(ch => ch.id !== displayChannel.id)]
         : channels;
 
+    // Sort language codes by their formatted names and map to code and label
+    const sortedLanguages = useSortedLanguages(displayChannel?.availableLanguageCodes);
+
     useEffect(() => {
         if (activeChannel?.availableLanguageCodes) {
             // Ensure the current content language is a valid one for the active
@@ -150,7 +154,7 @@ export function ChannelSwitcher() {
                                                 </div>
                                             </DropdownMenuSubTrigger>
                                             <DropdownMenuSubContent>
-                                                {channel.availableLanguageCodes?.map(languageCode => (
+                                                {sortedLanguages?.map(({ code: languageCode, label }) => (
                                                     <DropdownMenuItem
                                                         key={`${channel.code}-${languageCode}`}
                                                         onClick={() => setContentLanguage(languageCode)}
@@ -161,7 +165,7 @@ export function ChannelSwitcher() {
                                                                 {languageCode.toUpperCase()}
                                                             </span>
                                                         </div>
-                                                        <span>{formatLanguageName(languageCode)}</span>
+                                                        <span>{label}</span>
                                                         {contentLanguage === languageCode && (
                                                             <span className="ml-auto text-xs text-muted-foreground">
                                                                 <Trans context="active language">

+ 6 - 7
packages/dashboard/src/lib/components/layout/content-language-selector.tsx

@@ -1,6 +1,6 @@
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
-import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
 import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
+import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
 import { cn } from '@/vdb/lib/utils.js';
 
@@ -12,14 +12,13 @@ interface ContentLanguageSelectorProps {
 
 export function ContentLanguageSelector({ value, onChange, className }: ContentLanguageSelectorProps) {
     const serverConfig = useServerConfig();
-    const { formatLanguageName } = useLocalFormat();
     const {
         settings: { contentLanguage },
         setContentLanguage,
     } = useUserSettings();
 
-    // Fallback to empty array if serverConfig is null
-    const languages = serverConfig?.availableLanguages || [];
+    // Map languages to code and label, then sort by label
+    const sortedLanguages = useSortedLanguages(serverConfig?.availableLanguages);
 
     // If no value is provided but languages are available, use the first language
     const currentValue = contentLanguage;
@@ -36,9 +35,9 @@ export function ContentLanguageSelector({ value, onChange, className }: ContentL
                 <SelectValue placeholder="Select language" />
             </SelectTrigger>
             <SelectContent>
-                {languages.map(language => (
-                    <SelectItem key={language} value={language}>
-                        {formatLanguageName(language)}
+                {sortedLanguages.map(({ code, label }) => (
+                    <SelectItem key={code} value={code}>
+                        {label}
                     </SelectItem>
                 ))}
             </SelectContent>

+ 26 - 13
packages/dashboard/src/lib/components/layout/language-dialog.tsx

@@ -1,10 +1,11 @@
 import { CurrencyCode } from '@/vdb/constants.js';
 import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
+import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
 import { useUiLanguageLoader } from '@/vdb/hooks/use-ui-language-loader.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
 import { Trans } from '@lingui/react/macro';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
 import { uiConfig } from 'virtual:vendure-ui-config';
 import { Button } from '../ui/button.js';
 import { DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog.js';
@@ -18,12 +19,24 @@ export function LanguageDialog() {
     const { settings, setDisplayLanguage, setDisplayLocale } = useUserSettings();
     const { humanReadableLanguageAndLocale } = useDisplayLocale();
     const availableCurrencyCodes = Object.values(CurrencyCode);
-    const { formatCurrency, formatLanguageName, formatRegionName, formatCurrencyName, formatDate } =
-        useLocalFormat();
+    const { formatCurrency, formatRegionName, formatCurrencyName, formatDate } = useLocalFormat();
     const [selectedCurrency, setSelectedCurrency] = useState<string>('USD');
 
-    const orderedAvailableLanguages = availableLanguages.slice().sort((a, b) => a.localeCompare(b));
-    const orderedAvailableLocales = availableLocales.slice().sort((a, b) => a.localeCompare(b));
+    // Map and sort languages by their formatted names
+    const sortedLanguages = useSortedLanguages(availableLanguages);
+
+    // Map and sort locales by their formatted region names
+    const sortedLocales = useMemo(
+        () =>
+            availableLocales
+                .map(code => ({
+                    code,
+                    label: formatRegionName(code),
+                }))
+                .sort((a, b) => a.label.localeCompare(b.label)),
+        [availableLocales, formatRegionName],
+    );
+
     const handleLanguageChange = async (value: string) => {
         setDisplayLanguage(value);
         void loadAndActivateLocale(value);
@@ -46,10 +59,10 @@ export function LanguageDialog() {
                             <SelectValue placeholder="Select a language" />
                         </SelectTrigger>
                         <SelectContent>
-                            {orderedAvailableLanguages.map(language => (
-                                <SelectItem key={language} value={language} className="flex gap-1">
-                                    <span className="uppercase text-muted-foreground">{language}</span>
-                                    <span>{formatLanguageName(language)}</span>
+                            {sortedLanguages.map(({ code, label }) => (
+                                <SelectItem key={code} value={code} className="flex gap-1">
+                                    <span className="uppercase text-muted-foreground">{code}</span>
+                                    <span>{label}</span>
                                 </SelectItem>
                             ))}
                         </SelectContent>
@@ -64,10 +77,10 @@ export function LanguageDialog() {
                             <SelectValue placeholder="Select a locale" />
                         </SelectTrigger>
                         <SelectContent>
-                            {orderedAvailableLocales.map(locale => (
-                                <SelectItem key={locale} value={locale} className="flex gap-1">
-                                    <span className="uppercase text-muted-foreground">{locale}</span>
-                                    <span>{formatRegionName(locale)}</span>
+                            {sortedLocales.map(({ code, label }) => (
+                                <SelectItem key={code} value={code} className="flex gap-1">
+                                    <span className="uppercase text-muted-foreground">{code}</span>
+                                    <span>{label}</span>
                                 </SelectItem>
                             ))}
                         </SelectContent>

+ 8 - 5
packages/dashboard/src/lib/components/layout/manage-languages-dialog.tsx

@@ -17,6 +17,7 @@ import { graphql } from '@/vdb/graphql/graphql.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
 import { usePermissions } from '@/vdb/hooks/use-permissions.js';
+import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
 import { Trans } from '@lingui/react/macro';
 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { AlertCircle, Lock } from 'lucide-react';
@@ -115,6 +116,9 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
     const [channelLanguages, setChannelLanguages] = useState<string[]>([]);
     const [channelDefaultLanguage, setChannelDefaultLanguage] = useState<string>('');
 
+    // Map and sort channel languages by their formatted names
+    const sortedChannelLanguages = useSortedLanguages(channelLanguages || []);
+
     // Queries
     const {
         data: globalSettingsData,
@@ -363,7 +367,7 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
                                     )}
                                 </div>
 
-                                {channelLanguages.length > 0 && (
+                                {sortedChannelLanguages.length > 0 && (
                                     <div>
                                         <Label className="text-sm font-medium mb-2 block">
                                             <Trans>Default Language</Trans>
@@ -377,10 +381,9 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
                                                 <SelectValue placeholder="Select default language" />
                                             </SelectTrigger>
                                             <SelectContent>
-                                                {channelLanguages.map(languageCode => (
-                                                    <SelectItem key={languageCode} value={languageCode}>
-                                                        {formatLanguageName(languageCode)} (
-                                                        {languageCode.toUpperCase()})
+                                                {sortedChannelLanguages.map(({ code, label }) => (
+                                                    <SelectItem key={code} value={code}>
+                                                        {label} ({code.toUpperCase()})
                                                     </SelectItem>
                                                 ))}
                                             </SelectContent>

+ 14 - 6
packages/dashboard/src/lib/components/shared/language-selector.tsx

@@ -1,8 +1,9 @@
 import { api } from '@/vdb/graphql/api.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
-import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
+import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
 import { useLingui } from '@lingui/react/macro';
 import { useQuery } from '@tanstack/react-query';
+import { useMemo } from 'react';
 import { MultiSelect } from './multi-select.js';
 
 const availableGlobalLanguages = graphql(`
@@ -26,14 +27,21 @@ export function LanguageSelector<T extends boolean>(props: LanguageSelectorProps
         queryFn: () => api.query(availableGlobalLanguages),
         staleTime: 1000 * 60 * 5, // 5 minutes
     });
-    const { formatLanguageName } = useLocalFormat();
     const { value, onChange, multiple, availableLanguageCodes } = props;
     const { t } = useLingui();
 
-    const items = (availableLanguageCodes ?? data?.globalSettings.availableLanguages ?? []).map(language => ({
-        value: language,
-        label: formatLanguageName(language),
-    }));
+    const sortedLanguages = useSortedLanguages(
+        availableLanguageCodes ?? data?.globalSettings.availableLanguages ?? undefined,
+    );
+
+    const items = useMemo(
+        () =>
+            sortedLanguages.map(language => ({
+                value: language.code,
+                label: language.label,
+            })),
+        [sortedLanguages],
+    );
 
     return (
         <MultiSelect

+ 41 - 0
packages/dashboard/src/lib/hooks/use-sorted-languages.ts

@@ -0,0 +1,41 @@
+import { useMemo } from 'react';
+
+import { useLocalFormat } from './use-local-format.js';
+
+export interface SortedLanguage {
+    code: string;
+    label: string;
+}
+
+/**
+ * @description
+ * This hook takes an array of language codes and returns a sorted array of language objects
+ * with code and localized label, sorted alphabetically by the label.
+ *
+ * @example
+ * ```ts
+ * const sortedLanguages = useSortedLanguages(['en', 'fr', 'de']);
+ * // Returns: [{ code: 'de', label: 'German' }, { code: 'en', label: 'English' }, { code: 'fr', label: 'French' }]
+ * ```
+ *
+ * @param availableLanguages - Array of language codes to sort
+ * @returns Sorted array of language objects with code and label
+ *
+ * @docsCategory hooks
+ * @docsPage useSortedLanguages
+ * @docsWeight 0
+ */
+export function useSortedLanguages(availableLanguages?: string[] | null): SortedLanguage[] {
+    const { formatLanguageName } = useLocalFormat();
+
+    return useMemo(
+        () =>
+            (availableLanguages ?? [])
+                .map(code => ({
+                    code,
+                    label: formatLanguageName(code),
+                }))
+                .sort((a, b) => a.label.localeCompare(b.label)),
+        [availableLanguages, formatLanguageName],
+    );
+}