Explorar el Código

Add Subscript page

- Fixed node management page input does not show correctly;
- Tweaked node management page forms layout;
BrettonYe hace 3 meses
padre
commit
957d888661

+ 31 - 1
app/Http/Controllers/User/SubscribeController.php

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
 use App\Models\UserSubscribe;
 use App\Models\UserSubscribeLog;
 use App\Services\ProxyService;
+use App\Services\UserService;
 use App\Utils\IP;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
@@ -16,6 +17,36 @@ class SubscribeController extends Controller
 
     private ProxyService $proxyServer;
 
+    public function __construct(ProxyService $proxyServer)
+    {
+        $this->proxyServer = $proxyServer;
+    }
+
+    public function index(Request $request, string $code)
+    {
+        preg_match('/[0-9A-Za-z]+/', $code, $matches, PREG_UNMATCHED_AS_NULL);
+
+        if (empty($matches) || empty($code)) {
+            return redirect()->route('login');
+        }
+
+        $code = $matches[0];
+        self::$subType = is_numeric($request->input('type')) ? $request->input('type') : null;
+
+        // 检查订阅码是否有效
+        $subscribe = UserSubscribe::whereCode($code)->firstOrFail();
+        $user = $subscribe->user;
+
+        $userService = new UserService($user);
+
+        return view('user.subscribe', [
+            'remainDays' => $userService->getRemainingDays(),
+            'unusedPercent' => $userService->getUnusedTrafficPercent(),
+            'user' => $user,
+            'subscribe' => $subscribe,
+        ]);
+    }
+
     public function getSubscribeByCode(Request $request, string $code): RedirectResponse|string
     { // 通过订阅码获取订阅信息
         preg_match('/[0-9A-Za-z]+/', $code, $matches, PREG_UNMATCHED_AS_NULL);
@@ -28,7 +59,6 @@ class SubscribeController extends Controller
 
         // 检查订阅码是否有效
         $subscribe = UserSubscribe::whereCode($code)->firstOrFail();
-        $this->proxyServer = new ProxyService;
 
         if (! $subscribe) {
             $this->failed(trans('errors.subscribe.unknown'));

+ 12 - 0
app/helpers.php

@@ -1,5 +1,6 @@
 <?php
 
+use Carbon\Carbon;
 use Carbon\CarbonInterval;
 
 const MiB = 1048576;
@@ -96,3 +97,14 @@ if (! function_exists('string_urlsafe')) {
         return $clean;
     }
 }
+
+if (! function_exists('localized_date')) {
+    function localized_date($date): string
+    {
+        $locale = app()->getLocale();
+        $carbon = Carbon::parse($date);
+        $format = config("common.language.$locale.3") ?? 'Y-m-d';
+
+        return $carbon->format($format);
+    }
+}

+ 9 - 8
config/common.php

@@ -62,13 +62,14 @@ return [
     ],
 
     'language' => [
-        'de' => ['Deutsch', 'de', 'de-DE'],
-        'en' => ['English', 'us', 'en-US'],
-        'fa' => ['فارسی', 'ir', 'fa-IR'],
-        'ja' => ['日本語', 'jp', 'ja-JP'],
-        'ko' => ['한국어', 'kr', 'ko-KR'],
-        'vi' => ['Tiếng Việt', 'vn', 'vi-VN'],
-        'zh_CN' => ['简体中文', 'cn', 'zh-CN'],
+        'de' => ['Deutsch', 'de', 'de-DE', 'd.m.Y'],
+        'en' => ['English', 'us', 'en-US', 'F d, Y'],
+        'fa' => ['فارسی', 'ir', 'fa-IR', 'Y/m/d'],
+        'ja' => ['日本語', 'jp', 'ja-JP', 'Y年m月d日'],
+        'ko' => ['한국어', 'kr', 'ko-KR', 'Y년 m월 d일'],
+        'vi' => ['Tiếng Việt', 'vn', 'vi-VN', 'd/m/Y'],
+        'zh_CN' => ['简体中文', 'cn', 'zh-CN', 'Y年m月d日'],
+        'ru' => ['Русский', 'ru', 'ru', 'd.m.Y'],
     ],
 
     'currency' => [
@@ -85,7 +86,7 @@ return [
     ],
 
     'contact' => [
-        'telegram' => env('CONTACT_TELEGRAM', 'https://t.me/+nW8AwsPPUsliYzg1'),
+        'telegram' => env('CONTACT_TELEGRAM', null),
         'qq' => env('CONTACT_QQ', null),
     ],
 ];

+ 220 - 0
public/assets/css/subscribe.css

@@ -0,0 +1,220 @@
+/* ========== 响应式主卡片与全局 ========== */
+body {
+    background: linear-gradient(135deg, #22293b 0%, #16191d 100%) center top !important;
+}
+
+.container {
+    max-width: 700px;
+    margin: 0 auto;
+}
+
+.panel {
+    background: #181d25;
+    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
+    border-radius: 1.5rem;
+    padding: 1.5rem 2rem 1.5rem 2rem;
+}
+
+@media (max-width: 600px) {
+    .panel {
+        max-width: 99vw;
+        padding: 0.8rem 1.6rem;
+    }
+}
+
+.avatar-big {
+    width: 44px;
+    height: 44px;
+    border-radius: 50%;
+    object-fit: cover;
+    margin: 0.3rem 1rem 0.3rem 0;
+    background: #293651;
+}
+
+@media (max-width: 600px) {
+    .avatar-big {
+        width: 32px;
+        height: 32px;
+    }
+}
+
+/* ======== 平台nav与客户端btn ======== */
+.nav-platforms {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 0.5rem;
+    margin-bottom: 1rem;
+    justify-content: center;
+}
+
+.nav-platforms .nav-link {
+    flex: 1 1 auto;
+    min-width: 80px;
+    padding: 0.5rem 0.75rem;
+    font-size: 0.95rem;
+}
+
+.nav-platforms .nav-link.active {
+    color: #fff !important;
+    background: linear-gradient(90deg, #19acf4 50%, #13e5ce 150%);
+    font-weight: 700;
+    border-radius: 9px;
+}
+
+.nav-platforms .nav-link {
+    color: #2ec2e6 !important;
+    border-radius: 8px;
+    margin-right: 4px;
+    font-weight: 500;
+    background: #191e2e;
+    font-size: 1rem;
+    min-width: 64px;
+    text-align: center;
+}
+
+.info-label {
+    color: #fff;
+}
+
+.info-value {
+    color: #fff;
+}
+
+#apps-tab {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 0.5rem;
+    justify-content: center;
+    margin-bottom: 1rem;
+}
+
+#apps-tab .btn-app.selected,
+#apps-tab .btn-app.btn-primary {
+    background: linear-gradient(83deg, #24a4fa 20%, #23e1b7 150%) !important;
+    color: #fff !important;
+    border: none;
+    font-weight: 600;
+}
+
+#apps-tab .btn-app {
+    margin-right: 8px;
+    margin-bottom: 6px;
+    border: 1.1px solid #1ed6eb40;
+    background: #1a283b;
+    color: #65e8fc;
+    min-width: 96px;
+    text-align: center;
+    font-weight: 500;
+}
+
+/* ======= stepper主线和icon完美对齐 ======= */
+.stepper {
+    margin-top: 2em;
+}
+
+@media (max-width: 600px) {
+    .stepper {
+        margin-top: 1em;
+    }
+}
+
+.stepper {
+    position: relative;
+    --dot-width: 36px;
+    --line-width: 4px;
+}
+
+.stepper::before {
+    content: "";
+    position: absolute;
+    top: calc(var(--dot-width) / 2);
+    bottom: calc(var(--dot-width) / 2);
+    left: calc((var(--dot-width) - var(--line-width)) / 2);
+    width: var(--line-width);
+    background: #29dbe236;
+    border-radius: 3px;
+    z-index: 0;
+}
+
+.step-item {
+    display: flex;
+    align-items: flex-start;
+    position: relative;
+    margin-bottom: 2.5rem;
+}
+
+.step-dot {
+    width: var(--dot-width);
+    height: var(--dot-width);
+    min-width: var(--dot-width);
+    min-height: var(--dot-width);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    position: relative;
+    z-index: 2;
+    margin-right: 1.2rem;
+    margin-left: 0;
+    box-shadow: 0 0 8px rgba(34, 202, 236, 0.8);
+    background: linear-gradient(145deg, #30b1ef 0%, #50edaf 100%);
+    color: #fff;
+    font-size: 1.2rem;
+    font-weight: bold;
+}
+
+.step-title {
+    font-weight: 600;
+    margin-bottom: 0.2em;
+    color: #e0f2ff;
+    font-size: 1.15em;
+}
+
+.step-desc {
+    color: #b1c9de;
+    font-size: 0.98em;
+    margin-bottom: 0.33em;
+}
+
+.btn-group .btn, .btn-round-sm,
+.addsub-btn {
+    border-radius: 9px !important;
+}
+
+.dropdown-menu {
+    border-radius: 12px;
+}
+
+.app-download {
+    display: flex;
+    justify-content: center;
+}
+
+.addsub-btn {
+    background: linear-gradient(83deg, #24a4fa 20%, #23e1b7 150%) !important;
+    color: #fff !important;
+    border: none;
+    font-weight: 600;
+    font-size: 1.12em;
+    margin-top: 0.5em;
+    padding: 0.57em 1.55em !important;
+}
+
+.addsub-btn:not(.selected):hover,
+#apps-tab .btn-app:not(.selected):hover,
+.nav-platforms .nav-link:not(.active):hover {
+    transform: translateY(-2px);
+    transition: all 0.2s ease-in-out;
+}
+
+#apps-tab .btn-app:not(.selected):hover,
+.nav-platforms .nav-link:not(.active):hover {
+    filter: brightness(1.2);
+}
+
+@media (max-width: 400px) {
+    .step-dot {
+        width: 29px;
+        height: 29px;
+    }
+}

+ 397 - 0
public/clients/app.json

@@ -0,0 +1,397 @@
+{
+  "ios": [
+    {
+      "id": "happ",
+      "name": "Happ",
+      "isFeatured": true,
+      "urlScheme": "happ://add/",
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://apps.apple.com/us/app/happ-proxy-utility/id6504287215",
+            "buttonText": {
+              "zh_CN": "在App Store[EU]中打开",
+              "en": "Open in App Store [EU]",
+              "fa": "باز کردن در App Store [EU]",
+              "ru": "Открыть в App Store [EU]"
+            }
+          },
+          {
+            "buttonLink": "https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973",
+            "buttonText": {
+              "zh_CN": "在App Store[RU]中打开",
+              "en": "Open in App Store [RU]",
+              "fa": "باز کردن در App Store [RU]",
+              "ru": "Открыть в App Store [RU]"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "在 App Store 中打开该页面并安装应用。启动应用后,在 VPN 配置权限弹窗中点击“允许”并输入您的锁屏密码。",
+          "en": "Open the page in App Store and install the app. Launch it, in the VPN configuration permission window click Allow and enter your passcode.",
+          "fa": "صفحه را در App Store باز کنید و برنامه را نصب کنید. آن را اجرا کنید، در پنجره مجوز پیکربندی VPN روی Allow کلیک کنید و رمز عبور خود را وارد کنید.",
+          "ru": "Откройте страницу в App Store и установите приложение. Запустите его, в окне разрешения VPN-конфигурации нажмите Allow и введите свой пароль."
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击下方按钮——应用将自动打开,订阅随即生效。",
+          "en": "Click the button below — the app will open and the subscription will be added automatically",
+          "fa": "برای افزودن خودکار اشتراک روی دکمه زیر کلیک کنید - برنامه باز خواهد شد",
+          "ru": "Нажмите кнопку ниже — приложение откроется, и подписка добавится автоматически."
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "zh_CN": "在主界面中,点击中央按钮即可连接VPN。别忘了先从服务器列表中选择一个服务器。如有需要,您也可以随时从服务器列表中切换其他服务器。",
+          "en": "In the main section, click the large power button in the center to connect to VPN. Don't forget to select a server from the server list. If needed, choose another server from the server list.",
+          "fa": "در بخش اصلی، دکمه بزرگ روشن/خاموش در مرکز را برای اتصال به VPN کلیک کنید. فراموش نکنید که یک سرور را از لیست سرورها انتخاب کنید. در صورت نیاز، سرور دیگری را از لیست سرورها انتخاب کنید.",
+          "ru": "В главном разделе нажмите большую кнопку включения в центре для подключения к VPN. Не забудьте выбрать сервер в списке серверов. При необходимости выберите другой сервер из списка серверов."
+        }
+      }
+    },
+    {
+      "id": "streisand",
+      "name": "Streisand",
+      "isFeatured": false,
+      "urlScheme": "streisand://import/",
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://apps.apple.com/ru/app/streisand/id6450534064",
+            "buttonText": {
+              "zh_CN": "在App Store中打开",
+              "en": "Open in App Store",
+              "fa": "باز کردن در App Store",
+              "ru": "Открыть в App Store"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "在 App Store 中打开该页面并安装应用。启动应用后,在 VPN 配置权限弹窗中点击“允许”并输入您的锁屏密码。",
+          "en": "Open the page in App Store and install the app. Launch it, in the VPN configuration permission window click Allow and enter your passcode.",
+          "fa": "صفحه را در App Store باز کنید و برنامه را نصب کنید. آن را اجرا کنید، در پنجره مجوز پیکربندی VPN روی Allow کلیک کنید و رمز عبور خود را وارد کنید.",
+          "ru": "Откройте страницу в App Store и установите приложение. Запустите его, в окне разрешения VPN-конфигурации нажмите Allow и введите свой пароль."
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击下方按钮——应用将自动打开,订阅随即生效。",
+          "en": "Click the button below — the app will open and the subscription will be added automatically",
+          "fa": "برای افزودن خودکار اشتراک روی دکمه زیر کلیک کنید - برنامه باز خواهد شد",
+          "ru": "Нажмите кнопку ниже — приложение откроется, и подписка добавится автоматически."
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "en": "In the main section, click the large power button in the center to connect to VPN. Don't forget to select a server from the server list. If needed, choose another server from the server list.",
+          "fa": "در بخش اصلی، دکمه بزرگ روشن/خاموش در مرکز را برای اتصال به VPN کلیک کنید. فراموش نکنید که یک سرور را از لیست سرورها انتخاب کنید. در صورت نیاز، سرور دیگری را از لیست سرورها انتخاب کنید.",
+          "ru": "В главном разделе нажмите большую кнопку включения в центре для подключения к VPN. Не забудьте выбрать сервер в списке серверов. При необходимости выберите другой сервер из списка серверов."
+        }
+      }
+    },
+    {
+      "id": "shadowrocket",
+      "name": "Shadowrocket",
+      "isFeatured": false,
+      "urlScheme": "sub://",
+      "isNeedBase64Encoding": true,
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://apps.apple.com/ru/app/shadowrocket/id932747118",
+            "buttonText": {
+              "zh_CN": "在App Store中打开",
+              "en": "Open in App Store",
+              "fa": "باز کردن در App Store",
+              "ru": "Открыть в App Store"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "在 App Store 中打开该页面并安装应用。启动应用后,在 VPN 配置权限弹窗中点击“允许”并输入您的锁屏密码。",
+          "en": "Open the page in App Store and install the app. Launch it, in the VPN configuration permission window click Allow and enter your passcode.",
+          "fa": "صفحه را در App Store باز کنید و برنامه را نصب کنید. آن را اجرا کنید، در پنجره مجوز پیکربندی VPN روی Allow کلیک کنید و رمز عبور خود را وارد کنید.",
+          "ru": "Откройте страницу в App Store и установите приложение. Запустите его, в окне разрешения VPN-конфигурации нажмите Allow и введите свой пароль."
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击下方按钮——应用将自动打开,订阅随即生效。",
+          "en": "Click the button below — the app will open and the subscription will be added automatically",
+          "fa": "برای افزودن خودکار اشتراک روی دکمه زیر کلیک کنید - برنامه باز خواهد شد",
+          "ru": "Нажмите кнопку ниже — приложение откроется, и подписка добавится автоматически."
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "zh_CN": "在主界面中,点击开关即可连接VPN。别忘了先从服务器列表中选择一个服务器。如有需要,您也可以随时从服务器列表中切换其他服务器。",
+          "en": "In the main section, click the large power button in the center to connect to VPN. Don't forget to select a server from the server list. If needed, choose another server from the server list.",
+          "fa": "در بخش اصلی، دکمه بزرگ روشن/خاموش در مرکز را برای اتصال به VPN کلیک کنید. فراموش نکنید که یک سرور را از لیست سرورها انتخاب کنید. در صورت نیاز، سرور دیگری را از لیست سرورها انتخاب کنید.",
+          "ru": "В главном разделе нажмите большую кнопку включения в центре для подключения к VPN. Не забудьте выбрать сервер в списке серверов. При необходимости выберите другой сервер из списка серверов."
+        }
+      }
+    }
+  ],
+  "android": [
+    {
+      "id": "happ",
+      "name": "Happ",
+      "isFeatured": true,
+      "urlScheme": "happ://add/",
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://play.google.com/store/apps/details?id=com.happproxy",
+            "buttonText": {
+              "zh_CN": "在Google Play中打开",
+              "en": "Open in Google Play",
+              "fa": "باز کردن در Google Play",
+              "ru": "Открыть в Google Play"
+            }
+          },
+          {
+            "buttonLink": "https://github.com/Happ-proxy/happ-android/releases/latest/download/Happ.apk",
+            "buttonText": {
+              "zh_CN": "下载安装包",
+              "en": "Download APK",
+              "fa": "دانلود APK",
+              "ru": "Скачать APK"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "在Google Play中打开该页面并安装应用。或者,如果Google Play不可用,直接从APK文件安装应用。",
+          "en": "Open the page in Google Play and install the app. Or install the app directly from the APK file if Google Play is not working.",
+          "fa": "صفحه را در Google Play باز کنید و برنامه را نصب کنید. یا برنامه را مستقیماً از فایل APK نصب کنید، اگر Google Play کار نمی کند.",
+          "ru": "Откройте страницу в Google Play и установите приложение. Или установите приложение из APK файла напрямую, если Google Play не работает."
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击下方按钮添加订阅",
+          "en": "Click the button below to add subscription",
+          "fa": "برای افزودن اشتراک روی دکمه زیر کلیک کنید",
+          "ru": "Нажмите кнопку ниже, чтобы добавить подписку"
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "zh_CN": "打开应用并连接至服务器",
+          "en": "Open the app and connect to the server",
+          "fa": "برنامه را باز کنید و به سرور متصل شوید",
+          "ru": "Откройте приложение и подключитесь к серверу"
+        }
+      }
+    },
+    {
+      "id": "clash-meta",
+      "name": "Clash Meta",
+      "isFeatured": false,
+      "urlScheme": "clash://install-config?url=",
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://github.com/MetaCubeX/ClashMetaForAndroid/releases/download/v2.11.7/cmfa-2.11.7-meta-universal-release.apk",
+            "buttonText": {
+              "zh_CN": "下载安装包",
+              "en": "Download APK",
+              "fa": "دانلود APK",
+              "ru": "Скачать APK"
+            }
+          },
+          {
+            "buttonLink": "https://f-droid.org/packages/com.github.metacubex.clash.meta/",
+            "buttonText": {
+              "zh_CN": "在F-Droid中打开",
+              "en": "Open in F-Droid",
+              "fa": "در F-Droid باز کنید",
+              "ru": "Открыть в F-Droid"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "下载并安装Clash Meta",
+          "en": "Download and install Clash Meta APK",
+          "fa": "دانلود و نصب Clash Meta APK",
+          "ru": "Скачайте и установите Clash Meta APK"
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击按钮以导入配置",
+          "en": "Tap the button to import configuration",
+          "fa": "برای وارد کردن پیکربندی روی دکمه ضربه بزنید",
+          "ru": "Нажмите кнопку, чтобы импортировать конфигурацию"
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "zh_CN": "打开Clash Meta并点击“连接”",
+          "en": "Open Clash Meta and tap on Connect",
+          "fa": "Clash Meta را باز کنید و روی اتصال ضربه بزنید",
+          "ru": "Откройте Clash Meta и нажмите Подключиться"
+        }
+      }
+    }
+  ],
+  "pc": [
+    {
+      "id": "clash-verge",
+      "name": "Clash Verge",
+      "isFeatured": true,
+      "urlScheme": "clash://install-config?url=",
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v2.3.1/Clash.Verge_2.3.1_x64-setup.exe",
+            "buttonText": {
+              "zh_CN": "Windows",
+              "en": "Windows",
+              "fa": "ویندوز",
+              "ru": "Windows"
+            }
+          },
+          {
+            "buttonLink": "https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v2.3.1/Clash.Verge_2.3.1_x64.dmg",
+            "buttonText": {
+              "zh_CN": "macOS (Intel处理器)",
+              "en": "macOS (Intel)",
+              "fa": "مک (اینتل)",
+              "ru": "macOS (Intel)"
+            }
+          },
+          {
+            "buttonLink": "https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v2.3.1/Clash.Verge_2.3.1_aarch64.dmg",
+            "buttonText": {
+              "zh_CN": "macOS (Apple Silicon M系列处理器)",
+              "en": "macOS (Apple Silicon)",
+              "fa": "مک (Apple Silicon)",
+              "ru": "macOS (Apple Silicon)"
+            }
+          },
+          {
+            "buttonLink": "https://github.com/clash-verge-rev/clash-verge-rev/releases",
+            "buttonText": {
+              "zh_CN": "Linux",
+              "en": "Linux",
+              "fa": "لینوکس",
+              "ru": "Linux"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "选择适合您设备的版本,点击下方按钮并安装应用。",
+          "en": "Choose the version for your device, click the button below and install the app.",
+          "fa": "نسخه مناسب برای دستگاه خود را انتخاب کنید، دکمه زیر را فشار دهید و برنامه را نصب کنید",
+          "ru": "Выберите подходящую версию для вашего устройства, нажмите на кнопку ниже и установите приложение."
+        }
+      },
+      "additionalBeforeAddSubscriptionStep": {
+        "buttons": [],
+        "description": {
+          "zh_CN": "启动应用后,您可以在设置中更改语言。在左侧面板找到齿轮图标,然后导航至Verge 设置并选择语言设置。",
+          "en": "After launching the app, you can change the language in settings. In the left panel, find the gear icon, then navigate to Verge 设置 and select 语言设置.",
+          "fa": "پس از راه‌اندازی برنامه، می‌توانید زبان را در تنظیمات تغییر دهید. در پنل سمت چپ، نماد چرخ دنده را پیدا کنید، سپس به Verge 设置 بروید و 语言设置 را انتخاب کنید.",
+          "ru": "После запуска приложения вы можете сменить язык в настройках. В левой панели найдите иконку шестеренки, далее ориентируйтесь на Verge 设置 и выберите пункт 语言设置."
+        },
+        "title": {
+          "zh_CN": "更改语言",
+          "en": "Change language",
+          "fa": "تغییر زبان",
+          "ru": "Смена языка"
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击下方按钮添加订阅",
+          "en": "Click the button below to add subscription",
+          "fa": "برای افزودن اشتراک روی دکمه زیر کلیک کنید",
+          "ru": "Нажмите кнопку ниже, чтобы добавить подписку"
+        }
+      },
+      "additionalAfterAddSubscriptionStep": {
+        "buttons": [],
+        "title": {
+          "zh_CN": "如果订阅未添加成功",
+          "en": "If the subscription is not added",
+          "fa": "اگر اشتراک در برنامه نصب نشده است",
+          "ru": "Если подписка не добавилась"
+        },
+        "description": {
+          "zh_CN": "如果点击按钮后无反应,请手动添加订阅。点击本页面右上角的“获取链接”按钮,复制链接。在Clash Verge中,进入配置集(Profiles)部分,将链接粘贴至文本框,然后点击导入(Import)按钮。",
+          "en": "If nothing happens after clicking the button, add the subscription manually. Click the Get Link button in the top right corner of this page, copy the link. In Clash Verge, go to the Profiles section and paste the link in the text field, then click the Import button.",
+          "fa": "اگر پس از کلیک روی دکمه اتفاقی نیفتاد، اشتراک را به صورت دستی اضافه کنید. در گوشه بالا سمت راست این صفحه روی دکمه دریافت لینک کلیک کنید، لینک را کپی کنید. در Clash Verge به بخش پروفایل‌ها بروید و لینک را در فیلد متنی وارد کنید، سپس روی دکمه وارد کردن کلیک کنید.",
+          "ru": "Если после нажатия на кнопку ничего не произошло, добавьте подписку вручную. Нажмите на этой страницу кнопку Получить ссылку в правом верхнем углу, скопируйте ссылку. В Clash Verge перейдите в раздел Профили и вставьте ссылку в текстовое поле, затем нажмите на кнопку Импорт."
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "zh_CN": "您可以在代理(Proxy)部分选择服务器,并在设置(Settings)部分启用VPN。将TUN模式开关设置为开启。",
+          "en": "You can select a server in the Proxy section, and enable VPN in the Settings section. Set the TUN Mode switch to ON.",
+          "fa": "می‌توانید در بخش پروکسی سرور را انتخاب کنید و در بخش تنظیمات VPN را فعال کنید. کلید TUN Mode را در حالت روشن قرار دهید.",
+          "ru": "Выбрать сервер можно в разделе Прокси, включить VPN можно в разделе Настройки. Установите переключатель TUN Mode в положение ВКЛ."
+        }
+      }
+    },
+    {
+      "id": "hiddify",
+      "name": "Hiddify",
+      "isFeatured": false,
+      "urlScheme": "hiddify://import/",
+      "installationStep": {
+        "buttons": [
+          {
+            "buttonLink": "https://github.com/hiddify/hiddify-app/releases/download/v2.5.7/Hiddify-Windows-Setup-x64.exe",
+            "buttonText": {
+              "zh_CN": "Windows",
+              "en": "Windows",
+              "fa": "ویندوز",
+              "ru": "Windows"
+            }
+          },
+          {
+            "buttonLink": "https://github.com/hiddify/hiddify-app/releases/download/v2.5.7/Hiddify-MacOS.dmg",
+            "buttonText": {
+              "zh_CN": "macOS",
+              "en": "macOS",
+              "fa": "مک",
+              "ru": "macOS"
+            }
+          },
+          {
+            "buttonLink": "https://github.com/hiddify/hiddify-app/releases/download/v2.5.7/Hiddify-Linux-x64.AppImage",
+            "buttonText": {
+              "zh_CN": "Linux",
+              "en": "Linux",
+              "fa": "لینوکس",
+              "ru": "Linux"
+            }
+          }
+        ],
+        "description": {
+          "zh_CN": "在主界面中,点击中央按钮以连接VPN。如有需要,可在代理(Proxy)部分选择其他服务器。",
+          "en": "In the main section, click the large power button in the center to connect to VPN. If needed, select a different server in the Proxy section",
+          "fa": "در بخش اصلی، دکمه بزرگ روشن/خاموش در مرکز را برای اتصال به VPN کلیک کنید. در صورت نیاز، سرور دیگری را در بخش پروکسی انتخاب کنید",
+          "ru": "В главном разделе нажмите большую кнопку включения в центре для подключения к VPN. При необходимости выберите другой сервер в разделе Прокси."
+        }
+      },
+      "addSubscriptionStep": {
+        "description": {
+          "zh_CN": "点击下方按钮添加订阅",
+          "en": "Click the button below to add subscription",
+          "fa": "برای افزودن اشتراک روی دکمه زیر کلیک کنید",
+          "ru": "Нажмите кнопку ниже, чтобы добавить подписку"
+        }
+      },
+      "connectAndUseStep": {
+        "description": {
+          "zh_CN": "在主界面中,点击中央按钮以连接VPN。别忘了先从服务器列表中选择一个服务器。如有需要,您也可以从服务器列表中切换其他服务器。",
+          "en": "In the main section, click the large power button in the center to connect to VPN. Don't forget to select a server from the server list. If needed, select a different server from the server list.",
+          "fa": "در بخش اصلی، دکمه بزرگ روشن/خاموش در مرکز را برای اتصال به VPN کلیک کنید. فراموش نکنید که یک سرور را از لیست سرورها انتخاب کنید. در صورت نیاز، سرور دیگری را از لیست سرورها انتخاب کنید.",
+          "ru": "В главном разделе нажмите большую кнопку включения в центре для подключения к VPN. Не забудьте выбрать сервер в списке серверов. При необходимости выберите другой сервер из списка серверов."
+        }
+      }
+    }
+  ]
+}

+ 1 - 1
resources/lang/de/admin.php

@@ -501,7 +501,7 @@ return [
             'active_times' => 'Über E-Mail in 24 Stunden',
             'admin_invite_days' => 'Admin-Einladungslaufzeit',
             'aff_salt' => 'Verschlüsselungssalt für Empfehlungs-URL',
-            'auto_release_port' => 'Automatische Freigabe des Ports nach <code>'.config('tasks.release_port').'</code> Tagen Sperre/Ablauf',
+            'auto_release_port' => 'Automatische Freigabe des Ports nach <code>'.sysConfig('tasks.release_port').'</code> Tagen Sperre/Ablauf',
             'bark_key' => 'Geräteschlüssel für iOS-Push',
             'captcha_key' => 'Anleitung <a href="https://proxypanel.gitbook.io/wiki/captcha" target="_blank">hier</a>',
             'data_anomaly_notification' => 'Benachrichtige Admin, wenn stündliche Daten den Schwellenwert überschreiten',

+ 1 - 2
resources/lang/en/model.php

@@ -16,7 +16,6 @@ return [
         'created_at' => 'Published Date',
         'language' => 'Language',
         'logo' => 'Cover',
-        'updated_at' => 'Last Updated',
     ],
     'common' => [
         'description' => 'Description',
@@ -26,7 +25,6 @@ return [
         'type' => 'Type',
     ],
     'country' => [
-        'code' => 'Country Code',
         'icon' => 'Flag Icon',
         'name' => 'Country Name',
     ],
@@ -172,6 +170,7 @@ return [
         'type' => 'Group Type',
     ],
     'subscribe' => [
+        'attribute' => 'Subscription',
         'ban_desc' => 'Suspension Reason',
         'ban_time' => 'Suspension Time',
         'code' => 'Subscription Code',

+ 7 - 0
resources/lang/en/user.php

@@ -232,6 +232,13 @@ return [
         'tips' => 'Warning: This link is for personal use only. Sharing may result in account suspension.',
         'trojan_only' => 'Trojan Only',
         'v2ray_only' => 'V2Ray Only',
+        'page' => [
+            'get_link' => 'Get link',
+            'connect' => 'Connect & Use',
+            'error' => [
+                'no_app' => 'No available clients',
+            ],
+        ],
     ],
     'telegram' => [
         'bind_exists' => 'Telegram account already linked',

+ 1 - 1
resources/lang/fa/admin.php

@@ -501,7 +501,7 @@ return [
             'active_times' => 'تعداد فعال‌سازی حساب از طریق ایمیل در 24 ساعت',
             'admin_invite_days' => 'مدت اعتبار کدهای دعوت ایجاد شده توسط مدیر',
             'aff_salt' => 'رمزنگاری لینک دعوت با استفاده از متن مخفی',
-            'auto_release_port' => 'پورت‌های مسدود شده/منقضی شده پس از <code>'.config('tasks.release_port').'</code> روز به‌صورت خودکار آزاد می‌شوند',
+            'auto_release_port' => 'پورت‌های مسدود شده/منقضی شده پس از <code>'.sysConfig('tasks.release_port').'</code> روز به‌صورت خودکار آزاد می‌شوند',
             'bark_key' => 'کلید دستگاه برای ارسال پیام به iOS',
             'captcha_key' => 'راهنمای تنظیمات <a href="https://proxypanel.gitbook.io/wiki/captcha" target="_blank">اینجا</a>',
             'data_anomaly_notification' => 'اطلاع‌رسانی به مدیر در صورت استفاده غیرعادی از داده در یک ساعت',

+ 75 - 48
resources/lang/ja/admin.php

@@ -8,13 +8,13 @@ return [
         'edit_item' => ':attribute を編集',
     ],
     'aff' => [
-        'apply_counts' => '合計 <code>:num</code> 引き出し申請',
-        'commission_counts' => 'この申請には合計 <code>:num</code> の注文が含まれます',
-        'commission_title' => '申請詳細',
-        'counts' => '合計 <code>:num</code> リベート履歴',
+        'apply_counts' => '合計 <code>:num</code> 件の出金リクエスト',
+        'commission_counts' => '関連注文件数: <code>:num</code>',
+        'commission_title' => '出金申請詳細',
+        'counts' => '合計 <code>:num</code> 件のリベート記録',
         'rebate_title' => 'リベート履歴',
         'referral' => '紹介リベート',
-        'title' => '引き出し申請リスト',
+        'title' => '出金申請リスト',
     ],
     'article' => [
         'category_hint' => '同じカテゴリは一緒にグループ化されます',
@@ -258,7 +258,6 @@ return [
             'subscribe' => 'サブスクリプション管理',
         ],
     ],
-    'minute' => '分',
     'monitor' => [
         'daily_chart' => '日次トラフィック使用量',
         'hint' => '<strong>ヒント:</strong> データがない場合はスケジュールタスクを確認してください。',
@@ -428,18 +427,16 @@ return [
         ],
         'no_permission' => 'パラメータを変更する権限がありません!',
         'system' => [
+            'web' => 'ウェブ一般',
             'account' => 'アカウント設定',
+            'node' => 'ノード設定',
+            'security' => 'セキュリティ&検証',
+            'payment' => '支払いシステム',
+            'notify' => '通知システム',
             'auto_job' => '自動タスク',
             'check_in' => 'チェックインシステム',
-            'extend' => '拡張機能',
             'menu' => 'メニュー',
-            'node' => 'ノード設定',
-            'notify' => '通知システム',
-            'other' => 'ロゴ|カスタマーサービス|統計',
-            'payment' => '支払いシステム',
-            'promotion' => 'プロモーションシステム',
             'title' => 'システム設定',
-            'web' => 'ウェブ一般',
         ],
     ],
     'sort_asc' => 'ソート値が大きいほど優先度が高くなります',
@@ -454,7 +451,7 @@ return [
         ],
         'active_times' => 'アカウント有効化回数',
         'admin_invite_days' => '管理者-招待コード有効期',
-        'aff_salt' => '招待リンク ユーザーID暗号化',
+        'affiliate_link_salt' => '招待リンク ユーザーID暗号化',
         'alipay_qrcode' => 'Alipay QRコード',
         'auto_release_port' => 'ポート回収メカニズム',
         'bark_key' => 'Barkデバイスキー',
@@ -470,6 +467,34 @@ return [
         'codepay_id' => 'CodePay ID',
         'codepay_key' => '通信キー',
         'codepay_url' => 'リクエストURL',
+        'tasks_chunk' => '分割処理数',
+        'recently_heartbeat' => '最近のノードポートレート閾値',
+        'tasks' => [
+            'clean' => [
+                'notification_logs' => '通知ログ',
+                'node_daily_logs' => 'ノードによる毎日のトラフィックデータ',
+                'node_hourly_logs' => 'データノード',
+                'node_heartbeats' => 'ノード負荷 / 心拍数データ',
+                'node_online_logs' => 'ノードオンラインユーザーデータ',
+                'payments' => '支払データ。',
+                'rule_logs' => '監査トリガー データ',
+                'node_online_ips' => 'ピア接続のIPデータ',
+                'user_baned_logs' => 'ユーザーの禁止ログ',
+                'user_daily_logs_nodes' => 'ユーザー毎日のノードデータ使用量',
+                'user_daily_logs_total' => 'ユーザー毎日データ使用量',
+                'user_hourly_logs' => '時々 節ごとにノードあたりのデータ量',
+                'login_logs' => 'ユーザーログインデータ',
+                'subscribe_logs' => 'サブスクリプションリクエスト データ',
+                'traffic_logs' => 'トラフィックの消費量',
+                'unpaid_orders' => '未払い',
+            ],
+            'close' => [
+                'tickets' => 'チケット',
+                'confirmation_orders' => '手動で注文する',
+                'orders' => '注文',
+                'verify' => 'Eメール認証コード時の動作',
+            ],
+        ],
         'data_anomaly_notification' => 'データ異常通知',
         'data_exhaust_notification' => 'データ使用量通知',
         'ddns_key' => 'DNSプロバイダーキー',
@@ -500,8 +525,8 @@ return [
             'account_expire_notification' => 'ユーザーにアカウントの有効期限を通知',
             'active_times' => '24時間以内にメールでアカウントを有効化できる回数',
             'admin_invite_days' => '管理者が生成する招待コードの有効期限',
-            'aff_salt' => '空白の場合、招待リンクにユーザーIDが表示されます。任意の英数字を入力すると、ユーザーIDが暗号化されます。',
-            'auto_release_port' => '封鎖/有効期限切れ <code>'.config('tasks.release_port').'</code> 日後に自動的にポートを解放',
+            'affiliate_link_salt' => '空白の場合、招待リンクにユーザーIDが表示されます。任意の英数字を入力すると、ユーザーIDが暗号化されます。',
+            'auto_release_port' => '停止/期限が切れたアカウントポートは、N日後に自動解放されます。',
             'bark_key' => 'iOSデバイスにBarkアプリをインストールし、URLの後に続く一連の文字列を入力。Barkを有効にするには、この値を必ず入力してください。',
             'captcha_key' => '設定ガイドは<a href="https://proxypanel.gitbook.io/wiki/captcha" target="_blank">こちら</a>を参照してください。',
             'data_anomaly_notification' => '1時間以内にデータ使用量が異常な閾値を超えた場合、管理者に通知します。',
@@ -523,7 +548,6 @@ return [
             'is_activate_account' => '有効にすると、ユーザーはメールでアカウントを有効化する必要があります。',
             'is_ban_status' => '(慎重に)アカウント全体を禁止すると、アカウントのすべてのデータがリセットされ、ユーザーはログインできなくなります。無効にした場合、ユーザーの代理のみを禁止します。',
             'is_captcha' => '有効にすると、ログイン/登録時にCaptcha認証が必要です。',
-            'is_checkin' => 'ログイン時にデータ範囲に基づいてランダムにデータを取得します。',
             'is_clear_log' => '(推奨)有効にすると、自動的に不要なログをクリアします。',
             'is_custom_subscribe' => '有効にすると、サブスクリプション情報の上部に有効期限と残りのデータが表示されます(特定のクライアントのみサポート)。',
             'is_email_filtering' => 'ブラックリスト:ユーザーはブラックリスト外の任意のメールアドレスで登録できます。ホワイトリスト:ユーザーはホワイトリスト内のメールアドレスでのみ登録できます。',
@@ -531,12 +555,11 @@ return [
             'is_free_code' => '無効にすると、無料の招待コードは表示されません。',
             'is_rand_port' => 'ユーザー登録時、ユーザー追加時にランダムなポートを生成します。',
             'is_register' => '無効にすると、登録できません。',
-            'is_subscribe_ban' => '有効にすると、ユーザーのサブスクリプションリンクリクエストが設定された閾値を超えた場合、自動的に禁止されます。',
-            'is_traffic_ban' => '1時間以内にデータ使用量が異常な閾値を超えた場合、自動的にアカウントを禁止します(代理のみ禁止)。',
             'maintenance_content' => 'カスタムメンテナンス情報。',
             'maintenance_mode' => '有効にすると、ユーザーはメンテナンスページにリダイレクトされます。管理者は<a href=\'javascript:(0)\'>:url</a>でログインできます。',
             'maintenance_time' => 'メンテナンスページのカウントダウンに使用されます。',
             'min_port' => 'ポート範囲:1000〜65535。',
+            'checkin_reward' => '毎回サインインする事でランダムトラフィック範囲',
             'node_blocked_notification' => '毎時ノードのブロックを検出し、管理者に通知します。',
             'node_daily_notification' => '各ノードの前日のデータ使用量を報告します。',
             'node_offline_notification' => '10分ごとにノードのオフライン状況を検出し、管理者に通知します。',
@@ -557,14 +580,14 @@ return [
             'referral_percent' => '招待リンクから登録されたアカウントの各注文のリベート割合。',
             'referral_status' => '無効にすると、ユーザーには表示されませんが、既存のデータには影響しません。',
             'referral_traffic' => '招待リンクまたは招待コードを使用して登録すると、対応するデータ量が贈られます。',
-            'referral_type' => 'モードを切り替えた後、古いデータは変更されず、新しいリベートは新しいモードに基づいて計算されます。',
+            'referral_reward_type' => 'モードを切り替えた後、古いデータは変更されず、新しいリベートは新しいモードに基づいて計算されます。',
             'register_ip_limit' => '24時間以内に同一IPで許可される登録数。0または空白の場合は無制限。',
             'reset_password_times' => '24時間以内にメールでパスワードをリセットできる回数。',
             'reset_traffic' => 'ユーザーは購入したプランの日付に基づいて自動的にデータがリセットされます。',
             'server_chan_key' => 'ServerChanを有効にするには、この値を必ず入力してください(<a href="https://sct.ftqq.com/r/2626" target="_blank">SCKEYを申請</a>)。',
             'standard_currency' => 'ウェブサイトで使用されるデフォルトの通貨。',
             'subject_name' => '支払いチャネルで表示される商品のタイトル。',
-            'subscribe_ban_times' => '24時間以内にサブスクリプションリンクリクエストの回数制限。',
+            'subscribe_rate_limit' => '24時間以内にサブスクリプションリンクを要求できる最大試行可能。0に設定するとこの機能が使用されません。',
             'subscribe_domain' => '(推奨)パネルドメインがDNS汚染された場合に備えて、http://またはhttps://を含める必要があります。',
             'subscribe_max' => 'クライアントがサブスクリプション時に取得するノード数。0または空白の場合はすべてのノードを返します。',
             'telegram_token' => 'TelegramボットのTOKENを<a href=https://t.me/BotFather target=_blank>BotFather</a>から取得してください。',
@@ -572,18 +595,18 @@ return [
             'ticket_closed_notification' => 'チケットがクローズされたときにユーザーに通知します。',
             'ticket_created_notification' => '新しいチケットが作成されたときに管理者またはユーザーに通知します。作成者に依存します。',
             'ticket_replied_notification' => 'チケットに返信があったときに相手に通知します。',
-            'traffic_ban_time' => '異常が発生したためにユーザー/サブスクリプションが禁止された期間。期限が切れると自動的に解除されます。',
-            'traffic_ban_value' => '1時間以内にこの値を超えた場合、自動的にアカウントが禁止されます。',
-            'traffic_limit_time' => '再度チェックインできるまでの時間間隔。',
+            'ban_duration' => 'なんらかの例外によりユーザー/購読が禁止された後の期間、期限および全てのチケットが再生されます',
+            'traffic_abuse_limit' => '1時間を超えるとプロキシでのみ無効に設定します。0に設定するとこの機能は無効となります。',
+            'checkin_interval' => 'ユーザーはログインの度に一定間隔に設定され、0に設定するとチェックイン機能を表します。',
             'traffic_warning_percent' => '【データ使用量通知】の開始閾値。毎日ユーザーに通知します。',
             'user_invite_days' => 'ユーザーが生成する招待コードの有効期限。',
             'username_type' => 'ユーザーアカウントのデフォルトのタイプ。デフォルトはメールアドレスです。',
             'v2ray_tls_provider' => 'ノードの設定がこのTLS設定を上書きします。',
             'web_api_url' => '例:'.config('app.url'),
             'webmaster_email' => 'エラーメッセージに表示される管理者の連絡先メールアドレス。',
-            'website_analytics' => 'ウェブサイトの分析JavaScriptコード。',
-            'website_callback_url' => 'DNS汚染による支払いコールバックの失敗を防ぐために、http://またはhttps://を含める必要があります。',
-            'website_customer_service' => 'カスタマーサービスのJavaScriptコード。',
+            'website_statistics_code' => 'ウェブサイトの分析JavaScriptコード。',
+            'payment_callback_url' => 'DNS汚染による支払いコールバックの失敗を防ぐために、http://またはhttps://を含める必要があります。',
+            'website_customer_service_code' => 'カスタマーサービスのJavaScriptコード。',
             'website_name' => 'メール送信時に表示されるウェブサイト名。',
             'website_security_code' => '空白でない場合、<a href=":url" target="_blank">セキュリティエントリ</a>にセキュリティコードを追加してアクセスする必要があります。',
             'website_url' => 'パスワードリセットやオンライン支払いに必要なURL。',
@@ -601,7 +624,6 @@ return [
         'is_activate_account' => 'アカウント有効化',
         'is_ban_status' => '有効期限自動禁止',
         'is_captcha' => 'Captchaモード',
-        'is_checkin' => 'チェックインデータ追加',
         'is_clear_log' => 'ログ自動クリア',
         'is_custom_subscribe' => 'カスタムサブスクリプション',
         'is_email_filtering' => 'メールフィルタリング',
@@ -611,13 +633,11 @@ return [
         'is_otherPay' => 'カスタム支払い',
         'is_rand_port' => 'ランダムポート',
         'is_register' => 'ユーザー登録',
-        'is_subscribe_ban' => '異常サブスクリプションリクエスト自動禁止',
-        'is_traffic_ban' => '異常データ使用自動禁止',
         'maintenance_content' => 'メンテナンス内容',
         'maintenance_mode' => 'メンテナンスモード',
         'maintenance_time' => 'メンテナンス終了時間',
         'min_port' => 'ポート範囲',
-        'min_rand_traffic' => 'データ範囲',
+        'checkin_reward' => 'ログインボーナス範囲',
         'node_blocked_notification' => 'ノードブロック通知',
         'node_daily_notification' => 'ノード使用レポート',
         'node_offline_notification' => 'ノードオフライン通知',
@@ -655,8 +675,9 @@ return [
         'payment' => [
             'attribute' => '支払い設定',
             'channel' => [
-                'alipay' => 'Alipay F2F',
+                'f2fpay' => 'Alipay F2F',
                 'codepay' => 'CodePay',
+                'credit' => '残高',
                 'epay' => 'ePay',
                 'manual' => '手動支払い',
                 'paybeaver' => 'PayBeaver',
@@ -664,15 +685,19 @@ return [
                 'paypal' => 'PayPal',
                 'stripe' => 'Stripe',
                 'theadpay' => 'THeadPay',
+                'cryptomus' => 'Cryptomus',
+                'youzan' => 'YouZan',
+                'bitpayx' => 'Bitpayx',
             ],
             'hint' => [
-                'alipay' => 'この機能を使用するには、<a href="https://open.alipay.com/platform/appManage.htm?#/create/" target="_blank">Ant Financial Services Open Platform</a>で権限とアプリケーションを申請する必要があります。',
+                'f2fpay' => 'この機能を使用するには、<a href="https://open.alipay.com/platform/appManage.htm?#/create/" target="_blank">Ant Financial Services Open Platform</a>で権限とアプリケーションを申請する必要があります。',
                 'codepay' => 'CodePayに<a href="https://codepay.fateqq.com/i/377289" target="_blank">こちら</a>から登録し、ソフトウェアをダウンロードして設定してください。',
                 'manual' => '設定後、ユーザーエンドに対応する表示が自動的に表示されます。',
                 'paybeaver' => 'PayBeaverに<a href="https://merchant.paybeaver.com/?aff_code=iK4GNuX8" target="_blank">こちら</a>から登録してください。',
                 'payjs' => 'PayJsに<a href="https://payjs.cn/ref/zgxjnb" target="_blank">こちら</a>から登録してください。',
                 'paypal' => '商人アカウントで<a href="https://www.paypal.com/businessprofile/mytools/apiaccess/firstparty" target="_blank">API資格情報申請ページ</a>にログインし、同意して設定情報を取得してください。',
                 'theadpay' => 'THeadPayに<a href="https://theadpay.com/" target="_blank">こちら</a>から登録してください。',
+                'cryptomus' => '<a href="https://app.cryptomus.com/signup" target="_blank">Cryptomus</a>にアクセスしてアカウントを申請し、<a href="https://doc.cryptomus.com/business/general/getting-api-keys" target="_blank">こちらのチュートリアル</a>に従って必要な情報を取得してください。',
             ],
         ],
         'payment_confirm_notification' => '手動支払い確認通知',
@@ -708,7 +733,7 @@ return [
         'referral_percent' => 'リベート率',
         'referral_status' => 'アフィリエイト機能',
         'referral_traffic' => '登録ボーナス',
-        'referral_type' => 'リベートモード',
+        'referral_reward_type' => 'リベートモード',
         'register_ip_limit' => '同一IP登録制限',
         'reset_password_times' => 'パスワードリセット回数',
         'reset_traffic' => 'データ自動リセット',
@@ -717,8 +742,10 @@ return [
         'stripe_public_key' => 'パブリックキー',
         'stripe_secret_key' => 'シークレットキー',
         'stripe_signing_secret' => 'Webhook署名シークレット',
+        'cryptomus_merchant_uuid' => 'Merchant ID',
+        'cryptomus_api_key' => 'API キー',
         'subject_name' => 'カスタム商品名',
-        'subscribe_ban_times' => 'サブスクリプションリクエストしきい値',
+        'subscribe_rate_limit' => 'サブスクリプション支払拒否の制限',
         'subscribe_domain' => 'ノードサブスクリプションURL',
         'subscribe_max' => 'サブスクリプションノード数',
         'telegram_token' => 'Telegramトークン',
@@ -729,9 +756,9 @@ return [
         'ticket_closed_notification' => 'チケットクローズ通知',
         'ticket_created_notification' => '新しいチケット通知',
         'ticket_replied_notification' => 'チケット返信通知',
-        'traffic_ban_time' => '禁止期間',
-        'traffic_ban_value' => 'データ異常しきい値',
-        'traffic_limit_time' => '時間間隔',
+        'ban_duration' => 'ブロックする時間',
+        'traffic_abuse_limit' => 'データ異常しきい値',
+        'checkin_interval' => 'サインイン間隔',
         'traffic_warning_percent' => 'データ警告しきい値',
         'trojan_license' => 'Trojanライセンス',
         'user_invite_days' => 'ユーザー-招待コード有効期',
@@ -744,20 +771,20 @@ return [
         'v2ray_license' => 'V2Rayライセンス',
         'v2ray_tls_provider' => 'V2Ray TLS設定',
         'web_api_url' => 'APIアクセスURL',
-        'webmaster_email' => '管理者メール',
-        'website_analytics' => 'ウェブサイト分析コード',
-        'website_callback_url' => '支払いコールバックURL',
-        'website_customer_service' => 'カスタマーサービスコード',
+        'webmaster_email' => '管理者連絡先',
+        'website_statistics_code' => 'アクセス解析コード',
+        'payment_callback_url' => '決済コールバックURL',
+        'website_customer_service_code' => 'カスタマーサポートコード',
         'website_home_logo' => 'ホームページロゴ',
         'website_logo' => 'サイトロゴ',
-        'website_name' => 'ウェブサイト名',
-        'website_security_code' => 'ウェブサイトセキュリティコード',
-        'website_url' => 'ウェブサイトURL',
+        'website_name' => 'サイト名',
+        'website_security_code' => 'セキュリティアクセスコード',
+        'website_url' => 'サイトURL',
         'wechat_aid' => 'WeChatアプリID',
         'wechat_cid' => 'WeChat企業ID',
-        'wechat_encodingAESKey' => 'WeChatエンコーディングキー',
+        'wechat_encodingAESKey' => 'WeChat暗号化キー',
         'wechat_qrcode' => 'WeChat QRコード',
-        'wechat_secret' => 'WeChatシークレット',
+        'wechat_secret' => 'WeChat秘密鍵',
         'wechat_token' => 'WeChatトークン',
     ],
     'system_generate' => 'システム生成',
@@ -829,5 +856,5 @@ return [
     ],
     'user_dashboard' => 'ユーザーダッシュボード',
     'yes' => 'はい',
-    'zero_unlimited_hint' => '設定しない/0の場合、無制限',
+    'zero_unlimited_hint' => '0または未設定=無制限',
 ];

+ 21 - 21
resources/lang/ja/auth.php

@@ -3,13 +3,13 @@
 declare(strict_types=1);
 
 return [
-    'accept_term' => '私は読んで同意しました',
+    'accept_term' => '内容を読み、同意します',
     'active' => [
         'attribute' => 'アクティベート',
         'error' => [
-            'activated' => 'アカウントは既にアクティベートされています、再度アクティベートする必要はありません',
-            'disable' => 'アカウントのアクティベートは無効です、直接ログインできます!',
-            'throttle' => 'アクティベートリクエストの制限に達しました、後でもう一度お試しください。ご質問がある場合は:emailに連絡してください。',
+            'activated' => 'アカウントがアクティブです。今すぐログインしてください!',
+            'disable' => 'このサイトはアカウントが停止されているため、直接ログインすることができます。',
+            'throttle' => '起動要求の上限に達しました。後でもう一度試してください。',
         ],
         'promotion' => 'アカウントがまだアクティベートされていません、まず「:action」を行ってください!',
         'sent' => 'アクティベートリンクがメールに送信されましたので、受信トレイ(スパムフォルダも含む)を確認してください。',
@@ -18,36 +18,36 @@ return [
     'captcha' => [
         'attribute' => 'キャプチャ',
         'error' => [
-            'failed' => 'キャプチャの検証に失敗しました、再試行してください',
-            'timeout' => 'キャプチャの有効期限が切れました、再読み込みして再試行してください。',
+            'failed' => '確認コードが正しくありません。入力し直してください!',
+            'timeout' => '認証コードが期限切れです。再読み込みしてから再度お試しください。',
         ],
         'required' => 'キャプチャを完了してください!',
         'sent' => 'キャプチャがメールに送信されましたので、受信トレイ(スパムフォルダも含む)を確認してください。',
     ],
     'email' => [
         'error' => [
-            'banned' => '使用しているメールプロバイダーはブロックされています、他のメールを使用してください。',
-            'invalid' => 'サポートされていないメールです。',
+            'banned' => 'メールプロバイダーはサポートされていません。メールボックスを換えてください!',
+            'invalid' => 'あなたのメールアドレスはこのサイトでサポートされるメールアドレスではありません!',
         ],
     ],
     'error' => [
-        'account_baned' => 'アカウントが禁止されています!',
-        'login_error' => 'ログインエラーが発生しました、後でもう一度お試しください!',
+        'account_baned' => 'あなたのアカウントは禁止されています!',
+        'login_error' => 'ログインエラーが起こりました。後ほど再試行してください。',
         'login_failed' => 'ログインに失敗しました、ユーザー名とパスワードを確認してください!',
-        'not_found_user' => 'アカウントが見つかりません、他のログイン方法を試してください。',
-        'repeat_request' => 'リクエストを繰り返さないでください、再読み込みして再試行してください。',
-        'url_timeout' => 'リンクの有効期限が切れました、再度リクエストしてください。',
+        'not_found_user' => '関連付けられているアカウントが見つかりませんでした。他の方法でサインインしてください。',
+        'repeat_request' => '再度リクエストする必要はありません。リフレッシュしてからもう一度お試しください!',
+        'url_timeout' => 'リンクは無効になっています、別の操作をやり直してください!',
     ],
     'failed' => '無効な資格情報です。',
     'invite' => [
         'get' => '招待コードを取得',
         'not_required' => '招待コードは不要です、直接登録できます!',
-        'unavailable' => '無効な招待コードです、再試行してください。',
+        'unavailable' => '無効な招待コードです。再試行して下さい!',
     ],
     'login' => 'ログイン',
     'logout' => 'ログアウト',
     'maintenance' => 'メンテナンス',
-    'maintenance_tip' => 'メンテナンス中',
+    'maintenance_tip' => 'システムメンテナンス中です。少々お待ち下さい。',
     'oauth' => [
         'login_failed' => 'サードパーティのログインに失敗しました!',
         'register' => 'クイック登録',
@@ -62,14 +62,14 @@ return [
         'reset' => [
             'attribute' => 'パスワードの再設定',
             'error' => [
-                'demo' => 'デモモードでは管理者パスワードを変更できません。',
-                'disabled' => 'パスワードリセットは無効です、サポートが必要な場合は:emailに連絡してください。',
-                'same' => '新しいパスワードは古いパスワードと同じにできません、再入力してください。',
-                'throttle' => '24時間以内に:time回しかパスワードをリセットできません、頻繁な操作は避けてください。',
-                'wrong' => 'パスワードが間違っています、再試行してください。',
+                'demo' => 'このデモ版では管理者のパスワードの変更が無効になっています。',
+                'disabled' => '今はパスワードリセット機能を無効にしています!',
+                'same' => '新しいパスワードを古いパスワードと同じにはできません。もう一度設定してください。',
+                'throttle' => '24時間ごとにユーザ名のみ再設定できます。パスワードは time 回、頻繁に行わないでください。',
+                'wrong' => '古いパスワードが正しくありません。再度入力してください。',
             ],
             'sent' => 'リセットリンクがメールに送信されましたので、受信トレイ(スパムフォルダも含む)を確認してください。',
-            'success' => '新しいパスワードが正常にリセットされました、ログインできます。',
+            'success' => '新しいパスワードが設定されました。ログインページに移動してください。',
         ],
     ],
     'register' => [

+ 18 - 18
resources/lang/ja/errors.php

@@ -4,30 +4,30 @@ declare(strict_types=1);
 
 return [
     'forbidden' => [
-        'access' => '不明なIPまたはプロキシアクセスが検出されました。アクセス禁止',
-        'bots' => 'ボットアクセスが検出されました。アクセス禁止',
-        'china' => '中国のIPまたはプロキシアクセスが検出されました。アクセス禁止',
-        'oversea' => '海外のIPまたはプロキシアクセスが検出されました。アクセス禁止',
-        'redirect' => '(:ip :url)がサブスクリプションリンクを通じてアクセスしていることを検出しました。強制リダイレクトを実行します。',
-        'unknown' => '不明な禁止アクセスモード!システム設定で[アクセス禁止モード]を変更してください!',
+        'access' => '不明なIP またはプロキシが検出されました。アクセス禁止',
+        'bots' => 'ロボットがアクセスするとアクセスが拒否されました!',
+        'china' => '中国の IP またはプロキシが検出されました。アクセス禁止 IP の表示',
+        'oversea' => '海外のIPまたはプロキシが検出されました。アクセス禁止されました!',
+        'redirect' => '購読リンクを使用して接続する (:IP:url) を検出しました 強制リダイレクトです',
+        'unknown' => '不明なブロックモードです。 システム設定で設定を確認してください。',
     ],
-    'get_ip' => 'IP情報の取得に失敗しました',
+    'get_ip' => 'IP データの取得に失敗しました',
     'log' => 'ログ',
     'refresh' => 'リフレッシュ',
-    'refresh_page' => 'ページをリフレッシュしてから再度アクセスしてください',
-    'report' => 'エラーにレポートが含まれています:',
+    'refresh_page' => 'ページをリフレッシュしたら、再試行してください。',
+    'report' => 'クラッシュレポート:',
     'safe_code' => '安全コードを入力してください',
     'safe_enter' => '安全な入口',
     'subscribe' => [
-        'banned_until' => 'アカウントは:timeまで禁止されています。解除後に更新してください!',
-        'expired' => 'アカウントの有効期限が切れています。更新してください!',
-        'none' => '利用可能なノードがありません',
-        'out' => 'データが不足しています。再購入またはデータをリセットしてください!',
-        'question' => 'アカウントに問題があります。公式サイトをご覧ください!',
-        'sub_banned' => 'リンクが禁止されました。詳細は公式サイトをご覧ください',
-        'unknown' => '無効なリンクです。新しいリンクを取得してください!',
-        'user' => '無効なリンクです。アカウントが存在しません!新しいリンクを取得してください!',
-        'user_disabled' => 'アカウントが無効です!',
+        'banned_until' => 'アカウントのアクセス制限が禁止されました。更新を試みてください。',
+        'expired' => 'アカウントの有効期限が切れています。お手数ですが再度試してみてください',
+        'none' => '利用できるノードがありません。',
+        'out' => 'トラフィックは使い切れです。購入またはリセットしてください。',
+        'question' => 'エラーが発生しました。ネットクエリの詳細を確認してください!',
+        'sub_banned' => '購読リンクはブロックされています。理由を確認するにはホームページに移動してください!',
+        'unknown' => '無効なフィードリンクです。再取得してください!',
+        'user' => 'リンクが無効です。アカウントが存在しません。取得し直してください!',
+        'user_disabled' => 'アカウントが停止されています!',
     ],
     'title' => '⚠️ エラーが発生しました',
     'unsafe_enter' => '安全でない入口',

+ 176 - 44
resources/lang/ja/model.php

@@ -16,7 +16,6 @@ return [
         'created_at' => '公開日',
         'language' => '言語',
         'logo' => 'カバー',
-        'updated_at' => '更新日',
     ],
     'common' => [
         'description' => '説明',
@@ -26,7 +25,6 @@ return [
         'type' => 'タイプ',
     ],
     'country' => [
-        'code' => '国コード',
         'icon' => '国旗',
         'name' => '国名',
     ],
@@ -35,16 +33,16 @@ return [
         'groups' => 'グループ制限',
         'levels' => 'レベル制限',
         'logo' => 'ロゴ',
-        'minimum' => '最低利用額',
+        'minimum' => '最低注文数以下では販売しません',
         'name' => '名前',
         'newbie' => '新規ユーザー専用',
         'num' => '数量',
         'priority' => '優先度',
         'services_blacklist' => 'ブラックリスト商品',
         'services_whitelist' => 'ホワイトリスト商品',
-        'sn' => 'コード',
+        'sn' => 'クーポンコード',
         'usable_times' => '使用制限',
-        'used' => '個人制限',
+        'used' => '1人限定',
         'users_blacklist' => 'ブラックリストユーザー',
         'users_whitelist' => 'ホワイトリストユーザー',
         'value' => '価値',
@@ -56,13 +54,13 @@ return [
         'color' => '色',
         'hot' => 'ベストセラー',
         'info' => 'カスタム情報',
-        'invite_num' => 'ボーナス招待数',
-        'limit_num' => '購入制限',
-        'logo' => 'ロゴ',
+        'invite_num' => '招待者の署名に力が付与されます',
+        'limit_num' => '1人限定購入',
+        'logo' => '商品画像',
         'name' => '名前',
-        'period' => 'リセットサイクル',
+        'period' => '期間をリセット',
         'price' => '価格',
-        'renew' => 'データ更新価格',
+        'renew' => '使用量のリセット',
         'traffic' => 'データ許容量',
         'user_limit' => 'ユーザー速度制限',
     ],
@@ -74,23 +72,23 @@ return [
         'attribute' => 'ノード',
         'client_limit' => 'クライアント制限',
         'country' => '国',
-        'data_consume' => 'データ消費',
-        'data_rate' => 'データ比率',
+        'data_consume' => 'トラフィックの消費',
+        'data_rate' => 'データ使用量:',
         'ddns' => 'DDNS',
-        'detection' => 'ブロック検',
+        'detection' => 'ブロック検',
         'display' => '表示とサブスクリプション',
         'domain' => 'ドメイン',
         'id' => 'ノードID',
-        'ipv4' => 'IPv4アドレス',
+        'ipv4' => 'IPv4アドレス',
         'ipv6' => 'IPv6アドレス',
         'label' => 'ラベル',
         'method' => '暗号化方式',
-        'name' => '名',
+        'name' => 'ノード名',
         'next_renewal_date' => '次回更新日',
-        'obfs' => '難読化',
+        'obfs' => '混同契約',
         'obfs_param' => '難読化パラメータ',
         'online_user' => 'オンラインユーザー',
-        'protocol' => 'プロトコル',
+        'protocol' => '伝送プロトコル',
         'protocol_param' => 'プロトコルパラメータ',
         'push_port' => 'プッシュポート',
         'relay_port' => 'リレーポート',
@@ -98,23 +96,23 @@ return [
         'service_port' => 'サービスポート',
         'single' => 'シングルポート',
         'single_passwd' => 'シングルポートパスワード',
-        'static' => 'ステータス',
+        'static' => '操作のステータス',
         'subscription_term' => '契約期間',
-        'traffic_limit' => '速度制限',
-        'transfer' => 'リレー',
-        'udp' => 'UDP',
-        'v2_alter_id' => '代替ID',
-        'v2_cover' => 'カバー',
+        'traffic_limit' => 'データ上限',
+        'transfer' => '継承設定',
+        'udp' => 'UDP サポート',
+        'v2_alter_id' => '追加ID',
+        'v2_cover' => 'トラフィック',
         'v2_host' => 'ホスト',
-        'v2_net' => 'ネットワーク',
-        'v2_path' => 'パスまたはキー',
+        'v2_net' => '伝送プロトコル',
+        'v2_path' => 'パス/シークレット',
         'v2_sni' => 'SNI',
-        'v2_tls' => 'TLS',
-        'v2_tls_provider' => 'TLS設定',
+        'v2_tls' => 'TLS の暗号化',
+        'v2_tls_provider' => 'TLS 証明書の設定',
     ],
     'node_auth' => [
         'attribute' => 'ノード認証',
-        'key' => '通信キー<small>ノード用</small>',
+        'key' => '通信キー',
         'secret' => 'リバースキー',
     ],
     'node_cert' => [
@@ -122,7 +120,7 @@ return [
         'domain' => 'ドメイン',
         'expired_date' => '有効期限',
         'issuer' => '発行者',
-        'key' => 'キー',
+        'key' => '秘密鍵',
         'pem' => '証明書',
         'signed_date' => '発行日',
     ],
@@ -163,21 +161,23 @@ return [
         'attribute' => 'ルール',
         'name' => '説明',
         'pattern' => '値',
+        'logs' => 'トリガー履歴',
     ],
     'rule_group' => [
         'attribute' => 'ルールグループ',
-        'name' => '名',
+        'name' => 'グループ名',
         'rules' => 'ルール',
         'type' => 'タイプ',
     ],
     'subscribe' => [
+        'attribute' => '購読する',
         'ban_desc' => '禁止理由',
         'ban_time' => '禁止時間',
         'code' => 'サブスクリプションコード',
         'req_header' => 'リクエストヘッダー',
         'req_ip' => 'リクエストIP',
         'req_times' => 'リクエスト回数',
-        'updated_at' => '最終リクエスト時間',
+        'updated_at' => '最後のリクエスト',
     ],
     'user' => [
         'account_status' => 'アカウントステータス',
@@ -186,13 +186,13 @@ return [
         'credit' => '残高',
         'expired_date' => '有効期限',
         'id' => 'ユーザーID',
-        'invite_num' => '利用可能な招待数',
+        'invite_num' => '招待者の名前',
         'inviter' => '招待者',
         'nickname' => 'ニックネーム',
         'password' => 'パスワード',
         'port' => 'ポート',
-        'proxy_method' => '暗号化方式',
-        'proxy_obfs' => '難読化',
+        'proxy_method' => '暗号化',
+        'proxy_obfs' => 'トラフィック',
         'proxy_passwd' => 'プロキシパスワード',
         'proxy_protocol' => 'プロトコル',
         'proxy_status' => 'プロキシステータス',
@@ -201,7 +201,7 @@ return [
         'reset_date' => 'データリセット日',
         'role' => '権限',
         'service' => 'プロキシサービス',
-        'speed_limit' => '速度制限',
+        'speed_limit' => 'レート制限は',
         'traffic_used' => '使用済みデータ',
         'usable_traffic' => '利用可能なデータ',
         'username' => 'ユーザー名',
@@ -209,15 +209,15 @@ return [
         'wechat' => 'WeChat',
     ],
     'user_credit' => [
-        'after' => '変更後の残高',
-        'amount' => '変更金額',
-        'before' => '変更前の残高',
-        'created_at' => '変更日時',
+        'after' => '変更後',
+        'amount' => 'お釣り',
+        'before' => '変更前',
+        'created_at' => '記録時間',
     ],
     'user_data_modify' => [
-        'after' => '変更後のデータ',
-        'before' => '変更前のデータ',
-        'created_at' => '変更日時',
+        'after' => '変更後',
+        'before' => '変更前',
+        'created_at' => '記録時間',
     ],
     'user_group' => [
         'attribute' => 'ユーザーグループ',
@@ -225,9 +225,141 @@ return [
         'nodes' => 'ノード',
     ],
     'user_traffic' => [
-        'download' => 'ダウンロード',
+        'download' => '通信量',
         'log_time' => '記録時間',
         'total' => '合計',
-        'upload' => 'アップロード',
+        'upload' => '送信データ',
+    ],
+    'config' => [
+        'AppStore_id' => 'AppleID',
+        'AppStore_password' => 'アップル パスワード',
+        'account_expire_notification' => 'アカウント有効期限通知',
+        'active_times' => '上限を有効にする',
+        'admin_invite_days' => '招待コードを管理する',
+        'affiliate_link_salt' => '招待リンク暗号化',
+        'alipay_qrcode' => 'PayPal決済コード',
+        'auto_release_port' => 'ポート回収',
+        'ban_duration' => 'ブロックする時間',
+        'bark_key' => 'Barkデバイスキー',
+        'captcha_key' => 'Captcha キー',
+        'captcha_secret' => 'Secret/ID',
+        'checkin_interval' => 'サインイン間隔',
+        'checkin_reward' => 'ボーナス記録',
+        'codepay_id' => 'CodePay ID',
+        'codepay_key' => '通信キー',
+        'codepay_url' => 'リクエストURL',
+        'cryptomus_api_key' => 'API キー',
+        'cryptomus_merchant_uuid' => 'Merchant ID',
+        'data_anomaly_notification' => 'データ異常通知',
+        'data_exhaust_notification' => 'データ使用量通知',
+        'ddns_key' => 'DNSキー',
+        'ddns_mode' => 'DDNSモード',
+        'ddns_secret' => 'DNS Secret',
+        'default_days' => '初期有効期限',
+        'default_traffic' => '初期データ量',
+        'detection_check_times' => '通知をブロックする',
+        'dingTalk_access_token' => 'Todoubles をピン止めする',
+        'dingTalk_secret' => 'ピン留め',
+        'epay_key' => 'ePayキー',
+        'epay_mch_id' => 'ePay商人ID',
+        'epay_url' => 'THeadPay URL',
+        'expire_days' => '有効期限警告しきい値',
+        'f2fpay_app_id' => 'アプリID',
+        'f2fpay_private_key' => 'アプリプライベートキー',
+        'f2fpay_public_key' => 'Alipayパブリックキー',
+        'forbid_mode' => 'アクセスブロックモード',
+        'iYuu_token' => 'IYUUトークン',
+        'invite_num' => '初期招待者の名前',
+        'is_AliPay' => 'アリペイ',
+        'is_QQPay' => 'QQウォレット',
+        'is_WeChatPay' => 'WeChat支払い',
+        'is_activate_account' => 'アカウントの有効化',
+        'is_ban_status' => '有効期限自動禁止',
+        'is_captcha' => 'キャプチャ',
+        'is_clear_log' => 'ログの自動クリーンアップ',
+        'is_custom_subscribe' => 'カスタムサブスクリプション',
+        'is_email_filtering' => 'Eメールフィルター',
+        'is_forbid_robot' => 'ドロイドをブロック',
+        'is_free_code' => '無料招待コード',
+        'is_invite_register' => '招待登録',
+        'is_otherPay' => 'カスタム支払い',
+        'is_rand_port' => 'ランダムポート',
+        'is_register' => 'すぐ実行',
+        'maintenance_content' => 'メンテナンスのお知らせ',
+        'maintenance_mode' => 'メンテナンスモード',
+        'maintenance_time' => 'メンテナンス終了時間',
+        'min_port' => 'ポート範囲',
+        'node_blocked_notification' => 'ノードブロック通知',
+        'node_daily_notification' => '毎日のノードレポート',
+        'node_offline_notification' => 'ノードオフライン通知',
+        'node_renewal_notification' => 'ノード更新のリマインダー',
+        'oauth_path' => 'サードパーティーログイン',
+        'offline_check_times' => 'オフライン通知回数',
+        'password_reset_notification' => 'パスワードリセット',
+        'paybeaver_app_id' => 'App ID',
+        'paybeaver_app_secret' => 'Appシークレット',
+        'payjs_key' => '通信キー',
+        'payjs_mch_id' => '商人ID',
+        'payment_callback_url' => '支払コールバックアドレス',
+        'payment_confirm_notification' => '支払い通知',
+        'payment_received_notification' => '支払い成功通知',
+        'paypal_app_id' => 'App ID',
+        'paypal_client_id' => 'クライアントID',
+        'paypal_client_secret' => 'Client Secret キー',
+        'pushDeer_key' => 'PushDeerキー',
+        'pushplus_token' => 'PushPlusトークン',
+        'rand_subscribe' => 'ランダムサブスクリプション',
+        'recently_heartbeat' => '最近のノードポートレート閾値',
+        'redirect_url' => 'リダイレクトURL',
+        'referral_money' => '最低金額',
+        'referral_percent' => 'リベート率',
+        'referral_reward_type' => 'リベートモード',
+        'referral_status' => 'アフィリエイト機能',
+        'referral_traffic' => '登録トラフィック合計',
+        'register_ip_limit' => '同一IP登録制限',
+        'reset_password_times' => '1日のリセット上限',
+        'reset_traffic' => '定期的にトラフィックリセット',
+        'server_chan_key' => 'ServerChan SCKEY',
+        'standard_currency' => '基準通貨',
+        'stripe_public_key' => 'パブリックキー',
+        'stripe_secret_key' => 'シークレットキー',
+        'stripe_signing_secret' => 'WebHook Signing Secret',
+        'subject_name' => 'このキャンペーンの送信に使用される電子メールアカウント',
+        'subscribe_domain' => 'サブスクリプション用URL',
+        'subscribe_max' => 'サブスクリプションノード数',
+        'subscribe_rate_limit' => 'サブスクリプション支払拒否の制限',
+        'tasks_chunk' => '分割処理数',
+        'tasks_clean' => 'タスクを終了',
+        'tasks_close' => 'タスクを閉じる',
+        'telegram_token' => 'Telegramトークン',
+        'tg_chat_token' => 'TGチャットトークン',
+        'theadpay_key' => 'ePayキー',
+        'theadpay_mchid' => 'ePay商人ID',
+        'theadpay_url' => 'THeadPay URL',
+        'ticket_closed_notification' => 'チケットクローズ通知',
+        'ticket_created_notification' => '新しいチケット通知',
+        'ticket_replied_notification' => 'チケット返信通知',
+        'traffic_abuse_limit' => 'データ異常しきい値',
+        'traffic_warning_percent' => 'トラフィック使用警告閾値',
+        'trojan_license' => 'Trojanライセンス',
+        'user_invite_days' => 'ユーザー招待コード有効期限',
+        'username_type' => 'アカウントの種類',
+        'v2ray_license' => 'V2Rayライセンス',
+        'v2ray_tls_provider' => 'V2Ray TLSS',
+        'web_api_url' => 'APIアクセスURL',
+        'webmaster_email' => 'サイト管理者の電子メール',
+        'website_customer_service_code' => 'サポート コード',
+        'website_home_logo' => 'ホームページロゴ',
+        'website_logo' => 'サイトロゴ',
+        'website_name' => 'サイト名',
+        'website_security_code' => 'セキュリティコード',
+        'website_statistics_code' => '統計コード',
+        'website_url' => 'サイトURL',
+        'wechat_aid' => 'アプリID',
+        'wechat_cid' => '企業ID',
+        'wechat_encodingAESKey' => 'EncodingAESキー',
+        'wechat_qrcode' => 'WeChat番号',
+        'wechat_secret' => 'アプリキー',
+        'wechat_token' => 'TOKEN',
     ],
 ];

+ 1 - 1
resources/lang/ko/admin.php

@@ -501,7 +501,7 @@ return [
             'active_times' => '24시간 내 이메일을 통해 계정 활성화 횟수',
             'admin_invite_days' => '관리자가 생성한 초대 코드의 유효 기간',
             'aff_salt' => '비워두면 초대 링크에 사용자 ID가 표시됩니다; 임의의 영문/숫자를 입력하면 사용자 링크 ID가 암호화됩니다',
-            'auto_release_port' => '차단/만료된 계정의 포트를 자동으로 회수합니다 <code>'.config('tasks.release_port').'</code> 일 후 자동으로 포트를 회수합니다',
+            'auto_release_port' => '차단/만료된 계정의 포트를 자동으로 회수합니다 <code>'.sysConfig('tasks.release_port').'</code> 일 후 자동으로 포트를 회수합니다',
             'bark_key' => 'iOS 장치로 푸시 메시지를 보내려면 Bark라는 앱을 설치하고, URL 뒤에 있는 긴 문자열을 입력해야 합니다. Bark를 활성화하려면 반드시 이 값을 입력해야 합니다.',
             'captcha_key' => '설정 가이드를 보려면 <a href="https://proxypanel.gitbook.io/wiki/captcha" target="_blank">여기</a>를 클릭하세요',
             'data_anomaly_notification' => '1시간 내 데이터 이상 임계값을 초과하면 관리자에게 알림',

+ 141 - 0
resources/lang/ru.json

@@ -0,0 +1,141 @@
+{
+  "(and :count more error)": "(и ещё :count ошибка)",
+  "(and :count more errors)": "(и ещё :count ошибок)",
+  "----「:job」Completed, Used :time seconds ----": "----「:job」завершено,затрачено :time секунд ----",
+  "[Auto Task] Blocked service: Abnormal traffic within 1 hour": "[Автоматическая задача] Сервис заблокирован: обнаружен аномальный трафик в течение 1 часа",
+  "[Auto Task] Blocked service: Run out of traffic": "[Автоматическая задача] Сервис заблокирован: трафик исчерпан",
+  "[Auto Task] Blocked Subscription: Subscription with abnormal requests within 24 hours": "[Автоматическая задача] Подписка заблокирована: обнаружены аномальные запросы в течение 24 часов",
+  "[Auto Task] Unblocked Service: Account ban expired": "[Автоматическая задача] Сервис разблокирован: срок блокировки аккаунта истек",
+  "[Auto Task] Unblocked Service: Account has available data traffic": "[Автоматическая задача] Сервис разблокирован: доступен остаток трафика аккаунта",
+  "[Daily Task] Account Expiration: Block Login & Clear Account": "[Ежедневная задача] Срок действия аккаунта истек: вход запрещен, данные аккаунта очищены",
+  "[Daily Task] Account Expiration: Stop Service": "[Ежедневная задача] Срок действия аккаунта истек: обслуживание остановлено",
+  "[Daily Task] Reset Account Traffic, Next Reset Date: :date": "[Ежедневная задача] Сброс трафика аккаунта, следующая дата сброса::date",
+  "[Service Timer] Service Expiration": "[Периодическая задача] Сервис истек срок действия",
+  "A Timeout Occurred": "Истекло время ожидания",
+  "Accepted": "Принято",
+  "All rights reserved.": "Все права защищены.",
+  "Already Reported": "Уже сообщалось",
+  "Bad Gateway": "Проблема с шлюзом",
+  "Bad Request": "Некорректный запрос",
+  "Bandwidth Limit Exceeded": "Превышена нагрузка на канал связи",
+  "Client Closed Request": "Запрос закрыт клиентом",
+  "Conflict": "Конфликт",
+  "Connection Closed Without Response": "Соединение закрыто без ответа",
+  "Connection Timed Out": "Соединение не отвечает",
+  "Continue": "Продолжить",
+  "Created": "Создано",
+  "Daily Data Usage Report": "Ежедневный отчет о использовании трафика",
+  "Expectation Failed": "Истекло время ожидания",
+  "Failed Dependency": "Ошибка зависимости",
+  "Forbidden": "Запрещено",
+  "Found": "Найдено",
+  "Gateway Timeout": "Шлюз не отвечает",
+  "Go to page :page": "Перейти к :page-й странице",
+  "Gone": "Удалено",
+  "Hello!": "Здравствуйте!",
+  "HTTP Version Not Supported": "Версия HTTP не поддерживается",
+  "I'm a teapot": "Я - чайник",
+  "If you did not create an account, no further action is required.": "Если Вы не создавали учетную запись, никаких дополнительных действий не требуется.",
+  "If you did not request a password reset, no further action is required.": "Если Вы не запрашивали восстановление пароля, никаких дополнительных действий не требуется.",
+  "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Если у Вас возникли проблемы с нажатием кнопки \":actionText\", скопируйте и вставьте приведенный ниже URL-адрес в свой браузер:",
+  "IM Used": "Использовано IM",
+  "Insufficient Storage": "Переполнение хранилища",
+  "Internal Server Error": "Внутренняя ошибка",
+  "Invalid JSON was returned from the route.": "Маршрут вернул некорректный JSON.",
+  "Invalid SSL Certificate": "Недействительный SSL сертификат",
+  "Invoice Detail": "Детали заказа",
+  "Length Required": "Необходима длина",
+  "Locked": "Доступ заблокирован",
+  "Login": "Войти",
+  "Logout": "Выйти",
+  "Loop Detected": "Обнаружен бесконечный цикл",
+  "Maintenance Mode": "Ведутся технические работы",
+  "Method Not Allowed": "Метод запрещён",
+  "Misdirected Request": "Неверный запрос",
+  "Moved Permanently": "Перемещено навсегда",
+  "Multi-Status": "Много статусов",
+  "Multiple Choices": "Много вариантов",
+  "Network Authentication Required": "Требуется сетевая аутентификация",
+  "Network Connect Timeout Error": "Истекло время подключения",
+  "Network Read Timeout Error": "Истекло время ожидания",
+  "No Content": "Содержимое отсутствует",
+  "Non-Authoritative Information": "Информация не авторитетна",
+  "Not Acceptable": "Неприемлемо",
+  "Not Extended": "Не расширено",
+  "Not Found": "Не найдено",
+  "Not Implemented": "Не реализовано",
+  "Not Modified": "Не изменялось",
+  "of": "из",
+  "OK": "ОК",
+  "Origin Is Unreachable": "Источник недоступен",
+  "Page Expired": "Страница устарела",
+  "Pagination Navigation": "Навигация",
+  "Partial Content": "Не полное содержимое",
+  "Payload Too Large": "Большой объём данных",
+  "Payment for #:sn has been received! Total amount: :amount.": "Платеж по заказу #:sn получен! Общая сумма::amount",
+  "Payment Received": "Платеж успешно выполнен.",
+  "Payment Required": "Требуется оплата",
+  "Permanent Redirect": "Постоянное перенаправление",
+  "Please click the button below to verify your email address.": "Пожалуйста, нажмите кнопку ниже, чтобы подтвердить свой адрес электронной почты.",
+  "Precondition Failed": "Условие ложно",
+  "Precondition Required": "Требуется предусловие",
+  "Processing": "Идет обработка",
+  "Proxy Authentication Required": "Требуется аутентификация прокси",
+  "Railgun Error": "Ошибка соединения с Railgun",
+  "Range Not Satisfiable": "Диапазон недостижим",
+  "Regards": "С уважением",
+  "Register": "Регистрация",
+  "Request Header Fields Too Large": "Поля заголовка слишком большие",
+  "Request Timeout": "Истекло время ожидания",
+  "Reset Content": "Сброс содержимого",
+  "Reset Password": "Сбросить пароль",
+  "Reset Password Notification": "Оповещение о сбросе пароля",
+  "results": "результатов",
+  "Retry With": "Повторить с",
+  "See Other": "Смотри другое",
+  "Server Error": "Ошибка сервера",
+  "Service Unavailable": "Сервис недоступен",
+  "Session Has Expired": "Сессия устарела",
+  "Showing": "Показано с",
+  "SSL Handshake Failed": "Квитирование SSL не удалось",
+  "Subscription link receive abnormal access and banned by the system": "Ссылка подписки обнаружила аномальный доступ, система автоматически заблокировала её.",
+  "Switching Protocols": "Переключение протоколов",
+  "Temporary Redirect": "Временное перенаправление",
+  "Thank you for signing up! Before you start, you need to verify your email by clicking on the link we have just sent to your email! If you haven't received an email, we would be happy to send another one.": "Спасибо за регистрацию! Перед началом, пожалуйста, нажмите на ссылку, которую мы только что отправили на ваш адрес электронной почты, чтобы завершить проверку почты. Если вы не получили письмо, мы с радостью отправим его повторно.",
+  "The given data was invalid.": "Указанные данные недействительны.",
+  "The response is not a streamed response.": "Ответ не является потоковым.",
+  "The response is not a view.": "Ответ не является представлением.",
+  "This password reset link will expire in :count minutes.": "Срок действия ссылки для сброса пароля истекает через :count минут.",
+  "to": "по",
+  "Toggle navigation": "Переключить навигацию",
+  "Too Early": "Слишком рано",
+  "Too Many Requests": "Слишком много запросов",
+  "Unauthorized": "Не авторизован",
+  "Unavailable For Legal Reasons": "Недоступно по юридическим причинам",
+  "Unknown Error": "Неизвестная ошибка",
+  "Unprocessable Entity": "Необрабатываемый экземпляр",
+  "Unsupported Media Type": "Неподдерживаемый тип данных",
+  "Upgrade Required": "Требуется обновление",
+  "URI Too Long": "URI слишком длинный",
+  "Use Proxy": "Используй прокси",
+  "Variant Also Negotiates": "Вариант тоже проводит согласование",
+  "Verify Email Address": "Подтвердить адрес электронной почты",
+  "Verify Your Email Address": "Подтвердите ваш адрес электронной почты.",
+  "Web Server is Down": "Веб-сервер не работает",
+  "Whoops!": "Упс!",
+  "You are receiving this email because we received a password reset request for your account.": "Вы получили это письмо, потому что мы получили запрос на сброс пароля для Вашей учётной записи.",
+  "You have not responded this ticket in :num hours, System has closed your ticket.": "В течение:num часов не было ответа на этот тикет, система автоматически закрыла его.",
+  "You must have a valid subscription to view the content in this area!": "Вы должны иметь действующую подписку, чтобы просмотреть содержимое этой области!",
+  "Your subscription has been disabled by the administrator, please contact the administrator to restore it": "Ваша подписка была отключена администратором, пожалуйста, свяжитесь с администратором для восстановления подписки.",
+  "Manually add in dashboard.": "后台手动添加",
+  "Manually edit in dashboard.": "Редактировать вручную через панель управления.",
+  "Batch generate user accounts in dashboard.": "Пакетное создание пользовательских аккаунтов через панель управления.",
+  "Coupon used in order.": "Купон использован в заказе.",
+  "Order canceled, coupon reinstated.": "Заказ отменен, купон восстановлен.",
+  "Used for credit recharge.": "Используется для пополнения баланса.",
+  "The user manually reset the data.": "Пользователь самостоятельно сбросил трафик.",
+  "Recharge using a recharge voucher.": "Использовать купон для пополнения.",
+  "The user topped up the balance.": "Пользователь пополнил баланс.",
+  "Purchased an item.": "Товар приобретен.",
+  "[:payment] plus the user’s purchased data plan.": "【:payment】добавлено к трафику, приобретенному пользователем."
+}

+ 8 - 8
resources/lang/ru/notification.php

@@ -9,18 +9,18 @@ return [
     'active_email' => 'Пожалуйста, завершите проверку в течение 30 минут',
     'attribute' => 'Уведомление',
     'block_report' => 'Отчет о блокировке:',
-    'close_ticket' => 'Тикет :id: :title закрыт',
-    'data_anomaly' => 'Предупреждение о аномалии данных пользователя',
-    'data_anomaly_content' => 'Пользователь :id: [Загрузка: :upload | Скачивание: :download | Всего: :total] за последний час',
+    'close_ticket' => 'Тикет [ID: :id, заголовок: :title] закрыт.',
+    'data_anomaly' => 'Уведомление о аномальном трафике пользователя.',
+    'data_anomaly_content' => 'Пользователь [ID: :id] трафика за последний час(Загрузка: :upload, Скачивание: :download, Всего: :total)',
     'details' => 'Просмотреть подробности',
     'details_btn' => 'Пожалуйста, нажмите кнопку ниже, чтобы просмотреть подробности.',
-    'ding_bot_limit' => 'Каждый бот может отправлять до 20 сообщений в минуту в группу. При превышении этого лимита будет применено ограничение на 10 минут.',
+    'ding_bot_limit' => 'Каждый бот может отправлять не более 20 сообщений в минуту. При превышении лимита будет установлена задержка в 10 минут.',
     'empty' => 'У вас нет новых сообщений',
     'error' => '[:channel] Ошибка отправки сообщения: :reason',
     'get_access_token_failed' => 'Не удалось получить токен доступа!\nС параметрами запроса: :body',
     'into_maintenance' => 'Автоматически переход в режим обслуживания',
     'new' => '{1} :num новое сообщение|[2,4] :num новых сообщения|[5,*] :num новых сообщений',
-    'new_ticket' => 'Получен новый тикет: :title',
+    'new_ticket' => 'Вы получили новый тикет [Заголовок: :title], пожалуйста, нажмите, чтобы просмотреть подробности.',
     'next_check_time' => 'Следующее время проверки блокировки узла: :time',
     'node' => [
         'download' => 'Скачивание',
@@ -35,13 +35,13 @@ return [
     'node_renewal_content' => 'Срок действия следующих узлов скоро истекает. Пожалуйста, продлите до истечения срока, чтобы избежать прерывания обслуживания.',
     'payment_received' => 'Платеж получен, сумма: :amount. Просмотреть детали заказа',
     'reply_ticket' => 'Ответ на тикет: :title',
-    'reset_failed' => '[Ежедневная задача] Сброс данных пользователя :uid - :username не удался',
+    'reset_failed' => '[Ежедневная задача] Сброс трафика пользователя [ID: :uid, Пользователь: :username] не удался.',
     'serverChan_exhausted' => 'Дневной лимит исчерпан!',
     'serverChan_limit' => 'Слишком высокая частота запросов в минуту. Пожалуйста, оптимизируйте настройки уведомлений!',
     'sign_failed' => 'Проверка безопасной подписи не удалась',
     'ticket_content' => 'Содержание тикета:',
-    'traffic_remain' => 'Использовано :percent% трафика, пожалуйста, обратите внимание',
-    'traffic_tips' => 'Пожалуйста, обратите внимание на дату сброса трафика и используйте его рационально, или пополните после исчерпания',
+    'traffic_remain' => 'Вы использовали:percent% трафика, пожалуйста, разумно распределите оставшийся трафик.',
+    'traffic_tips' => 'Пожалуйста, обратите внимание на дату сброса трафика, разумно планируйте использование или пополняйте баланс после исчерпания трафика.',
     'traffic_warning' => 'Предупреждение об использовании трафика',
     'verification' => 'Ваш код подтверждения:',
     'verification_account' => 'Подтверждение аккаунта',

+ 1 - 1
resources/lang/vi/admin.php

@@ -501,7 +501,7 @@ return [
             'active_times' => 'Số lần kích hoạt tài khoản qua email trong 24 giờ',
             'admin_invite_days' => 'Thời hạn hiệu lực mã mời của quản trị viên',
             'aff_salt' => 'Muối mã hóa cho URL giới thiệu',
-            'auto_release_port' => 'Tự động giải phóng cổng sau khi bị cấm hoặc hết hạn <code>'.config('tasks.release_port').'</code> ngày',
+            'auto_release_port' => 'Tự động giải phóng cổng sau khi bị cấm hoặc hết hạn <code>'.sysConfig('tasks.release_port').'</code> ngày',
             'bark_key' => 'Khóa thiết bị cho thông báo đẩy iOS',
             'captcha_key' => 'Xem <a href="https://proxypanel.gitbook.io/wiki/captcha" target="_blank">hướng dẫn cài đặt</a>',
             'data_anomaly_notification' => 'Thông báo cho quản trị viên khi lưu lượng hàng giờ vượt quá ngưỡng',

+ 1 - 0
resources/lang/zh_CN/common.php

@@ -46,6 +46,7 @@ return [
     'deleted_item' => ':attribute已删除',
     'developing' => '功能开发中,敬请期待!',
     'download' => '下载',
+    'download_item' => '下载:attribute',
     'edit' => '编辑',
     'error' => '错误',
     'error_action_item' => ':action:attribute出错',

+ 1 - 2
resources/lang/zh_CN/model.php

@@ -16,7 +16,6 @@ return [
         'created_at' => '发布时间',
         'language' => '语言',
         'logo' => '封面',
-        'updated_at' => '更新时间',
     ],
     'common' => [
         'description' => '描述',
@@ -26,7 +25,6 @@ return [
         'type' => '类型',
     ],
     'country' => [
-        'code' => '国家代码',
         'icon' => '国旗',
         'name' => '国家名称',
     ],
@@ -172,6 +170,7 @@ return [
         'type' => '模式',
     ],
     'subscribe' => [
+        'attribute' => '订阅',
         'ban_desc' => '封禁原因',
         'ban_time' => '封禁时间',
         'code' => '订阅码',

+ 7 - 0
resources/lang/zh_CN/user.php

@@ -232,6 +232,13 @@ return [
         'tips' => '警告:此链接仅限个人使用,传播将导致封号',
         'trojan_only' => '仅Trojan订阅',
         'v2ray_only' => '仅V2Ray订阅',
+        'page' => [
+            'get_link' => '获取链接',
+            'connect' => '连接&使用',
+            'error' => [
+                'no_app' => '暂无可用应用',
+            ],
+        ],
     ],
     'telegram' => [
         'bind_exists' => 'Telegram账号已绑定',

+ 1 - 1
resources/views/_layout.blade.php

@@ -14,7 +14,7 @@
     <link href="{{ asset('favicon.ico') }}" rel="shortcut icon apple-touch-icon">
     <!-- 样式表/Stylesheets -->
     <link href="/assets/bundle/app.min.css" rel="stylesheet">
-    <link href="https://cdn.staticfile.net/flag-icons/7.2.3/css/flag-icons.min.css" rel="stylesheet">
+    <link href="https://cdn.jsdelivr.net/npm/flag-icons@7/css/flag-icons.min.css" rel="stylesheet">
     @yield('layout_css')
     <!-- 字体/Fonts -->
     <link href="/assets/global/fonts/web-icons/web-icons.min.css" rel="stylesheet">

+ 3 - 3
resources/views/admin/config/common.blade.php

@@ -217,7 +217,7 @@
                                 <thead class="thead-default">
                                     <tr>
                                         <th> {{ trans('model.country.icon') }}</th>
-                                        <th> {{ trans('model.country.code') }}</th>
+                                        <th> {{ ucfirst(trans('validation.attributes.national_code')) }}</th>
                                         <th> {{ trans('model.country.name') }}</th>
                                         <th> {{ trans('common.action') }}</th>
                                     </tr>
@@ -399,7 +399,7 @@
                     <div class="row">
                         <div class="col-md-6 form-group">
                             <input class="form-control" id="add_country_code" name="country_code" type="text"
-                                   placeholder="{{ trans('model.country.code') }}">
+                                   placeholder="{{ ucfirst(trans('validation.attributes.national_code')) }}">
                         </div>
                         <div class="col-md-6 form-group">
                             <input class="form-control" id="add_country_name" name="country_name" type="text"
@@ -741,7 +741,7 @@
                 const country_code = $("#add_country_code").val();
 
                 if (country_code.trim() === "") {
-                    $("#country_msg").show().html('{{ trans('validation.required', ['attribute' => trans('model.country.code')]) }}');
+                    $("#country_msg").show().html('{{ trans('validation.required', ['attribute' => ucfirst(trans('validation.attributes.national_code'))]) }}');
                     $("#add_country_code").focus();
                     return false;
                 }

+ 23 - 24
resources/views/admin/node/info.blade.php

@@ -41,7 +41,7 @@
                                         <div class="col-md-9">
                                             <input id="is_ddns" name="is_ddns" data-plugin="switchery" type="checkbox" onchange="switchSetting('is_ddns')">
                                         </div>
-                                        <div class="text-help offset-md-3">
+                                        <div class="text-help col-md-9 offset-md-3 px-0">
                                             {!! trans('admin.node.info.ddns_hint') !!}
                                         </div>
                                     </div>
@@ -53,30 +53,30 @@
                                         <label class="col-md-3 col-form-label" for="server"> {{ trans('model.node.domain') }} </label>
                                         <input class="form-control col-md-4" id="server" name="server" type="text"
                                                placeholder="{{ trans('admin.node.info.domain_placeholder') }}">
-                                        <span class="text-help offset-md-3">{{ trans('admin.node.info.domain_hint') }}</span>
+                                        <span class="text-help col-md-9 offset-md-3 px-0">{{ trans('admin.node.info.domain_hint') }}</span>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="ip"> {{ trans('model.node.ipv4') }} </label>
                                         <input class="form-control col-md-4" id="ip" name="ip" type="text"
                                                placeholder="{{ trans('admin.node.info.ipv4_placeholder') }}" required>
-                                        <span class="text-help offset-md-3">{{ trans('admin.node.info.ipv4_hint') }}</span>
+                                        <span class="text-help col-md-9 offset-md-3 px-0">{{ trans('admin.node.info.ipv4_hint') }}</span>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="ipv6"> {{ trans('model.node.ipv6') }} </label>
                                         <input class="form-control col-md-4" id="ipv6" name="ipv6" type="text"
                                                placeholder="{{ trans('admin.node.info.ipv6_placeholder') }}">
-                                        <span class="text-help offset-md-3">{{ trans('admin.node.info.ipv6_hint') }}</span>
+                                        <span class="text-help col-md-9 offset-md-3 px-0">{{ trans('admin.node.info.ipv6_hint') }}</span>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="push_port"> {{ trans('model.node.push_port') }} </label>
                                         <input class="form-control col-md-4" id="push_port" name="push_port" type="number" value="1080">
-                                        <span class="text-help offset-md-3">{{ trans('admin.node.info.push_port_hint') }}</span>
+                                        <span class="text-help col-md-9 offset-md-3 px-0">{{ trans('admin.node.info.push_port_hint') }}</span>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="traffic_rate"> {{ trans('model.node.data_rate') }} </label>
                                         <input class="form-control col-md-4" id="traffic_rate" name="traffic_rate" type="number" value="1.0" step="0.01"
                                                required>
-                                        <div class="text-help offset-md-3">{{ trans('admin.node.info.data_rate_hint') }}</div>
+                                        <div class="text-help col-md-9 offset-md-3 px-0">{{ trans('admin.node.info.data_rate_hint') }}</div>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="level">{{ trans('model.common.level') }}</label>
@@ -86,7 +86,7 @@
                                                 <option value="{{ $level->level }}">{{ $level->name }}</option>
                                             @endforeach
                                         </select>
-                                        <div class="text-help offset-md-3"> {{ trans('admin.node.info.level_hint') }}</div>
+                                        <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.node.info.level_hint') }}</div>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="rule_group_id">{{ trans('model.rule_group.attribute') }}</label>
@@ -113,7 +113,7 @@
                                         <label class="col-md-3 col-form-label" for="sort">{{ trans('model.common.sort') }}</label>
                                         <input class="form-control col-md-4" id="sort" name="sort" type="text" value="1" required />
                                         <span class="col-md-5"></span>
-                                        <div class="text-help offset-md-3"> {{ trans('admin.sort_asc') }}</div>
+                                        <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.sort_asc') }}</div>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="labels">{{ trans('model.node.label') }}</label>
@@ -206,7 +206,7 @@
                                                 </div>
                                             </li>
                                         </ul>
-                                        <div class="text-help offset-md-3"> {{ trans('admin.node.info.display.hint') }}</div>
+                                        <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.node.info.display.hint') }}</div>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="detection_type">{{ trans('model.node.detection') }}</label>
@@ -236,7 +236,7 @@
                                                 </div>
                                             </li>
                                         </ul>
-                                        <div class="text-help offset-md-3"> {{ trans('admin.node.info.detection.hint') }}</div>
+                                        <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.node.info.detection.hint') }}</div>
                                     </div>
                                     <!-- 中转 设置部分 -->
                                     <div class="form-group row">
@@ -344,16 +344,15 @@
                                                     <input id="single" name="single" data-plugin="switchery" type="checkbox"
                                                            onchange="switchSetting('single')">
                                                 </div>
-                                                <div class="text-help offset-md-3">
+                                                <div class="text-help col-md-9 offset-md-3 px-0">
                                                     {!! trans('admin.node.info.additional_ports_hint') !!}
                                                 </div>
                                             </div>
                                             <div class="single-setting">
                                                 <div class="form-group row">
                                                     <label class="col-md-3 col-form-label" for="single_port">{{ trans('model.node.service_port') }}</label>
-                                                    <input class="form-control col-md-4" id="single_port" name="port" type="number" value="443"
-                                                           hidden />
-                                                    <span class="text-help offset-md-3"> {!! trans('admin.node.info.single_hint') !!}</span>
+                                                    <input class="form-control col-md-4" id="single_port" name="port" type="number" value="443" />
+                                                    <span class="text-help col-md-9 offset-md-3 px-0"> {!! trans('admin.node.info.single_hint') !!}</span>
                                                 </div>
                                                 <div class="form-group row ssr-setting">
                                                     <label class="col-md-3 col-form-label" for="passwd">{{ trans('model.node.single_passwd') }}</label>
@@ -370,7 +369,7 @@
                                             </div>
                                             <div class="form-group row">
                                                 <label class="col-md-3 col-form-label" for="v2_port">{{ trans('model.node.service_port') }}</label>
-                                                <input class="form-control col-md-4" id="v2_port" name="port" type="number" value="10053" hidden />
+                                                <input class="form-control col-md-4" id="v2_port" name="port" type="number" value="10053" />
                                             </div>
                                             <div class="form-group row">
                                                 <label class="col-md-3 col-form-label" for="v2_method">{{ trans('model.node.method') }}</label>
@@ -382,7 +381,7 @@
                                                     <option value="aes-128-gcm">aes-128-gcm</option>
                                                     <option value="chacha20-poly1305">chacha20-poly1305</option>
                                                 </select>
-                                                <div class="text-help offset-md-3"> {{ trans('admin.node.info.v2_method_hint') }}</div>
+                                                <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.node.info.v2_method_hint') }}</div>
                                             </div>
                                             <div class="form-group row">
                                                 <label class="col-md-3 col-form-label" for="v2_net">{{ trans('model.node.v2_net') }}</label>
@@ -395,7 +394,7 @@
                                                     <option value="domainsocket">DomainSocket</option>
                                                     <option value="quic">QUIC</option>
                                                 </select>
-                                                <div class="text-help offset-md-3"> {{ trans('admin.node.info.v2_net_hint') }}</div>
+                                                <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.node.info.v2_net_hint') }}</div>
                                             </div>
                                             <div class="form-group row v2_type">
                                                 <label class="col-md-3 col-form-label" for="v2_type">{{ trans('model.node.v2_cover') }}</label>
@@ -417,7 +416,7 @@
                                                 <div class="col-md-4 pl-0">
                                                     <input class="form-control" id="v2_host" name="v2_host" type="text">
                                                 </div>
-                                                <div class="text-help offset-md-3">
+                                                <div class="text-help col-md-9 offset-md-3 px-0">
                                                     {{ trans('admin.node.info.v2_host_hint') }}
                                                 </div>
                                             </div>
@@ -439,7 +438,7 @@
                                             <div class="form-group row">
                                                 <label class="col-md-3 col-form-label" for="tls_provider">{{ trans('model.node.v2_tls_provider') }}</label>
                                                 <input class="form-control col-md-9" id="tls_provider" name="tls_provider" type="text" />
-                                                <div class="text-help offset-md-3"> {{ trans('admin.node.info.v2_tls_provider_hint') }}
+                                                <div class="text-help col-md-9 offset-md-3 px-0"> {{ trans('admin.node.info.v2_tls_provider_hint') }}
                                                     <a href="https://proxypanel.gitbook.io/wiki/webapi/webapi-basic-setting#vnet-v2-ray-hou-duan"
                                                        target="_blank">VNET-V2Ray</a>、
                                                     <a href="https://proxypanel.gitbook.io/wiki/webapi/webapi-basic-setting#v-2-ray-poseidon-hou-duan"
@@ -451,14 +450,14 @@
                                         <div class="trojan-setting">
                                             <div class="form-group row">
                                                 <label class="col-md-3 col-form-label" for="trojan_port">{{ trans('model.node.service_port') }}</label>
-                                                <input class="form-control col-md-4" id="trojan_port" name="port" type="number" value="443" hidden />
+                                                <input class="form-control col-md-4" id="trojan_port" name="port" type="number" value="443" />
                                             </div>
                                         </div>
                                     </div>
                                     <div class="relay-config">
                                         <div class="form-group row">
                                             <label class="col-md-3 col-form-label" for="relay_port">{{ trans('model.node.relay_port') }}</label>
-                                            <input class="form-control col-md-4" id="relay_port" name="port" type="number" value="443" hidden />
+                                            <input class="form-control col-md-4" id="relay_port" name="port" type="number" value="443" />
                                         </div>
                                     </div>
                                     <div class="form-group row">
@@ -549,11 +548,11 @@
             initializeUI(); // 初始化
             bindEvents(); // 事件绑定
 
-            @isset($node)
+            @if (isset($node))
                 setupNodeData(@json($node));
             @else
                 setupDefaultValues();
-            @endisset
+            @endif
 
         });
 
@@ -610,6 +609,7 @@
         }
 
         function setupDefaultValues() {
+            $(".relay-config").hide();
             switchSetting("single");
             switchSetting("is_ddns");
             $("input[name='type'][value='0']").click();
@@ -736,7 +736,6 @@
                 3: [".trojan-setting", "#trojan_port"],
                 4: [".ss-setting", ".ssr-setting"]
             };
-
             $(".ss-setting, .ssr-setting, .v2ray-setting, .trojan-setting").hide();
             Object.keys(settingsMap).forEach(key => $(settingsMap[key].join(",")).hide());
             (settingsMap[type] || []).forEach(selector => $(selector).show());

+ 1 - 1
resources/views/components/system/input-limit.blade.php

@@ -35,7 +35,7 @@
             @endisset
             @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
                 <span class="text-help"> {!! trans("admin.system.hint.$code") !!} </span>
-            @endisset
+            @endif
         </div>
     </div>
 </div>

+ 1 - 1
resources/views/components/system/textarea.blade.php

@@ -12,7 +12,7 @@
             </div>
             @if (trans("admin.system.hint.$code") !== "admin.system.hint.$code")
                 <span class="text-help"> {!! trans("admin.system.hint.$code") !!} </span>
-            @endisset
+            @endif
         </div>
     </div>
 </div>

+ 249 - 0
resources/views/user/subscribe.blade.php

@@ -0,0 +1,249 @@
+@extends('_layout')
+@section('title', '订 阅')
+
+@section('layout_css')
+    <link href="/assets/global/fonts/font-awesome/css/all.min.css" rel="stylesheet">
+    <link href="/assets/global/vendor/toastr/toastr.min.css" rel="stylesheet">
+    <link href="/assets/css/subscribe.css" rel="stylesheet">
+@endsection
+@section('body_class', 'layout-full position-relative')
+@section('layout_content')
+    <div class="container pt-15 pt-md-45 px-1 px-md-15">
+        <div class="panel m-0">
+            <!-- Top bar -->
+            <div class="d-flex align-items-center justify-content-between mb-15">
+                <h3 class="font-weight-bold text-white">{{ trans('model.subscribe.attribute') }}</h3>
+                <div>
+                    <button class="btn btn-outline-info btn-sm mt-clipboard btn-round-sm" data-clipboard-action="copy"><i
+                           class="fas fa-copy mr-1"></i>{{ trans('user.subscribe.page.get_link') }}</button>
+                    <!-- 语言选择下拉 -->
+                    <div class="dropdown d-inline">
+                        <button class="btn btn-outline-primary btn-sm dropdown-toggle btn-round-sm" id="langDropdown" data-toggle="dropdown" type="button"
+                                aria-haspopup="true" aria-expanded="false">
+                            <i class="fi fi-{{ config('common.language.' . app()->getLocale())[1] }}"></i>
+                        </button>
+                        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="langDropdown">
+                            @foreach (['zh_CN', 'en', 'fa', 'ru'] as $key)
+                                @if (config("common.language.$key") && $key !== app()->getLocale())
+                                    <a class="dropdown-item" href="{{ route('lang', ['locale' => $key]) }}">
+                                        <i class="fi fi-{{ config("common.language.$key")[1] }} mr-2"></i>
+                                        {{ config("common.language.$key")[0] }}
+                                    </a>
+                                @endif
+                            @endforeach
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <!-- 用户信息卡片 -->
+            <div class="info-card mb-3">
+                <div class="row align-items-center no-gutters">
+                    <div class="col-auto text-center pr-0">
+                        <img class="avatar-big d-block mx-auto rounded-circle" src="{{ $user->avatar }}" alt="avatar" />
+                    </div>
+                    <div class="col">
+                        <div class="row text-center text-md-left pt-2 pt-md-0">
+                            <div class="col-6 col-md-3 mb-2">
+                                <div class="info-label"><i class="fa fa-user"></i> {{ trans('model.user.nickname') }}</div>
+                                <div class="info-value">{{ $user->nickname }}</div>
+                            </div>
+                            <div class="col-6 col-md-3 mb-2">
+                                <div class="info-label"><i class="fa fa-circle-dot text-success"></i> {{ trans('common.status.attribute') }}</div>
+                                <div class="info-value text-success">
+                                    @if ($user->enable)
+                                        <div class="info-value text-success">
+                                            <i class="fa fa-check mr-1"></i> {{ ucfirst(trans('common.status.enabled')) }}
+                                        </div>
+                                    @else
+                                        <div class="info-value text-warning">
+                                            <i class="fa fa-times mr-1"></i> {{ ucfirst(trans('common.status.disabled')) }}
+                                        </div>
+                                    @endif
+                                </div>
+                            </div>
+                            <div class="col-6 col-md-3 mb-2">
+                                <div class="info-label"><i class="fa-regular fa-clock text-warning"></i> {{ trans('model.user.expired_date') }}</div>
+                                <div class="info-value">{{ localized_date($user->expiration_date) }}</div>
+                            </div>
+                            <div class="col-6 col-md-3 mb-2">
+                                <div class="info-label"><i class="fa fa-gauge-high text-primary"></i> {{ trans('user.attribute.data') }}</div>
+                                <div class="info-value text-info">{{ $user->used_traffic / GiB }} / {{ $user->transfer_enable_formatted }}</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Installation -->
+            <div class="d-flex align-items-center justify-content-between mb-2">
+                <h4 class="font-weight-semibold mb-0 text-white"><i class="fa fa-download text-primary mr-1"></i>{{ trans('user.tutorials') }}</h4>
+                <ul class="nav nav-pills nav-platforms p-1 mb-0">
+                    <li class="nav-item"><a class="nav-link" data-platform="pc" href="#"><i class="fa-solid fa-desktop mr-1"></i>PC</a></li>
+                    <li class="nav-item"><a class="nav-link" data-platform="ios" href="#"><i class="fa-brands fa-apple mr-1"></i>iOS</a></li>
+                    <li class="nav-item"><a class="nav-link" data-platform="android" href="#"><i class="fa-brands fa-android mr-1"></i>Android</a></li>
+                </ul>
+            </div>
+            <div class="mb-3" id="apps-tab"></div>
+            <!-- Step -->
+            <div class="stepper" id="steps-container"></div>
+        </div>
+    </div>
+@endsection
+@section('layout_javascript')
+    <script src="/assets/global/vendor/toastr/toastr.min.js"></script>
+    <script src="/assets/global/js/Plugin/toastr.js"></script>
+    <script src="/assets/custom/clipboardjs/clipboard.min.js"></script>
+
+    <script>
+        const createLocalizedGetter = (lang = "en") =>
+            obj => obj?.[lang] ?? obj?.en;
+
+        const trans = createLocalizedGetter(@json(app()->getLocale()));
+
+        const sublink = @json($user->sub_url);
+        const sublink_base64 = @json(base64url_encode($user->sub_url));
+        let clients = {};
+        const clipboard = new ClipboardJS(".mt-clipboard", {
+            text: function() {
+                return sublink;
+            }
+        });
+        clipboard.on("success", function() {
+            toastr.success('{{ trans('common.copy.success') }}');
+        });
+        clipboard.on("error", function() {
+            toastr.error('{{ trans('common.copy.failed') }}');
+        });
+
+        async function loadClients() {
+            try {
+                const response = await fetch("/clients/app.json");
+                if (!response.ok) throw new Error(`HTTP ${response.status}`);
+                return await response.json();
+            } catch (error) {
+                console.error("{{ trans('errors.report') }}", error);
+                return {};
+            }
+        }
+
+        loadClients().then(data => {
+            clients = data;
+            renderAppTabs();
+            renderSteps();
+        });
+
+        let currentPlatform = "pc",
+            currentAppIdx = 0;
+
+        document.querySelectorAll(".nav-platforms .nav-link").forEach(function(tab, idx) {
+            tab.onclick = function(e) {
+                e.preventDefault();
+                document.querySelectorAll(".nav-platforms .nav-link").forEach((el) => el.classList.remove("active"));
+                tab.classList.add("active");
+                currentPlatform = tab.dataset.platform;
+                currentAppIdx = 0;
+                renderAppTabs();
+                renderSteps();
+            };
+            if (idx === 0) tab.classList.add("active");
+        });
+
+        function renderAppTabs() {
+            const el = document.getElementById("apps-tab");
+            el.innerHTML = "";
+            const apps = clients[currentPlatform] || [];
+            if (apps.length === 0) {
+                el.innerHTML = "<span class=\"text-secondary\">{{ trans('user.subscribe.page.error.no_app') }}</span>";
+                document.getElementById("steps-container").innerHTML = "";
+                return;
+            }
+            apps.forEach(function(app, i) {
+                const b = document.createElement("button");
+                b.className = "btn btn-app btn-sm mr-1 mb-1" + (i === currentAppIdx ? " selected" : "");
+                b.innerHTML = (app.isFeatured ? "<i class=\"fa fa-star text-warning\"></i> " : "") + app.name;
+                b.onclick = function() {
+                    currentAppIdx = i;
+                    renderAppTabs();
+                    renderSteps();
+                };
+                el.appendChild(b);
+            });
+        }
+
+        function renderSteps() {
+            const app = (clients[currentPlatform] || [])[currentAppIdx];
+            if (!app) {
+                document.getElementById("steps-container").innerHTML = "";
+                return;
+            }
+
+            const steps = [];
+            if (app.installationStep) {
+                steps.push({
+                    dot: "install",
+                    title: `{{ trans('common.download_item', ['attribute' => '${app.name}']) }}`,
+                    desc: trans(app.installationStep.description),
+                    btns: app.installationStep.buttons?.map((btn) => ({
+                        url: btn.buttonLink,
+                        txt: trans(btn.buttonText)
+                    })),
+                    ic: "fa fa-download"
+                });
+            }
+
+            if (app.additionalBeforeAddSubscriptionStep) {
+                steps.push({
+                    dot: "simple",
+                    title: trans(app.additionalBeforeAddSubscriptionStep.title),
+                    desc: trans(app.additionalBeforeAddSubscriptionStep.description),
+                    ic: "fa fa-language"
+                });
+            }
+
+            if (app.addSubscriptionStep) {
+                steps.push({
+                    dot: "sub",
+                    title: "{{ trans('admin.action.add_item', ['attribute' => trans('model.subscribe.attribute')]) }}",
+                    desc: trans(app.addSubscriptionStep.description),
+                    addSubBtn: app.urlScheme + (app.isNeedBase64Encoding ? sublink_base64 : sublink),
+                    ic: "fa fa-plus"
+                });
+            }
+
+            if (app.additionalAfterAddSubscriptionStep) {
+                steps.push({
+                    dot: "simple",
+                    title: trans(app.additionalAfterAddSubscriptionStep.title),
+                    desc: trans(app.additionalAfterAddSubscriptionStep.description),
+                    ic: "fa fa-comment"
+                });
+            }
+
+            if (app.connectAndUseStep) {
+                steps.push({
+                    dot: "last",
+                    title: "{{ trans('user.subscribe.page.connect') }}",
+                    desc: trans(app.connectAndUseStep.description),
+                    ic: "fa fa-bolt"
+                });
+            }
+
+            let html = "";
+            steps.forEach((step, idx) => {
+                html += `
+          <div class="step-item step-${step.dot}">
+            <div class="step-dot"><i class="${step.ic || "fa fa-circle"}"></i></div>
+            <div class="step-content">
+              ${step.title ? `<div class="step-title text-white">${step.title || ""}</div>` : ""}
+              ${step.desc ? `<div class="step-desc">${step.desc}</div>` : ""}
+              ${step.btns && step.btns.length ? `<div class="btn-group flex-wrap mb-2 app-download">${step.btns.map((btn) => `<a href="${btn.url}" target="_blank" class="btn btn-outline-info btn-sm mr-2 mb-1">${btn.txt}</a>`).join("")}</div>` : ""}
+              ${step.addSubBtn ? `<a class="btn addsub-btn" href="${step.addSubBtn}"><i class="fa fa-plus mr-2"></i>{{ trans('admin.action.add_item', ['attribute' => trans('model.subscribe.attribute')]) }}</a>` : ""}
+            </div>
+          </div>
+        `;
+            });
+
+            document.getElementById("steps-container").innerHTML = html;
+        }
+    </script>
+@endsection

+ 5 - 2
routes/web.php

@@ -9,9 +9,12 @@ use App\Http\Controllers\TelegramController;
 use App\Http\Controllers\User\SubscribeController;
 
 if (config('app.key') && config('settings')) {
-    Route::domain(sysConfig('subscribe_domain') ?: sysConfig('website_url'))->get('s/{code}', [SubscribeController::class, 'getSubscribeByCode'])->name('sub'); // 节点订阅地址
+    Route::domain(sysConfig('subscribe_domain') ?: sysConfig('website_url'))->group(function () {
+        Route::get('s/{code}', [SubscribeController::class, 'getSubscribeByCode'])->name('sub'); // 节点订阅
+        Route::get('subscribe/{code}', [SubscribeController::class, 'index'])->name('subscribe.index'); // 节点订阅页面
+    });
 
-    Route::domain(sysConfig('payment_callback_url') ?: sysConfig('website_url'))->match(['get', 'post'], 'callback/notify', [PaymentController::class, 'notify'])->name('payment.notify'); //支付回调
+    Route::domain(sysConfig('payment_callback_url') ?: sysConfig('website_url'))->match(['get', 'post'], 'callback/notify', [PaymentController::class, 'notify'])->name('payment.notify'); // 支付回调
 }
 
 Route::post('api/telegram/webhook', [TelegramController::class, 'webhook'])->middleware('telegram'); // Telegram fallback