ソースを参照

style(settings): fold the Happ settings into four tabs

Seven tabs, several holding one or two settings, made the page hard to
scan. The encrypted-links switch moves above the tabs under
auto-detection, the colour profile joins the banners under Appearance &
Theme, and Android per-app proxy joins Network & TUN Engine. The QR
modal's settings link no longer needs a happTab selector, and the three
orphaned tab-label keys are dropped from every locale.
MHSanaei 6 時間 前
親
コミット
8f47b53879

+ 1 - 1
frontend/src/pages/clients/ClientQrModal.tsx

@@ -50,7 +50,7 @@ type QrVariant = 'standard' | 'happ';
 type HappError = 'too_long' | 'unavailable' | null;
 
 const HAPP_CRYPT5_PREFIX = 'happ://crypt5/';
-const HAPP_SETTINGS_PATH = '/settings?subscriptionTab=happ&happTab=links#subscription';
+const HAPP_SETTINGS_PATH = '/settings?subscriptionTab=happ#subscription';
 // QrPanel encodes at error level L; QR version 40 holds 2953 UTF-8 bytes at that level.
 const HAPP_QR_MAX_BYTES = 2953;
 const UTF8_ENCODER = new TextEncoder();

+ 98 - 131
frontend/src/pages/settings/HappSettingsContent.tsx

@@ -6,9 +6,6 @@ import {
   BuildOutlined,
   CloudSyncOutlined,
   DesktopOutlined,
-  LinkOutlined,
-  MobileOutlined,
-  NotificationOutlined,
   ThunderboltOutlined,
 } from '@ant-design/icons';
 import type { AllSetting } from '@/models/setting';
@@ -22,7 +19,6 @@ interface HappSettingsContentProps {
   updateSetting: (patch: Partial<AllSetting>) => void;
   isMobile: boolean;
   remoteSourceBadge: (val: string) => React.ReactNode;
-  defaultActiveTab?: 'routing' | 'links';
 }
 
 export default function HappSettingsContent({
@@ -30,7 +26,6 @@ export default function HappSettingsContent({
   updateSetting,
   isMobile,
   remoteSourceBadge,
-  defaultActiveTab = 'routing',
 }: HappSettingsContentProps) {
   const { t } = useTranslation();
   // Generator choices stay local until Apply updates the draft; page Save persists it.
@@ -65,17 +60,27 @@ export default function HappSettingsContent({
         />
       </SettingListItem>
 
+      <SettingListItem
+        paddings="small"
+        title={t('pages.settings.happLinkEnable')}
+        description={t('pages.settings.happLinkEnableDesc')}
+      >
+        <Switch
+          checked={allSetting.happLinkEnable}
+          onChange={(v) => updateSetting({ happLinkEnable: v })}
+        />
+      </SettingListItem>
+
       <Tabs
         type="card"
         size="small"
-        defaultActiveKey={defaultActiveTab}
         items={[
           {
             key: 'routing',
-            label: (
-              <span>
-                <BranchesOutlined /> {!isMobile && t('pages.settings.subHappGroupRouting')}
-              </span>
+            label: catTabLabel(
+              <BranchesOutlined />,
+              t('pages.settings.subHappGroupRouting'),
+              isMobile,
             ),
             children: (
               <>
@@ -173,27 +178,11 @@ export default function HappSettingsContent({
             ),
           },
           {
-            key: 'links',
-            label: catTabLabel(<LinkOutlined />, t('pages.settings.subHappGroupLinks'), isMobile),
-            children: (
-              <SettingListItem
-                paddings="small"
-                title={t('pages.settings.happLinkEnable')}
-                description={t('pages.settings.happLinkEnableDesc')}
-              >
-                <Switch
-                  checked={allSetting.happLinkEnable}
-                  onChange={(v) => updateSetting({ happLinkEnable: v })}
-                />
-              </SettingListItem>
-            ),
-          },
-          {
-            key: 'banners',
-            label: (
-              <span>
-                <NotificationOutlined /> {!isMobile && t('pages.settings.subHappGroupBanners')}
-              </span>
+            key: 'appearance',
+            label: catTabLabel(
+              <DesktopOutlined />,
+              t('pages.settings.subHappGroupThemes'),
+              isMobile,
             ),
             children: (
               <>
@@ -285,15 +274,59 @@ export default function HappSettingsContent({
                     onChange={(v) => updateSetting({ subHappNotificationExpire: v })}
                   />
                 </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subHappColorProfile')}
+                  description={t('pages.settings.subHappColorProfileDesc')}
+                >
+                  <Space orientation="vertical" style={{ width: '100%' }}>
+                    <Input
+                      value={allSetting.subHappColorProfile}
+                      placeholder='{"serverRowBackgroundColor":"#21003D67"} or resetcolors'
+                      onChange={(e) => updateSetting({ subHappColorProfile: e.target.value })}
+                    />
+                    <Space wrap size="small">
+                      <Button
+                        size="small"
+                        onClick={() => updateSetting({ subHappColorProfile: 'resetcolors' })}
+                      >
+                        {t('reset')}
+                      </Button>
+                      <Button
+                        size="small"
+                        onClick={() =>
+                          updateSetting({
+                            subHappColorProfile:
+                              '{"serverRowBackgroundColor":"#21003D67","cardBackgroundColor":"#120023B3"}',
+                          })
+                        }
+                      >
+                        Violet
+                      </Button>
+                      <Button
+                        size="small"
+                        onClick={() =>
+                          updateSetting({
+                            subHappColorProfile:
+                              '{"serverRowBackgroundColor":"#002B3667","cardBackgroundColor":"#001F27B3"}',
+                          })
+                        }
+                      >
+                        Turquoise
+                      </Button>
+                    </Space>
+                  </Space>
+                </SettingListItem>
               </>
             ),
           },
           {
             key: 'network',
-            label: (
-              <span>
-                <ThunderboltOutlined /> {!isMobile && t('pages.settings.subHappGroupNetwork')}
-              </span>
+            label: catTabLabel(
+              <ThunderboltOutlined />,
+              t('pages.settings.subHappGroupNetwork'),
+              isMobile,
             ),
             children: (
               <>
@@ -422,70 +455,45 @@ export default function HappSettingsContent({
                     ]}
                   />
                 </SettingListItem>
-              </>
-            ),
-          },
-          {
-            key: 'themes',
-            label: (
-              <span>
-                <DesktopOutlined /> {!isMobile && t('pages.settings.subHappGroupThemes')}
-              </span>
-            ),
-            children: (
-              <>
+
                 <SettingListItem
                   paddings="small"
-                  title={t('pages.settings.subHappColorProfile')}
-                  description={t('pages.settings.subHappColorProfileDesc')}
+                  title={t('pages.settings.subHappPerAppMode')}
+                  description={t('pages.settings.subHappPerAppModeDesc')}
                 >
-                  <Space orientation="vertical" style={{ width: '100%' }}>
-                    <Input
-                      value={allSetting.subHappColorProfile}
-                      placeholder='{"serverRowBackgroundColor":"#21003D67"} or resetcolors'
-                      onChange={(e) => updateSetting({ subHappColorProfile: e.target.value })}
-                    />
-                    <Space wrap size="small">
-                      <Button
-                        size="small"
-                        onClick={() => updateSetting({ subHappColorProfile: 'resetcolors' })}
-                      >
-                        {t('reset')}
-                      </Button>
-                      <Button
-                        size="small"
-                        onClick={() =>
-                          updateSetting({
-                            subHappColorProfile:
-                              '{"serverRowBackgroundColor":"#21003D67","cardBackgroundColor":"#120023B3"}',
-                          })
-                        }
-                      >
-                        Violet
-                      </Button>
-                      <Button
-                        size="small"
-                        onClick={() =>
-                          updateSetting({
-                            subHappColorProfile:
-                              '{"serverRowBackgroundColor":"#002B3667","cardBackgroundColor":"#001F27B3"}',
-                          })
-                        }
-                      >
-                        Turquoise
-                      </Button>
-                    </Space>
-                  </Space>
+                  <Select
+                    value={allSetting.subHappPerAppMode || 'off'}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subHappPerAppMode: v })}
+                    options={[
+                      { value: 'off', label: t('pages.settings.subHappPerAppOff') },
+                      { value: 'on', label: t('pages.settings.subHappPerAppOn') },
+                      { value: 'bypass', label: t('pages.settings.subHappPerAppBypass') },
+                    ]}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subHappPerAppList')}
+                  description={t('pages.settings.subHappPerAppListDesc')}
+                >
+                  <Input.TextArea
+                    value={allSetting.subHappPerAppList}
+                    rows={4}
+                    placeholder="org.telegram.messenger, com.google.android.youtube"
+                    onChange={(e) => updateSetting({ subHappPerAppList: e.target.value })}
+                  />
                 </SettingListItem>
               </>
             ),
           },
           {
             key: 'failover',
-            label: (
-              <span>
-                <CloudSyncOutlined /> {!isMobile && t('pages.settings.subHappGroupFailover')}
-              </span>
+            label: catTabLabel(
+              <CloudSyncOutlined />,
+              t('pages.settings.subHappGroupFailover'),
+              isMobile,
             ),
             children: (
               <>
@@ -538,47 +546,6 @@ export default function HappSettingsContent({
               </>
             ),
           },
-          {
-            key: 'android',
-            label: (
-              <span>
-                <MobileOutlined /> {!isMobile && t('pages.settings.subHappGroupAndroid')}
-              </span>
-            ),
-            children: (
-              <>
-                <SettingListItem
-                  paddings="small"
-                  title={t('pages.settings.subHappPerAppMode')}
-                  description={t('pages.settings.subHappPerAppModeDesc')}
-                >
-                  <Select
-                    value={allSetting.subHappPerAppMode || 'off'}
-                    style={{ width: '100%' }}
-                    onChange={(v) => updateSetting({ subHappPerAppMode: v })}
-                    options={[
-                      { value: 'off', label: t('pages.settings.subHappPerAppOff') },
-                      { value: 'on', label: t('pages.settings.subHappPerAppOn') },
-                      { value: 'bypass', label: t('pages.settings.subHappPerAppBypass') },
-                    ]}
-                  />
-                </SettingListItem>
-
-                <SettingListItem
-                  paddings="small"
-                  title={t('pages.settings.subHappPerAppList')}
-                  description={t('pages.settings.subHappPerAppListDesc')}
-                >
-                  <Input.TextArea
-                    value={allSetting.subHappPerAppList}
-                    rows={4}
-                    placeholder="org.telegram.messenger, com.google.android.youtube"
-                    onChange={(e) => updateSetting({ subHappPerAppList: e.target.value })}
-                  />
-                </SettingListItem>
-              </>
-            ),
-          },
         ]}
       />
 

+ 0 - 2
frontend/src/pages/settings/SubscriptionGeneralTab.tsx

@@ -414,8 +414,6 @@ export default function SubscriptionGeneralTab({
               updateSetting={updateSetting}
               isMobile={isMobile}
               remoteSourceBadge={remoteSourceBadge}
-              // QR settings links select the link control; ordinary Happ visits still start on routing.
-              defaultActiveTab={searchParams.get('happTab') === 'links' ? 'links' : 'routing'}
             />
           ),
         },

+ 1 - 1
frontend/src/test/client-qr-modal.test.tsx

@@ -241,7 +241,7 @@ describe('ClientQrModal Happ presentation', () => {
     expect(view.onOpenChange).toHaveBeenCalledOnce();
     expect(view.onOpenChange).toHaveBeenCalledWith(false);
     expect(screen.getByTestId('location').textContent).toBe(
-      '/settings?subscriptionTab=happ&happTab=links#subscription',
+      '/settings?subscriptionTab=happ#subscription',
     );
     expect(HttpUtil.post).not.toHaveBeenCalled();
   });

+ 3 - 35
frontend/src/test/subscription-general-tab.test.tsx

@@ -144,7 +144,7 @@ describe('SubscriptionGeneralTab', () => {
   });
 
   it.each([false, true])(
-    'updates the Happ link gate from its own tab when stored as %s',
+    'updates the Happ link gate beside auto-detection when stored as %s',
     (enabled) => {
       const updateSetting = vi.fn();
 
@@ -158,11 +158,6 @@ describe('SubscriptionGeneralTab', () => {
       );
 
       fireEvent.click(screen.getByRole('tab', { name: /Happ/ }));
-      expect(
-        screen.getByRole('tab', { name: /Routing & Rules/ }).getAttribute('aria-selected'),
-      ).toBe('true');
-      expect(screen.queryByRole('switch', { name: 'Encrypted subscription links' })).toBeNull();
-      fireEvent.click(screen.getByRole('tab', { name: /Subscription Links/ }));
       const linkSwitch = screen.getByRole('switch', { name: 'Encrypted subscription links' });
       expect(linkSwitch.getAttribute('aria-checked')).toBe(String(enabled));
       expect(updateSetting).not.toHaveBeenCalled();
@@ -172,51 +167,24 @@ describe('SubscriptionGeneralTab', () => {
     },
   );
 
-  it('opens the Happ link tab from the QR settings deep link without enabling generation', () => {
+  it('shows the Happ link gate from the QR settings deep link without enabling generation', () => {
     const updateSetting = vi.fn();
 
     renderWithProviders(
-      <MemoryRouter initialEntries={['/settings?subscriptionTab=happ&happTab=links#subscription']}>
+      <MemoryRouter initialEntries={['/settings?subscriptionTab=happ#subscription']}>
         <SubscriptionGeneralTab
           allSetting={new AllSetting({ happLinkEnable: false })}
           updateSetting={updateSetting}
         />
-        <LocationProbe />
       </MemoryRouter>,
     );
 
     expect(screen.getByRole('tab', { name: /Happ/ }).getAttribute('aria-selected')).toBe('true');
-    expect(
-      screen.getByRole('tab', { name: /Subscription Links/ }).getAttribute('aria-selected'),
-    ).toBe('true');
     expect(
       screen
         .getByRole('switch', { name: 'Encrypted subscription links' })
         .getAttribute('aria-checked'),
     ).toBe('false');
-    expect(screen.getByTestId('location').textContent).toBe(
-      '/settings?subscriptionTab=happ&happTab=links#subscription',
-    );
     expect(updateSetting).not.toHaveBeenCalled();
   });
-
-  it.each(['', '&happTab=unknown'])(
-    'keeps the routing default for a general Happ deep link %s',
-    (query) => {
-      const updateSetting = vi.fn();
-
-      renderWithProviders(
-        <MemoryRouter initialEntries={['/settings?subscriptionTab=happ' + query + '#subscription']}>
-          <SubscriptionGeneralTab allSetting={new AllSetting()} updateSetting={updateSetting} />
-        </MemoryRouter>,
-      );
-
-      expect(screen.getByRole('tab', { name: /Happ/ }).getAttribute('aria-selected')).toBe('true');
-      expect(
-        screen.getByRole('tab', { name: /Routing & Rules/ }).getAttribute('aria-selected'),
-      ).toBe('true');
-      expect(screen.queryByRole('switch', { name: 'Encrypted subscription links' })).toBeNull();
-      expect(updateSetting).not.toHaveBeenCalled();
-    },
-  );
 });

+ 0 - 3
internal/web/translation/ar-EG.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "IPs / CIDRs عبر البروكسي",
       "subHappBlockIPs": "IPs / CIDRs محظورة",
       "subHappDeeplinkGenerated": "تم إنشاء الرابط العميق وتطبيقه على قواعد التوجيه",
-      "subHappGroupLinks": "روابط الاشتراك",
       "subHappGroupRouting": "التوجيه والقواعد",
-      "subHappGroupBanners": "اللافتات والإعلانات",
       "subHappGroupNetwork": "الشبكة ومحرك TUN",
       "subHappGroupThemes": "المظهر والسمة",
       "subHappGroupFailover": "الترحيل وإدارة التطبيق",
-      "subHappGroupAndroid": "البروكسي لكل تطبيق على Android",
       "discordSettings": "بوت ديسكورد",
       "discordBotEnable": "تفعيل إشعارات ديسكورد",
       "discordBotEnableDesc": "إرسال تنبيهات النظام والأحداث إلى قناة ديسكورد عبر البوت",

+ 0 - 3
internal/web/translation/en-US.json

@@ -1761,13 +1761,10 @@
       "subHappProxyIPs": "Proxy IPs / CIDRs",
       "subHappBlockIPs": "Blocked IPs / CIDRs",
       "subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
-      "subHappGroupLinks": "Subscription Links",
       "subHappGroupRouting": "Routing & Rules",
-      "subHappGroupBanners": "Banners & Announcements",
       "subHappGroupNetwork": "Network & TUN Engine",
       "subHappGroupThemes": "Appearance & Theme",
       "subHappGroupFailover": "Migration & App Management",
-      "subHappGroupAndroid": "Android Per-App Proxy",
       "discordSettings": "Discord Bot",
       "discordBotEnable": "Enable Discord Notifications",
       "discordBotEnableDesc": "Send system and event alerts to a Discord channel via bot",

+ 0 - 3
internal/web/translation/es-ES.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "IPs / CIDR por proxy",
       "subHappBlockIPs": "IPs / CIDR bloqueados",
       "subHappDeeplinkGenerated": "Deeplink generado y aplicado a las reglas de enrutamiento",
-      "subHappGroupLinks": "Enlaces de suscripción",
       "subHappGroupRouting": "Enrutamiento y reglas",
-      "subHappGroupBanners": "Banners y anuncios",
       "subHappGroupNetwork": "Red y motor TUN",
       "subHappGroupThemes": "Apariencia y tema",
       "subHappGroupFailover": "Migración y gestión de la app",
-      "subHappGroupAndroid": "Proxy por aplicación en Android",
       "discordSettings": "Bot de Discord",
       "discordBotEnable": "Habilitar notificaciones de Discord",
       "discordBotEnableDesc": "Enviar alertas del sistema y eventos a un canal de Discord a través del bot",

+ 0 - 3
internal/web/translation/fa-IR.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "آی‌پی‌های پراکسی (Proxy CIDRs)",
       "subHappBlockIPs": "آی‌پی‌های مسدود (Block CIDRs)",
       "subHappDeeplinkGenerated": "دیپ‌لینک تولید و در قوانین روتینگ اعمال شد",
-      "subHappGroupLinks": "لینک‌های اشتراک",
       "subHappGroupRouting": "قوانین و روتینگ",
-      "subHappGroupBanners": "بنرها و اعلانات",
       "subHappGroupNetwork": "تنظیمات شبکه و TUN",
       "subHappGroupThemes": "ظاهر و پوسته برنامه",
       "subHappGroupFailover": "مهاجرت و مدیریت برنامه",
-      "subHappGroupAndroid": "پراکسی برنامه‌های اندروید",
       "discordSettings": "ربات دیسکورد",
       "discordBotEnable": "فعال‌سازی اعلان‌های دیسکورد",
       "discordBotEnableDesc": "ارسال هشدارهای سیستم و رویدادها به کانال دیسکورد از طریق ربات",

+ 0 - 3
internal/web/translation/id-ID.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "IP / CIDR Proxy",
       "subHappBlockIPs": "IP / CIDR Diblokir",
       "subHappDeeplinkGenerated": "Deeplink dibuat dan diterapkan ke aturan routing",
-      "subHappGroupLinks": "Tautan Langganan",
       "subHappGroupRouting": "Routing & Aturan",
-      "subHappGroupBanners": "Banner & Pengumuman",
       "subHappGroupNetwork": "Jaringan & Mesin TUN",
       "subHappGroupThemes": "Tampilan & Tema",
       "subHappGroupFailover": "Migrasi & Manajemen Aplikasi",
-      "subHappGroupAndroid": "Proxy Per-Aplikasi Android",
       "discordSettings": "Bot Discord",
       "discordBotEnable": "Aktifkan Notifikasi Discord",
       "discordBotEnableDesc": "Kirim peringatan sistem dan acara ke saluran Discord melalui bot",

+ 0 - 3
internal/web/translation/ja-JP.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "プロキシする IP / CIDR",
       "subHappBlockIPs": "ブロックする IP / CIDR",
       "subHappDeeplinkGenerated": "ディープリンクを生成し、ルーティングルールに適用しました",
-      "subHappGroupLinks": "サブスクリプションリンク",
       "subHappGroupRouting": "ルーティングとルール",
-      "subHappGroupBanners": "バナーとお知らせ",
       "subHappGroupNetwork": "ネットワークと TUN エンジン",
       "subHappGroupThemes": "外観とテーマ",
       "subHappGroupFailover": "移行とアプリ管理",
-      "subHappGroupAndroid": "Android アプリ別プロキシ",
       "discordSettings": "Discord Bot",
       "discordBotEnable": "Discord通知を有効化",
       "discordBotEnableDesc": "Botを介してDiscordチャンネルにシステムおよびイベント通知を送信します",

+ 0 - 3
internal/web/translation/pt-BR.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "IPs / CIDRs via proxy",
       "subHappBlockIPs": "IPs / CIDRs bloqueados",
       "subHappDeeplinkGenerated": "Deeplink gerado e aplicado às regras de roteamento",
-      "subHappGroupLinks": "Links de assinatura",
       "subHappGroupRouting": "Roteamento e regras",
-      "subHappGroupBanners": "Banners e anúncios",
       "subHappGroupNetwork": "Rede e mecanismo TUN",
       "subHappGroupThemes": "Aparência e tema",
       "subHappGroupFailover": "Migração e gerenciamento do app",
-      "subHappGroupAndroid": "Proxy por aplicativo no Android",
       "discordSettings": "Bot do Discord",
       "discordBotEnable": "Ativar notificações do Discord",
       "discordBotEnableDesc": "Enviar alertas de sistema e eventos para um canal do Discord via bot",

+ 0 - 3
internal/web/translation/ru-RU.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "Проксируемые IP / CIDR",
       "subHappBlockIPs": "Заблокированные IP / CIDR",
       "subHappDeeplinkGenerated": "Диплинк сгенерирован и применен к правилам маршрутизации",
-      "subHappGroupLinks": "Ссылки подписки",
       "subHappGroupRouting": "Маршрутизация и правила",
-      "subHappGroupBanners": "Баннеры и уведомления",
       "subHappGroupNetwork": "Сетевые настройки и TUN",
       "subHappGroupThemes": "Внешний вид и темы",
       "subHappGroupFailover": "Миграция и управление",
-      "subHappGroupAndroid": "Прокси приложений Android",
       "discordSettings": "Discord бот",
       "discordBotEnable": "Включить уведомления в Discord",
       "discordBotEnableDesc": "Отправлять оповещения о событиях в канал Discord через бота",

+ 0 - 3
internal/web/translation/tr-TR.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "Proxy IP'ler / CIDR'ler",
       "subHappBlockIPs": "Engellenen IP'ler / CIDR'ler",
       "subHappDeeplinkGenerated": "Derin bağlantı oluşturuldu ve yönlendirme kurallarına uygulandı",
-      "subHappGroupLinks": "Abonelik Bağlantıları",
       "subHappGroupRouting": "Yönlendirme ve Kurallar",
-      "subHappGroupBanners": "Afişler ve Duyurular",
       "subHappGroupNetwork": "Ağ ve TUN Motoru",
       "subHappGroupThemes": "Görünüm ve Tema",
       "subHappGroupFailover": "Geçiş ve Uygulama Yönetimi",
-      "subHappGroupAndroid": "Android Uygulama Başına Proxy",
       "discordSettings": "Discord Botu",
       "discordBotEnable": "Discord Bildirimlerini Etkinleştir",
       "discordBotEnableDesc": "Sistem ve olay uyarılarını bot aracılığıyla bir Discord kanalına gönderin",

+ 0 - 3
internal/web/translation/uk-UA.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "IP / CIDR через проксі",
       "subHappBlockIPs": "Заблоковані IP / CIDR",
       "subHappDeeplinkGenerated": "Діплінк згенеровано та застосовано до правил маршрутизації",
-      "subHappGroupLinks": "Посилання на підписку",
       "subHappGroupRouting": "Маршрутизація та правила",
-      "subHappGroupBanners": "Банери та оголошення",
       "subHappGroupNetwork": "Мережа та рушій TUN",
       "subHappGroupThemes": "Зовнішній вигляд і тема",
       "subHappGroupFailover": "Міграція та керування програмою",
-      "subHappGroupAndroid": "Проксі для окремих програм Android",
       "discordSettings": "Discord бот",
       "discordBotEnable": "Увімкнути сповіщення Discord",
       "discordBotEnableDesc": "Надсилати системні сповіщення та події в канал Discord через бота",

+ 0 - 3
internal/web/translation/vi-VN.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "IP / CIDR proxy",
       "subHappBlockIPs": "IP / CIDR bị chặn",
       "subHappDeeplinkGenerated": "Đã tạo deeplink và áp dụng vào quy tắc định tuyến",
-      "subHappGroupLinks": "Liên kết đăng ký",
       "subHappGroupRouting": "Định tuyến & quy tắc",
-      "subHappGroupBanners": "Biểu ngữ & thông báo",
       "subHappGroupNetwork": "Mạng & lõi TUN",
       "subHappGroupThemes": "Giao diện & chủ đề",
       "subHappGroupFailover": "Di chuyển & quản lý ứng dụng",
-      "subHappGroupAndroid": "Proxy theo ứng dụng Android",
       "discordSettings": "Bot Discord",
       "discordBotEnable": "Bật thông báo Discord",
       "discordBotEnableDesc": "Gửi cảnh báo hệ thống và sự kiện đến kênh Discord qua bot",

+ 0 - 3
internal/web/translation/zh-CN.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "代理 IP / CIDR",
       "subHappBlockIPs": "阻止 IP / CIDR",
       "subHappDeeplinkGenerated": "DeepLink 已生成并填入路由规则",
-      "subHappGroupLinks": "订阅链接",
       "subHappGroupRouting": "路由分流与规则",
-      "subHappGroupBanners": "横幅公告与通知",
       "subHappGroupNetwork": "网络与 TUN 引擎",
       "subHappGroupThemes": "界面与主题外观",
       "subHappGroupFailover": "迁移与客户端管理",
-      "subHappGroupAndroid": "Android 分应用代理",
       "discordSettings": "Discord 机器人",
       "discordBotEnable": "启用 Discord 通知",
       "discordBotEnableDesc": "通过机器人向 Discord 频道发送系统和事件警报",

+ 0 - 3
internal/web/translation/zh-TW.json

@@ -1643,13 +1643,10 @@
       "subHappProxyIPs": "代理 IP / CIDR",
       "subHappBlockIPs": "封鎖 IP / CIDR",
       "subHappDeeplinkGenerated": "已產生深層連結並套用至路由規則",
-      "subHappGroupLinks": "訂閱連結",
       "subHappGroupRouting": "路由與規則",
-      "subHappGroupBanners": "橫幅與公告",
       "subHappGroupNetwork": "網路與 TUN 引擎",
       "subHappGroupThemes": "外觀與主題",
       "subHappGroupFailover": "遷移與應用程式管理",
-      "subHappGroupAndroid": "Android 個別應用程式代理",
       "discordSettings": "Discord 機器人",
       "discordBotEnable": "啟用 Discord 通知",
       "discordBotEnableDesc": "透過機器人向 Discord 頻道發送系統與事件警報",