Parcourir la source

feat: added language switch menu

BaiLuoYan il y a 1 mois
Parent
commit
1e59c7005c

+ 29 - 16
src/components/LanguageSwitch.tsx

@@ -1,22 +1,35 @@
-import { Select } from 'antd';
-import { useTranslation } from 'react-i18next';
+import { Dropdown, type MenuProps } from 'antd'
+import { useTranslation } from 'react-i18next'
 
-const { Option } = Select;
+const LANGUAGES = [
+    { code: 'en-US', label: 'English' },
+    { code: 'zh-CN', label: '简体中文' },
+    { code: 'fa-IR', label: 'فارسی' },
+]
 
-const LanguageSwitch = () => {
-    const { i18n } = useTranslation();
+interface LanguageSwitchProps {
+    className?: string
+}
 
-    const handleChange = (value: string) => {
-        i18n.changeLanguage(value);
-    };
+export function LanguageSwitch({ className }: LanguageSwitchProps) {
+    const { i18n, t } = useTranslation()
+
+    const menuProps: MenuProps = {
+        items: LANGUAGES.map((lang) => ({
+            key: lang.code,
+            label: lang.label,
+        })),
+        selectedKeys: [i18n.language],
+        onClick: ({ key }) => i18n.changeLanguage(key),
+    }
 
     return (
-        <Select value={i18n.language} onChange={handleChange} style={{ width: 120 }}>
-            <Option value="fa-IR">فارسی</Option>
-            <Option value="en-US">English</Option>
-            <Option value="zh-CN">简体中文</Option>
-        </Select>
-    );
-};
+        <Dropdown menu={menuProps} trigger={['click']} placement="bottomRight">
+            <button type="button" className={className}>
+                {t('components.topbar.language')}
+            </button>
+        </Dropdown>
+    )
+}
 
-export default LanguageSwitch;
+export default LanguageSwitch

+ 7 - 0
src/components/Topbar/index.tsx

@@ -8,6 +8,7 @@ import logoUnion from '@/assets/iconify/multi-color/logo-union.svg';
 import chevronDownIcon from '@/assets/iconify/single-color/chevron-down.svg';
 import closeIcon from '@/assets/iconify/single-color/close.svg';
 import menuIcon from '@/assets/iconify/single-color/menu.svg';
+import { LanguageSwitch } from '@/components/LanguageSwitch';
 import { useAuth } from '@/hooks/useAuth';
 import { useLoginDialog } from '@/hooks/useLoginDialog';
 import { useResponsive } from '@/hooks/useSize';
@@ -25,6 +26,7 @@ const Topbar = memo(() => {
     const {
         menuContainerRef,
         loginButtonRef,
+        languageButtonRef,
         isMobileMenuOpen,
         isMobileMenuClosing,
         isOverflowMenuOpen,
@@ -110,6 +112,10 @@ const Topbar = memo(() => {
                                 )}
                             </div>
 
+                            <div ref={languageButtonRef}>
+                                <LanguageSwitch className="px-2.5 h-10 flex items-center justify-center transition-colors text-base leading-6 border-none bg-transparent font-normal text-white/80 hover:text-white whitespace-nowrap cursor-pointer" />
+                            </div>
+
                             {!isLoggedIn && (
                                 <button
                                     ref={loginButtonRef}
@@ -170,6 +176,7 @@ const Topbar = memo(() => {
                                     </button>
                                 );
                             })}
+                            <LanguageSwitch className="text-base leading-[1.5] font-normal text-[#999] hover:text-white transition-colors border-none bg-transparent whitespace-nowrap cursor-pointer" />
                             {!isLoggedIn && (
                                 <button
                                     type="button"

+ 6 - 1
src/components/Topbar/useAction.ts

@@ -23,6 +23,7 @@ export function useAction({ menuItems, isMobile, isLoggedIn }: UseActionParams)
     const menuItemsRef = useRef<Map<string, HTMLButtonElement>>(new Map());
     const itemWidthCache = useRef<Map<string, number>>(new Map());
     const loginButtonRef = useRef<HTMLButtonElement>(null);
+    const languageButtonRef = useRef<HTMLDivElement>(null);
     const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
     const [isMobileMenuClosing, setIsMobileMenuClosing] = useState(false);
     const [isOverflowMenuOpen, setIsOverflowMenuOpen] = useState(false);
@@ -40,7 +41,10 @@ export function useAction({ menuItems, isMobile, isLoggedIn }: UseActionParams)
         const loginBtnWidth = loginButtonRef.current
             ? loginButtonRef.current.offsetWidth + MENU_ITEM_GAP
             : 0;
-        const containerWidth = container.offsetWidth - loginBtnWidth;
+        const langBtnWidth = languageButtonRef.current
+            ? languageButtonRef.current.offsetWidth + MENU_ITEM_GAP
+            : 0;
+        const containerWidth = container.offsetWidth - loginBtnWidth - langBtnWidth;
 
         let totalWidth = 0;
         const visible: NavMenuItem[] = [];
@@ -149,6 +153,7 @@ export function useAction({ menuItems, isMobile, isLoggedIn }: UseActionParams)
     return {
         menuContainerRef,
         loginButtonRef,
+        languageButtonRef,
         menuItemsRef,
         isMobileMenuOpen,
         isMobileMenuClosing,

+ 1 - 0
src/locales/en-US/components.ts

@@ -2,6 +2,7 @@ export default {
     topbar: {
         logo: 'NOMO VPN',
         login: 'Login',
+        language: 'Language',
     },
     footerbar: {
         logo: 'NOMO VPN',

+ 1 - 0
src/locales/fa-IR/components.ts

@@ -2,6 +2,7 @@ export default {
     topbar: {
         logo: 'NOMO VPN',
         login: 'ورود',
+        language: 'زبان',
     },
     footerbar: {
         logo: 'NOMO VPN',

+ 1 - 0
src/locales/zh-CN/components.ts

@@ -2,6 +2,7 @@ export default {
     topbar: {
         logo: 'NOMO VPN',
         login: '登录',
+        language: '语言',
     },
     footerbar: {
         logo: 'NOMO VPN',