7 Angajamente ece1655939 ... e2f75acad2

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  dependabot[bot] e2f75acad2 chore(deps): bump github.com/klauspost/compress from 1.19.1 to 1.19.2 (#6212) 12 ore în urmă
  Chen, Ting-An 69a8237581 fix(i18n): localize Chinese Xray labels (#6202) 12 ore în urmă
  Sanaei f3f57e66f5 fix(frontend): wait out Collapse fade before a11y scan in ConfigBlock story 12 ore în urmă
  Sanaei 8a8da88548 fix(frontend): isolate swagger deps from main vendor chunk 13 ore în urmă
  Sanaei 1f846c3cb2 fix(frontend): clean test validation output 14 ore în urmă
  Sanaei 1c255fc00c chore(frontend): bump npm dependencies 15 ore în urmă
  dependabot[bot] 75032fd498 chore(deps): bump dompurify (#6193) 15 ore în urmă

Fișier diff suprimat deoarece este prea mare
+ 237 - 269
frontend/package-lock.json


+ 14 - 14
frontend/package.json

@@ -31,30 +31,30 @@
     "@codemirror/lang-json": "^6.0.2",
     "@codemirror/theme-one-dark": "^6.1.3",
     "@hookform/resolvers": "^5.7.1",
-    "@noble/hashes": "^2.2.0",
+    "@noble/hashes": "^2.3.0",
     "@tanstack/react-query": "^5.101.4",
     "@tanstack/react-query-devtools": "^5.101.4",
-    "antd": "^6.5.3",
+    "antd": "^6.6.0",
     "codemirror": "^6.0.2",
     "dayjs": "^1.11.21",
     "i18next": "^26.3.6",
     "otpauth": "^9.5.1",
-    "persian-calendar-suite": "^1.5.5",
+    "persian-calendar-suite": "^1.5.6",
     "react": "^19.2.8",
     "react-dom": "^19.2.8",
-    "react-hook-form": "^7.84.0",
+    "react-hook-form": "^7.85.0",
     "react-i18next": "^17.0.11",
     "react-router": "^8.3.0",
-    "swagger-ui-react": "^5.32.12",
+    "swagger-ui-react": "^5.32.13",
     "uplot": "^1.6.32",
     "zod": "^4.4.3"
   },
   "devDependencies": {
     "@eslint/js": "^10.0.1",
-    "@storybook/addon-a11y": "^10.5.6",
-    "@storybook/addon-docs": "^10.5.6",
-    "@storybook/addon-vitest": "^10.5.6",
-    "@storybook/react-vite": "^10.5.6",
+    "@storybook/addon-a11y": "^10.5.7",
+    "@storybook/addon-docs": "^10.5.7",
+    "@storybook/addon-vitest": "^10.5.7",
+    "@storybook/react-vite": "^10.5.7",
     "@testing-library/dom": "^10.4.1",
     "@testing-library/react": "^16.3.2",
     "@types/react": "^19.2.18",
@@ -63,19 +63,19 @@
     "@vitejs/plugin-react": "^6.0.5",
     "@vitest/browser-playwright": "4.1.10",
     "@vitest/coverage-v8": "^4.1.10",
-    "eslint": "^10.8.0",
+    "eslint": "^10.8.1",
     "eslint-plugin-jsx-a11y": "^6.10.2",
     "eslint-plugin-react-hooks": "^7.1.1",
-    "globals": "^17.9.0",
+    "globals": "^17.11.0",
     "husky": "^9.1.7",
     "jsdom": "^30.0.1",
     "lint-staged": "^17.3.0",
     "msw": "^2.15.0",
     "playwright": "^1.62.1",
-    "storybook": "^10.5.6",
+    "storybook": "^10.5.7",
     "typescript": "6.0.3",
-    "typescript-eslint": "^8.66.0",
-    "vite": "8.2.0",
+    "typescript-eslint": "^8.67.0",
+    "vite": "8.2.1",
     "vitest": "^4.1.10"
   },
   "overrides": {

+ 2 - 2
frontend/src/api/queries/useAllSettings.ts

@@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { HttpUtil, Msg } from '@/utils';
 import { parseMsg } from '@/utils/zodValidate';
 import { AllSetting } from '@/models/setting';
-import { AllSettingSchema, type AllSettingInput } from '@/schemas/setting';
+import { AllSettingResponseSchema, AllSettingSchema, type AllSettingInput } from '@/schemas/setting';
 import { keys } from '@/api/queryKeys';
 import { useServerDraft } from '@/hooks/useServerDraft';
 
@@ -17,7 +17,7 @@ type SettingSaveResult = {
 async function fetchAllSetting(): Promise<AllSettingInput | null> {
   const msg = await HttpUtil.post('/panel/api/setting/all', undefined, { silent: true });
   if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch settings');
-  const validated = parseMsg(msg, AllSettingSchema, 'setting/all');
+  const validated = parseMsg(msg, AllSettingResponseSchema, 'setting/all');
   return validated.obj;
 }
 

+ 7 - 1
frontend/src/components/clients/ConfigBlock.stories.tsx

@@ -36,11 +36,17 @@ const sampleLink = 'vless://[email protected]
 
 export const Collapsed: Story = {
   args: { label: 'vless', text: sampleLink, fileName: 'client-config.txt' },
-  play: async ({ canvas, userEvent }) => {
+  play: async ({ canvas, canvasElement, userEvent }) => {
     await expect(canvas.queryByText(/vless:\/\/11112222/)).not.toBeInTheDocument();
     await userEvent.click(canvas.getByText('vless'));
     const configText = await canvas.findByText(/vless:\/\/11112222/);
     await waitFor(() => expect(configText).toBeVisible());
+    // Collapse fades content in over motionDurationMid; wait it out so the a11y
+    // scan doesn't sample a mid-transition, lower-contrast opacity.
+    await waitFor(() => {
+      const panel = canvasElement.querySelector('.ant-collapse-panel');
+      expect(panel && getComputedStyle(panel).opacity).toBe('1');
+    });
     await expect(canvas.getByRole('button', { name: 'Copy' })).toBeVisible();
     await expect(canvas.getByRole('button', { name: 'Download' })).toBeVisible();
     await expect(canvas.getByRole('button', { name: 'QR Code' })).toBeVisible();

+ 9 - 0
frontend/src/schemas/setting.ts

@@ -104,6 +104,15 @@ export const AllSettingSchema = z.object({
 
 export type AllSettingInput = z.infer<typeof AllSettingSchema>;
 
+// Existing installations can contain regex values saved before the backend
+// enforced its 2,048-character limit. Accept those values when reading so the
+// settings page can display and let users correct them, while keeping the
+// stricter schema above for outgoing updates.
+export const AllSettingResponseSchema = AllSettingSchema.extend({
+  subJsonUserAgentRegex: z.string().optional(),
+  subClashUserAgentRegex: z.string().optional(),
+});
+
 export const FactoryDefaultsSchema = z.record(z.string(), z.string());
 
 export type FactoryDefaults = z.infer<typeof FactoryDefaultsSchema>;

+ 5 - 1
frontend/src/test/clients-query-gating.test.tsx

@@ -99,7 +99,11 @@ describe('useClients query gating', () => {
   });
 
   it('reports settingsReady even when the settings request fails, so the page can still render', async () => {
-    vi.spyOn(HttpUtil, 'get').mockResolvedValue(new Msg(true, '', emptyPage));
+    vi.spyOn(HttpUtil, 'get').mockImplementation(async (url: string) => new Msg(
+      true,
+      '',
+      url.includes('/inbounds/options') ? [] : emptyPage,
+    ));
     vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(false, 'boom', null));
     const { result } = renderHook(() => useClients(), { wrapper: wrapperFor() });
 

+ 3 - 0
frontend/src/test/httpUtil.test.ts

@@ -35,6 +35,7 @@ const envelope = (data: unknown): HttpResponse => ({ ok: true, status: 200, stat
 describe('HttpUtil', () => {
   beforeEach(() => {
     vi.clearAllMocks();
+    vi.spyOn(console, 'error').mockImplementation(() => undefined);
   });
 
   it('unwraps a success envelope and shows a success toast', async () => {
@@ -80,6 +81,7 @@ describe('HttpUtil', () => {
 
     expect(msg.success).toBe(false);
     expect(msg.msg).toBe('bad input');
+    expect(console.error).not.toHaveBeenCalled();
   });
 
   it('maps a thrown native error to a failure Msg via its message', async () => {
@@ -88,6 +90,7 @@ describe('HttpUtil', () => {
     const msg = await HttpUtil.get('/x', undefined, { silent: true });
 
     expect(msg.msg).toBe('Network down');
+    expect(console.error).not.toHaveBeenCalled();
   });
 
   it('returns "No response data" for an empty body', async () => {

+ 16 - 3
frontend/src/test/setup.components.ts

@@ -48,6 +48,16 @@ if (!Element.prototype.scrollIntoView) {
   Element.prototype.scrollIntoView = () => {};
 }
 
+// jsdom does not implement pseudo-element styles or Range geometry. Ant
+// Design and CodeMirror use these APIs for layout, so supply harmless test
+// fallbacks instead of emitting noisy "Not implemented" errors.
+const nativeGetComputedStyle = window.getComputedStyle.bind(window);
+window.getComputedStyle = ((element: Element) => nativeGetComputedStyle(element)) as typeof window.getComputedStyle;
+
+if (!Range.prototype.getClientRects) {
+  Range.prototype.getClientRects = () => [] as unknown as DOMRectList;
+}
+
 if (!i18next.isInitialized) {
   void i18next.use(initReactI18next).init({
     lng: 'en-US',
@@ -75,9 +85,12 @@ afterEach(async () => {
   }
 });
 
-import { HttpUtil } from '@/utils';
+import { HttpUtil, Msg } from '@/utils';
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 vi.spyOn(HttpUtil, 'post').mockResolvedValue({ success: true, obj: {} } as any);
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-vi.spyOn(HttpUtil, 'get').mockResolvedValue({ success: true, obj: {} } as any);
+vi.spyOn(HttpUtil, 'get').mockImplementation(async (url: string) => new Msg(
+  true,
+  '',
+  url.includes('/panel/api/inbounds/options') ? [] : {},
+));

+ 3 - 1
frontend/src/test/use-all-settings.test.tsx

@@ -13,9 +13,10 @@ afterEach(() => {
 });
 
 describe('useAllSettings', () => {
-  it('keeps backend-accepted settings editable when the frontend schema is stricter', async () => {
+  it('accepts legacy overlength regex settings without logging a response validation warning', async () => {
     const subJsonUserAgentRegex = 'x'.repeat(2_049);
     vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', { subJsonUserAgentRegex }));
+    const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
     const queryClient = makeTestQueryClient();
     const wrapper = ({ children }: { children: ReactNode }) => (
       <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
@@ -25,6 +26,7 @@ describe('useAllSettings', () => {
 
     await waitFor(() => expect(result.current.fetched).toBe(true));
     expect(result.current.allSetting.subJsonUserAgentRegex).toBe(subJsonUserAgentRegex);
+    expect(warning).not.toHaveBeenCalled();
   });
 
   it('keeps an edited setting when a refetch returns older server data', async () => {

+ 15 - 0
frontend/src/test/zodValidate.test.ts

@@ -13,10 +13,15 @@ afterEach(() => {
 describe('parseMsg', () => {
   it('rejects a successful response whose payload violates its schema', () => {
     const msg = new Msg(true, '', { id: 'not-a-number' });
+    const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
 
     expect(() => parseMsg(msg, z.object({ id: z.number() }), 'test/value', { strict: true })).toThrow(
       'test/value response failed validation',
     );
+    expect(warning).toHaveBeenCalledWith(
+      '[zod] test/value response failed validation',
+      expect.arrayContaining([expect.objectContaining({ code: 'invalid_type', path: ['id'] })]),
+    );
   });
 
   it('preserves a missing successful payload for callers that handle empty values', () => {
@@ -25,17 +30,27 @@ describe('parseMsg', () => {
 
   it('rejects malformed paged-client payloads', () => {
     const payload = { items: [], total: 'one', filtered: 1, page: 1, pageSize: 20 };
+    const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
 
     expect(() => parseMsg(new Msg(true, '', payload), ClientPageResponseSchema, 'clients/list/paged', { strict: true })).toThrow(
       'clients/list/paged response failed validation',
     );
+    expect(warning).toHaveBeenCalledWith(
+      '[zod] clients/list/paged response failed validation',
+      expect.arrayContaining([expect.objectContaining({ code: 'invalid_type', path: ['total'] })]),
+    );
   });
 });
 
 describe('fetchXrayConfig', () => {
   it('keeps a malformed xray payload available for repair', async () => {
     vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', JSON.stringify({ xraySetting: 'not-an-object' })));
+    const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
 
     await expect(fetchXrayConfig()).resolves.toEqual({ xraySetting: 'not-an-object' });
+    expect(warning).toHaveBeenCalledWith(
+      '[zod] xray/ config payload failed validation',
+      expect.arrayContaining([expect.objectContaining({ code: 'invalid_type', path: ['xraySetting'] })]),
+    );
   });
 });

+ 8 - 4
frontend/src/utils/index.ts

@@ -75,11 +75,13 @@ export class HttpUtil {
       if (!silent) this._handleMsg(msg, silentSuccess);
       return msg;
     } catch (error) {
-      console.error('GET request failed:', error);
       const err = error as { response?: { data?: { msg?: string; message?: string } }; message?: string };
       const data = err.response?.data;
       const errorMsg = new Msg<T>(false, data?.msg || data?.message || err.message || 'Request failed');
-      if (!silent) this._handleMsg(errorMsg);
+      if (!silent) {
+        console.error('GET request failed:', error);
+        this._handleMsg(errorMsg);
+      }
       return errorMsg;
     }
   }
@@ -92,11 +94,13 @@ export class HttpUtil {
       if (!silent) this._handleMsg(msg, silentSuccess);
       return msg;
     } catch (error) {
-      console.error('POST request failed:', error);
       const err = error as { response?: { data?: { msg?: string; message?: string } }; message?: string };
       const data = err.response?.data;
       const errorMsg = new Msg<T>(false, data?.msg || data?.message || err.message || 'Request failed');
-      if (!silent) this._handleMsg(errorMsg);
+      if (!silent) {
+        console.error('POST request failed:', error);
+        this._handleMsg(errorMsg);
+      }
       return errorMsg;
     }
   }

+ 52 - 0
frontend/vite.config.js

@@ -151,6 +151,57 @@ function makeBackendProxy(target) {
   };
 }
 
+// Deps only reachable through swagger-ui-react (verified via `npm ls`). The
+// catch-all `vendor` chunk would otherwise eager-load them on first paint,
+// although the api-docs page is the only lazy route importing them.
+const SWAGGER_ONLY_DEPS = [
+  '@babel/runtime-corejs3',
+  '@scarf/scarf',
+  '@swagger-api/apidom-',
+  '@swaggerexpert/',
+  'base64-js',
+  'buffer',
+  'classnames',
+  'css.escape',
+  'deep-extend',
+  'dompurify',
+  'fast-json-patch',
+  'highlight.js',
+  'highlightjs-vue',
+  'ieee754',
+  'immutable',
+  'js-file-download',
+  'js-yaml',
+  'lodash',
+  'lowlight',
+  'neotraverse',
+  'node-abort-controller',
+  'openapi-path-templating',
+  'openapi-server-url-templating',
+  'prismjs',
+  'prop-types',
+  'ramda',
+  'ramda-adjunct',
+  'randexp',
+  'react-copy-to-clipboard',
+  'react-debounce-input',
+  'react-immutable-proptypes',
+  'react-immutable-pure-component',
+  'react-inspector',
+  'react-redux',
+  'react-syntax-highlighter',
+  'redux',
+  'redux-immutable',
+  'remarkable',
+  'reselect',
+  'serialize-error',
+  'sha.js',
+  'url-parse',
+  'xml',
+  'xml-but-prettier',
+  'zenscroll',
+];
+
 export default defineConfig({
   plugins: [react(), injectBasePathPlugin(), rocketLoaderOptOutPlugin()],
   resolve: {
@@ -222,6 +273,7 @@ export default defineConfig({
             id.includes('/node_modules/swagger-ui-react/')
             || id.includes('/node_modules/swagger-ui/')
             || id.includes('/node_modules/swagger-client/')
+            || SWAGGER_ONLY_DEPS.some((dep) => id.includes(`/node_modules/${dep}/`))
           ) return 'vendor-swagger';
           if (id.includes('/node_modules/uplot/')) return 'vendor-uplot';
           if (id.includes('dayjs')) return 'vendor-dayjs';

+ 3 - 2
frontend/vitest.config.ts

@@ -1,12 +1,11 @@
 import path from 'node:path';
-import { fileURLToPath } from 'node:url';
 
 import react from '@vitejs/plugin-react';
 import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
 import { playwright } from '@vitest/browser-playwright';
 import { defineConfig } from 'vitest/config';
 
-const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
+const dirname = import.meta.dirname;
 
 export default defineConfig({
   plugins: [react()],
@@ -17,6 +16,8 @@ export default defineConfig({
   },
   test: {
     globals: false,
+    // Keep jsdom-heavy form tests within the memory budget of local and CI runners.
+    maxWorkers: 2,
     projects: [
       {
         extends: true,

+ 1 - 1
go.mod

@@ -64,7 +64,7 @@ require (
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/juju/ratelimit v1.0.2 // indirect
-	github.com/klauspost/compress v1.19.1
+	github.com/klauspost/compress v1.19.2
 	github.com/klauspost/cpuid/v2 v2.4.0 // indirect
 	github.com/leodido/go-urn v1.5.0 // indirect
 	github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 // indirect

+ 2 - 2
go.sum

@@ -115,8 +115,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
 github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
-github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
-github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
+github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
+github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
 github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
 github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

+ 8 - 8
internal/web/translation/zh-CN.json

@@ -1503,8 +1503,8 @@
       },
       "outbound": {
         "tag": "标签",
-        "egress": "Egress",
-        "egressHint": "Run an HTTP test to show egress IP and country.",
+        "egress": "出口",
+        "egressHint": "执行 HTTP 测试以显示出口 IP 和所在国家/地区。",
         "outboundStatus": "出站状态",
         "sendThrough": "发送通过",
         "targetStrategy": "目标解析策略",
@@ -1607,14 +1607,14 @@
         "tagDuplicate": "该标签已被其他均衡器使用",
         "tagPlaceholder": "唯一均衡器标签",
         "selector": "选择器",
-        "fallback": "Fallback",
+        "fallback": "备用负载均衡器",
         "cycleTooltip": "循环: {path} → (回到 {start})",
         "expected": "期望",
         "expectedPlaceholder": "最佳节点数",
         "maxRtt": "最大 RTT",
         "tolerance": "容差",
-        "baselines": "Baselines",
-        "costs": "Costs",
+        "baselines": "基准值",
+        "costs": "成本设置",
         "costMatch": "标签匹配模式",
         "costValue": "权重",
         "costRegexp": "正则表达式匹配",
@@ -1635,7 +1635,7 @@
         "userLevel": "用户级别"
       },
       "nord": {
-        "accessToken": "Access token",
+        "accessToken": "访问令牌",
         "privateKey": "私钥",
         "noServers": "未找到选定国家/地区的服务器",
         "noPublicKey": "选定的服务器未公布 NordLynx 公钥。",
@@ -1651,7 +1651,7 @@
         "licenseError": "设置 WARP 许可证失败。",
         "fetchFirst": "请先获取 WARP 配置。",
         "createAccount": "创建 WARP 账户",
-        "accessToken": "Access token",
+        "accessToken": "访问令牌",
         "deviceId": "设备 ID",
         "licenseKey": "许可证密钥",
         "privateKey": "私钥",
@@ -1702,7 +1702,7 @@
         "timeoutMs": "超时 (毫秒)",
         "skipFallback": "跳过回退",
         "finalQuery": "最终查询",
-        "hosts": "Hosts",
+        "hosts": "主机映射",
         "hostsAdd": "添加 Host",
         "hostsEmpty": "未定义任何 Host",
         "hostsDomain": "域名 (例如 domain:example.com)",

+ 9 - 9
internal/web/translation/zh-TW.json

@@ -107,7 +107,7 @@
     "outbounds": "出站",
     "apiDocs": "API 文件",
     "donate": "捐贈",
-    "hosts": "Hosts",
+    "hosts": "主機",
     "docs": "文件",
     "openMenu": "開啟選單",
     "pinSidebar": "固定側邊欄",
@@ -1503,8 +1503,8 @@
       },
       "outbound": {
         "tag": "標籤",
-        "egress": "Egress",
-        "egressHint": "Run an HTTP test to show egress IP and country.",
+        "egress": "出口",
+        "egressHint": "執行 HTTP 測試以顯示出口 IP 與所在國家/地區。",
         "outboundStatus": "出站狀態",
         "sendThrough": "傳送通過",
         "targetStrategy": "目標解析策略",
@@ -1607,14 +1607,14 @@
         "tagDuplicate": "該標籤已被其他均衡器使用",
         "tagPlaceholder": "唯一均衡器標籤",
         "selector": "選擇器",
-        "fallback": "Fallback",
+        "fallback": "備用負載平衡器",
         "cycleTooltip": "循環: {path} → (回到 {start})",
         "expected": "期望",
         "expectedPlaceholder": "最佳節點數",
         "maxRtt": "最大 RTT",
         "tolerance": "容差",
-        "baselines": "Baselines",
-        "costs": "Costs",
+        "baselines": "基準值",
+        "costs": "成本設定",
         "costMatch": "標籤比對模式",
         "costValue": "權重",
         "costRegexp": "正規表示式比對",
@@ -1635,7 +1635,7 @@
         "userLevel": "用戶級別"
       },
       "nord": {
-        "accessToken": "Access token",
+        "accessToken": "存取權杖",
         "privateKey": "私鑰",
         "noServers": "未找到選定國家/地區的伺服器",
         "noPublicKey": "選定的伺服器未公布 NordLynx 公鑰。",
@@ -1651,7 +1651,7 @@
         "licenseError": "設定 WARP 授權失敗。",
         "fetchFirst": "請先取得 WARP 設定。",
         "createAccount": "建立 WARP 帳號",
-        "accessToken": "Access token",
+        "accessToken": "存取權杖",
         "deviceId": "裝置 ID",
         "licenseKey": "授權金鑰",
         "privateKey": "私鑰",
@@ -1702,7 +1702,7 @@
         "timeoutMs": "逾時 (毫秒)",
         "skipFallback": "跳過回退",
         "finalQuery": "最終查詢",
-        "hosts": "Hosts",
+        "hosts": "主機對應",
         "hostsAdd": "新增 Host",
         "hostsEmpty": "未定義任何 Host",
         "hostsDomain": "網域 (例如 domain:example.com)",

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff