Compare commits

...

76 Commits

Author SHA1 Message Date
sepakeloudest b31c70a5c1 🐛 fix: äŋŪæ”đ样垏 2025-12-15 18:30:29 -08:00
sepakeloudest 75db379624 🐛 fix: äŋŪæ”đ样垏 2025-12-15 18:29:56 -08:00
sepakeloudest c8401af672 🐛 fix: æ›ī新nextį‰ˆæœŽ 2025-12-07 05:59:23 -08:00
semantic-release-bot ea3964ebe5 🔖 chore(release): v1.6.1 [skip ci]
## [1.6.1](https://github.com/perfect-panel/ppanel-web/compare/v1.6.0...v1.6.1) (2025-11-05)

### 🐛 Bug Fixes

* Fixing issues with generating standard and quantum-resistant encryption keys ([5eac6a9](https://github.com/perfect-panel/ppanel-web/commit/5eac6a9))
2025-11-05 04:45:55 +00:00
web 5eac6a9f4a 🐛 fix: Fixing issues with generating standard and quantum-resistant encryption keys 2025-11-04 20:40:53 -08:00
web@ppanel 2182400adc Merge pull request #61 from Ember-Moth/main
fix: æœåŠĄå™Ļįž–čū‘æ—ķ合åđķ协čŪŪéŧ˜čŪĪ配į―Ū
2025-11-04 19:51:05 -08:00
Ember Moth 5318b9cf44 Refactor protocol configuration logic in server form 2025-11-02 16:48:41 +08:00
semantic-release-bot 705391f82a 🔖 chore(release): v1.6.0 [skip ci]
# [1.6.0](https://github.com/perfect-panel/ppanel-web/compare/v1.5.4...v1.6.0) (2025-10-28)

### âœĻ Features

* Add server installation dialog and commands ([4429c9d](https://github.com/perfect-panel/ppanel-web/commit/4429c9d))

### 🐛 Bug Fixes

* Add typeRoots configuration to ensure type definitions are resolved correctly ([ad60ea9](https://github.com/perfect-panel/ppanel-web/commit/ad60ea9))
2025-10-28 09:13:37 +00:00
web 4429c9ddc9 âœĻ feat: Add server installation dialog and commands 2025-10-28 02:10:12 -07:00
web ad60ea9b18 🐛 fix: Add typeRoots configuration to ensure type definitions are resolved correctly 2025-10-27 04:05:20 -07:00
semantic-release-bot 5025fd1103 🔖 chore(release): v1.5.4 [skip ci]
## [1.5.4](https://github.com/perfect-panel/ppanel-web/compare/v1.5.3...v1.5.4) (2025-10-26)

### 🐛 Bug Fixes

* Update generateRealityKeyPair to use async key generation ([e60e369](https://github.com/perfect-panel/ppanel-web/commit/e60e369))
* Update the wallet localization file and add new fields such as automatic reset and recharge ([88aa965](https://github.com/perfect-panel/ppanel-web/commit/88aa965))
2025-10-26 18:24:43 +00:00
web 88aa9656b2 🐛 fix: Update the wallet localization file and add new fields such as automatic reset and recharge 2025-10-26 11:20:52 -07:00
web e60e369bbe 🐛 fix: Update generateRealityKeyPair to use async key generation 2025-10-26 08:48:52 -07:00
semantic-release-bot c3d0ef8317 🔖 chore(release): v1.5.3 [skip ci]
## [1.5.3](https://github.com/perfect-panel/ppanel-web/compare/v1.5.2...v1.5.3) (2025-10-21)

### 🐛 Bug Fixes

* Fix bugs ([a46657d](https://github.com/perfect-panel/ppanel-web/commit/a46657d))
* Fix dependencies ([8bd25d6](https://github.com/perfect-panel/ppanel-web/commit/8bd25d6))
* Remove unnecessary migration function code and add device configuration options ([521a7a9](https://github.com/perfect-panel/ppanel-web/commit/521a7a9))
* Update bun.lockb to reflect dependency changes ([ca892dd](https://github.com/perfect-panel/ppanel-web/commit/ca892dd))
2025-10-21 12:32:02 +00:00
web 8bd25d651b 🐛 fix: Fix dependencies 2025-10-21 05:28:40 -07:00
web ca892dd359 🐛 fix: Update bun.lockb to reflect dependency changes 2025-10-21 04:38:50 -07:00
web 521a7a97fb 🐛 fix: Remove unnecessary migration function code and add device configuration options 2025-10-21 04:33:29 -07:00
web a46657d5ef 🐛 fix: Fix bugs 2025-10-21 04:10:34 -07:00
semantic-release-bot c2bfee1f31 🔖 chore(release): v1.5.2 [skip ci]
## [1.5.2](https://github.com/perfect-panel/ppanel-web/compare/v1.5.1...v1.5.2) (2025-09-29)

### 🐛 Bug Fixes

* Add step attribute to datetime-local inputs for precise time selection in forms ([32fd181](https://github.com/perfect-panel/ppanel-web/commit/32fd181))
* Rename 'hysteria2' to 'hysteria' across protocol definitions and schemas for consistency ([5816dd5](https://github.com/perfect-panel/ppanel-web/commit/5816dd5))
* Update protocol options in ServerConfig for accuracy and consistency ([9266529](https://github.com/perfect-panel/ppanel-web/commit/9266529))
2025-09-29 10:01:27 +00:00
web 32fd181b52 🐛 fix: Add step attribute to datetime-local inputs for precise time selection in forms 2025-09-29 02:43:39 -07:00
web 5816dd5198 🐛 fix: Rename 'hysteria2' to 'hysteria' across protocol definitions and schemas for consistency 2025-09-29 02:32:28 -07:00
web 92665293ec 🐛 fix: Update protocol options in ServerConfig for accuracy and consistency 2025-09-29 02:18:16 -07:00
semantic-release-bot ec1e402419 🔖 chore(release): v1.5.1 [skip ci]
## [1.5.1](https://github.com/perfect-panel/ppanel-web/compare/v1.5.0...v1.5.1) (2025-09-28)

### 🐛 Bug Fixes

* Simplify protocol enable checks by removing unnecessary false comparisons ([4828700](https://github.com/perfect-panel/ppanel-web/commit/4828700))
2025-09-28 16:24:12 +00:00
web 4828700776 🐛 fix: Simplify protocol enable checks by removing unnecessary false comparisons 2025-09-28 09:19:54 -07:00
semantic-release-bot 35e1cef18e 🔖 chore(release): v1.5.0 [skip ci]
# [1.5.0](https://github.com/perfect-panel/ppanel-web/compare/v1.4.8...v1.5.0) (2025-09-28)

### âœĻ Features

* Update server configuration translations for multiple languages ([fc43de1](https://github.com/perfect-panel/ppanel-web/commit/fc43de1))

### 🐛 Bug Fixes

* Add DynamicMultiplier component for managing node multipliers and update ServersPage layout ([bb6671c](https://github.com/perfect-panel/ppanel-web/commit/bb6671c))
* Remove unnecessary blank lines in multiple index files for cleaner code structure ([6a823b8](https://github.com/perfect-panel/ppanel-web/commit/6a823b8))
* Remove unused ratio variable from server traffic log and server form for cleaner code ([55034dc](https://github.com/perfect-panel/ppanel-web/commit/55034dc))
* Update Badge variants and restructure traffic ratio display in ServersPage ([3d778e5](https://github.com/perfect-panel/ppanel-web/commit/3d778e5))
* Update minimum ratio value to 0 in protocol fields and adjust related schemas; enhance unit conversion in ServerConfig ([3b6ef17](https://github.com/perfect-panel/ppanel-web/commit/3b6ef17))
* Update protocol fields to use 'obfs' instead of 'security' and adjust related configurations ([4abdd36](https://github.com/perfect-panel/ppanel-web/commit/4abdd36))
2025-09-28 16:03:36 +00:00
web 55034dc97d 🐛 fix: Remove unused ratio variable from server traffic log and server form for cleaner code 2025-09-28 09:01:03 -07:00
web 6a823b8faa 🐛 fix: Remove unnecessary blank lines in multiple index files for cleaner code structure 2025-09-28 08:49:52 -07:00
web 3b6ef177ba 🐛 fix: Update minimum ratio value to 0 in protocol fields and adjust related schemas; enhance unit conversion in ServerConfig 2025-09-28 08:48:17 -07:00
web 3d778e5e36 🐛 fix: Update Badge variants and restructure traffic ratio display in ServersPage 2025-09-28 07:23:28 -07:00
web 4abdd367ee 🐛 fix: Update protocol fields to use 'obfs' instead of 'security' and adjust related configurations 2025-09-28 06:57:41 -07:00
web fc43de16f0 âœĻ feat: Update server configuration translations for multiple languages
- Added new keys for server configuration actions and descriptions in Russian, Thai, Turkish, Ukrainian, Vietnamese, Chinese (Simplified and Traditional).
- Removed outdated configuration structure and replaced it with a more organized server_config object.
- Enhanced the dynamic Inputs component to support textarea input type for better user experience.
2025-09-24 06:05:24 -07:00
web bb6671c14f 🐛 fix: Add DynamicMultiplier component for managing node multipliers and update ServersPage layout 2025-09-24 03:00:37 -07:00
semantic-release-bot 47f66030db 🔖 chore(release): v1.4.8 [skip ci]
## [1.4.8](https://github.com/perfect-panel/ppanel-web/compare/v1.4.7...v1.4.8) (2025-09-23)

### 🐛 Bug Fixes

* Rename 'server_id' to 'protocol' in NodesPage and clean up unused imports and code in ServerConfig ([70b3484](https://github.com/perfect-panel/ppanel-web/commit/70b3484))
* Update announcement page to display timeline of announcements with Markdown content ([3c036eb](https://github.com/perfect-panel/ppanel-web/commit/3c036eb))
* Update Empty component to support border prop and adjust usage in various pages ([ce9ab89](https://github.com/perfect-panel/ppanel-web/commit/ce9ab89))
2025-09-23 13:11:32 +00:00
web 70b3484f98 🐛 fix: Rename 'server_id' to 'protocol' in NodesPage and clean up unused imports and code in ServerConfig 2025-09-23 06:08:36 -07:00
web 3c036eb09c 🐛 fix: Update announcement page to display timeline of announcements with Markdown content 2025-09-23 05:48:25 -07:00
web ce9ab89c1c 🐛 fix: Update Empty component to support border prop and adjust usage in various pages 2025-09-23 05:46:21 -07:00
semantic-release-bot bb0a811432 🔖 chore(release): v1.4.7 [skip ci]
## [1.4.7](https://github.com/perfect-panel/ppanel-web/compare/v1.4.6...v1.4.7) (2025-09-23)

### 🐛 Bug Fixes

* Add unique key to ProTable for improved rendering with user ID filters ([2bff15f](https://github.com/perfect-panel/ppanel-web/commit/2bff15f))
* Adjust layout spacing and chart aspect ratio in ServerConfig component ([05a61d8](https://github.com/perfect-panel/ppanel-web/commit/05a61d8))
* Refactor server ID cell rendering for improved readability and consistency ([0345b7c](https://github.com/perfect-panel/ppanel-web/commit/0345b7c))
* Update announcement page to format creation date and enhance content display ([8445e30](https://github.com/perfect-panel/ppanel-web/commit/8445e30))
* Update OnlineUsersCell to display user count with icon instead of badge ([7a4ebdf](https://github.com/perfect-panel/ppanel-web/commit/7a4ebdf))
* Update subscribe name fallback to return '--' instead of 'Unknown' ([0a07d25](https://github.com/perfect-panel/ppanel-web/commit/0a07d25))
2025-09-23 12:16:40 +00:00
web 8445e302e6 🐛 fix: Update announcement page to format creation date and enhance content display 2025-09-23 05:12:36 -07:00
web 2bff15fd13 🐛 fix: Add unique key to ProTable for improved rendering with user ID filters 2025-09-23 05:04:33 -07:00
web 0345b7cced 🐛 fix: Refactor server ID cell rendering for improved readability and consistency 2025-09-23 05:01:17 -07:00
web 7a4ebdf985 🐛 fix: Update OnlineUsersCell to display user count with icon instead of badge 2025-09-23 04:46:33 -07:00
web 05a61d8bf2 🐛 fix: Adjust layout spacing and chart aspect ratio in ServerConfig component 2025-09-23 04:42:55 -07:00
web 0a07d2578e 🐛 fix: Update subscribe name fallback to return '--' instead of 'Unknown' 2025-09-23 04:40:35 -07:00
semantic-release-bot ee05a73834 🔖 chore(release): v1.4.6 [skip ci]
## [1.4.6](https://github.com/perfect-panel/ppanel-web/compare/v1.4.5...v1.4.6) (2025-09-17)

### ðŸŽŦ Chores

* Merge branch 'main' into develop ([41f06bf](https://github.com/perfect-panel/ppanel-web/commit/41f06bf))

### 🐛 Bug Fixes

* Add loaded state to node, server, and subscribe stores for better loading management ([13dce0c](https://github.com/perfect-panel/ppanel-web/commit/13dce0c))
* Removed node metadata fields in subscription schema to simplify structure ([0cadd83](https://github.com/perfect-panel/ppanel-web/commit/0cadd83))
2025-09-17 14:29:53 +00:00
web 13dce0c20b 🐛 fix: Add loaded state to node, server, and subscribe stores for better loading management 2025-09-17 07:26:56 -07:00
web 41f06bfe54 🔧 chore: Merge branch 'main' into develop 2025-09-17 03:21:49 -07:00
web 0cadd83e45 🐛 fix: Removed node metadata fields in subscription schema to simplify structure 2025-09-17 03:19:31 -07:00
semantic-release-bot d7879a8654 🔖 chore(release): v1.4.5 [skip ci]
## [1.4.5](https://github.com/perfect-panel/ppanel-web/compare/v1.4.4...v1.4.5) (2025-09-17)

### â™ŧ Code Refactoring

* Replace useQuery with Zustand store for subscription and node data management ([c6dd0b6](https://github.com/perfect-panel/ppanel-web/commit/c6dd0b6))
* Simplify TemplatePreview component structure by consolidating Sheet and Button elements ([1b715c5](https://github.com/perfect-panel/ppanel-web/commit/1b715c5))

### 🐛 Bug Fixes

*  Add showLineNumbers prop handling in MonacoEditor for improved placeholder positioning ([bd67ece](https://github.com/perfect-panel/ppanel-web/commit/bd67ece))
* Add fetchTags method to NodesPage and ensure tags are fetched alongside nodes ([a3c5e31](https://github.com/perfect-panel/ppanel-web/commit/a3c5e31))
* Add NEXT_PUBLIC_HIDDEN_TUTORIAL_DOCUMENT to control tutorial visibility and update page query accordingly ([e94405d](https://github.com/perfect-panel/ppanel-web/commit/e94405d))
* Add subscribeSchema for subscription management with detailed proxy and user information ([49b3dcc](https://github.com/perfect-panel/ppanel-web/commit/49b3dcc))
* Enhance server ID display in ServerTrafficLogPage with badges and server ratio ([6dfac27](https://github.com/perfect-panel/ppanel-web/commit/6dfac27))
* Update platform handling in Content component to ensure available platforms are correctly filtered and displayed ([1dde708](https://github.com/perfect-panel/ppanel-web/commit/1dde708))
2025-09-17 10:13:51 +00:00
web 49b3dcc591 🐛 fix: Add subscribeSchema for subscription management with detailed proxy and user information 2025-09-17 03:10:44 -07:00
web e94405d1cd 🐛 fix: Add NEXT_PUBLIC_HIDDEN_TUTORIAL_DOCUMENT to control tutorial visibility and update page query accordingly 2025-09-17 03:03:12 -07:00
web 1dde7088bc 🐛 fix: Update platform handling in Content component to ensure available platforms are correctly filtered and displayed 2025-09-17 02:55:33 -07:00
web bd67ece479 🐛 fix: Add showLineNumbers prop handling in MonacoEditor for improved placeholder positioning 2025-09-17 02:45:16 -07:00
web a3c5e31094 🐛 fix: Add fetchTags method to NodesPage and ensure tags are fetched alongside nodes 2025-09-17 02:35:53 -07:00
web 1b715c5f8b â™ŧïļ refactor: Simplify TemplatePreview component structure by consolidating Sheet and Button elements 2025-09-17 02:29:29 -07:00
web 6dfac27bc3 🐛 fix: Enhance server ID display in ServerTrafficLogPage with badges and server ratio 2025-09-17 02:24:54 -07:00
web c6dd0b63f2 â™ŧïļ refactor: Replace useQuery with Zustand store for subscription and node data management 2025-09-17 02:20:54 -07:00
semantic-release-bot bdd53b1551 🔖 chore(release): v1.4.4 [skip ci]
## [1.4.4](https://github.com/perfect-panel/ppanel-web/compare/v1.4.3...v1.4.4) (2025-09-16)

### 🐛 Bug Fixes

* Add minimum value constraint for count and user limit inputs in CouponForm ([6991b69](https://github.com/perfect-panel/ppanel-web/commit/6991b69))
* Add protocol-related constants, default configurations, field definitions, and validation modes ([d685407](https://github.com/perfect-panel/ppanel-web/commit/d685407))
* Added the enabled field in the protocol configuration, updated the related type definition and default configuration ([2b0cf9a](https://github.com/perfect-panel/ppanel-web/commit/2b0cf9a))
* Filter available protocols to exclude disabled ones in NodeForm ([982d288](https://github.com/perfect-panel/ppanel-web/commit/982d288))
* Refactor key generation logic and update dependencies for ML-KEM-768 integration ([b8f630f](https://github.com/perfect-panel/ppanel-web/commit/b8f630f))
2025-09-16 17:42:46 +00:00
web 982d2882e9 🐛 fix: Filter available protocols to exclude disabled ones in NodeForm 2025-09-16 10:30:49 -07:00
web 2b0cf9a46d 🐛 fix: Added the enabled field in the protocol configuration, updated the related type definition and default configuration 2025-09-16 10:17:39 -07:00
web d6854076fe 🐛 fix: Add protocol-related constants, default configurations, field definitions, and validation modes 2025-09-16 04:50:05 -07:00
web 6991b69d40 🐛 fix: Add minimum value constraint for count and user limit inputs in CouponForm 2025-09-16 04:33:42 -07:00
web b8f630f8ab 🐛 fix: Refactor key generation logic and update dependencies for ML-KEM-768 integration 2025-09-16 04:32:16 -07:00
semantic-release-bot 5ce5d62b22 🔖 chore(release): v1.4.3 [skip ci]
## [1.4.3](https://github.com/perfect-panel/ppanel-web/compare/v1.4.2...v1.4.3) (2025-09-16)

### 🐛 Bug Fixes

* Add success toast message for sorting in nodes and servers pages ([2d5175d](https://github.com/perfect-panel/ppanel-web/commit/2d5175d))
* Implement encryption and obfuscation features in protocol configuration ([54de16b](https://github.com/perfect-panel/ppanel-web/commit/54de16b))
* Refactor toB64 function to toB64Url for URL-safe base64 encoding in VlessX25519Pair generation ([8700cf6](https://github.com/perfect-panel/ppanel-web/commit/8700cf6))
* Simplify initialValues assignment and update node submission logic in NodesPage ([05d6c89](https://github.com/perfect-panel/ppanel-web/commit/05d6c89))
* Update bun.lockb to reflect dependency changes ([ebcebd7](https://github.com/perfect-panel/ppanel-web/commit/ebcebd7))
2025-09-16 05:13:48 +00:00
web 2d5175d70f 🐛 fix: Add success toast message for sorting in nodes and servers pages 2025-09-15 21:51:02 -07:00
web 05d6c8947c 🐛 fix: Simplify initialValues assignment and update node submission logic in NodesPage 2025-09-15 21:39:13 -07:00
web 8700cf6a91 🐛 fix: Refactor toB64 function to toB64Url for URL-safe base64 encoding in VlessX25519Pair generation 2025-09-15 21:12:25 -07:00
web ebcebd7ddf 🐛 fix: Update bun.lockb to reflect dependency changes 2025-09-15 09:45:08 -07:00
web 54de16bdd5 🐛 fix: Implement encryption and obfuscation features in protocol configuration 2025-09-15 09:35:03 -07:00
semantic-release-bot 597a01885b 🔖 chore(release): v1.4.2 [skip ci]
## [1.4.2](https://github.com/perfect-panel/ppanel-web/compare/v1.4.1...v1.4.2) (2025-09-15)

### 🐛 Bug Fixes

* Add GitHub template repository link to ProtocolForm header for easier access ([8a0baf3](https://github.com/perfect-panel/ppanel-web/commit/8a0baf3))
* Add readOnly prop to MonacoEditor and TemplatePreview components for improved content handling ([c4c4d5a](https://github.com/perfect-panel/ppanel-web/commit/c4c4d5a))
2025-09-15 11:12:27 +00:00
web 8a0baf3a61 🐛 fix: Add GitHub template repository link to ProtocolForm header for easier access 2025-09-15 04:09:14 -07:00
web c4c4d5aea3 🐛 fix: Add readOnly prop to MonacoEditor and TemplatePreview components for improved content handling 2025-09-15 03:50:15 -07:00
semantic-release-bot 15540bfe16 🔖 chore(release): v1.4.1 [skip ci]
## [1.4.1](https://github.com/perfect-panel/ppanel-web/compare/v1.4.0...v1.4.1) (2025-09-15)

### 🐛 Bug Fixes

* Add copy subscription functionality to user subscription dashboard and improve localization for new features ([e2a357f](https://github.com/perfect-panel/ppanel-web/commit/e2a357f))
* Simplify handleInputBlur function by removing unnecessary setTimeout ([6341562](https://github.com/perfect-panel/ppanel-web/commit/6341562))
* Update TemplatePreview to use MonacoEditor for content display and improve error handling ([1d52642](https://github.com/perfect-panel/ppanel-web/commit/1d52642))
* Update user dashboard link to correct path ([131693b](https://github.com/perfect-panel/ppanel-web/commit/131693b))
2025-09-15 06:21:43 +00:00
web 634156206a 🐛 fix: Simplify handleInputBlur function by removing unnecessary setTimeout 2025-09-14 23:18:42 -07:00
web e2a357fea7 🐛 fix: Add copy subscription functionality to user subscription dashboard and improve localization for new features 2025-09-14 23:09:59 -07:00
web 1d52642cb7 🐛 fix: Update TemplatePreview to use MonacoEditor for content display and improve error handling 2025-09-14 22:54:50 -07:00
web 131693b604 🐛 fix: Update user dashboard link to correct path 2025-09-14 08:30:24 -07:00
185 changed files with 10603 additions and 2781 deletions
+235 -63
View File
@@ -1,77 +1,249 @@
<a name="readme-top"></a> <a name="readme-top"></a>
# Changelog # Changelog
# [1.4.0](https://github.com/perfect-panel/ppanel-web/compare/v1.3.0...v1.4.0) (2025-09-14) ## [1.6.1](https://github.com/perfect-panel/ppanel-web/compare/v1.6.0...v1.6.1) (2025-11-05)
### â™ŧ Code Refactoring
* **logs**: Add localization files and update existing translations for multiple languages ([2f20ac9](https://github.com/perfect-panel/ppanel-web/commit/2f20ac9))
* **subscribe-form**: Replace server_group and server with node_tags and nodes in default values and form schema ([38dda84](https://github.com/perfect-panel/ppanel-web/commit/38dda84))
* Add localization updates for log and server files across multiple languages ([2bcd4cf](https://github.com/perfect-panel/ppanel-web/commit/2bcd4cf))
* Clean up NodeForm and ServerForm components by removing unused functions and optimizing state management ([10250d9](https://github.com/perfect-panel/ppanel-web/commit/10250d9))
* Enhance log pages with Badge component and update translations ([a4de9df](https://github.com/perfect-panel/ppanel-web/commit/a4de9df))
* Make 'scheme' field optional in client form schema ([f4a1237](https://github.com/perfect-panel/ppanel-web/commit/f4a1237))
* Refactor server management API endpoints and typings ([4f7cc80](https://github.com/perfect-panel/ppanel-web/commit/4f7cc80))
* Remove application management forms and related configurations ([0c43844](https://github.com/perfect-panel/ppanel-web/commit/0c43844))
* Remove default options from TagInput component for improved flexibility ([6a3bb70](https://github.com/perfect-panel/ppanel-web/commit/6a3bb70))
* Remove unused preview state variables and add sort order to node properties ([e63f823](https://github.com/perfect-panel/ppanel-web/commit/e63f823))
* Rename buildScheme to buildSchema and update imports in server form components ([ee98e7e](https://github.com/perfect-panel/ppanel-web/commit/ee98e7e))
* Simplify node display in subscribe form and remove unused Badge import ([551135d](https://github.com/perfect-panel/ppanel-web/commit/551135d))
* Update bun.lockb to reflect dependency changes ([ba2b50e](https://github.com/perfect-panel/ppanel-web/commit/ba2b50e))
* Update component imports and improve code consistency ([59faeab](https://github.com/perfect-panel/ppanel-web/commit/59faeab))
* Update dependencies and improve code consistency across multiple files ([e37ae49](https://github.com/perfect-panel/ppanel-web/commit/e37ae49))
* Update localization files and service imports ([d4b37e4](https://github.com/perfect-panel/ppanel-web/commit/d4b37e4))
### âœĻ Features
* **config**: Add translations for server configuration in multiple languages ([f9a7ece](https://github.com/perfect-panel/ppanel-web/commit/f9a7ece))
* **logs**: Add various log pages for tracking user activities and system events ([d85af49](https://github.com/perfect-panel/ppanel-web/commit/d85af49))
* Add bandwidth fields and placeholders for upload and download in server configuration forms; update localization files for multiple languages ([3e5402f](https://github.com/perfect-panel/ppanel-web/commit/3e5402f))
* Add batch delete functionality and enhance chart tooltips in statistics cards ([c4f536e](https://github.com/perfect-panel/ppanel-web/commit/c4f536e))
* Add language support and descriptions in product localization files ([fd48856](https://github.com/perfect-panel/ppanel-web/commit/fd48856))
* Add log cleanup settings and update localization files ([6ccf9b8](https://github.com/perfect-panel/ppanel-web/commit/6ccf9b8))
* Add queryNodeTag function and integrate tag retrieval in NodeForm and SubscribeForm components ([4563c57](https://github.com/perfect-panel/ppanel-web/commit/4563c57))
* Add quota management features and localization updates ([fce627b](https://github.com/perfect-panel/ppanel-web/commit/fce627b))
* Add server form component with protocol configuration and localization support ([217ddce](https://github.com/perfect-panel/ppanel-web/commit/217ddce))
* Enhance TagInput component with option handling and improved tag addition logic ([b6e778d](https://github.com/perfect-panel/ppanel-web/commit/b6e778d))
* Implement data migration functionality and update localization files ([6d81bfd](https://github.com/perfect-panel/ppanel-web/commit/6d81bfd))
* Refactor user detail and subscription management components ([973c06f](https://github.com/perfect-panel/ppanel-web/commit/973c06f))
* Update server list fetching logic and adjust query parameters ([5272360](https://github.com/perfect-panel/ppanel-web/commit/5272360))
### 🐛 Bug Fixes ### 🐛 Bug Fixes
* **page**: Refine version checking logic and remove unnecessary comments for clarity ([26176a7](https://github.com/perfect-panel/ppanel-web/commit/26176a7)) * Fixing issues with generating standard and quantum-resistant encryption keys ([5eac6a9](https://github.com/perfect-panel/ppanel-web/commit/5eac6a9))
* Add localization updates and new utility functions ([4da5960](https://github.com/perfect-panel/ppanel-web/commit/4da5960))
* Add user_subscribe_id filter to SubscribeLogPage and update typings; disable eslint in service index files ([ab6f6a6](https://github.com/perfect-panel/ppanel-web/commit/ab6f6a6))
* Added system version card and system log dialog components; updated statistics page to include total server and user statistics ([fe69980](https://github.com/perfect-panel/ppanel-web/commit/fe69980))
* Adjust timestamp calculations to use milliseconds instead of seconds in quota broadcast form submission ([8dffd69](https://github.com/perfect-panel/ppanel-web/commit/8dffd69))
* Correct cookie key format for sidebar state retrieval ([9e01f4f](https://github.com/perfect-panel/ppanel-web/commit/9e01f4f))
* Improve UI for protocol status display in server form component ([461fdb1](https://github.com/perfect-panel/ppanel-web/commit/461fdb1))
* Increase pagination size limit for server and node lists to improve data retrieval ([7c0a312](https://github.com/perfect-panel/ppanel-web/commit/7c0a312))
* Optimize Task Manager to use milliseconds to calculate timers, and simplify import statements ([d8fa13b](https://github.com/perfect-panel/ppanel-web/commit/d8fa13b))
* Refactor protocol status display for improved readability in server form component ([f700be0](https://github.com/perfect-panel/ppanel-web/commit/f700be0))
* Remove GroupTable and related components, simplify SubscribeTable and update language handling in subscription forms ([1ab9b39](https://github.com/perfect-panel/ppanel-web/commit/1ab9b39))
* Remove redundant transport label from localization files ([88ce8d7](https://github.com/perfect-panel/ppanel-web/commit/88ce8d7))
* Remove unnecessary comments and improve variable handling in GoTemplateEditor; ensure zero value displays as empty in EnhancedInput ([c4a47a4](https://github.com/perfect-panel/ppanel-web/commit/c4a47a4))
* Remove unnecessary comments to simplify code readability ([a988cb3](https://github.com/perfect-panel/ppanel-web/commit/a988cb3))
* Replace MarkdownEditor with HTMLEditor in EmailBroadcastForm; simplify content rendering in EmailTaskManager; disable eslint in service index files ([e2d83ec](https://github.com/perfect-panel/ppanel-web/commit/e2d83ec))
* Replace redundant icon rendering with a single instance in system version card component ([e4429a5](https://github.com/perfect-panel/ppanel-web/commit/e4429a5))
* Update billing URL fetching logic and improve version handling in system version card component ([1c8d4af](https://github.com/perfect-panel/ppanel-web/commit/1c8d4af))
* Update condition for plugin field in PROTOCOL_FIELDS to include specific plugins ([6376ec1](https://github.com/perfect-panel/ppanel-web/commit/6376ec1))
* Update getAppSubLink function to improve URL handling and encoding logic ([351fffc](https://github.com/perfect-panel/ppanel-web/commit/351fffc))
* Update order_id to order_no in BalanceLogPage and related typings; enhance timezone switch component with additional features and localization updates ([ac36075](https://github.com/perfect-panel/ppanel-web/commit/ac36075))
* Update protocol plugin handling and add new options in typings ([6ca2433](https://github.com/perfect-panel/ppanel-web/commit/6ca2433))
* Update release URLs to include 'v' prefix for versioning in system version card component ([1d526b5](https://github.com/perfect-panel/ppanel-web/commit/1d526b5))
* Update SidebarLeft component styles to enhance hover effects ([e4fbd5c](https://github.com/perfect-panel/ppanel-web/commit/e4fbd5c))
* Update validation for days and gift_value fields in QuotaBroadcastForm; set default values to avoid errors ([39d746f](https://github.com/perfect-panel/ppanel-web/commit/39d746f))
<a name="readme-top"></a>
# Changelog
# [1.6.0](https://github.com/perfect-panel/ppanel-web/compare/v1.5.4...v1.6.0) (2025-10-28)
### âœĻ Features
- Add server installation dialog and commands ([4429c9d](https://github.com/perfect-panel/ppanel-web/commit/4429c9d))
### 🐛 Bug Fixes
- Add typeRoots configuration to ensure type definitions are resolved correctly ([ad60ea9](https://github.com/perfect-panel/ppanel-web/commit/ad60ea9))
<a name="readme-top"></a>
# Changelog
## [1.5.4](https://github.com/perfect-panel/ppanel-web/compare/v1.5.3...v1.5.4) (2025-10-26)
### 🐛 Bug Fixes
- Update generateRealityKeyPair to use async key generation ([e60e369](https://github.com/perfect-panel/ppanel-web/commit/e60e369))
- Update the wallet localization file and add new fields such as automatic reset and recharge ([88aa965](https://github.com/perfect-panel/ppanel-web/commit/88aa965))
## [1.5.3](https://github.com/perfect-panel/ppanel-web/compare/v1.5.2...v1.5.3) (2025-10-21)
### 🐛 Bug Fixes
- Fix bugs ([a46657d](https://github.com/perfect-panel/ppanel-web/commit/a46657d))
- Fix dependencies ([8bd25d6](https://github.com/perfect-panel/ppanel-web/commit/8bd25d6))
- Remove unnecessary migration function code and add device configuration options ([521a7a9](https://github.com/perfect-panel/ppanel-web/commit/521a7a9))
- Update bun.lockb to reflect dependency changes ([ca892dd](https://github.com/perfect-panel/ppanel-web/commit/ca892dd))
<a name="readme-top"></a>
# Changelog
## [1.5.2](https://github.com/perfect-panel/ppanel-web/compare/v1.5.1...v1.5.2) (2025-09-29)
### 🐛 Bug Fixes
- Add step attribute to datetime-local inputs for precise time selection in forms ([32fd181](https://github.com/perfect-panel/ppanel-web/commit/32fd181))
- Rename 'hysteria2' to 'hysteria' across protocol definitions and schemas for consistency ([5816dd5](https://github.com/perfect-panel/ppanel-web/commit/5816dd5))
- Update protocol options in ServerConfig for accuracy and consistency ([9266529](https://github.com/perfect-panel/ppanel-web/commit/9266529))
## [1.5.1](https://github.com/perfect-panel/ppanel-web/compare/v1.5.0...v1.5.1) (2025-09-28)
### 🐛 Bug Fixes
- Simplify protocol enable checks by removing unnecessary false comparisons ([4828700](https://github.com/perfect-panel/ppanel-web/commit/4828700))
# [1.5.0](https://github.com/perfect-panel/ppanel-web/compare/v1.4.8...v1.5.0) (2025-09-28)
### âœĻ Features
- Update server configuration translations for multiple languages ([fc43de1](https://github.com/perfect-panel/ppanel-web/commit/fc43de1))
### 🐛 Bug Fixes
- Add DynamicMultiplier component for managing node multipliers and update ServersPage layout ([bb6671c](https://github.com/perfect-panel/ppanel-web/commit/bb6671c))
- Remove unnecessary blank lines in multiple index files for cleaner code structure ([6a823b8](https://github.com/perfect-panel/ppanel-web/commit/6a823b8))
- Remove unused ratio variable from server traffic log and server form for cleaner code ([55034dc](https://github.com/perfect-panel/ppanel-web/commit/55034dc))
- Update Badge variants and restructure traffic ratio display in ServersPage ([3d778e5](https://github.com/perfect-panel/ppanel-web/commit/3d778e5))
- Update minimum ratio value to 0 in protocol fields and adjust related schemas; enhance unit conversion in ServerConfig ([3b6ef17](https://github.com/perfect-panel/ppanel-web/commit/3b6ef17))
- Update protocol fields to use 'obfs' instead of 'security' and adjust related configurations ([4abdd36](https://github.com/perfect-panel/ppanel-web/commit/4abdd36))
<a name="readme-top"></a>
# Changelog
## [1.4.8](https://github.com/perfect-panel/ppanel-web/compare/v1.4.7...v1.4.8) (2025-09-23)
### 🐛 Bug Fixes
- Rename 'server_id' to 'protocol' in NodesPage and clean up unused imports and code in ServerConfig ([70b3484](https://github.com/perfect-panel/ppanel-web/commit/70b3484))
- Update announcement page to display timeline of announcements with Markdown content ([3c036eb](https://github.com/perfect-panel/ppanel-web/commit/3c036eb))
- Update Empty component to support border prop and adjust usage in various pages ([ce9ab89](https://github.com/perfect-panel/ppanel-web/commit/ce9ab89))
## [1.4.7](https://github.com/perfect-panel/ppanel-web/compare/v1.4.6...v1.4.7) (2025-09-23)
### 🐛 Bug Fixes
- Add unique key to ProTable for improved rendering with user ID filters ([2bff15f](https://github.com/perfect-panel/ppanel-web/commit/2bff15f))
- Adjust layout spacing and chart aspect ratio in ServerConfig component ([05a61d8](https://github.com/perfect-panel/ppanel-web/commit/05a61d8))
- Refactor server ID cell rendering for improved readability and consistency ([0345b7c](https://github.com/perfect-panel/ppanel-web/commit/0345b7c))
- Update announcement page to format creation date and enhance content display ([8445e30](https://github.com/perfect-panel/ppanel-web/commit/8445e30))
- Update OnlineUsersCell to display user count with icon instead of badge ([7a4ebdf](https://github.com/perfect-panel/ppanel-web/commit/7a4ebdf))
- Update subscribe name fallback to return '--' instead of 'Unknown' ([0a07d25](https://github.com/perfect-panel/ppanel-web/commit/0a07d25))
## [1.4.6](https://github.com/perfect-panel/ppanel-web/compare/v1.4.5...v1.4.6) (2025-09-17)
### ðŸŽŦ Chores
- Merge branch 'main' into develop ([41f06bf](https://github.com/perfect-panel/ppanel-web/commit/41f06bf))
### 🐛 Bug Fixes
- Add loaded state to node, server, and subscribe stores for better loading management ([13dce0c](https://github.com/perfect-panel/ppanel-web/commit/13dce0c))
- Removed node metadata fields in subscription schema to simplify structure ([0cadd83](https://github.com/perfect-panel/ppanel-web/commit/0cadd83))
## [1.4.5](https://github.com/perfect-panel/ppanel-web/compare/v1.4.4...v1.4.5) (2025-09-17)
### â™ŧ Code Refactoring
- Replace useQuery with Zustand store for subscription and node data management ([c6dd0b6](https://github.com/perfect-panel/ppanel-web/commit/c6dd0b6))
- Simplify TemplatePreview component structure by consolidating Sheet and Button elements ([1b715c5](https://github.com/perfect-panel/ppanel-web/commit/1b715c5))
### 🐛 Bug Fixes
- Add showLineNumbers prop handling in MonacoEditor for improved placeholder positioning ([bd67ece](https://github.com/perfect-panel/ppanel-web/commit/bd67ece))
- Add fetchTags method to NodesPage and ensure tags are fetched alongside nodes ([a3c5e31](https://github.com/perfect-panel/ppanel-web/commit/a3c5e31))
- Add NEXT_PUBLIC_HIDDEN_TUTORIAL_DOCUMENT to control tutorial visibility and update page query accordingly ([e94405d](https://github.com/perfect-panel/ppanel-web/commit/e94405d))
- Add subscribeSchema for subscription management with detailed proxy and user information ([49b3dcc](https://github.com/perfect-panel/ppanel-web/commit/49b3dcc))
- Enhance server ID display in ServerTrafficLogPage with badges and server ratio ([6dfac27](https://github.com/perfect-panel/ppanel-web/commit/6dfac27))
- Update platform handling in Content component to ensure available platforms are correctly filtered and displayed ([1dde708](https://github.com/perfect-panel/ppanel-web/commit/1dde708))
<a name="readme-top"></a>
# Changelog
## [1.4.4](https://github.com/perfect-panel/ppanel-web/compare/v1.4.3...v1.4.4) (2025-09-16)
### 🐛 Bug Fixes
- Add minimum value constraint for count and user limit inputs in CouponForm ([6991b69](https://github.com/perfect-panel/ppanel-web/commit/6991b69))
- Add protocol-related constants, default configurations, field definitions, and validation modes ([d685407](https://github.com/perfect-panel/ppanel-web/commit/d685407))
- Added the enabled field in the protocol configuration, updated the related type definition and default configuration ([2b0cf9a](https://github.com/perfect-panel/ppanel-web/commit/2b0cf9a))
- Filter available protocols to exclude disabled ones in NodeForm ([982d288](https://github.com/perfect-panel/ppanel-web/commit/982d288))
- Refactor key generation logic and update dependencies for ML-KEM-768 integration ([b8f630f](https://github.com/perfect-panel/ppanel-web/commit/b8f630f))
<a name="readme-top"></a>
# Changelog
## [1.4.3](https://github.com/perfect-panel/ppanel-web/compare/v1.4.2...v1.4.3) (2025-09-16)
### 🐛 Bug Fixes
- Add success toast message for sorting in nodes and servers pages ([2d5175d](https://github.com/perfect-panel/ppanel-web/commit/2d5175d))
- Implement encryption and obfuscation features in protocol configuration ([54de16b](https://github.com/perfect-panel/ppanel-web/commit/54de16b))
- Refactor toB64 function to toB64Url for URL-safe base64 encoding in VlessX25519Pair generation ([8700cf6](https://github.com/perfect-panel/ppanel-web/commit/8700cf6))
- Simplify initialValues assignment and update node submission logic in NodesPage ([05d6c89](https://github.com/perfect-panel/ppanel-web/commit/05d6c89))
- Update bun.lockb to reflect dependency changes ([ebcebd7](https://github.com/perfect-panel/ppanel-web/commit/ebcebd7))
<a name="readme-top"></a>
# Changelog
## [1.4.2](https://github.com/perfect-panel/ppanel-web/compare/v1.4.1...v1.4.2) (2025-09-15)
### 🐛 Bug Fixes
- Add GitHub template repository link to ProtocolForm header for easier access ([8a0baf3](https://github.com/perfect-panel/ppanel-web/commit/8a0baf3))
- Add readOnly prop to MonacoEditor and TemplatePreview components for improved content handling ([c4c4d5a](https://github.com/perfect-panel/ppanel-web/commit/c4c4d5a))
## [1.4.1](https://github.com/perfect-panel/ppanel-web/compare/v1.4.0...v1.4.1) (2025-09-15)
### 🐛 Bug Fixes
- Add copy subscription functionality to user subscription dashboard and improve localization for new features ([e2a357f](https://github.com/perfect-panel/ppanel-web/commit/e2a357f))
- Simplify handleInputBlur function by removing unnecessary setTimeout ([6341562](https://github.com/perfect-panel/ppanel-web/commit/6341562))
- Update TemplatePreview to use MonacoEditor for content display and improve error handling ([1d52642](https://github.com/perfect-panel/ppanel-web/commit/1d52642))
- Update user dashboard link to correct path ([131693b](https://github.com/perfect-panel/ppanel-web/commit/131693b))
<a name="readme-top"></a>
# Changelog
# [1.4.0](https://github.com/perfect-panel/ppanel-web/compare/v1.3.0...v1.4.0) (2025-09-14)
### â™ŧ Code Refactoring
- **logs**: Add localization files and update existing translations for multiple languages ([2f20ac9](https://github.com/perfect-panel/ppanel-web/commit/2f20ac9))
- **subscribe-form**: Replace server_group and server with node_tags and nodes in default values and form schema ([38dda84](https://github.com/perfect-panel/ppanel-web/commit/38dda84))
- Add localization updates for log and server files across multiple languages ([2bcd4cf](https://github.com/perfect-panel/ppanel-web/commit/2bcd4cf))
- Clean up NodeForm and ServerForm components by removing unused functions and optimizing state management ([10250d9](https://github.com/perfect-panel/ppanel-web/commit/10250d9))
- Enhance log pages with Badge component and update translations ([a4de9df](https://github.com/perfect-panel/ppanel-web/commit/a4de9df))
- Make 'scheme' field optional in client form schema ([f4a1237](https://github.com/perfect-panel/ppanel-web/commit/f4a1237))
- Refactor server management API endpoints and typings ([4f7cc80](https://github.com/perfect-panel/ppanel-web/commit/4f7cc80))
- Remove application management forms and related configurations ([0c43844](https://github.com/perfect-panel/ppanel-web/commit/0c43844))
- Remove default options from TagInput component for improved flexibility ([6a3bb70](https://github.com/perfect-panel/ppanel-web/commit/6a3bb70))
- Remove unused preview state variables and add sort order to node properties ([e63f823](https://github.com/perfect-panel/ppanel-web/commit/e63f823))
- Rename buildScheme to buildSchema and update imports in server form components ([ee98e7e](https://github.com/perfect-panel/ppanel-web/commit/ee98e7e))
- Simplify node display in subscribe form and remove unused Badge import ([551135d](https://github.com/perfect-panel/ppanel-web/commit/551135d))
- Update bun.lockb to reflect dependency changes ([ba2b50e](https://github.com/perfect-panel/ppanel-web/commit/ba2b50e))
- Update component imports and improve code consistency ([59faeab](https://github.com/perfect-panel/ppanel-web/commit/59faeab))
- Update dependencies and improve code consistency across multiple files ([e37ae49](https://github.com/perfect-panel/ppanel-web/commit/e37ae49))
- Update localization files and service imports ([d4b37e4](https://github.com/perfect-panel/ppanel-web/commit/d4b37e4))
### âœĻ Features
- **config**: Add translations for server configuration in multiple languages ([f9a7ece](https://github.com/perfect-panel/ppanel-web/commit/f9a7ece))
- **logs**: Add various log pages for tracking user activities and system events ([d85af49](https://github.com/perfect-panel/ppanel-web/commit/d85af49))
- Add bandwidth fields and placeholders for upload and download in server configuration forms; update localization files for multiple languages ([3e5402f](https://github.com/perfect-panel/ppanel-web/commit/3e5402f))
- Add batch delete functionality and enhance chart tooltips in statistics cards ([c4f536e](https://github.com/perfect-panel/ppanel-web/commit/c4f536e))
- Add language support and descriptions in product localization files ([fd48856](https://github.com/perfect-panel/ppanel-web/commit/fd48856))
- Add log cleanup settings and update localization files ([6ccf9b8](https://github.com/perfect-panel/ppanel-web/commit/6ccf9b8))
- Add queryNodeTag function and integrate tag retrieval in NodeForm and SubscribeForm components ([4563c57](https://github.com/perfect-panel/ppanel-web/commit/4563c57))
- Add quota management features and localization updates ([fce627b](https://github.com/perfect-panel/ppanel-web/commit/fce627b))
- Add server form component with protocol configuration and localization support ([217ddce](https://github.com/perfect-panel/ppanel-web/commit/217ddce))
- Enhance TagInput component with option handling and improved tag addition logic ([b6e778d](https://github.com/perfect-panel/ppanel-web/commit/b6e778d))
- Implement data migration functionality and update localization files ([6d81bfd](https://github.com/perfect-panel/ppanel-web/commit/6d81bfd))
- Refactor user detail and subscription management components ([973c06f](https://github.com/perfect-panel/ppanel-web/commit/973c06f))
- Update server list fetching logic and adjust query parameters ([5272360](https://github.com/perfect-panel/ppanel-web/commit/5272360))
### 🐛 Bug Fixes
- **page**: Refine version checking logic and remove unnecessary comments for clarity ([26176a7](https://github.com/perfect-panel/ppanel-web/commit/26176a7))
- Add localization updates and new utility functions ([4da5960](https://github.com/perfect-panel/ppanel-web/commit/4da5960))
- Add user_subscribe_id filter to SubscribeLogPage and update typings; disable eslint in service index files ([ab6f6a6](https://github.com/perfect-panel/ppanel-web/commit/ab6f6a6))
- Added system version card and system log dialog components; updated statistics page to include total server and user statistics ([fe69980](https://github.com/perfect-panel/ppanel-web/commit/fe69980))
- Adjust timestamp calculations to use milliseconds instead of seconds in quota broadcast form submission ([8dffd69](https://github.com/perfect-panel/ppanel-web/commit/8dffd69))
- Correct cookie key format for sidebar state retrieval ([9e01f4f](https://github.com/perfect-panel/ppanel-web/commit/9e01f4f))
- Improve UI for protocol status display in server form component ([461fdb1](https://github.com/perfect-panel/ppanel-web/commit/461fdb1))
- Increase pagination size limit for server and node lists to improve data retrieval ([7c0a312](https://github.com/perfect-panel/ppanel-web/commit/7c0a312))
- Optimize Task Manager to use milliseconds to calculate timers, and simplify import statements ([d8fa13b](https://github.com/perfect-panel/ppanel-web/commit/d8fa13b))
- Refactor protocol status display for improved readability in server form component ([f700be0](https://github.com/perfect-panel/ppanel-web/commit/f700be0))
- Remove GroupTable and related components, simplify SubscribeTable and update language handling in subscription forms ([1ab9b39](https://github.com/perfect-panel/ppanel-web/commit/1ab9b39))
- Remove redundant transport label from localization files ([88ce8d7](https://github.com/perfect-panel/ppanel-web/commit/88ce8d7))
- Remove unnecessary comments and improve variable handling in GoTemplateEditor; ensure zero value displays as empty in EnhancedInput ([c4a47a4](https://github.com/perfect-panel/ppanel-web/commit/c4a47a4))
- Remove unnecessary comments to simplify code readability ([a988cb3](https://github.com/perfect-panel/ppanel-web/commit/a988cb3))
- Replace MarkdownEditor with HTMLEditor in EmailBroadcastForm; simplify content rendering in EmailTaskManager; disable eslint in service index files ([e2d83ec](https://github.com/perfect-panel/ppanel-web/commit/e2d83ec))
- Replace redundant icon rendering with a single instance in system version card component ([e4429a5](https://github.com/perfect-panel/ppanel-web/commit/e4429a5))
- Update billing URL fetching logic and improve version handling in system version card component ([1c8d4af](https://github.com/perfect-panel/ppanel-web/commit/1c8d4af))
- Update condition for plugin field in PROTOCOL_FIELDS to include specific plugins ([6376ec1](https://github.com/perfect-panel/ppanel-web/commit/6376ec1))
- Update getAppSubLink function to improve URL handling and encoding logic ([351fffc](https://github.com/perfect-panel/ppanel-web/commit/351fffc))
- Update order_id to order_no in BalanceLogPage and related typings; enhance timezone switch component with additional features and localization updates ([ac36075](https://github.com/perfect-panel/ppanel-web/commit/ac36075))
- Update protocol plugin handling and add new options in typings ([6ca2433](https://github.com/perfect-panel/ppanel-web/commit/6ca2433))
- Update release URLs to include 'v' prefix for versioning in system version card component ([1d526b5](https://github.com/perfect-panel/ppanel-web/commit/1d526b5))
- Update SidebarLeft component styles to enhance hover effects ([e4fbd5c](https://github.com/perfect-panel/ppanel-web/commit/e4fbd5c))
- Update validation for days and gift_value fields in QuotaBroadcastForm; set default values to avoid errors ([39d746f](https://github.com/perfect-panel/ppanel-web/commit/39d746f))
### 📝 Documentation ### 📝 Documentation
* Merge branch 'main' into develop ([d7f8b3b](https://github.com/perfect-panel/ppanel-web/commit/d7f8b3b)) - Merge branch 'main' into develop ([d7f8b3b](https://github.com/perfect-panel/ppanel-web/commit/d7f8b3b))
<a name="readme-top"></a> <a name="readme-top"></a>
@@ -225,6 +225,7 @@ export default function AdsForm<T extends Record<string, any>>({
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
placeholder={t('form.enterStartTime')} placeholder={t('form.enterStartTime')}
value={field.value ? new Date(field.value).toISOString().slice(0, 16) : ''} value={field.value ? new Date(field.value).toISOString().slice(0, 16) : ''}
min={Number(new Date().toISOString().slice(0, 16))} min={Number(new Date().toISOString().slice(0, 16))}
@@ -253,6 +254,7 @@ export default function AdsForm<T extends Record<string, any>>({
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
placeholder={t('form.enterEndTime')} placeholder={t('form.enterEndTime')}
value={ value={
field.value ? new Date(field.value).toISOString().slice(0, 16) : '' field.value ? new Date(field.value).toISOString().slice(0, 16) : ''
@@ -1,8 +1,7 @@
'use client'; 'use client';
import { getSubscribeList } from '@/services/admin/subscribe'; import { useSubscribe } from '@/store/subscribe';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { import {
Form, Form,
@@ -81,16 +80,7 @@ export default function CouponForm<T extends Record<string, any>>({
const type = form.watch('type'); const type = form.watch('type');
const { data: subscribe } = useQuery({ const { subscribes } = useSubscribe();
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 9999,
});
return data.data?.list as API.Subscribe[];
},
});
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
@@ -247,9 +237,9 @@ export default function CouponForm<T extends Record<string, any>>({
onChange={(value) => { onChange={(value) => {
form.setValue(field.name, value); form.setValue(field.name, value);
}} }}
options={subscribe?.map((item: API.Subscribe) => ({ options={subscribes?.map((item) => ({
value: item.id, value: item.id!,
label: item.name, label: item.name!,
}))} }))}
/> />
</FormControl> </FormControl>
@@ -307,6 +297,7 @@ export default function CouponForm<T extends Record<string, any>>({
<EnhancedInput <EnhancedInput
placeholder={t('form.countPlaceholder')} placeholder={t('form.countPlaceholder')}
type='number' type='number'
min={0}
step={1} step={1}
{...field} {...field}
onValueChange={(value) => { onValueChange={(value) => {
@@ -328,6 +319,7 @@ export default function CouponForm<T extends Record<string, any>>({
<EnhancedInput <EnhancedInput
placeholder={t('form.userLimitPlaceholder')} placeholder={t('form.userLimitPlaceholder')}
type='number' type='number'
min={0}
step={1} step={1}
{...field} {...field}
onValueChange={(value) => { onValueChange={(value) => {
+4 -14
View File
@@ -9,9 +9,8 @@ import {
getCouponList, getCouponList,
updateCoupon, updateCoupon,
} from '@/services/admin/coupon'; } from '@/services/admin/coupon';
import { getSubscribeList } from '@/services/admin/subscribe'; import { useSubscribe } from '@/store/subscribe';
import { formatDate } from '@/utils/common'; import { formatDate } from '@/utils/common';
import { useQuery } from '@tanstack/react-query';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { Switch } from '@workspace/ui/components/switch'; import { Switch } from '@workspace/ui/components/switch';
@@ -24,16 +23,7 @@ import CouponForm from './coupon-form';
export default function Page() { export default function Page() {
const t = useTranslations('coupon'); const t = useTranslations('coupon');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { data } = useQuery({ const { subscribes } = useSubscribe();
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 9999,
});
return data.data?.list as API.SubscribeGroup[];
},
});
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
return ( return (
<ProTable<API.Coupon, { group_id: number; query: string }> <ProTable<API.Coupon, { group_id: number; query: string }>
@@ -67,8 +57,8 @@ export default function Page() {
{ {
key: 'subscribe', key: 'subscribe',
placeholder: t('subscribe'), placeholder: t('subscribe'),
options: data?.map((item) => ({ options: subscribes?.map((item) => ({
label: item.name, label: item.name!,
value: String(item.id), value: String(item.id),
})), })),
}, },
@@ -2,8 +2,8 @@
import { ProTable } from '@/components/pro-table'; import { ProTable } from '@/components/pro-table';
import { filterServerTrafficLog } from '@/services/admin/log'; import { filterServerTrafficLog } from '@/services/admin/log';
import { filterServerList } from '@/services/admin/server'; import { useServer } from '@/store/server';
import { useQuery } from '@tanstack/react-query'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { formatBytes } from '@workspace/ui/utils'; import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
@@ -13,20 +13,10 @@ import { useSearchParams } from 'next/navigation';
export default function ServerTrafficLogPage() { export default function ServerTrafficLogPage() {
const t = useTranslations('log'); const t = useTranslations('log');
const sp = useSearchParams(); const sp = useSearchParams();
const { getServerName, getServerById } = useServer();
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const { data: servers = [] } = useQuery({
queryKey: ['filterServerListAll'],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 999999999 });
return data?.data?.list || [];
},
});
const getServerName = (id?: number) =>
id ? (servers.find((s) => s.id === id)?.name ?? `Server ${id}`) : 'Unknown';
const initialFilters = { const initialFilters = {
date: sp.get('date') || today, date: sp.get('date') || today,
server_id: sp.get('server_id') ? Number(sp.get('server_id')) : undefined, server_id: sp.get('server_id') ? Number(sp.get('server_id')) : undefined,
@@ -50,11 +40,14 @@ export default function ServerTrafficLogPage() {
{ {
accessorKey: 'server_id', accessorKey: 'server_id',
header: t('column.server'), header: t('column.server'),
cell: ({ row }) => ( cell: ({ row }) => {
<span> return (
{getServerName(row.original.server_id)} ({row.original.server_id}) <div className='flex items-center gap-2'>
</span> <Badge>{row.original.server_id}</Badge>
), <span>{getServerName(row.original.server_id)}</span>
</div>
);
},
}, },
{ {
accessorKey: 'upload', accessorKey: 'upload',
@@ -3,9 +3,8 @@
import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail'; import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table'; import { ProTable } from '@/components/pro-table';
import { filterTrafficLogDetails } from '@/services/admin/log'; import { filterTrafficLogDetails } from '@/services/admin/log';
import { filterServerList } from '@/services/admin/server'; import { useServer } from '@/store/server';
import { formatDate } from '@/utils/common'; import { formatDate } from '@/utils/common';
import { useQuery } from '@tanstack/react-query';
import { formatBytes } from '@workspace/ui/utils'; import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
@@ -13,20 +12,10 @@ import { useSearchParams } from 'next/navigation';
export default function TrafficDetailsPage() { export default function TrafficDetailsPage() {
const t = useTranslations('log'); const t = useTranslations('log');
const sp = useSearchParams(); const sp = useSearchParams();
const { getServerName } = useServer();
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const { data: servers = [] } = useQuery({
queryKey: ['filterServerListAll'],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 999999999 });
return data?.data?.list || [];
},
});
const getServerName = (id?: number) =>
id ? (servers.find((s) => s.id === id)?.name ?? `Server ${id}`) : 'Unknown';
const initialFilters = { const initialFilters = {
date: sp.get('date') || today, date: sp.get('date') || today,
server_id: sp.get('server_id') ? Number(sp.get('server_id')) : undefined, server_id: sp.get('server_id') ? Number(sp.get('server_id')) : undefined,
@@ -366,6 +366,7 @@ export default function EmailBroadcastForm() {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
disabled={form.watch('scope') === 5} // ScopeSkip disabled={form.watch('scope') === 5} // ScopeSkip
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
@@ -384,6 +385,7 @@ export default function EmailBroadcastForm() {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
disabled={form.watch('scope') === 5} // ScopeSkip disabled={form.watch('scope') === 5} // ScopeSkip
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
@@ -425,6 +427,7 @@ export default function EmailBroadcastForm() {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
placeholder={t('leaveEmptyForImmediateSend')} placeholder={t('leaveEmptyForImmediateSend')}
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
@@ -2,9 +2,8 @@
import { Display } from '@/components/display'; import { Display } from '@/components/display';
import { createQuotaTask, queryQuotaTaskPreCount } from '@/services/admin/marketing'; import { createQuotaTask, queryQuotaTaskPreCount } from '@/services/admin/marketing';
import { getSubscribeList } from '@/services/admin/subscribe'; import { useSubscribe } from '@/store/subscribe';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { import {
Form, Form,
@@ -73,17 +72,7 @@ export default function QuotaBroadcastForm() {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
// Get subscribe list const { subscribes } = useSubscribe();
const { data: subscribeList } = useQuery({
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 999999999,
});
return data.data?.list as API.SubscribeItem[];
},
});
// Calculate recipient count // Calculate recipient count
const calculateRecipients = async () => { const calculateRecipients = async () => {
@@ -217,8 +206,7 @@ export default function QuotaBroadcastForm() {
value={field.value || []} value={field.value || []}
onChange={field.onChange} onChange={field.onChange}
placeholder={t('pleaseSelectSubscribers')} placeholder={t('pleaseSelectSubscribers')}
options={ options={subscribes?.map((subscribe) => ({
subscribeList?.map((subscribe) => ({
value: subscribe.id!, value: subscribe.id!,
label: subscribe.name!, label: subscribe.name!,
children: ( children: (
@@ -230,8 +218,7 @@ export default function QuotaBroadcastForm() {
</div> </div>
</div> </div>
), ),
})) || [] }))}
}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -282,6 +269,7 @@ export default function QuotaBroadcastForm() {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
/> />
@@ -299,6 +287,7 @@ export default function QuotaBroadcastForm() {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='datetime-local' type='datetime-local'
step='1'
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
/> />
@@ -3,9 +3,8 @@
import { Display } from '@/components/display'; import { Display } from '@/components/display';
import { ProTable } from '@/components/pro-table'; import { ProTable } from '@/components/pro-table';
import { queryQuotaTaskList } from '@/services/admin/marketing'; import { queryQuotaTaskList } from '@/services/admin/marketing';
import { getSubscribeList } from '@/services/admin/subscribe'; import { useSubscribe } from '@/store/subscribe';
import { formatDate } from '@/utils/common'; import { formatDate } from '@/utils/common';
import { useQuery } from '@tanstack/react-query';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { ScrollArea } from '@workspace/ui/components/scroll-area';
import { import {
@@ -23,21 +22,9 @@ export default function QuotaTaskManager() {
const t = useTranslations('marketing'); const t = useTranslations('marketing');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
// Get subscribe list to show subscription names const { subscribes } = useSubscribe();
const { data: subscribeList } = useQuery({
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 999999999,
});
return data.data?.list as API.SubscribeItem[];
},
});
// Create a map for quick lookup of subscription names
const subscribeMap = const subscribeMap =
subscribeList?.reduce( subscribes?.reduce(
(acc, subscribe) => { (acc, subscribe) => {
acc[subscribe.id!] = subscribe.name!; acc[subscribe.id!] = subscribe.name!;
return acc; return acc;
+96 -71
View File
@@ -1,8 +1,8 @@
'use client'; 'use client';
import { filterServerList, queryNodeTag } from '@/services/admin/server'; import { useNode } from '@/store/node';
import { useServer } from '@/store/server';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { import {
Form, Form,
@@ -36,11 +36,13 @@ export type ProtocolName =
| 'vmess' | 'vmess'
| 'vless' | 'vless'
| 'trojan' | 'trojan'
| 'hysteria2' | 'hysteria'
| 'tuic' | 'tuic'
| 'anytls'; | 'anytls'
| 'naive'
type ServerRow = API.Server; | 'http'
| 'socks'
| 'mieru';
const buildSchema = (t: ReturnType<typeof useTranslations>) => const buildSchema = (t: ReturnType<typeof useTranslations>) =>
z.object({ z.object({
@@ -74,6 +76,20 @@ export default function NodeForm(props: {
const Scheme = useMemo(() => buildSchema(t), [t]); const Scheme = useMemo(() => buildSchema(t), [t]);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [autoFilledFields, setAutoFilledFields] = useState<Set<string>>(new Set());
const addAutoFilledField = (fieldName: string) => {
setAutoFilledFields((prev) => new Set(prev).add(fieldName));
};
const removeAutoFilledField = (fieldName: string) => {
setAutoFilledFields((prev) => {
const newSet = new Set(prev);
newSet.delete(fieldName);
return newSet;
});
};
const form = useForm<NodeFormValues>({ const form = useForm<NodeFormValues>({
resolver: zodResolver(Scheme), resolver: zodResolver(Scheme),
defaultValues: { defaultValues: {
@@ -89,36 +105,12 @@ export default function NodeForm(props: {
const serverId = form.watch('server_id'); const serverId = form.watch('server_id');
const { data } = useQuery({ const { servers, getAvailableProtocols } = useServer();
enabled: open, const { tags } = useNode();
queryKey: ['filterServerListAll'],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 999999999 });
return data?.data?.list || [];
},
});
const servers: ServerRow[] = data as ServerRow[];
const { data: tagsData } = useQuery({ const existingTags: string[] = tags || [];
enabled: open,
queryKey: ['queryNodeTag'],
queryFn: async () => {
const { data } = await queryNodeTag();
return data?.data?.tags || [];
},
});
const existingTags: string[] = tagsData as string[];
const currentServer = useMemo(() => servers?.find((s) => s.id === serverId), [servers, serverId]); const availableProtocols = getAvailableProtocols(serverId);
const availableProtocols = useMemo(() => {
return (currentServer?.protocols || [])
.map((p) => ({
protocol: (p as any).type as ProtocolName,
port: (p as any).port as number | undefined,
}))
.filter((p) => !!p.protocol);
}, [currentServer]);
useEffect(() => { useEffect(() => {
if (initialValues) { if (initialValues) {
@@ -139,60 +131,93 @@ export default function NodeForm(props: {
const id = nextId ?? undefined; const id = nextId ?? undefined;
form.setValue('server_id', id); form.setValue('server_id', id);
const sel = servers.find((s) => s.id === id); if (!id) {
const dirty = form.formState.dirtyFields as Record<string, any>; setAutoFilledFields(new Set());
return;
}
const selectedServer = servers.find((s) => s.id === id);
if (!selectedServer) return;
const currentValues = form.getValues(); const currentValues = form.getValues();
const fieldsToFill: string[] = [];
if (!dirty.name) { if (!currentValues.name || autoFilledFields.has('name')) {
form.setValue('name', (sel?.name as string) || '', { shouldDirty: false }); form.setValue('name', selectedServer.name as string, { shouldDirty: false });
fieldsToFill.push('name');
} }
if ( if (!currentValues.address || autoFilledFields.has('address')) {
!dirty.address && form.setValue('address', selectedServer.address as string, { shouldDirty: false });
(!currentValues.address || currentValues.address === (sel?.address as string)) fieldsToFill.push('address');
) {
form.setValue('address', (sel?.address as string) || '', { shouldDirty: false });
} }
const allowed = (sel?.protocols || []) const protocols = getAvailableProtocols(id);
.map((p) => (p as any).type as ProtocolName) const firstProtocol = protocols[0];
.filter(Boolean);
const currentProtocol = form.getValues('protocol') as ProtocolName; if (firstProtocol && (!currentValues.protocol || autoFilledFields.has('protocol'))) {
form.setValue('protocol', firstProtocol.protocol, { shouldDirty: false });
fieldsToFill.push('protocol');
if (!allowed.includes(currentProtocol)) { if (!currentValues.port || currentValues.port === 0 || autoFilledFields.has('port')) {
const firstProtocol = allowed[0] || ''; const port = firstProtocol.port || 0;
form.setValue('protocol', firstProtocol as any); form.setValue('port', port, { shouldDirty: false });
fieldsToFill.push('port');
if (firstProtocol) {
handleProtocolChange(firstProtocol);
} }
} }
setAutoFilledFields(new Set(fieldsToFill));
} }
const handleManualFieldChange = (fieldName: keyof NodeFormValues, value: any) => {
form.setValue(fieldName, value);
removeAutoFilledField(fieldName);
};
function handleProtocolChange(nextProto?: ProtocolName | null) { function handleProtocolChange(nextProto?: ProtocolName | null) {
const p = (nextProto || '') as ProtocolName | ''; const protocol = (nextProto || '') as ProtocolName | '';
form.setValue('protocol', p); form.setValue('protocol', protocol);
if (!p || !currentServer) return;
const dirty = form.formState.dirtyFields as Record<string, any>; if (!protocol || !serverId) {
const currentValues = form.getValues(); removeAutoFilledField('protocol');
return;
if (!dirty.port) {
const hit = (currentServer.protocols as any[]).find((x) => (x as any).type === p);
const port = (hit as any)?.port as number | undefined;
const newPort = typeof port === 'number' && port > 0 ? port : 0;
if (!currentValues.port || currentValues.port === 0 || currentValues.port === newPort) {
form.setValue('port', newPort, { shouldDirty: false });
} }
const currentValues = form.getValues();
const isPortAutoFilled = autoFilledFields.has('port');
removeAutoFilledField('protocol');
if (!currentValues.port || currentValues.port === 0 || isPortAutoFilled) {
const protocolData = availableProtocols.find((p) => p.protocol === protocol);
if (protocolData) {
const port = protocolData.port || 0;
form.setValue('port', port, { shouldDirty: false });
addAutoFilledField('port');
}
}
}
async function handleSubmit(values: NodeFormValues) {
const result = await onSubmit(values);
if (result) {
setOpen(false);
setAutoFilledFields(new Set());
} }
} }
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button onClick={() => form.reset()}>{trigger}</Button> <Button
onClick={() => {
form.reset();
setAutoFilledFields(new Set());
}}
>
{trigger}
</Button>
</SheetTrigger> </SheetTrigger>
<SheetContent className='w-[560px] max-w-full'> <SheetContent className='w-[560px] max-w-full'>
@@ -253,7 +278,7 @@ export default function NodeForm(props: {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
{...field} {...field}
onValueChange={(v) => form.setValue(field.name, v as string)} onValueChange={(v) => handleManualFieldChange('name', v as string)}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -270,7 +295,7 @@ export default function NodeForm(props: {
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
{...field} {...field}
onValueChange={(v) => form.setValue(field.name, v as string)} onValueChange={(v) => handleManualFieldChange('address', v as string)}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -291,7 +316,7 @@ export default function NodeForm(props: {
min={1} min={1}
max={65535} max={65535}
placeholder='1-65535' placeholder='1-65535'
onValueChange={(v) => form.setValue(field.name, Number(v))} onValueChange={(v) => handleManualFieldChange('port', Number(v))}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -327,7 +352,7 @@ export default function NodeForm(props: {
</Button> </Button>
<Button <Button
disabled={loading} disabled={loading}
onClick={form.handleSubmit(onSubmit, (errors) => { onClick={form.handleSubmit(handleSubmit, (errors) => {
const key = Object.keys(errors)[0] as keyof typeof errors; const key = Object.keys(errors)[0] as keyof typeof errors;
if (key) toast.error(String(errors[key]?.message)); if (key) toast.error(String(errors[key]?.message));
return false; return false;
+33 -57
View File
@@ -5,12 +5,12 @@ import {
createNode, createNode,
deleteNode, deleteNode,
filterNodeList, filterNodeList,
filterServerList,
resetSortWithNode, resetSortWithNode,
toggleNodeStatus, toggleNodeStatus,
updateNode, updateNode,
} from '@/services/admin/server'; } from '@/services/admin/server';
import { useQuery } from '@tanstack/react-query'; import { useNode } from '@/store/node';
import { useServer } from '@/store/server';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { Switch } from '@workspace/ui/components/switch'; import { Switch } from '@workspace/ui/components/switch';
@@ -25,24 +25,9 @@ export default function NodesPage() {
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { data: servers = [] } = useQuery({ // Use our zustand store for server data
queryKey: ['filterServerListAll'], const { getServerName, getServerAddress, getProtocolPort } = useServer();
queryFn: async () => { const { fetchNodes, fetchTags } = useNode();
const { data } = await filterServerList({ page: 1, size: 999999999 });
return data?.data?.list || [];
},
});
const getServerName = (id?: number) =>
id ? (servers.find((s) => s.id === id)?.name ?? `#${id}`) : '—';
const getServerOriginAddr = (id?: number) =>
id ? (servers.find((s) => s.id === id)?.address ?? '—') : '—';
const getProtocolOriginPort = (id?: number, proto?: string) => {
if (!id || !proto) return '—';
const hit = servers.find((s) => s.id === id)?.protocols?.find((p) => (p as any).type === proto);
const p = (hit as any)?.port as number | undefined;
return typeof p === 'number' ? String(p) : '—';
};
return ( return (
<ProTable<API.Node, { search: string }> <ProTable<API.Node, { search: string }>
@@ -69,6 +54,8 @@ export default function NodesPage() {
await createNode(body); await createNode(body);
toast.success(t('created')); toast.success(t('created'));
ref.current?.refresh(); ref.current?.refresh();
fetchNodes();
fetchTags();
setLoading(false); setLoading(false);
return true; return true;
} catch (e) { } catch (e) {
@@ -90,6 +77,8 @@ export default function NodesPage() {
await toggleNodeStatus({ id: row.original.id, enable: v }); await toggleNodeStatus({ id: row.original.id, enable: v });
toast.success(v ? t('enabled_on') : t('enabled_off')); toast.success(v ? t('enabled_on') : t('enabled_off'));
ref.current?.refresh(); ref.current?.refresh();
fetchNodes();
fetchTags();
}} }}
/> />
), ),
@@ -99,24 +88,20 @@ export default function NodesPage() {
{ {
id: 'address_port', id: 'address_port',
header: `${t('address')}:${t('port')}`, header: `${t('address')}:${t('port')}`,
cell: ({ row }) => (row.original.address || '—') + ':' + (row.original.port ?? '—'), cell: ({ row }) => `${row.original.address || '—'}:${row.original.port || '—'}`,
}, },
{ {
id: 'server_id', id: 'server_id',
header: t('server'), header: t('server'),
cell: ({ row }) => ( cell: ({ row }) =>
<div className='flex flex-wrap gap-2'> `${getServerName(row.original.server_id)}:${getServerAddress(row.original.server_id)}`,
<Badge variant='outline'> },
{getServerName(row.original.server_id)} ·{' '} {
{getServerOriginAddr(row.original.server_id)} id: 'protocol',
</Badge> header: ` ${t('protocol')}:${t('port')}`,
<Badge variant='outline'> cell: ({ row }) =>
{row.original.protocol || '—'} ·{' '} `${row.original.protocol}:${getProtocolPort(row.original.server_id, row.original.protocol)}`,
{getProtocolOriginPort(row.original.server_id, row.original.protocol)}
</Badge>
</div>
),
}, },
{ {
accessorKey: 'tags', accessorKey: 'tags',
@@ -152,30 +137,19 @@ export default function NodesPage() {
trigger={t('edit')} trigger={t('edit')}
title={t('drawerEditTitle')} title={t('drawerEditTitle')}
loading={loading} loading={loading}
initialValues={{ initialValues={row}
name: row.name,
server_id: row.server_id,
protocol: row.protocol as any,
address: row.address as any,
port: row.port as any,
tags: (row.tags as any) || [],
}}
onSubmit={async (values) => { onSubmit={async (values) => {
setLoading(true); setLoading(true);
try { try {
const body: API.UpdateNodeRequest = { const body: API.UpdateNodeRequest = {
id: row.id, ...row,
name: values.name, ...values,
server_id: Number(values.server_id!),
protocol: values.protocol,
address: values.address,
port: Number(values.port!),
tags: values.tags || [],
enabled: row.enabled,
} as any; } as any;
await updateNode(body); await updateNode(body);
toast.success(t('updated')); toast.success(t('updated'));
ref.current?.refresh(); ref.current?.refresh();
fetchNodes();
fetchTags();
setLoading(false); setLoading(false);
return true; return true;
} catch (e) { } catch (e) {
@@ -193,6 +167,8 @@ export default function NodesPage() {
await deleteNode({ id: row.id } as any); await deleteNode({ id: row.id } as any);
toast.success(t('deleted')); toast.success(t('deleted'));
ref.current?.refresh(); ref.current?.refresh();
fetchNodes();
fetchTags();
}} }}
cancelText={t('cancel')} cancelText={t('cancel')}
confirmText={t('confirm')} confirmText={t('confirm')}
@@ -201,18 +177,15 @@ export default function NodesPage() {
key='copy' key='copy'
variant='outline' variant='outline'
onClick={async () => { onClick={async () => {
const { id, enabled, created_at, updated_at, ...rest } = row as any; const { id, enabled, created_at, updated_at, sort, ...rest } = row as any;
await createNode({ await createNode({
name: rest.name, ...rest,
server_id: rest.server_id,
protocol: rest.protocol,
address: rest.address,
port: rest.port,
tags: rest.tags || [],
enabled: false, enabled: false,
} as any); });
toast.success(t('copied')); toast.success(t('copied'));
ref.current?.refresh(); ref.current?.refresh();
fetchNodes();
fetchTags();
}} }}
> >
{t('copy')} {t('copy')}
@@ -229,6 +202,8 @@ export default function NodesPage() {
await Promise.all(rows.map((r) => deleteNode({ id: r.id } as any))); await Promise.all(rows.map((r) => deleteNode({ id: r.id } as any)));
toast.success(t('deleted')); toast.success(t('deleted'));
ref.current?.refresh(); ref.current?.refresh();
fetchNodes();
fetchTags();
}} }}
cancelText={t('cancel')} cancelText={t('cancel')}
confirmText={t('confirm')} confirmText={t('confirm')}
@@ -262,6 +237,7 @@ export default function NodesPage() {
sort: item.sort, sort: item.sort,
})) as API.SortItem[], })) as API.SortItem[],
}); });
toast.success(t('sorted_success'));
} }
return updatedItems; return updatedItems;
}} }}
+8 -17
View File
@@ -1,6 +1,5 @@
'use client'; 'use client';
import { useQuery } from '@tanstack/react-query';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useRef } from 'react'; import { useRef } from 'react';
@@ -8,7 +7,7 @@ import { useRef } from 'react';
import { Display } from '@/components/display'; import { Display } from '@/components/display';
import { ProTable, ProTableActions } from '@/components/pro-table'; import { ProTable, ProTableActions } from '@/components/pro-table';
import { getOrderList, updateOrderStatus } from '@/services/admin/order'; import { getOrderList, updateOrderStatus } from '@/services/admin/order';
import { getSubscribeList } from '@/services/admin/subscribe'; import { useSubscribe } from '@/store/subscribe';
import { formatDate } from '@/utils/common'; import { formatDate } from '@/utils/common';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
@@ -32,16 +31,7 @@ export default function Page() {
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
const { data: subscribeList } = useQuery({ const { subscribes, getSubscribeName } = useSubscribe();
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 999999999,
});
return data.data?.list as API.SubscribeGroup[];
},
});
const initialFilters = { const initialFilters = {
search: sp.get('search') || undefined, search: sp.get('search') || undefined,
@@ -68,9 +58,10 @@ export default function Page() {
accessorKey: 'subscribe_id', accessorKey: 'subscribe_id',
header: t('subscribe'), header: t('subscribe'),
cell: ({ row }) => { cell: ({ row }) => {
const name = subscribeList?.find( if (row.original.type === 4) {
(item) => item.id === row.getValue('subscribe_id'), return t(`type.${row.getValue('type')}`);
)?.name; }
const name = getSubscribeName(row.getValue('subscribe_id'));
const quantity = row.original.quantity; const quantity = row.original.quantity;
return name ? `${name} × ${quantity}` : ''; return name ? `${name} × ${quantity}` : '';
}, },
@@ -186,8 +177,8 @@ export default function Page() {
{ {
key: 'subscribe_id', key: 'subscribe_id',
placeholder: `${t('subscribe')}`, placeholder: `${t('subscribe')}`,
options: subscribeList?.map((item) => ({ options: subscribes?.map((item) => ({
label: item.name, label: item.name!,
value: String(item.id), value: String(item.id),
})), })),
}, },
@@ -1,9 +1,5 @@
import { getTranslations } from 'next-intl/server';
import SubscribeTable from './subscribe-table'; import SubscribeTable from './subscribe-table';
export default async function Page() { export default async function Page() {
const t = await getTranslations('product');
return <SubscribeTable />; return <SubscribeTable />;
} }
@@ -1,8 +1,8 @@
'use client'; 'use client';
import { filterNodeList, queryNodeTag } from '@/services/admin/server'; import useGlobalStore from '@/config/use-global';
import { useNode } from '@/store/node';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@@ -79,6 +79,9 @@ export default function SubscribeForm<T extends Record<string, any>>({
trigger, trigger,
title, title,
}: Readonly<SubscribeFormProps<T>>) { }: Readonly<SubscribeFormProps<T>>) {
const { common } = useGlobalStore();
const { currency } = common;
const t = useTranslations('product'); const t = useTranslations('product');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null); const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -214,7 +217,11 @@ export default function SubscribeForm<T extends Record<string, any>>({
form?.reset( form?.reset(
assign(defaultValues, shake(initialValues, (value) => value === null) as Record<string, any>), assign(defaultValues, shake(initialValues, (value) => value === null) as Record<string, any>),
); );
}, [form, initialValues]); const discount = form.getValues('discount') || [];
if (discount.length > 0) {
debouncedCalculateDiscount(discount, 'discount');
}
}, [form, initialValues, open]);
useEffect(() => { useEffect(() => {
return () => { return () => {
@@ -229,35 +236,9 @@ export default function SubscribeForm<T extends Record<string, any>>({
if (bool) setOpen(false); if (bool) setOpen(false);
} }
const { data: nodes } = useQuery({ const { getAllAvailableTags, getNodesByTag, getNodesWithoutTags } = useNode();
queryKey: ['filterNodeListAll'],
queryFn: async () => {
const { data } = await filterNodeList({ page: 1, size: 999999999 });
return (data.data?.list || []) as API.Node[];
},
});
const { data: allTagsData } = useQuery({ const tagGroups = getAllAvailableTags();
queryKey: ['queryNodeTag'],
queryFn: async () => {
const { data } = await queryNodeTag();
return data?.data?.tags || [];
},
});
const nodeExtractedTags = Array.from(
new Set(
((nodes as API.Node[]) || [])
.flatMap((n) => (Array.isArray(n.tags) ? n.tags : []))
.filter(Boolean),
),
) as string[];
const allAvailableTags = (allTagsData as string[]) || [];
const tagGroups = Array.from(new Set([...allAvailableTags, ...nodeExtractedTags])).filter(
Boolean,
);
const unit_time = form.watch('unit_time'); const unit_time = form.watch('unit_time');
@@ -657,9 +638,9 @@ export default function SubscribeForm<T extends Record<string, any>>({
{ {
name: 'discount', name: 'discount',
type: 'number', type: 'number',
min: 0.01, min: 1,
max: 100, max: 100,
step: 0.01, step: 1,
placeholder: t('form.discountPercent'), placeholder: t('form.discountPercent'),
suffix: '%', suffix: '%',
}, },
@@ -669,6 +650,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
type: 'number', type: 'number',
min: 0, min: 0,
step: 0.01, step: 0.01,
prefix: currency.currency_symbol,
formatInput: (value) => unitConversion('centsToDollars', value), formatInput: (value) => unitConversion('centsToDollars', value),
formatOutput: (value) => formatOutput: (value) =>
unitConversion('dollarsToCents', value).toString(), unitConversion('dollarsToCents', value).toString(),
@@ -806,10 +788,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
{tagGroups.map((tag) => { {tagGroups.map((tag) => {
const value = field.value || []; const value = field.value || [];
const tagId = tag; const tagId = tag;
const nodesWithTag = const nodesWithTag = getNodesByTag(tag);
(nodes as API.Node[])?.filter((n) =>
(n.tags || []).includes(tag),
) || [];
return ( return (
<AccordionItem key={tag} value={String(tag)}> <AccordionItem key={tag} value={String(tag)}>
@@ -836,9 +815,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
</AccordionTrigger> </AccordionTrigger>
<AccordionContent> <AccordionContent>
<ul className='space-y-1'> <ul className='space-y-1'>
{(nodes as API.Node[]) {getNodesByTag(tag).map((node) => (
?.filter((n) => (n.tags || []).includes(tag))
?.map((node) => (
<li <li
key={node.id} key={node.id}
className='flex items-center justify-between gap-3' className='flex items-center justify-between gap-3'
@@ -872,9 +849,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
<FormLabel>{t('form.node')}</FormLabel> <FormLabel>{t('form.node')}</FormLabel>
<FormControl> <FormControl>
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
{(nodes as API.Node[]) {getNodesWithoutTags().map((item) => {
?.filter((item) => (item.tags || []).length === 0)
?.map((item) => {
const value = field.value || []; const value = field.value || [];
return ( return (
@@ -10,6 +10,7 @@ import {
subscribeSort, subscribeSort,
updateSubscribe, updateSubscribe,
} from '@/services/admin/subscribe'; } from '@/services/admin/subscribe';
import { useSubscribe } from '@/store/subscribe';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { Switch } from '@workspace/ui/components/switch'; import { Switch } from '@workspace/ui/components/switch';
@@ -23,6 +24,7 @@ export default function SubscribeTable() {
const t = useTranslations('product'); const t = useTranslations('product');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
const { fetchSubscribes } = useSubscribe();
return ( return (
<ProTable<API.SubscribeItem, { group_id: number; query: string }> <ProTable<API.SubscribeItem, { group_id: number; query: string }>
action={ref} action={ref}
@@ -42,6 +44,7 @@ export default function SubscribeTable() {
}); });
toast.success(t('createSuccess')); toast.success(t('createSuccess'));
ref.current?.refresh(); ref.current?.refresh();
fetchSubscribes();
setLoading(false); setLoading(false);
return true; return true;
@@ -83,6 +86,7 @@ export default function SubscribeTable() {
show: checked, show: checked,
} as API.UpdateSubscribeRequest); } as API.UpdateSubscribeRequest);
ref.current?.refresh(); ref.current?.refresh();
fetchSubscribes();
}} }}
/> />
); );
@@ -101,6 +105,7 @@ export default function SubscribeTable() {
sell: checked, sell: checked,
} as API.UpdateSubscribeRequest); } as API.UpdateSubscribeRequest);
ref.current?.refresh(); ref.current?.refresh();
fetchSubscribes();
}} }}
/> />
); );
@@ -186,6 +191,7 @@ export default function SubscribeTable() {
} as API.UpdateSubscribeRequest); } as API.UpdateSubscribeRequest);
toast.success(t('updateSuccess')); toast.success(t('updateSuccess'));
ref.current?.refresh(); ref.current?.refresh();
fetchSubscribes();
setLoading(false); setLoading(false);
return true; return true;
} catch (error) { } catch (error) {
@@ -206,6 +212,7 @@ export default function SubscribeTable() {
}); });
toast.success(t('deleteSuccess')); toast.success(t('deleteSuccess'));
ref.current?.refresh(); ref.current?.refresh();
fetchSubscribes();
}} }}
cancelText={t('cancel')} cancelText={t('cancel')}
confirmText={t('confirm')} confirmText={t('confirm')}
@@ -224,6 +231,7 @@ export default function SubscribeTable() {
} as API.CreateSubscribeRequest); } as API.CreateSubscribeRequest);
toast.success(t('copySuccess')); toast.success(t('copySuccess'));
ref.current?.refresh(); ref.current?.refresh();
fetchSubscribes();
setLoading(false); setLoading(false);
return true; return true;
} catch (error) { } catch (error) {
@@ -248,6 +256,7 @@ export default function SubscribeTable() {
toast.success(t('deleteSuccess')); toast.success(t('deleteSuccess'));
ref.current?.reset(); ref.current?.reset();
fetchSubscribes();
}} }}
cancelText={t('cancel')} cancelText={t('cancel')}
confirmText={t('confirm')} confirmText={t('confirm')}
@@ -0,0 +1,121 @@
'use client';
import { getNodeMultiplier, setNodeMultiplier } from '@/services/admin/system';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import { Card, CardContent } from '@workspace/ui/components/card';
import { ScrollArea } from '@workspace/ui/components/scroll-area';
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@workspace/ui/components/sheet';
import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs';
import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { toast } from 'sonner';
export default function DynamicMultiplier() {
const t = useTranslations('servers');
const [open, setOpen] = useState(false);
const [timeSlots, setTimeSlots] = useState<API.TimePeriod[]>([]);
const { data: periodsResp, refetch: refetchPeriods } = useQuery({
queryKey: ['getNodeMultiplier'],
queryFn: async () => {
const { data } = await getNodeMultiplier();
return (data.data?.periods || []) as API.TimePeriod[];
},
enabled: open,
});
useEffect(() => {
if (periodsResp) {
setTimeSlots(periodsResp);
}
}, [periodsResp]);
async function savePeriods() {
await setNodeMultiplier({ periods: timeSlots });
await refetchPeriods();
toast.success(t('server_config.saveSuccess'));
setOpen(false);
}
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Card>
<CardContent className='p-4'>
<div className='flex cursor-pointer items-center justify-between'>
<div className='flex items-center gap-3'>
<div className='bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg'>
<Icon icon='mdi:clock-time-eight' className='text-primary h-5 w-5' />
</div>
<div className='flex-1'>
<p className='font-medium'>{t('server_config.dynamic_multiplier')}</p>
<p className='text-muted-foreground truncate text-sm'>
{t('server_config.dynamic_multiplier_desc')}
</p>
</div>
</div>
<Icon icon='mdi:chevron-right' className='size-6' />
</div>
</CardContent>
</Card>
</SheetTrigger>
<SheetContent className='w-[600px] max-w-full md:max-w-screen-md'>
<SheetHeader>
<SheetTitle>{t('server_config.dynamic_multiplier')}</SheetTitle>
<SheetDescription>{t('server_config.dynamic_multiplier_desc')}</SheetDescription>
</SheetHeader>
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-60px-env(safe-area-inset-top))] px-6'>
<div className='space-y-4 pt-4'>
<ArrayInput<API.TimePeriod>
fields={[
{
name: 'start_time',
prefix: t('server_config.fields.start_time'),
type: 'time',
step: '1',
},
{
name: 'end_time',
prefix: t('server_config.fields.end_time'),
type: 'time',
step: '1',
},
{
name: 'multiplier',
prefix: t('server_config.fields.multiplier'),
type: 'number',
placeholder: '0',
},
]}
value={timeSlots}
onChange={setTimeSlots}
/>
</div>
</ScrollArea>
<SheetFooter className='flex-row justify-between pt-3'>
<Button variant='outline' onClick={() => setTimeSlots(periodsResp || [])}>
{t('server_config.fields.reset')}
</Button>
<div className='flex gap-2'>
<Button variant='outline' onClick={() => setOpen(false)}>
{t('actions.cancel')}
</Button>
<Button onClick={savePeriods}>{t('actions.save')}</Button>
</div>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
@@ -1,928 +0,0 @@
import { z } from 'zod';
export const protocols = [
'shadowsocks',
'vmess',
'vless',
'trojan',
'hysteria2',
'tuic',
'anytls',
'socks',
'naive',
'http',
'meru',
] as const;
export type FieldConfig = {
name: string;
type: 'input' | 'select' | 'switch' | 'number' | 'textarea';
label: string;
placeholder?: string | ((t: (key: string) => string, protocol: any) => string);
options?: readonly string[];
defaultValue?: any;
min?: number;
max?: number;
step?: number;
suffix?: string;
password?: number;
condition?: (protocol: any, values: any) => boolean;
group?: 'basic' | 'transport' | 'security' | 'reality' | 'plugin';
gridSpan?: 1 | 2;
};
// Global label map for display; fallback to raw value if missing
export const LABELS = {
// transport
'tcp': 'TCP',
'udp': 'UDP',
'websocket': 'WebSocket',
'grpc': 'gRPC',
'mkcp': 'mKCP',
'httpupgrade': 'HTTP Upgrade',
'xhttp': 'XHTTP',
// security
'none': 'NONE',
'tls': 'TLS',
'reality': 'Reality',
// fingerprint
'chrome': 'Chrome',
'firefox': 'Firefox',
'safari': 'Safari',
'ios': 'IOS',
'android': 'Android',
'edge': 'edge',
'360': '360',
'qq': 'QQ',
// multiplex
'off': 'Off',
'low': 'Low',
'middle': 'Middle',
'high': 'High',
// ss plugins
'v2ray-plugin': 'V2Ray Plugin',
'simple-obfs': 'Simple Obfs',
} as const;
// Flat arrays for enum-like sets
export const SS_CIPHERS = [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305',
] as const;
export const SS_PLUGINS = ['none', 'simple-obfs', 'v2ray-plugin'] as const;
export const TRANSPORTS = {
vmess: ['tcp', 'websocket', 'grpc'] as const,
vless: ['tcp', 'websocket', 'grpc', 'mkcp', 'httpupgrade', 'xhttp'] as const,
trojan: ['tcp', 'websocket', 'grpc'] as const,
meru: ['tcp', 'udp'] as const,
} as const;
export const SECURITY = {
vmess: ['none', 'tls'] as const,
vless: ['none', 'tls', 'reality'] as const,
trojan: ['tls'] as const,
hysteria2: ['tls'] as const,
tuic: ['tls'] as const,
anytls: ['tls'] as const,
naive: ['none', 'tls'] as const,
http: ['none', 'tls'] as const,
} as const;
export const FLOWS = {
vless: ['none', 'xtls-rprx-direct', 'xtls-rprx-splice', 'xtls-rprx-vision'] as const,
} as const;
export const TUIC_UDP_RELAY_MODES = ['native', 'quic'] as const;
export const TUIC_CONGESTION = ['bbr', 'cubic', 'new_reno'] as const;
export const FINGERPRINTS = [
'chrome',
'firefox',
'safari',
'ios',
'android',
'edge',
'360',
'qq',
] as const;
export const multiplexLevels = ['off', 'low', 'middle', 'high'] as const;
export function getLabel(value: string): string {
const label = (LABELS as Record<string, string>)[value];
return label ?? value.toUpperCase();
}
const nullableString = z.string().nullish();
const nullableBool = z.boolean().nullish();
const nullablePort = z.number().int().min(0).max(65535).nullish();
const ss = z.object({
type: z.literal('shadowsocks'),
host: nullableString,
port: nullablePort,
cipher: z.enum(SS_CIPHERS as any).nullish(),
server_key: nullableString,
plugin: z.enum(SS_PLUGINS as any).nullish(),
plugin_options: nullableString,
});
const vmess = z.object({
type: z.literal('vmess'),
host: nullableString,
port: nullablePort,
transport: z.enum(TRANSPORTS.vmess as any).nullish(),
security: z.enum(SECURITY.vmess as any).nullish(),
path: nullableString,
service_name: nullableString,
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
});
const vless = z.object({
type: z.literal('vless'),
host: nullableString,
port: nullablePort,
transport: z.enum(TRANSPORTS.vless as any).nullish(),
security: z.enum(SECURITY.vless as any).nullish(),
path: nullableString,
service_name: nullableString,
flow: z.enum(FLOWS.vless as any).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
reality_server_addr: nullableString,
reality_server_port: nullablePort,
reality_private_key: nullableString,
reality_public_key: nullableString,
reality_short_id: nullableString,
});
const trojan = z.object({
type: z.literal('trojan'),
host: nullableString,
port: nullablePort,
transport: z.enum(TRANSPORTS.trojan as any).nullish(),
security: z.enum(SECURITY.trojan as any).nullish(),
path: nullableString,
service_name: nullableString,
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
});
const hysteria2 = z.object({
type: z.literal('hysteria2'),
hop_ports: nullableString,
hop_interval: z.number().nullish(),
obfs_password: nullableString,
port: nullablePort,
security: z.enum(SECURITY.hysteria2 as any).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
up_mbps: z.number().nullish(),
down_mbps: z.number().nullish(),
});
const tuic = z.object({
type: z.literal('tuic'),
host: nullableString,
port: nullablePort,
disable_sni: z.boolean().nullish(),
reduce_rtt: z.boolean().nullish(),
udp_relay_mode: z.enum(TUIC_UDP_RELAY_MODES as any).nullish(),
congestion_controller: z.enum(TUIC_CONGESTION as any).nullish(),
security: z.enum(SECURITY.tuic as any).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
});
const anytls = z.object({
type: z.literal('anytls'),
port: nullablePort,
security: z.enum(SECURITY.anytls as any).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
padding_scheme: nullableString,
});
const socks = z.object({
type: z.literal('socks'),
port: nullablePort,
});
const naive = z.object({
type: z.literal('naive'),
port: nullablePort,
security: z.enum(SECURITY.naive as any).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
});
const http = z.object({
type: z.literal('http'),
port: nullablePort,
security: z.enum(SECURITY.http as any).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
});
const meru = z.object({
type: z.literal('meru'),
port: nullablePort,
multiplex: z.enum(multiplexLevels).nullish(),
transport: z.enum(TRANSPORTS.meru as any).nullish(),
});
export const protocolApiScheme = z.discriminatedUnion('type', [
ss,
vmess,
vless,
trojan,
hysteria2,
tuic,
anytls,
socks,
naive,
http,
meru,
]);
export const formSchema = z.object({
name: z.string().min(1),
address: z.string().min(1),
country: z.string().optional(),
city: z.string().optional(),
ratio: z.number().default(1),
protocols: z.array(protocolApiScheme),
});
export type ServerFormValues = z.infer<typeof formSchema>;
export type ProtocolType = (typeof protocols)[number];
export function getProtocolDefaultConfig(proto: ProtocolType) {
switch (proto) {
case 'shadowsocks':
return {
type: 'shadowsocks',
port: null,
cipher: 'chacha20-ietf-poly1305',
server_key: null,
plugin: 'none',
plugin_opts: null,
} as any;
case 'vmess':
return { type: 'vmess', port: null, transport: 'tcp', security: 'none' } as any;
case 'vless':
return { type: 'vless', port: null, transport: 'tcp', security: 'none', flow: 'none' } as any;
case 'trojan':
return { type: 'trojan', port: null, transport: 'tcp', security: 'tls' } as any;
case 'hysteria2':
return {
type: 'hysteria2',
port: null,
hop_ports: null,
hop_interval: null,
obfs_password: null,
security: 'tls',
up_mbps: null,
down_mbps: null,
} as any;
case 'tuic':
return {
type: 'tuic',
port: null,
disable_sni: false,
reduce_rtt: false,
udp_relay_mode: 'native',
congestion_controller: 'bbr',
security: 'tls',
sni: null,
allow_insecure: false,
fingerprint: 'chrome',
} as any;
case 'socks':
return {
type: 'socks',
port: null,
} as any;
case 'naive':
return {
type: 'naive',
port: null,
security: 'none',
} as any;
case 'http':
return {
type: 'http',
port: null,
security: 'none',
} as any;
case 'meru':
return {
type: 'meru',
port: null,
multiplex: 'off',
transport: 'tcp',
} as any;
case 'anytls':
return {
type: 'anytls',
port: null,
security: 'tls',
padding_scheme: null,
sni: null,
allow_insecure: false,
fingerprint: 'chrome',
} as any;
default:
return {} as any;
}
}
export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
shadowsocks: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'cipher',
type: 'select',
label: 'encryption_method',
options: SS_CIPHERS,
defaultValue: 'chacha20-ietf-poly1305',
group: 'basic',
},
{
name: 'server_key',
type: 'input',
label: 'server_key',
password: 32,
group: 'basic',
condition: (p) =>
[
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305',
].includes(p.cipher),
},
{
name: 'plugin',
type: 'select',
label: 'plugin',
options: SS_PLUGINS,
defaultValue: 'none',
group: 'plugin',
},
{
name: 'plugin_opts',
type: 'textarea',
label: 'plugin_opts',
placeholder: (t: (key: string) => string, p: any) => {
switch (p.plugin) {
case 'simple-obfs':
return 'obfs=http;obfs-host=www.bing.com;path=/';
case 'v2ray-plugin':
return 'WebSocket: mode=websocket;host=mydomain.me;path=/;tls=true\n\nQUIC: mode=quic;host=mydomain.me';
default:
return 'key=value;key2=value2';
}
},
group: 'plugin',
condition: (p) => ['simple-obfs', 'v2ray-plugin'].includes(p.plugin),
},
],
vmess: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'transport',
type: 'select',
label: 'transport',
options: TRANSPORTS.vmess,
defaultValue: 'tcp',
group: 'transport',
},
{
name: 'security',
type: 'select',
label: 'security',
options: SECURITY.vmess,
defaultValue: 'none',
group: 'security',
},
{
name: 'host',
type: 'input',
label: 'host',
group: 'transport',
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
},
{
name: 'path',
type: 'input',
label: 'path',
group: 'transport',
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
},
{
name: 'service_name',
type: 'input',
label: 'service_name',
group: 'transport',
condition: (p) => p.transport === 'grpc',
},
{
name: 'sni',
type: 'input',
label: 'security_sni',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'allow_insecure',
type: 'switch',
label: 'security_allow_insecure',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
condition: (p) => p.security !== 'none',
},
],
vless: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'flow',
type: 'select',
label: 'flow',
options: FLOWS.vless,
defaultValue: 'none',
group: 'basic',
},
{
name: 'transport',
type: 'select',
label: 'transport',
options: TRANSPORTS.vless,
defaultValue: 'tcp',
group: 'transport',
},
{
name: 'security',
type: 'select',
label: 'security',
options: SECURITY.vless,
defaultValue: 'none',
group: 'security',
},
{
name: 'host',
type: 'input',
label: 'host',
group: 'transport',
condition: (p) => ['websocket', 'mkcp', 'httpupgrade', 'xhttp'].includes(p.transport),
},
{
name: 'path',
type: 'input',
label: 'path',
group: 'transport',
condition: (p) => ['websocket', 'mkcp', 'httpupgrade', 'xhttp'].includes(p.transport),
},
{
name: 'service_name',
type: 'input',
label: 'service_name',
group: 'transport',
condition: (p) => p.transport === 'grpc',
},
{
name: 'sni',
type: 'input',
label: 'security_sni',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'allow_insecure',
type: 'switch',
label: 'security_allow_insecure',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'reality_server_addr',
type: 'input',
label: 'security_server_address',
placeholder: (t) => t('security_server_address_placeholder'),
group: 'reality',
condition: (p) => p.security === 'reality',
},
{
name: 'reality_server_port',
type: 'number',
label: 'security_server_port',
min: 1,
max: 65535,
placeholder: '1-65535',
group: 'reality',
condition: (p) => p.security === 'reality',
},
{
name: 'reality_private_key',
type: 'input',
label: 'security_private_key',
placeholder: (t) => t('security_private_key_placeholder'),
group: 'reality',
condition: (p) => p.security === 'reality',
},
{
name: 'reality_public_key',
type: 'input',
label: 'security_public_key',
placeholder: (t) => t('security_public_key_placeholder'),
group: 'reality',
condition: (p) => p.security === 'reality',
},
{
name: 'reality_short_id',
type: 'input',
label: 'security_short_id',
group: 'reality',
condition: (p) => p.security === 'reality',
},
],
trojan: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'transport',
type: 'select',
label: 'transport',
options: TRANSPORTS.trojan,
defaultValue: 'tcp',
group: 'transport',
},
{
name: 'security',
type: 'select',
label: 'security',
options: SECURITY.trojan,
defaultValue: 'tls',
group: 'security',
},
{
name: 'host',
type: 'input',
label: 'host',
group: 'transport',
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
},
{
name: 'path',
type: 'input',
label: 'path',
group: 'transport',
condition: (p) => ['websocket', 'xhttp', 'httpupgrade'].includes(p.transport),
},
{
name: 'service_name',
type: 'input',
label: 'service_name',
group: 'transport',
condition: (p) => p.transport === 'grpc',
},
{
name: 'sni',
type: 'input',
label: 'security_sni',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'allow_insecure',
type: 'switch',
label: 'security_allow_insecure',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
condition: (p) => p.security !== 'none',
},
],
hysteria2: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'hop_ports',
type: 'input',
label: 'hop_ports',
placeholder: (t) => t('hop_ports_placeholder'),
group: 'basic',
},
{
name: 'hop_interval',
type: 'number',
label: 'hop_interval',
placeholder: 'e.g. 300',
min: 0,
suffix: 'S',
group: 'basic',
},
{
name: 'obfs_password',
type: 'input',
label: 'obfs_password',
placeholder: (t) => t('obfs_password_placeholder'),
password: 16,
group: 'basic',
},
{
name: 'up_mbps',
type: 'number',
label: 'up_mbps',
min: 0,
placeholder: (t) => t('bandwidth_placeholder'),
suffix: 'Mbps',
group: 'basic',
},
{
name: 'down_mbps',
type: 'number',
label: 'down_mbps',
min: 0,
placeholder: (t) => t('bandwidth_placeholder'),
suffix: 'Mbps',
group: 'basic',
},
{ name: 'sni', type: 'input', label: 'security_sni', group: 'security' },
{ name: 'allow_insecure', type: 'switch', label: 'security_allow_insecure', group: 'security' },
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
},
],
tuic: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'udp_relay_mode',
type: 'select',
label: 'udp_relay_mode',
options: TUIC_UDP_RELAY_MODES,
defaultValue: 'native',
group: 'basic',
},
{
name: 'congestion_controller',
type: 'select',
label: 'congestion_controller',
options: TUIC_CONGESTION,
defaultValue: 'bbr',
group: 'basic',
},
{ name: 'disable_sni', type: 'switch', label: 'disable_sni', group: 'basic' },
{ name: 'reduce_rtt', type: 'switch', label: 'reduce_rtt', group: 'basic' },
{ name: 'sni', type: 'input', label: 'security_sni', group: 'security' },
{ name: 'allow_insecure', type: 'switch', label: 'security_allow_insecure', group: 'security' },
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
},
],
socks: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
],
naive: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'security',
type: 'select',
label: 'security',
options: SECURITY.naive,
defaultValue: 'none',
group: 'security',
},
{
name: 'sni',
type: 'input',
label: 'security_sni',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'allow_insecure',
type: 'switch',
label: 'security_allow_insecure',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
condition: (p) => p.security !== 'none',
},
],
http: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'security',
type: 'select',
label: 'security',
options: SECURITY.http,
defaultValue: 'none',
group: 'security',
},
{
name: 'sni',
type: 'input',
label: 'security_sni',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'allow_insecure',
type: 'switch',
label: 'security_allow_insecure',
group: 'security',
condition: (p) => p.security !== 'none',
},
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
condition: (p) => p.security !== 'none',
},
],
meru: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'multiplex',
type: 'select',
label: 'multiplex',
options: multiplexLevels,
defaultValue: 'off',
group: 'basic',
},
{
name: 'transport',
type: 'select',
label: 'transport',
options: TRANSPORTS.meru,
defaultValue: 'tcp',
group: 'transport',
},
],
anytls: [
{
name: 'port',
type: 'number',
label: 'port',
min: 0,
max: 65535,
placeholder: '1-65535',
group: 'basic',
},
{
name: 'padding_scheme',
type: 'textarea',
label: 'padding_scheme',
placeholder: (t: (key: string) => string) => t('padding_scheme_placeholder'),
group: 'basic',
},
{ name: 'sni', type: 'input', label: 'security_sni', group: 'security' },
{ name: 'allow_insecure', type: 'switch', label: 'security_allow_insecure', group: 'security' },
{
name: 'fingerprint',
type: 'select',
label: 'security_fingerprint',
options: FINGERPRINTS,
defaultValue: 'chrome',
group: 'security',
},
],
};
@@ -0,0 +1,102 @@
export const protocols = [
'shadowsocks',
'vmess',
'vless',
'trojan',
'hysteria',
'tuic',
'anytls',
'socks',
'naive',
'http',
'mieru',
] as const;
// Global label map for display; fallback to raw value if missing
export const LABELS = {
// transport
'tcp': 'TCP',
'udp': 'UDP',
'websocket': 'WebSocket',
'grpc': 'gRPC',
'mkcp': 'mKCP',
'httpupgrade': 'HTTP Upgrade',
'xhttp': 'XHTTP',
// security
'none': 'NONE',
'tls': 'TLS',
'reality': 'Reality',
// fingerprint
'chrome': 'Chrome',
'firefox': 'Firefox',
'safari': 'Safari',
'ios': 'IOS',
'android': 'Android',
'edge': 'edge',
'360': '360',
'qq': 'QQ',
// multiplex
'low': 'Low',
'middle': 'Middle',
'high': 'High',
} as const;
// Flat arrays for enum-like sets
export const SS_CIPHERS = [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305',
] as const;
export const TRANSPORTS = {
vmess: ['tcp', 'websocket', 'grpc'] as const,
vless: ['tcp', 'websocket', 'grpc', 'mkcp', 'httpupgrade', 'xhttp'] as const,
trojan: ['tcp', 'websocket', 'grpc'] as const,
mieru: ['tcp', 'udp'] as const,
} as const;
export const SECURITY = {
shadowsocks: ['none', 'http', 'tls'] as const,
vmess: ['none', 'tls'] as const,
vless: ['none', 'tls', 'reality'] as const,
trojan: ['tls'] as const,
hysteria: ['tls'] as const,
tuic: ['tls'] as const,
anytls: ['tls'] as const,
naive: ['none', 'tls'] as const,
http: ['none', 'tls'] as const,
} as const;
export const FLOWS = {
vless: ['none', 'xtls-rprx-direct', 'xtls-rprx-splice', 'xtls-rprx-vision'] as const,
} as const;
export const TUIC_UDP_RELAY_MODES = ['native', 'quic'] as const;
export const TUIC_CONGESTION = ['bbr', 'cubic', 'new_reno'] as const;
export const XHTTP_MODES = ['auto', 'packet-up', 'stream-up', 'stream-one'] as const;
export const ENCRYPTION_TYPES = ['none', 'mlkem768x25519plus'] as const;
export const ENCRYPTION_MODES = ['native', 'xorpub', 'random'] as const;
export const ENCRYPTION_RTT = ['0rtt', '1rtt'] as const;
export const FINGERPRINTS = [
'chrome',
'firefox',
'safari',
'ios',
'android',
'edge',
'360',
'qq',
] as const;
export const CERT_MODES = ['none', 'http', 'dns', 'self'] as const;
export const multiplexLevels = ['none', 'low', 'middle', 'high'] as const;
export function getLabel(value: string): string {
const label = (LABELS as Record<string, string>)[value];
return label ?? value.toUpperCase();
}
@@ -0,0 +1,192 @@
import { XHTTP_MODES } from './constants';
import type { ProtocolType } from './types';
export function getProtocolDefaultConfig(proto: ProtocolType) {
switch (proto) {
case 'shadowsocks':
return {
type: 'shadowsocks',
enable: false,
port: null,
cipher: 'chacha20-ietf-poly1305',
server_key: null,
obfs: 'none',
obfs_host: null,
obfs_path: null,
sni: null,
allow_insecure: null,
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'vmess':
return {
type: 'vmess',
enable: false,
host: null,
port: null,
transport: 'tcp',
security: 'none',
path: null,
service_name: null,
sni: null,
allow_insecure: null,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'vless':
return {
type: 'vless',
enable: false,
host: null,
port: null,
transport: 'tcp',
security: 'none',
flow: 'none',
path: null,
service_name: null,
sni: null,
allow_insecure: null,
fingerprint: 'chrome',
reality_server_addr: null,
reality_server_port: null,
reality_private_key: null,
reality_public_key: null,
reality_short_id: null,
xhttp_mode: XHTTP_MODES[0], // 'auto'
xhttp_extra: null,
encryption: 'none',
encryption_mode: null,
encryption_rtt: null,
encryption_ticket: null,
encryption_server_padding: null,
encryption_private_key: null,
encryption_client_padding: null,
encryption_password: null,
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'trojan':
return {
type: 'trojan',
enable: false,
host: null,
port: null,
transport: 'tcp',
security: 'tls',
path: null,
service_name: null,
sni: null,
allow_insecure: null,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'hysteria':
return {
type: 'hysteria',
enable: false,
port: null,
hop_ports: null,
hop_interval: null,
obfs: 'none',
obfs_password: null,
security: 'tls',
up_mbps: null,
down_mbps: null,
sni: null,
allow_insecure: null,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'tuic':
return {
type: 'tuic',
enable: false,
port: null,
disable_sni: false,
reduce_rtt: false,
udp_relay_mode: 'native',
congestion_controller: 'bbr',
security: 'tls',
sni: null,
allow_insecure: false,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'socks':
return {
type: 'socks',
enable: false,
port: null,
ratio: 1,
} as any;
case 'naive':
return {
type: 'naive',
enable: false,
port: null,
security: 'none',
sni: null,
allow_insecure: null,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'http':
return {
type: 'http',
enable: false,
port: null,
security: 'none',
sni: null,
allow_insecure: null,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
case 'mieru':
return {
type: 'mieru',
enable: false,
port: null,
multiplex: 'none',
transport: 'tcp',
} as any;
case 'anytls':
return {
type: 'anytls',
enable: false,
port: null,
security: 'tls',
padding_scheme: null,
sni: null,
allow_insecure: false,
fingerprint: 'chrome',
cert_mode: 'none',
cert_dns_provider: null,
cert_dns_env: null,
ratio: 1,
} as any;
default:
return {} as any;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
// Re-export all constants
export {
ENCRYPTION_MODES,
ENCRYPTION_RTT,
ENCRYPTION_TYPES,
FINGERPRINTS,
FLOWS,
LABELS,
SECURITY,
SS_CIPHERS,
TRANSPORTS,
TUIC_CONGESTION,
TUIC_UDP_RELAY_MODES,
XHTTP_MODES,
getLabel,
multiplexLevels,
protocols,
} from './constants';
// Re-export all types
export type { FieldConfig, ProtocolType } from './types';
// Re-export all schemas
export { formSchema, protocolApiScheme } from './schemas';
// Re-export defaults
export { getProtocolDefaultConfig } from './defaults';
// Re-export fields
export { PROTOCOL_FIELDS } from './fields';
@@ -0,0 +1,225 @@
import { z } from 'zod';
import {
CERT_MODES,
ENCRYPTION_MODES,
ENCRYPTION_RTT,
ENCRYPTION_TYPES,
FLOWS,
multiplexLevels,
SECURITY,
SS_CIPHERS,
TRANSPORTS,
TUIC_CONGESTION,
TUIC_UDP_RELAY_MODES,
XHTTP_MODES,
} from './constants';
const nullableString = z.string().nullish();
const nullableBool = z.boolean().nullish();
const nullablePort = z.number().int().min(0).max(65535).nullish();
const nullableRatio = z.number().min(0).nullish();
const ss = z.object({
ratio: nullableRatio,
type: z.literal('shadowsocks'),
enable: nullableBool,
port: nullablePort,
cipher: z.enum(SS_CIPHERS).nullish(),
server_key: nullableString,
obfs: z.enum(['none', 'http', 'tls'] as const).nullish(),
obfs_host: nullableString,
obfs_path: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const vmess = z.object({
ratio: nullableRatio,
type: z.literal('vmess'),
enable: nullableBool,
host: nullableString,
port: nullablePort,
transport: z.enum(TRANSPORTS.vmess).nullish(),
security: z.enum(SECURITY.vmess).nullish(),
path: nullableString,
service_name: nullableString,
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const vless = z.object({
ratio: nullableRatio,
type: z.literal('vless'),
enable: nullableBool,
host: nullableString,
port: nullablePort,
transport: z.enum(TRANSPORTS.vless).nullish(),
security: z.enum(SECURITY.vless).nullish(),
path: nullableString,
service_name: nullableString,
flow: z.enum(FLOWS.vless).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
reality_server_addr: nullableString,
reality_server_port: nullablePort,
reality_private_key: nullableString,
reality_public_key: nullableString,
reality_short_id: nullableString,
xhttp_mode: z.enum(XHTTP_MODES).nullish(),
xhttp_extra: nullableString,
encryption: z.enum(ENCRYPTION_TYPES).nullish(),
encryption_mode: z.enum(ENCRYPTION_MODES).nullish(),
encryption_rtt: z.enum(ENCRYPTION_RTT).nullish(),
encryption_ticket: nullableString,
encryption_server_padding: nullableString,
encryption_private_key: nullableString,
encryption_client_padding: nullableString,
encryption_password: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const trojan = z.object({
ratio: nullableRatio,
type: z.literal('trojan'),
enable: nullableBool,
host: nullableString,
port: nullablePort,
transport: z.enum(TRANSPORTS.trojan).nullish(),
security: z.enum(SECURITY.trojan).nullish(),
path: nullableString,
service_name: nullableString,
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const hysteria = z.object({
ratio: nullableRatio,
type: z.literal('hysteria'),
enable: nullableBool,
hop_ports: nullableString,
hop_interval: z.number().nullish(),
obfs_password: nullableString,
obfs: z.enum(['none', 'salamander'] as const).nullish(),
port: nullablePort,
security: z.enum(SECURITY.hysteria).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
up_mbps: z.number().nullish(),
down_mbps: z.number().nullish(),
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const tuic = z.object({
ratio: nullableRatio,
type: z.literal('tuic'),
enable: nullableBool,
host: nullableString,
port: nullablePort,
disable_sni: z.boolean().nullish(),
reduce_rtt: z.boolean().nullish(),
udp_relay_mode: z.enum(TUIC_UDP_RELAY_MODES).nullish(),
congestion_controller: z.enum(TUIC_CONGESTION).nullish(),
security: z.enum(SECURITY.tuic).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const anytls = z.object({
ratio: nullableRatio,
type: z.literal('anytls'),
enable: nullableBool,
port: nullablePort,
security: z.enum(SECURITY.anytls).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
padding_scheme: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const socks = z.object({
ratio: nullableRatio,
type: z.literal('socks'),
enable: nullableBool,
port: nullablePort,
});
const naive = z.object({
ratio: nullableRatio,
type: z.literal('naive'),
enable: nullableBool,
port: nullablePort,
security: z.enum(SECURITY.naive).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const http = z.object({
ratio: nullableRatio,
type: z.literal('http'),
enable: nullableBool,
port: nullablePort,
security: z.enum(SECURITY.http).nullish(),
sni: nullableString,
allow_insecure: nullableBool,
fingerprint: nullableString,
cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString,
cert_dns_env: nullableString,
});
const mieru = z.object({
ratio: nullableRatio,
type: z.literal('mieru'),
enable: nullableBool,
port: nullablePort,
multiplex: z.enum(multiplexLevels).nullish(),
transport: z.enum(TRANSPORTS.mieru).nullish(),
});
export const protocolApiScheme = z.discriminatedUnion('type', [
ss,
vmess,
vless,
trojan,
hysteria,
tuic,
anytls,
socks,
naive,
http,
mieru,
]);
export const formSchema = z.object({
name: z.string().min(1),
address: z.string().min(1),
country: z.string().optional(),
city: z.string().optional(),
protocols: z.array(protocolApiScheme),
});
@@ -0,0 +1,27 @@
import { protocols } from './constants';
export type FieldConfig = {
name: string;
type: 'input' | 'select' | 'switch' | 'number' | 'textarea';
label: string;
placeholder?: string | ((t: (key: string) => string, protocol: any) => string);
options?: readonly string[];
defaultValue?: any;
min?: number;
max?: number;
step?: number;
suffix?: string;
generate?: {
function?: () => Promise<string | Record<string, string>> | string | Record<string, string>;
functions?: {
label: string | ((t: (key: string) => string, protocol: any) => string);
function: () => Promise<string | Record<string, string>> | string | Record<string, string>;
}[];
updateFields?: Record<string, string>;
};
condition?: (protocol: any, values: any) => boolean;
group?: 'basic' | 'transport' | 'security' | 'reality' | 'obfs' | 'encryption';
gridSpan?: 1 | 2;
};
export type ProtocolType = (typeof protocols)[number];
@@ -0,0 +1,4 @@
export { generateMLKEM768KeyPair } from './mlkem768';
export { generateRealityShortId } from './short-id';
export { generatePassword } from './uid';
export { generateRealityKeyPair } from './x25519';
@@ -0,0 +1,16 @@
import mlkem from 'mlkem-wasm';
import { toB64Url } from './util';
export async function generateMLKEM768KeyPair() {
const mlkemKeyPair = await mlkem.generateKey({ name: 'ML-KEM-768' }, true, [
'encapsulateBits',
'decapsulateBits',
]);
const mlkemPublicKeyRaw = await mlkem.exportKey('raw-public', mlkemKeyPair.publicKey);
const mlkemPrivateKeyRaw = await mlkem.exportKey('raw-seed', mlkemKeyPair.privateKey);
return {
publicKey: toB64Url(new Uint8Array(mlkemPublicKeyRaw)),
privateKey: toB64Url(new Uint8Array(mlkemPrivateKeyRaw)),
};
}
@@ -0,0 +1,15 @@
/**
* Generate a short ID for Reality
* @returns A random hexadecimal string of length 2, 4, 6, 8, 10, 12, 14, or 16
*/
export function generateRealityShortId() {
const hex = '0123456789abcdef';
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
const idx = Math.floor(Math.random() * lengths.length);
const len = lengths[idx] ?? 16;
let out = '';
for (let i = 0; i < len; i++) {
out += hex.charAt(Math.floor(Math.random() * hex.length));
}
return out;
}
@@ -0,0 +1,11 @@
import { uid } from 'radash';
/**
* Generate a random password
* @param length Length of the password
* @param charset Character set to use (defaults to alphanumeric)
* @returns Randomly generated password
*/
export function generatePassword(length = 16, charset?: string) {
return uid(length, charset).toLowerCase();
}
@@ -0,0 +1,6 @@
export function toB64Url(bytes: Uint8Array) {
return btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '');
}
@@ -0,0 +1,11 @@
import { x25519 } from '@noble/curves/ed25519.js';
import { toB64Url } from './util';
/**
* Generate a Reality key pair
* @returns An object containing the private and public keys in base64url format
*/
export function generateRealityKeyPair() {
const { secretKey, publicKey } = x25519.keygen();
return { privateKey: toB64Url(secretKey), publicKey: toB64Url(publicKey) };
}
@@ -15,6 +15,7 @@ import {
SheetTrigger, SheetTrigger,
} from '@workspace/ui/components/sheet'; } from '@workspace/ui/components/sheet';
import { formatBytes } from '@workspace/ui/utils'; import { formatBytes } from '@workspace/ui/utils';
import { Users } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useState } from 'react'; import { useState } from 'react';
@@ -92,8 +93,7 @@ export default function OnlineUsersCell({ status }: { status?: API.ServerStatus
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild> <SheetTrigger asChild>
<button className='hover:text-foreground text-muted-foreground flex items-center gap-2 bg-transparent p-0 text-sm'> <button className='hover:text-foreground text-muted-foreground flex items-center gap-2 bg-transparent p-0 text-sm'>
<Badge variant='secondary'>{status?.online.length}</Badge> <Users className='h-4 w-4' /> {status?.online.length}
<span>{t('onlineUsers')}</span>
</button> </button>
</SheetTrigger> </SheetTrigger>
<SheetContent className='h-screen w-screen max-w-none sm:h-auto sm:w-[900px] sm:max-w-[90vw]'> <SheetContent className='h-screen w-screen max-w-none sm:h-auto sm:w-[900px] sm:max-w-[90vw]'>
+40 -83
View File
@@ -1,39 +1,27 @@
'use client'; 'use client';
// Online users detail moved to separate component
import { ProTable, ProTableActions } from '@/components/pro-table'; import { ProTable, ProTableActions } from '@/components/pro-table';
import { import {
createServer, createServer,
deleteServer, deleteServer,
filterServerList, filterServerList,
hasMigrateSeverNode,
migrateServerNode,
resetSortWithServer, resetSortWithServer,
updateServer, updateServer,
} from '@/services/admin/server'; } from '@/services/admin/server';
import { useQuery } from '@tanstack/react-query'; import { useNode } from '@/store/node';
import { useServer } from '@/store/server';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { Card, CardContent } from '@workspace/ui/components/card';
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button'; import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
import { cn } from '@workspace/ui/lib/utils'; import { cn } from '@workspace/ui/lib/utils';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import DynamicMultiplier from './dynamic-multiplier';
import OnlineUsersCell from './online-users-cell'; import OnlineUsersCell from './online-users-cell';
import ServerConfig from './server-config'; import ServerConfig from './server-config';
import ServerForm from './server-form'; import ServerForm from './server-form';
import ServerInstall from './server-install';
type ProtocolName = 'shadowsocks' | 'vmess' | 'vless' | 'trojan' | 'hysteria2' | 'tuic' | 'anytls';
const PROTOCOL_COLORS: Record<ProtocolName, string> = {
shadowsocks: 'bg-green-500',
vmess: 'bg-rose-500',
vless: 'bg-blue-500',
trojan: 'bg-yellow-500',
hysteria2: 'bg-purple-500',
tuic: 'bg-cyan-500',
anytls: 'bg-gray-500',
};
function PctBar({ value }: { value: number }) { function PctBar({ value }: { value: number }) {
const v = value.toFixed(2); const v = value.toFixed(2);
@@ -62,63 +50,31 @@ function RegionIpCell({
return ( return (
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>
<Badge variant='outline'>{region}</Badge> <Badge variant='outline'>{region}</Badge>
<Badge variant='outline'>{ip || t('notAvailable')}</Badge> <Badge variant='secondary'>{ip || t('notAvailable')}</Badge>
</div> </div>
); );
} }
export default function ServersPage() { export default function ServersPage() {
const t = useTranslations('servers'); const t = useTranslations('servers');
const { isServerReferencedByNodes } = useNode();
const { fetchServers } = useServer();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [migrating, setMigrating] = useState(false);
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
const { data: hasMigrate, refetch: refetchHasMigrate } = useQuery({
queryKey: ['hasMigrateSeverNode'],
queryFn: async () => {
const { data } = await hasMigrateSeverNode();
return data.data?.has_migrate;
},
});
const handleMigrate = async () => {
setMigrating(true);
try {
const { data } = await migrateServerNode();
const fail = data.data?.fail || 0;
if (fail > 0) {
toast.error(data.data?.message);
} else {
toast.success(t('migrated'));
}
refetchHasMigrate();
ref.current?.refresh();
} catch (error) {
toast.error(t('migrateFailed'));
} finally {
setMigrating(false);
}
};
return ( return (
<div className='space-y-4'> <div className='space-y-4'>
<Card> <div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
<CardContent className='p-4'> <DynamicMultiplier />
<ServerConfig /> <ServerConfig />
</CardContent> </div>
</Card>
<ProTable<API.Server, { search: string }> <ProTable<API.Server, { search: string }>
action={ref} action={ref}
header={{ header={{
title: t('pageTitle'), title: t('pageTitle'),
toolbar: ( toolbar: (
<div className='flex gap-2'> <div className='flex gap-2'>
{hasMigrate && (
<Button variant='outline' onClick={handleMigrate} disabled={migrating}>
{migrating ? t('migrating') : t('migrate')}
</Button>
)}
<ServerForm <ServerForm
trigger={t('create')} trigger={t('create')}
title={t('drawerCreateTitle')} title={t('drawerCreateTitle')}
@@ -129,6 +85,7 @@ export default function ServersPage() {
await createServer(values as unknown as API.CreateServerRequest); await createServer(values as unknown as API.CreateServerRequest);
toast.success(t('created')); toast.success(t('created'));
ref.current?.refresh(); ref.current?.refresh();
fetchServers();
setLoading(false); setLoading(false);
return true; return true;
} catch (e) { } catch (e) {
@@ -163,24 +120,18 @@ export default function ServersPage() {
accessorKey: 'protocols', accessorKey: 'protocols',
header: t('protocols'), header: t('protocols'),
cell: ({ row }) => { cell: ({ row }) => {
const list = (row.original.protocols || []) as API.Protocol[]; const list = row.original.protocols.filter((p) => p.enable) as API.Protocol[];
if (!list.length) return t('noData'); if (!list.length) return '—';
return ( return (
<div className='flex flex-wrap gap-1'> <div className='flex flex-col gap-1'>
{list.map((p, idx) => { {list.map((p, idx) => {
const proto = ((p as any)?.type || '') as ProtocolName | ''; const ratio = Number(p.ratio ?? 1) || 1;
if (!proto) return null;
const color = PROTOCOL_COLORS[proto as ProtocolName];
const port = (p as any)?.port as number | undefined;
const label = `${proto}${port ? ` (${port})` : ''}`;
return ( return (
<Badge <div key={idx} className='flex items-center gap-2'>
key={idx} <Badge variant='outline'>{ratio.toFixed(2)}x</Badge>
variant='outline' <Badge variant='secondary'>{p.type}</Badge>
className={cn('text-primary-foreground', color)} <Badge variant='secondary'>{p.port}</Badge>
> </div>
{label}
</Badge>
); );
})} })}
</div> </div>
@@ -233,15 +184,7 @@ export default function ServersPage() {
header: t('onlineUsers'), header: t('onlineUsers'),
cell: ({ row }) => <OnlineUsersCell status={row.original.status as API.ServerStatus} />, cell: ({ row }) => <OnlineUsersCell status={row.original.status as API.ServerStatus} />,
}, },
{ // traffic ratio moved to per-protocol configs; column removed
id: 'traffic_ratio',
header: t('traffic_ratio'),
cell: ({ row }) => {
const raw = row.original.ratio as unknown;
const ratio = Number(raw ?? 1) || 1;
return <span className='text-sm'>{ratio.toFixed(2)}x</span>;
},
},
]} ]}
params={[{ key: 'search' }]} params={[{ key: 'search' }]}
request={async (pagination, filter) => { request={async (pagination, filter) => {
@@ -260,7 +203,7 @@ export default function ServersPage() {
key='edit' key='edit'
trigger={t('edit')} trigger={t('edit')}
title={t('drawerEditTitle')} title={t('drawerEditTitle')}
initialValues={row as any} initialValues={row}
loading={loading} loading={loading}
onSubmit={async (values) => { onSubmit={async (values) => {
setLoading(true); setLoading(true);
@@ -272,6 +215,7 @@ export default function ServersPage() {
}); });
toast.success(t('updated')); toast.success(t('updated'));
ref.current?.refresh(); ref.current?.refresh();
fetchServers();
setLoading(false); setLoading(false);
return true; return true;
} catch (e) { } catch (e) {
@@ -280,15 +224,21 @@ export default function ServersPage() {
} }
}} }}
/>, />,
<ServerInstall key='install' server={row} />,
<ConfirmButton <ConfirmButton
key='delete' key='delete'
trigger={<Button variant='destructive'>{t('delete')}</Button>} trigger={
<Button variant='destructive' disabled={isServerReferencedByNodes(row.id)}>
{t('delete')}
</Button>
}
title={t('confirmDeleteTitle')} title={t('confirmDeleteTitle')}
description={t('confirmDeleteDesc')} description={t('confirmDeleteDesc')}
onConfirm={async () => { onConfirm={async () => {
await deleteServer({ id: row.id } as any); await deleteServer({ id: row.id } as any);
toast.success(t('deleted')); toast.success(t('deleted'));
ref.current?.refresh(); ref.current?.refresh();
fetchServers();
}} }}
cancelText={t('cancel')} cancelText={t('cancel')}
confirmText={t('confirm')} confirmText={t('confirm')}
@@ -304,13 +254,13 @@ export default function ServersPage() {
name: others.name, name: others.name,
country: others.country, country: others.country,
city: others.city, city: others.city,
ratio: others.ratio,
address: others.address, address: others.address,
protocols: others.protocols || [], protocols: others.protocols || [],
}; };
await createServer(body); await createServer(body);
toast.success(t('copied')); toast.success(t('copied'));
ref.current?.refresh(); ref.current?.refresh();
fetchServers();
setLoading(false); setLoading(false);
}} }}
> >
@@ -318,16 +268,22 @@ export default function ServersPage() {
</Button>, </Button>,
], ],
batchRender(rows) { batchRender(rows) {
const hasReferencedServers = rows.some((row) => isServerReferencedByNodes(row.id));
return [ return [
<ConfirmButton <ConfirmButton
key='delete' key='delete'
trigger={<Button variant='destructive'>{t('delete')}</Button>} trigger={
<Button variant='destructive' disabled={hasReferencedServers}>
{t('delete')}
</Button>
}
title={t('confirmDeleteTitle')} title={t('confirmDeleteTitle')}
description={t('confirmDeleteDesc')} description={t('confirmDeleteDesc')}
onConfirm={async () => { onConfirm={async () => {
await Promise.all(rows.map((r) => deleteServer({ id: r.id }))); await Promise.all(rows.map((r) => deleteServer({ id: r.id })));
toast.success(t('deleted')); toast.success(t('deleted'));
ref.current?.refresh(); ref.current?.refresh();
fetchServers();
}} }}
cancelText={t('cancel')} cancelText={t('cancel')}
confirmText={t('confirm')} confirmText={t('confirm')}
@@ -361,6 +317,7 @@ export default function ServersPage() {
sort: item.sort, sort: item.sort,
})) as API.SortItem[], })) as API.SortItem[],
}); });
toast.success(t('sorted_success'));
} }
return updatedItems; return updatedItems;
}} }}
+314 -204
View File
@@ -1,15 +1,10 @@
'use client'; 'use client';
import { import { getNodeConfig, updateNodeConfig } from '@/services/admin/system';
getNodeConfig,
getNodeMultiplier,
setNodeMultiplier,
updateNodeConfig,
} from '@/services/admin/system';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { ChartContainer, ChartTooltip } from '@workspace/ui/components/chart'; import { Card, CardContent } from '@workspace/ui/components/card';
import { import {
Form, Form,
FormControl, FormControl,
@@ -19,8 +14,14 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@workspace/ui/components/form'; } from '@workspace/ui/components/form';
import { Label } from '@workspace/ui/components/label';
import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { ScrollArea } from '@workspace/ui/components/scroll-area';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@workspace/ui/components/select';
import { import {
Sheet, Sheet,
SheetContent, SheetContent,
@@ -29,75 +30,46 @@ import {
SheetTitle, SheetTitle,
SheetTrigger, SheetTrigger,
} from '@workspace/ui/components/sheet'; } from '@workspace/ui/components/sheet';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { Textarea } from '@workspace/ui/components/textarea';
import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs'; import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs';
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input'; import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { unitConversion } from '@workspace/ui/utils';
import { DicesIcon } from 'lucide-react'; import { DicesIcon } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { uid } from 'radash'; import { uid } from 'radash';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { Cell, Legend, Pie, PieChart } from 'recharts';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import { SS_CIPHERS } from './form-schema';
const COLORS = [ const dnsConfigSchema = z.object({
'hsl(var(--chart-1))', proto: z.string(), // z.enum(['tcp', 'udp', 'tls', 'https', 'quic']),
'hsl(var(--chart-2))', address: z.string(),
'hsl(var(--chart-3))', domains: z.array(z.string()),
'hsl(var(--chart-4))', });
'hsl(var(--chart-5))',
];
const MINUTES_IN_DAY = 1440; const outboundConfigSchema = z.object({
name: z.string(),
function getTimeRangeData(slots: API.TimePeriod[]) { protocol: z.string(),
const timePoints = slots address: z.string(),
.filter((slot) => slot.start_time && slot.end_time) port: z.number(),
.flatMap((slot) => { cipher: z.string().optional(),
const [startH = 0, startM = 0] = slot.start_time.split(':').map(Number); password: z.string().optional(),
const [endH = 0, endM = 0] = slot.end_time.split(':').map(Number); rules: z.array(z.string()).optional(),
const start = startH * 60 + startM; });
let end = endH * 60 + endM;
if (end < start) end += MINUTES_IN_DAY;
return { start, end, multiplier: slot.multiplier };
})
.sort((a, b) => a.start - b.start);
const result: { name: string; value: number; multiplier: number }[] = [];
let currentMinute = 0;
timePoints.forEach((point) => {
if (point.start > currentMinute) {
result.push({
name: `${Math.floor(currentMinute / 60)}:${String(currentMinute % 60).padStart(2, '0')} - ${Math.floor(point.start / 60)}:${String(point.start % 60).padStart(2, '0')}`,
value: point.start - currentMinute,
multiplier: 1,
});
}
result.push({
name: `${Math.floor(point.start / 60)}:${String(point.start % 60).padStart(2, '0')} - ${Math.floor((point.end / 60) % 24)}:${String(point.end % 60).padStart(2, '0')}`,
value: point.end - point.start,
multiplier: point.multiplier,
});
currentMinute = point.end % MINUTES_IN_DAY;
});
if (currentMinute < MINUTES_IN_DAY) {
result.push({
name: `${Math.floor(currentMinute / 60)}:${String(currentMinute % 60).padStart(2, '0')} - 24:00`,
value: MINUTES_IN_DAY - currentMinute,
multiplier: 1,
});
}
return result;
}
const nodeConfigSchema = z.object({ const nodeConfigSchema = z.object({
node_secret: z.string().optional(), node_secret: z.string().optional(),
node_pull_interval: z.number().optional(), node_pull_interval: z.number().optional(),
node_push_interval: z.number().optional(), node_push_interval: z.number().optional(),
traffic_report_threshold: z.number().optional(),
ip_strategy: z.enum(['prefer_ipv4', 'prefer_ipv6']).optional(),
dns: z.array(dnsConfigSchema).optional(),
block: z.array(z.string()).optional(),
outbound: z.array(outboundConfigSchema).optional(),
}); });
type NodeConfigFormData = z.infer<typeof nodeConfigSchema>; type NodeConfigFormData = z.infer<typeof nodeConfigSchema>;
@@ -105,7 +77,6 @@ export default function ServerConfig() {
const t = useTranslations('servers'); const t = useTranslations('servers');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [timeSlots, setTimeSlots] = useState<API.TimePeriod[]>([]);
const { data: cfgResp, refetch: refetchCfg } = useQuery({ const { data: cfgResp, refetch: refetchCfg } = useQuery({
queryKey: ['getNodeConfig'], queryKey: ['getNodeConfig'],
@@ -116,21 +87,17 @@ export default function ServerConfig() {
enabled: open, enabled: open,
}); });
const { data: periodsResp, refetch: refetchPeriods } = useQuery({
queryKey: ['getNodeMultiplier'],
queryFn: async () => {
const { data } = await getNodeMultiplier();
return (data.data?.periods || []) as API.TimePeriod[];
},
enabled: open,
});
const form = useForm<NodeConfigFormData>({ const form = useForm<NodeConfigFormData>({
resolver: zodResolver(nodeConfigSchema), resolver: zodResolver(nodeConfigSchema),
defaultValues: { defaultValues: {
node_secret: '', node_secret: '',
node_pull_interval: undefined, node_pull_interval: undefined,
node_push_interval: undefined, node_push_interval: undefined,
traffic_report_threshold: undefined,
ip_strategy: 'prefer_ipv4',
dns: [],
block: [],
outbound: [],
}, },
}); });
@@ -140,35 +107,21 @@ export default function ServerConfig() {
node_secret: cfgResp.node_secret ?? '', node_secret: cfgResp.node_secret ?? '',
node_pull_interval: cfgResp.node_pull_interval as number | undefined, node_pull_interval: cfgResp.node_pull_interval as number | undefined,
node_push_interval: cfgResp.node_push_interval as number | undefined, node_push_interval: cfgResp.node_push_interval as number | undefined,
traffic_report_threshold: cfgResp.traffic_report_threshold as number | undefined,
ip_strategy:
(cfgResp.ip_strategy as 'prefer_ipv4' | 'prefer_ipv6' | undefined) || 'prefer_ipv4',
dns: cfgResp.dns || [],
block: cfgResp.block || [],
outbound: cfgResp.outbound || [],
}); });
} }
}, [cfgResp, form]); }, [cfgResp, form]);
useEffect(() => {
if (periodsResp) {
setTimeSlots(periodsResp);
}
}, [periodsResp]);
const chartTimeSlots = useMemo(() => getTimeRangeData(timeSlots), [timeSlots]);
const chartConfig = useMemo(() => {
return chartTimeSlots.reduce(
(acc, item, index) => {
acc[item.name] = {
label: item.name,
color: COLORS[index % COLORS.length] || 'hsl(var(--default-chart-color))',
};
return acc;
},
{} as Record<string, { label: string; color: string }>,
);
}, [chartTimeSlots]);
async function onSubmit(values: NodeConfigFormData) { async function onSubmit(values: NodeConfigFormData) {
setSaving(true); setSaving(true);
try { try {
await updateNodeConfig(values as API.NodeConfig); await updateNodeConfig(values as API.NodeConfig);
toast.success(t('config.saveSuccess')); toast.success(t('server_config.saveSuccess'));
await refetchCfg(); await refetchCfg();
setOpen(false); setOpen(false);
} finally { } finally {
@@ -176,50 +129,55 @@ export default function ServerConfig() {
} }
} }
async function savePeriods() {
await setNodeMultiplier({ periods: timeSlots });
await refetchPeriods();
toast.success(t('config.saveSuccess'));
}
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild> <SheetTrigger asChild>
<Card>
<CardContent className='p-4'>
<div className='flex cursor-pointer items-center justify-between'> <div className='flex cursor-pointer items-center justify-between'>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<div className='bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg'> <div className='bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg'>
<Icon icon='mdi:resistor-nodes' className='text-primary h-5 w-5' /> <Icon icon='mdi:resistor-nodes' className='text-primary h-5 w-5' />
</div> </div>
<div className='flex-1'> <div className='flex-1'>
<p className='font-medium'>{t('config.title')}</p> <p className='font-medium'>{t('server_config.title')}</p>
<p className='text-muted-foreground text-sm'>{t('config.description')}</p> <p className='text-muted-foreground truncate text-sm'>
{t('server_config.description')}
</p>
</div> </div>
</div> </div>
<Icon icon='mdi:chevron-right' className='size-6' /> <Icon icon='mdi:chevron-right' className='size-6' />
</div> </div>
</CardContent>
</Card>
</SheetTrigger> </SheetTrigger>
<SheetContent className='w-[720px] max-w-full md:max-w-screen-md'> <SheetContent className='w-[720px] max-w-full md:max-w-screen-md'>
<SheetHeader> <SheetHeader>
<SheetTitle>{t('config.title')}</SheetTitle> <SheetTitle>{t('server_config.title')}</SheetTitle>
</SheetHeader> </SheetHeader>
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))] px-6'> <ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))] px-6'>
<Tabs defaultValue='basic' className='pt-4'>
<TabsList className='grid w-full grid-cols-4'>
<TabsTrigger value='basic'>{t('server_config.tabs.basic')}</TabsTrigger>
<TabsTrigger value='dns'>{t('server_config.tabs.dns')}</TabsTrigger>
<TabsTrigger value='outbound'>{t('server_config.tabs.outbound')}</TabsTrigger>
<TabsTrigger value='block'>{t('server_config.tabs.block')}</TabsTrigger>
</TabsList>
<Form {...form}> <Form {...form}>
<form <form id='server-config-form' onSubmit={form.handleSubmit(onSubmit)} className='mt-4'>
id='server-config-form' <TabsContent value='basic' className='space-y-4'>
onSubmit={form.handleSubmit(onSubmit)}
className='space-y-4 pt-4'
>
<FormField <FormField
control={form.control} control={form.control}
name='node_secret' name='node_secret'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('config.communicationKey')}</FormLabel> <FormLabel>{t('server_config.fields.communication_key')}</FormLabel>
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
placeholder={t('config.inputPlaceholder')} placeholder={t('server_config.fields.communication_key_placeholder')}
value={field.value || ''} value={field.value || ''}
onValueChange={field.onChange} onValueChange={field.onChange}
suffix={ suffix={
@@ -236,7 +194,9 @@ export default function ServerConfig() {
} }
/> />
</FormControl> </FormControl>
<FormDescription>{t('config.communicationKeyDescription')}</FormDescription> <FormDescription>
{t('server_config.fields.communication_key_desc')}
</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@@ -247,7 +207,7 @@ export default function ServerConfig() {
name='node_pull_interval' name='node_pull_interval'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('config.nodePullInterval')}</FormLabel> <FormLabel>{t('server_config.fields.node_pull_interval')}</FormLabel>
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='number' type='number'
@@ -255,10 +215,12 @@ export default function ServerConfig() {
suffix='S' suffix='S'
value={field.value as any} value={field.value as any}
onValueChange={field.onChange} onValueChange={field.onChange}
placeholder={t('config.inputPlaceholder')} placeholder={t('server_config.fields.communication_key_placeholder')}
/> />
</FormControl> </FormControl>
<FormDescription>{t('config.nodePullIntervalDescription')}</FormDescription> <FormDescription>
{t('server_config.fields.node_pull_interval_desc')}
</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@@ -269,7 +231,7 @@ export default function ServerConfig() {
name='node_push_interval' name='node_push_interval'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('config.nodePushInterval')}</FormLabel> <FormLabel>{t('server_config.fields.node_push_interval')}</FormLabel>
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='number' type='number'
@@ -278,120 +240,268 @@ export default function ServerConfig() {
step={0.1} step={0.1}
value={field.value as any} value={field.value as any}
onValueChange={field.onChange} onValueChange={field.onChange}
placeholder={t('config.inputPlaceholder')} placeholder={t('server_config.fields.communication_key_placeholder')}
/> />
</FormControl> </FormControl>
<FormDescription>{t('config.nodePushIntervalDescription')}</FormDescription> <FormDescription>
{t('server_config.fields.node_push_interval_desc')}
</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<div className='mt-6 space-y-3'> <FormField
<Label className='text-base'>{t('config.dynamicMultiplier')}</Label> control={form.control}
<p className='text-muted-foreground text-sm'> name='traffic_report_threshold'
{t('config.dynamicMultiplierDescription')} render={({ field }) => (
</p> <FormItem>
<FormLabel>{t('server_config.fields.traffic_report_threshold')}</FormLabel>
<FormControl>
<EnhancedInput
type='number'
min={0}
suffix='MB'
value={unitConversion('bitsToMb', field.value as number | undefined)}
onValueChange={(value) => {
field.onChange(unitConversion('mbToBits', value));
}}
placeholder='1'
/>
</FormControl>
<FormDescription>
{t('server_config.fields.traffic_report_threshold_desc')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</TabsContent>
<div className='flex flex-col-reverse gap-8 md:flex-row md:items-start'> <TabsContent value='dns' className='space-y-4'>
<div className='w-full md:w-1/2'> <FormField
<ArrayInput<API.TimePeriod> control={form.control}
name='ip_strategy'
render={({ field }) => (
<FormItem>
<FormLabel>{t('server_config.fields.ip_strategy')}</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue
placeholder={t('server_config.fields.ip_strategy_placeholder')}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value='prefer_ipv4'>
{t('server_config.fields.ip_strategy_ipv4')}
</SelectItem>
<SelectItem value='prefer_ipv6'>
{t('server_config.fields.ip_strategy_ipv6')}
</SelectItem>
</SelectContent>
</Select>
<FormDescription>
{t('server_config.fields.ip_strategy_desc')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='dns'
render={({ field }) => (
<FormItem>
<FormLabel>{t('server_config.fields.dns_config')}</FormLabel>
<FormControl>
<ArrayInput
className='grid grid-cols-2 gap-2'
fields={[ fields={[
{ name: 'start_time', prefix: t('config.startTime'), type: 'time' },
{ name: 'end_time', prefix: t('config.endTime'), type: 'time' },
{ {
name: 'multiplier', name: 'proto',
prefix: t('config.multiplier'), type: 'select',
type: 'number', placeholder: t('server_config.fields.dns_proto_placeholder'),
placeholder: '0', options: [
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
{ label: 'TLS', value: 'tls' },
{ label: 'HTTPS', value: 'https' },
{ label: 'QUIC', value: 'quic' },
],
},
{ name: 'address', type: 'text', placeholder: '8.8.8.8:53' },
{
name: 'domains',
type: 'textarea',
className: 'col-span-2',
placeholder: t('server_config.fields.dns_domains_placeholder'),
}, },
]} ]}
value={timeSlots} value={(field.value || []).map((item) => ({
onChange={setTimeSlots} ...item,
/> domains: Array.isArray(item.domains) ? item.domains.join('\n') : '',
<div className='mt-3 flex gap-2'> }))}
<Button onChange={(values) => {
size='sm' const converted = values.map((item: any) => ({
variant='outline' proto: item.proto,
onClick={() => setTimeSlots(periodsResp || [])} address: item.address,
> domains:
{t('config.reset')} typeof item.domains === 'string'
</Button> ? item.domains.split('\n').map((d: string) => d.trim())
<Button size='sm' onClick={savePeriods}> : item.domains || [],
{t('config.save')} }));
</Button> field.onChange(converted);
</div>
</div>
<div className='w-full md:w-1/2'>
<ChartContainer
config={chartConfig}
className='mx-auto aspect-[4/3] max-w-[400px]'
>
<PieChart>
<Pie
data={chartTimeSlots}
cx='50%'
cy='50%'
labelLine={false}
outerRadius='80%'
fill='#8884d8'
dataKey='value'
label={({ percent, multiplier }) =>
`${(multiplier || 0)?.toFixed(2)}x (${(percent * 100).toFixed(0)}%)`
}
>
{chartTimeSlots.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<ChartTooltip
content={({ payload }) => {
if (payload && payload.length) {
const d = payload[0]?.payload as any;
return (
<div className='bg-background rounded-lg border p-2 shadow-sm'>
<div className='grid grid-cols-2 gap-2'>
<div className='flex flex-col'>
<span className='text-muted-foreground text-[0.70rem] uppercase'>
{t('config.timeSlot')}
</span>
<span className='text-muted-foreground font-bold'>
{d.name || '—'}
</span>
</div>
<div className='flex flex-col'>
<span className='text-muted-foreground text-[0.70rem] uppercase'>
{t('config.multiplier')}
</span>
<span className='font-bold'>
{Number(d.multiplier).toFixed(2)}x
</span>
</div>
</div>
</div>
);
}
return null;
}} }}
/> />
<Legend /> </FormControl>
</PieChart> <FormMessage />
</ChartContainer> </FormItem>
</div> )}
</div> />
</div> </TabsContent>
<TabsContent value='outbound' className='space-y-4'>
<FormField
control={form.control}
name='outbound'
render={({ field }) => {
return (
<FormItem>
<FormControl>
<ArrayInput
className='grid grid-cols-2 gap-2'
fields={[
{
name: 'name',
type: 'text',
className: 'col-span-2',
placeholder: t('server_config.fields.outbound_name_placeholder'),
},
{
name: 'protocol',
type: 'select',
placeholder: t(
'server_config.fields.outbound_protocol_placeholder',
),
options: [
{ label: 'HTTP', value: 'http' },
{ label: 'SOCKS', value: 'socks' },
{ label: 'Shadowsocks', value: 'shadowsocks' },
{ label: 'Brook', value: 'brook' },
{ label: 'Snell', value: 'snell' },
{ label: 'VMess', value: 'vmess' },
{ label: 'VLESS', value: 'vless' },
{ label: 'Trojan', value: 'trojan' },
{ label: 'WireGuard', value: 'wireguard' },
{ label: 'Hysteria', value: 'hysteria' },
{ label: 'TUIC', value: 'tuic' },
{ label: 'AnyTLS', value: 'anytls' },
{ label: 'Naive', value: 'naive' },
{ label: 'Direct', value: 'direct' },
{ label: 'Reject', value: 'reject' },
],
},
{
name: 'cipher',
type: 'select',
options: SS_CIPHERS.map((cipher) => ({
label: cipher,
value: cipher,
})),
visible: (item: Record<string, any>) =>
item.protocol === 'shadowsocks',
},
{
name: 'address',
type: 'text',
placeholder: t(
'server_config.fields.outbound_address_placeholder',
),
},
{
name: 'port',
type: 'number',
placeholder: t('server_config.fields.outbound_port_placeholder'),
},
{
name: 'password',
type: 'text',
placeholder: t(
'server_config.fields.outbound_password_placeholder',
),
},
{
name: 'rules',
type: 'textarea',
className: 'col-span-2',
placeholder: t('server_config.fields.outbound_rules_placeholder'),
},
]}
value={(field.value || []).map((item) => ({
...item,
rules: Array.isArray(item.rules) ? item.rules.join('\n') : '',
}))}
onChange={(values) => {
const converted = values.map((item: any) => ({
name: item.name,
protocol: item.protocol,
address: item.address,
port: item.port,
cipher: item.cipher,
password: item.password,
rules:
typeof item.rules === 'string'
? item.rules.split('\n').map((r: string) => r.trim())
: item.rules || [],
}));
field.onChange(converted);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</TabsContent>
<TabsContent value='block' className='space-y-4'>
<FormField
control={form.control}
name='block'
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
placeholder={t('server_config.fields.block_rules_placeholder')}
value={(field.value || []).join('\n')}
onChange={(e) => {
const lines = e.target.value.split('\n').map((line) => line.trim());
field.onChange(lines);
}}
rows={10}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TabsContent>
</form> </form>
</Form> </Form>
</Tabs>
</ScrollArea> </ScrollArea>
<SheetFooter className='flex-row justify-end gap-2 pt-3'> <SheetFooter className='flex-row justify-end gap-2 pt-3'>
<Button variant='outline' disabled={saving} onClick={() => setOpen(false)}> <Button variant='outline' disabled={saving} onClick={() => setOpen(false)}>
{t('config.actions.cancel')} {t('actions.cancel')}
</Button> </Button>
<Button disabled={saving} type='submit' form='server-config-form'> <Button disabled={saving} type='submit' form='server-config-form'>
<Icon icon='mdi:loading' className={saving ? 'mr-2 animate-spin' : 'hidden'} /> <Icon icon='mdi:loading' className={saving ? 'mr-2 animate-spin' : 'hidden'} />
{t('config.actions.save')} {t('actions.save')}
</Button> </Button>
</SheetFooter> </SheetFooter>
</SheetContent> </SheetContent>
+162 -86
View File
@@ -1,5 +1,6 @@
'use client'; 'use client';
import { useNode } from '@/store/node';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { import {
Accordion, Accordion,
@@ -9,6 +10,12 @@ import {
} from '@workspace/ui/components/accordion'; } from '@workspace/ui/components/accordion';
import { Badge } from '@workspace/ui/components/badge'; import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@workspace/ui/components/dropdown-menu';
import { import {
Form, Form,
FormControl, FormControl,
@@ -38,7 +45,6 @@ import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { cn } from '@workspace/ui/lib/utils'; import { cn } from '@workspace/ui/lib/utils';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { uid } from 'radash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -49,18 +55,19 @@ import {
getProtocolDefaultConfig, getProtocolDefaultConfig,
PROTOCOL_FIELDS, PROTOCOL_FIELDS,
protocols as PROTOCOLS, protocols as PROTOCOLS,
ServerFormValues,
} from './form-schema'; } from './form-schema';
function DynamicField({ function DynamicField({
field, field,
control, control,
form,
protocolIndex, protocolIndex,
protocolData, protocolData,
t, t,
}: { }: {
field: FieldConfig; field: FieldConfig;
control: any; control: any;
form: any;
protocolIndex: number; protocolIndex: number;
protocolData: any; protocolData: any;
t: (key: string) => string; t: (key: string) => string;
@@ -97,18 +104,69 @@ function DynamicField({
} }
onValueChange={(v) => fieldProps.onChange(v)} onValueChange={(v) => fieldProps.onChange(v)}
suffix={ suffix={
field.password ? ( field.generate ? (
field.generate.functions && field.generate.functions.length > 0 ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type='button' variant='ghost' size='sm'>
<Icon icon='mdi:key' className='h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
{field.generate.functions.map((genFunc, idx) => (
<DropdownMenuItem
key={idx}
onClick={async () => {
const result = await genFunc.function();
if (typeof result === 'string') {
fieldProps.onChange(result);
} else if (field.generate!.updateFields) {
Object.entries(field.generate!.updateFields).forEach(
([fieldName, resultKey]) => {
const fullFieldName = `protocols.${protocolIndex}.${fieldName}`;
form.setValue(fullFieldName, (result as any)[resultKey]);
},
);
} else {
if (result.privateKey) {
fieldProps.onChange(result.privateKey);
}
}
}}
>
{typeof genFunc.label === 'function'
? genFunc.label(t, protocolData)
: genFunc.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : field.generate.function ? (
<Button <Button
type='button' type='button'
variant='ghost' variant='ghost'
onClick={() => { size='sm'
const length = field.password || 16; onClick={async () => {
const result = uid(length).toLowerCase(); const result = await field.generate!.function!();
if (typeof result === 'string') {
fieldProps.onChange(result); fieldProps.onChange(result);
} else if (field.generate!.updateFields) {
Object.entries(field.generate!.updateFields).forEach(
([fieldName, resultKey]) => {
const fullFieldName = `protocols.${protocolIndex}.${fieldName}`;
form.setValue(fullFieldName, (result as any)[resultKey]);
},
);
} else {
if (result.privateKey) {
fieldProps.onChange(result.privateKey);
}
}
}} }}
> >
<Icon icon='mdi:refresh' className='h-4 w-4' /> <Icon icon='mdi:key' className='h-4 w-4' />
</Button> </Button>
) : null
) : ( ) : (
field.suffix field.suffix
) )
@@ -225,7 +283,7 @@ function DynamicField({
field.placeholder field.placeholder
? typeof field.placeholder === 'function' ? typeof field.placeholder === 'function'
? field.placeholder(t, protocolData) ? field.placeholder(t, protocolData)
: t(field.placeholder) : field.placeholder
: undefined : undefined
} }
onChange={(e) => fieldProps.onChange(e.target.value)} onChange={(e) => fieldProps.onChange(e.target.value)}
@@ -246,6 +304,7 @@ function renderFieldsByGroup(
fields: FieldConfig[], fields: FieldConfig[],
group: string, group: string,
control: any, control: any,
form: any,
protocolIndex: number, protocolIndex: number,
protocolData: any, protocolData: any,
t: (key: string) => string, t: (key: string) => string,
@@ -260,6 +319,7 @@ function renderFieldsByGroup(
key={field.name} key={field.name}
field={field} field={field}
control={control} control={control}
form={form}
protocolIndex={protocolIndex} protocolIndex={protocolIndex}
protocolData={protocolData} protocolData={protocolData}
t={t} t={t}
@@ -274,6 +334,7 @@ function renderGroupCard(
fields: FieldConfig[], fields: FieldConfig[],
group: string, group: string,
control: any, control: any,
form: any,
protocolIndex: number, protocolIndex: number,
protocolData: any, protocolData: any,
t: (key: string) => string, t: (key: string) => string,
@@ -294,7 +355,7 @@ function renderGroupCard(
{t(title)} {t(title)}
</legend> </legend>
<div className='p-4 pt-2'> <div className='p-4 pt-2'>
{renderFieldsByGroup(fields, group, control, protocolIndex, protocolData, t)} {renderFieldsByGroup(fields, group, control, form, protocolIndex, protocolData, t)}
</div> </div>
</fieldset> </fieldset>
</div> </div>
@@ -305,14 +366,16 @@ export default function ServerForm(props: {
trigger: string; trigger: string;
title: string; title: string;
loading?: boolean; loading?: boolean;
initialValues?: Partial<ServerFormValues>; initialValues?: Partial<API.Server>;
onSubmit: (values: ServerFormValues) => Promise<boolean> | boolean; onSubmit: (values: Partial<API.Server>) => Promise<boolean> | boolean;
}) { }) {
const { trigger, title, loading, initialValues, onSubmit } = props; const { trigger, title, loading, initialValues, onSubmit } = props;
const t = useTranslations('servers'); const t = useTranslations('servers');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [accordionValue, setAccordionValue] = useState<string>(); const [accordionValue, setAccordionValue] = useState<string>();
const { isProtocolUsedInNodes } = useNode();
const form = useForm({ const form = useForm({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
@@ -320,8 +383,7 @@ export default function ServerForm(props: {
address: '', address: '',
country: '', country: '',
city: '', city: '',
ratio: 1, protocols: [] as any[],
protocols: [],
...initialValues, ...initialValues,
}, },
}); });
@@ -336,11 +398,11 @@ export default function ServerForm(props: {
address: '', address: '',
country: '', country: '',
city: '', city: '',
ratio: 1,
...initialValues, ...initialValues,
protocols: PROTOCOLS.map((type) => { protocols: PROTOCOLS.map((type) => {
const existingProtocol = initialValues.protocols?.find((p) => p.type === type); const existingProtocol = initialValues.protocols?.find((p) => p.type === type);
return existingProtocol || getProtocolDefaultConfig(type); const defaultConfig = getProtocolDefaultConfig(type);
return existingProtocol ? { ...defaultConfig, ...existingProtocol } : defaultConfig;
}), }),
}); });
} }
@@ -348,24 +410,17 @@ export default function ServerForm(props: {
}, [initialValues]); }, [initialValues]);
async function handleSubmit(values: Record<string, any>) { async function handleSubmit(values: Record<string, any>) {
const filtered = (values?.protocols || []).filter((p: any, index: number) => { const filteredProtocols = (values?.protocols || []).filter((protocol: any) => {
const port = Number(p?.port); const port = Number(protocol?.port);
const protocolType = PROTOCOLS[index]; return protocol && Number.isFinite(port) && port > 0 && port <= 65535;
return protocolType && p && Number.isFinite(port) && port > 0 && port <= 65535;
}); });
if (filtered.length === 0) {
toast.error(t('validation_failed'));
return;
}
const result = { const result = {
name: values.name, name: values.name,
country: values.country, country: values.country,
city: values.city, city: values.city,
ratio: Number(values.ratio || 1),
address: values.address, address: values.address,
protocols: filtered, protocols: filteredProtocols,
}; };
const ok = await onSubmit(result); const ok = await onSubmit(result);
@@ -387,7 +442,6 @@ export default function ServerForm(props: {
address: '', address: '',
country: '', country: '',
city: '', city: '',
ratio: 1,
protocols: full, protocols: full,
}); });
} }
@@ -404,7 +458,7 @@ export default function ServerForm(props: {
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))]'> <ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))]'>
<Form {...form}> <Form {...form}>
<form className='grid grid-cols-1 gap-2 px-6 pt-4'> <form className='grid grid-cols-1 gap-2 px-6 pt-4'>
<div className='grid grid-cols-3 gap-2'> <div className='grid grid-cols-2 gap-2 md:grid-cols-4'>
<FormField <FormField
control={control} control={control}
name='name' name='name'
@@ -418,6 +472,23 @@ export default function ServerForm(props: {
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={control}
name='address'
render={({ field }) => (
<FormItem>
<FormLabel>{t('address')}</FormLabel>
<FormControl>
<EnhancedInput
{...field}
placeholder={t('address_placeholder')}
onValueChange={(v) => field.onChange(v)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={control} control={control}
name='country' name='country'
@@ -445,44 +516,6 @@ export default function ServerForm(props: {
)} )}
/> />
</div> </div>
<div className='grid grid-cols-2 gap-2'>
<FormField
control={control}
name='address'
render={({ field }) => (
<FormItem>
<FormLabel>{t('address')}</FormLabel>
<FormControl>
<EnhancedInput
{...field}
placeholder={t('address_placeholder')}
onValueChange={(v) => field.onChange(v)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={control}
name='ratio'
render={({ field }) => (
<FormItem>
<FormLabel>{t('traffic_ratio')}</FormLabel>
<FormControl>
<EnhancedInput
{...field}
type='number'
step={0.1}
min={0}
onValueChange={(v) => field.onChange(v)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className='my-3'> <div className='my-3'>
<h3 className='text-foreground text-sm font-semibold'> <h3 className='text-foreground text-sm font-semibold'>
{t('protocol_configurations')} {t('protocol_configurations')}
@@ -491,6 +524,7 @@ export default function ServerForm(props: {
{t('protocol_configurations_desc')} {t('protocol_configurations_desc')}
</p> </p>
</div> </div>
<Accordion <Accordion
type='single' type='single'
collapsible collapsible
@@ -504,26 +538,15 @@ export default function ServerForm(props: {
PROTOCOLS.findIndex((t) => t === type), PROTOCOLS.findIndex((t) => t === type),
); );
const current = (protocolsValues[i] || {}) as Record<string, any>; const current = (protocolsValues[i] || {}) as Record<string, any>;
const isEnabled = current.port && Number(current.port) > 0; const isEnabled = current?.enable;
const fields = PROTOCOL_FIELDS[type] || []; const fields = PROTOCOL_FIELDS[type] || [];
return ( return (
<AccordionItem key={type} value={type} className='mb-2 rounded-lg border'> <AccordionItem key={type} value={type} className='mb-2 rounded-lg border'>
<AccordionTrigger className='px-4 py-3 hover:no-underline'> <AccordionTrigger className='px-4 py-3 hover:no-underline'>
<div className='flex w-full items-center justify-between'> <div className='flex w-full items-center justify-between'>
<div className='flex flex-col items-start'> <div className='flex flex-col items-start gap-1'>
<div className='flex items-center gap-2'> <div className='flex items-center gap-1'>
<span className='font-medium capitalize'>{type}</span> <span className='font-medium capitalize'>{type}</span>
</div>
<span
className={cn(
'text-muted-foreground text-xs',
isEnabled && 'text-green-500',
)}
>
{isEnabled ? t('enabled') : t('disabled')}
</span>
</div>
<div className='mr-2 flex items-center gap-1'>
{current.transport && ( {current.transport && (
<Badge variant='secondary' className='text-xs'> <Badge variant='secondary' className='text-xs'>
{current.transport.toUpperCase()} {current.transport.toUpperCase()}
@@ -534,26 +557,78 @@ export default function ServerForm(props: {
{current.security.toUpperCase()} {current.security.toUpperCase()}
</Badge> </Badge>
)} )}
{current.port && <Badge className='text-xs'>{current.port}</Badge>} {current.port && <Badge className='text-xs'>{current.port}</Badge>}
</div> </div>
<div className='flex items-center gap-1'>
<span
className={cn(
'text-xs',
isEnabled ? 'text-green-500' : 'text-muted-foreground',
)}
>
{isEnabled ? t('enabled') : t('disabled')}
</span>
</div>
</div>
<Switch
className='mr-2'
checked={!!isEnabled}
disabled={Boolean(
initialValues?.id &&
isProtocolUsedInNodes(initialValues?.id || 0, type) &&
isEnabled,
)}
onCheckedChange={(checked) => {
form.setValue(`protocols.${i}.enable`, checked);
}}
onClick={(e) => e.stopPropagation()}
/>
</div> </div>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className='px-4 pb-4 pt-0'> <AccordionContent className='px-4 pb-4 pt-0'>
<div className='-mx-4 space-y-4 rounded-b-lg border-t px-4 pt-4'> <div className='-mx-4 space-y-4 rounded-b-lg border-t px-4 pt-4'>
{renderGroupCard('basic', fields, 'basic', control, i, current, t)} {renderGroupCard('basic', fields, 'basic', control, form, i, current, t)}
{renderGroupCard('plugin', fields, 'plugin', control, i, current, t)} {renderGroupCard('obfs', fields, 'obfs', control, form, i, current, t)}
{renderGroupCard( {renderGroupCard(
'transport', 'transport',
fields, fields,
'transport', 'transport',
control, control,
form,
i,
current,
t,
)}
{renderGroupCard(
'security',
fields,
'security',
control,
form,
i,
current,
t,
)}
{renderGroupCard(
'reality',
fields,
'reality',
control,
form,
i,
current,
t,
)}
{renderGroupCard(
'encryption',
fields,
'encryption',
control,
form,
i, i,
current, current,
t, t,
)} )}
{renderGroupCard('security', fields, 'security', control, i, current, t)}
{renderGroupCard('reality', fields, 'reality', control, i, current, t)}
</div> </div>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
@@ -576,7 +651,8 @@ export default function ServerForm(props: {
return false; return false;
})} })}
> >
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />} {t('confirm')} {loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
{t('confirm')}
</Button> </Button>
</SheetFooter> </SheetFooter>
</SheetContent> </SheetContent>
@@ -0,0 +1,119 @@
'use client';
import { getNodeConfig } from '@/services/admin/system';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@workspace/ui/components/dialog';
import { Input } from '@workspace/ui/components/input';
import { Label } from '@workspace/ui/components/label';
import { useTranslations } from 'next-intl';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
type Props = {
server: API.Server;
};
export default function ServerInstall({ server }: Props) {
const t = useTranslations('servers');
const [open, setOpen] = useState(false);
const [domain, setDomain] = useState('');
const { data: cfgResp } = useQuery({
queryKey: ['getNodeConfig'],
queryFn: async () => {
const { data } = await getNodeConfig();
return data.data as API.NodeConfig | undefined;
},
enabled: open,
});
useEffect(() => {
if (open) {
const host = localStorage.getItem('API_HOST') ?? window.location.origin;
setDomain(host);
}
}, [open]);
const installCommand = useMemo(() => {
const secret = cfgResp?.node_secret ?? '';
return `wget -N https://raw.githubusercontent.com/perfect-panel/ppanel-node/master/scripts/install.sh && bash install.sh --api-host ${domain} --server-id ${server.id} --secret-key ${secret}`;
}, [domain, server.id, cfgResp?.node_secret]);
async function handleCopy() {
try {
if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(installCommand);
} else {
// fallback for environments without clipboard API
const el = document.createElement('textarea');
el.value = installCommand;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
toast.success(t('copied'));
setOpen(false);
} catch (error) {
toast.error(t('copyFailed'));
}
}
const onDomainChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setDomain(e.target.value);
localStorage.setItem('API_HOST', e.target.value);
}, []);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant='secondary'>{t('connect')}</Button>
</DialogTrigger>
<DialogContent className='w-[720px] max-w-full md:max-w-screen-md'>
<DialogHeader>
<DialogTitle>{t('oneClickInstall')}</DialogTitle>
</DialogHeader>
<div className='space-y-4'>
<div>
<Label>{t('apiHost')}</Label>
<div className='flex items-center gap-2'>
<Input
value={domain}
placeholder={t('apiHostPlaceholder')}
onChange={onDomainChange}
/>
</div>
</div>
<div>
<Label>{t('installCommand')}</Label>
<div className='flex flex-col gap-2'>
<textarea
readOnly
aria-label={t('installCommand')}
value={installCommand}
className='min-h-[88px] w-full rounded border p-2 font-mono text-sm'
/>
</div>
</div>
</div>
<DialogFooter className='flex-row justify-end gap-2 pt-3'>
<Button variant='outline' onClick={() => setOpen(false)}>
{t('close')}
</Button>
<Button onClick={handleCopy}>{t('copyAndClose')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -56,6 +56,7 @@ import { useRef, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import { subscribeSchema } from './schema';
import { TemplatePreview } from './template-preview'; import { TemplatePreview } from './template-preview';
const createClientFormSchema = (t: any) => const createClientFormSchema = (t: any) =>
@@ -313,7 +314,21 @@ export function ProtocolForm() {
columns={columns} columns={columns}
request={request} request={request}
header={{ header={{
title: <h2 className='text-lg font-semibold'>{t('protocol.title')}</h2>, title: (
<div className='flex items-center justify-between'>
<h2 className='text-lg font-semibold'>{t('protocol.title')}</h2>
<a
href='https://github.com/perfect-panel/subscription-template'
target='_blank'
rel='noreferrer'
className='text-primary inline-flex items-center gap-2 rounded-md px-3 py-1 text-sm font-medium hover:underline'
>
<Icon icon='mdi:github' className='h-4 w-4' />
<span>Template Repo</span>
<Icon icon='mdi:open-in-new' className='text-muted-foreground h-4 w-4' />
</a>
</div>
),
toolbar: <Button onClick={handleAdd}>{t('actions.add')}</Button>, toolbar: <Button onClick={handleAdd}>{t('actions.add')}</Button>,
}} }}
actions={{ actions={{
@@ -648,137 +663,7 @@ export function ProtocolForm() {
<FormControl> <FormControl>
<GoTemplateEditor <GoTemplateEditor
showLineNumbers showLineNumbers
schema={{ schema={subscribeSchema}
SiteName: { type: 'string', description: 'Site name' },
SubscribeName: { type: 'string', description: 'Subscribe name' },
Proxies: {
type: 'array',
description: 'Array of proxy nodes',
items: {
type: 'object',
properties: {
Name: { type: 'string', description: 'Node name' },
Server: { type: 'string', description: 'Server host' },
Port: { type: 'number', description: 'Server port' },
Type: { type: 'string', description: 'Proxy type' },
Tags: {
type: 'array',
description: 'Node tags',
items: { type: 'string' },
},
Sort: { type: 'number', description: 'Node sort order' },
// Security Options
Security: {
type: 'string',
description: 'Security protocol',
},
SNI: {
type: 'string',
description: 'Server Name Indication for TLS',
},
AllowInsecure: {
type: 'boolean',
description:
'Allow insecure connections (skip certificate verification)',
},
Fingerprint: {
type: 'string',
description: 'Client fingerprint for TLS connections',
},
RealityServerAddr: {
type: 'string',
description: 'Reality server address',
},
RealityServerPort: {
type: 'number',
description: 'Reality server port',
},
RealityPrivateKey: {
type: 'string',
description: 'Reality private key for authentication',
},
RealityPublicKey: {
type: 'string',
description: 'Reality public key for authentication',
},
RealityShortId: {
type: 'string',
description: 'Reality short ID for authentication',
},
// Transport Options
Transport: {
type: 'string',
description: 'Transport protocol (e.g., ws, http, grpc)',
},
Host: {
type: 'string',
description: 'For WebSocket/HTTP/HTTPS',
},
Path: { type: 'string', description: 'For HTTP/HTTPS' },
ServiceName: {
type: 'string',
description: 'For gRPC',
},
// Shadowsocks Options
Method: { type: 'string', description: 'Encryption method' },
ServerKey: {
type: 'string',
description: 'For Shadowsocks 2022',
},
// Vmess/Vless/Trojan Options
Flow: {
type: 'string',
description: 'Flow for Vmess/Vless/Trojan',
},
// Hysteria2 Options
HopPorts: {
type: 'string',
description: 'Comma-separated list of hop ports',
},
HopInterval: {
type: 'number',
description: 'Interval for hop ports in seconds',
},
ObfsPassword: {
type: 'string',
description: 'Obfuscation password for Hysteria2',
},
// Tuic Options
DisableSNI: {
type: 'boolean',
description: 'Disable SNI',
},
ReduceRtt: {
type: 'boolean',
description: 'Reduce RTT',
},
UDPRelayMode: {
type: 'string',
description: 'UDP relay mode (e.g., "full", "partial")',
},
CongestionController: {
type: 'string',
description: 'Congestion controller (e.g., "cubic", "bbr")',
},
},
},
},
UserInfo: {
type: 'object',
description: 'User information',
properties: {
Password: { type: 'string', description: 'User password' },
ExpiredAt: { type: 'string', description: 'Expiration date' },
Download: { type: 'number', description: 'Downloaded bytes' },
Upload: { type: 'number', description: 'Uploaded bytes' },
Traffic: { type: 'number', description: 'Total traffic bytes' },
SubscribeURL: {
type: 'string',
description: 'Subscription URL',
},
},
},
}}
enableSprig enableSprig
value={field.value || ''} value={field.value || ''}
onChange={(value) => field.onChange(value)} onChange={(value) => field.onChange(value)}
@@ -0,0 +1,225 @@
export const subscribeSchema = {
SiteName: { type: 'string', description: 'Site name' },
SubscribeName: { type: 'string', description: 'Subscribe name' },
Proxies: {
type: 'array',
description: 'Array of proxy nodes',
items: {
type: 'object',
properties: {
Name: { type: 'string', description: 'Node name' },
Server: { type: 'string', description: 'Server host' },
Port: { type: 'number', description: 'Server port' },
Type: { type: 'string', description: 'Proxy type' },
Tags: {
type: 'array',
description: 'Node tags',
items: { type: 'string' },
},
Sort: { type: 'number', description: 'Node sort order' },
// Security Options
Security: {
type: 'string',
description: 'Security protocol',
},
SNI: {
type: 'string',
description: 'Server Name Indication for TLS',
},
AllowInsecure: {
type: 'boolean',
description: 'Allow insecure connections (skip certificate verification)',
},
Fingerprint: {
type: 'string',
description: 'Client fingerprint for TLS connections',
},
RealityServerAddr: {
type: 'string',
description: 'Reality server address',
},
RealityServerPort: {
type: 'number',
description: 'Reality server port',
},
RealityPrivateKey: {
type: 'string',
description: 'Reality private key for authentication',
},
RealityPublicKey: {
type: 'string',
description: 'Reality public key for authentication',
},
RealityShortId: {
type: 'string',
description: 'Reality short ID for authentication',
},
// Transport Options
Transport: {
type: 'string',
description: 'Transport protocol (e.g., ws, http, grpc)',
},
Host: {
type: 'string',
description: 'For WebSocket/HTTP/HTTPS',
},
Path: { type: 'string', description: 'For HTTP/HTTPS' },
ServiceName: {
type: 'string',
description: 'For gRPC',
},
// Shadowsocks Options
Method: { type: 'string', description: 'Encryption method' },
ServerKey: {
type: 'string',
description: 'For Shadowsocks 2022',
},
// Vmess/Vless/Trojan Options
Flow: {
type: 'string',
description: 'Flow for Vmess/Vless/Trojan',
},
// Hysteria2 Options
HopPorts: {
type: 'string',
description: 'Comma-separated list of hop ports',
},
HopInterval: {
type: 'number',
description: 'Interval for hop ports in seconds',
},
ObfsPassword: {
type: 'string',
description: 'Obfuscation password for Hysteria2',
},
// Tuic Options
DisableSNI: {
type: 'boolean',
description: 'Disable SNI',
},
ReduceRtt: {
type: 'boolean',
description: 'Reduce RTT',
},
UDPRelayMode: {
type: 'string',
description: 'UDP relay mode (e.g., "full", "partial")',
},
CongestionController: {
type: 'string',
description: 'Congestion controller (e.g., "cubic", "bbr")',
},
// Hysteria2 additional options
UpMbps: {
type: 'number',
description: 'Upload bandwidth in Mbps',
},
DownMbps: {
type: 'number',
description: 'Download bandwidth in Mbps',
},
// VLESS encryption options
Encryption: {
type: 'string',
description: 'Encryption type for VLESS',
},
EncryptionMode: {
type: 'string',
description: 'Encryption mode (e.g., "native", "xorpub", "random")',
},
EncryptionRtt: {
type: 'string',
description: 'Encryption RTT (e.g., "0rtt", "1rtt")',
},
EncryptionTicket: {
type: 'string',
description: 'Encryption ticket',
},
EncryptionServerPadding: {
type: 'string',
description: 'Server padding for encryption',
},
EncryptionClientPadding: {
type: 'string',
description: 'Client padding for encryption',
},
EncryptionPassword: {
type: 'string',
description: 'Encryption password',
},
EncryptionPrivateKey: {
type: 'string',
description: 'Private key for encryption',
},
// XHTTP options
XhttpMode: {
type: 'string',
description: 'XHTTP mode (e.g., "auto", "packet-up", "stream-up", "stream-one")',
},
XhttpExtra: {
type: 'string',
description: 'XHTTP extra parameters',
},
// Shadowsocks obfs options (combined with Hysteria2 obfs)
ObfsHost: {
type: 'string',
description: 'Obfuscation host',
},
ObfsPath: {
type: 'string',
description: 'Obfuscation path',
},
// Shadowsocks cipher
Cipher: {
type: 'string',
description: 'Shadowsocks cipher method',
},
// AnyTLS options
PaddingScheme: {
type: 'string',
description: 'Padding scheme for AnyTLS',
},
// Mieru options
Multiplex: {
type: 'string',
description: 'Multiplex level (e.g., "none", "low", "middle", "high")',
},
// General protocol field
Enable: {
type: 'boolean',
description: 'Whether this protocol is enabled',
},
// UUID for vmess/vless
UUID: {
type: 'string',
description: 'User UUID for vmess/vless protocols',
},
// Alternative ID for vmess
AlterId: {
type: 'number',
description: 'Alternative ID for vmess (deprecated)',
},
// Password for trojan/tuic
Password: {
type: 'string',
description: 'Password for authentication',
},
},
},
},
UserInfo: {
type: 'object',
description: 'User information',
properties: {
Password: { type: 'string', description: 'User password' },
ExpiredAt: { type: 'string', description: 'Expiration date' },
Download: { type: 'number', description: 'Downloaded bytes' },
Upload: { type: 'number', description: 'Uploaded bytes' },
Traffic: { type: 'number', description: 'Total traffic bytes' },
SubscribeURL: {
type: 'string',
description: 'Subscription URL',
},
},
},
};
@@ -3,9 +3,15 @@
import { previewSubscribeTemplate } from '@/services/admin/application'; import { previewSubscribeTemplate } from '@/services/admin/application';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@workspace/ui/components/sheet'; import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@workspace/ui/components/sheet';
import { MonacoEditor } from '@workspace/ui/custom-components/editor/monaco-editor';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { Markdown } from '@workspace/ui/custom-components/markdown';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useState } from 'react'; import { useState } from 'react';
@@ -27,7 +33,6 @@ export function TemplatePreview({ applicationId, output_format }: TemplatePrevie
const originalContent = data?.data?.data?.template || ''; const originalContent = data?.data?.data?.template || '';
const errorMessage = (error as any)?.data?.msg || error?.message || t('failed'); const errorMessage = (error as any)?.data?.msg || error?.message || t('failed');
const displayContent = originalContent || (error ? errorMessage : '');
const getDecodedContent = () => { const getDecodedContent = () => {
if (output_format === 'base64' && originalContent) { if (output_format === 'base64' && originalContent) {
@@ -41,49 +46,61 @@ export function TemplatePreview({ applicationId, output_format }: TemplatePrevie
}; };
const getDisplayContent = () => { const getDisplayContent = () => {
if (error) return errorMessage;
if (!originalContent) return '';
switch (output_format) { switch (output_format) {
case 'base64': case 'base64': {
return `\`\`\`base64\n# ${t('base64.originalContent')}\n${displayContent}\n\n# ${t('base64.decodedContent')}\n${getDecodedContent()}\n\`\`\``; const decoded = getDecodedContent();
case 'yaml': return `${t('base64.originalContent')}:\n${originalContent}\n\n${t('base64.decodedContent')}:\n${decoded}`;
return `\`\`\`yaml\n${displayContent}\n\`\`\``; }
case 'json':
return `\`\`\`json\n${displayContent}\n\`\`\``;
case 'conf':
return `\`\`\`ini\n${displayContent}\n\`\`\``;
case 'plain':
return `\`\`\`text\n${displayContent}\n\`\`\``;
default: default:
return displayContent; return originalContent;
}
};
const mapLanguage = (fmt?: string) => {
switch (fmt) {
case 'json':
return 'json';
case 'yaml':
return 'yaml';
case 'base64':
return 'ini';
case 'plain':
return 'ini';
case 'conf':
return 'ini';
default:
return 'ini';
} }
}; };
const handleOpenChange = (newOpen: boolean) => {
setIsOpen(newOpen);
};
return ( return (
<> <Sheet open={isOpen} onOpenChange={setIsOpen}>
<Button variant='ghost' size='sm' onClick={() => setIsOpen(true)}> <SheetTrigger asChild>
<Icon icon='mdi:eye' className='mr-2 h-4 w-4' /> <Button variant='ghost'>
<Icon icon='mdi:eye' className='h-4 w-4' />
{t('preview')} {t('preview')}
</Button> </Button>
<Sheet open={isOpen} onOpenChange={handleOpenChange}> </SheetTrigger>
<SheetContent className='w-[800px] max-w-[90vw] md:max-w-screen-md'>
<SheetHeader> <SheetHeader>
<SheetTitle>{t('title')}</SheetTitle> <SheetTitle></SheetTitle>
</SheetHeader> </SheetHeader>
<SheetContent className='w-[800px] max-w-[90vw] pt-10 md:max-w-screen-md'>
{isLoading ? ( {isLoading ? (
<div className='flex items-center justify-center'> <div className='flex items-center justify-center'>
<Icon icon='mdi:loading' className='h-6 w-6 animate-spin' /> <Icon icon='mdi:loading' className='h-6 w-6 animate-spin' />
<span className='ml-2'>{t('loading')}</span> <span className='ml-2'>{t('loading')}</span>
</div> </div>
) : ( ) : (
<div className='*:text-sm [&_pre>div>div+div]:max-h-[calc(100dvh-48px-36px-36px)] [&_pre>div>div+div]:overflow-auto'> <MonacoEditor
<Markdown>{getDisplayContent()}</Markdown> title={t('title')}
</div> value={getDisplayContent()}
language={mapLanguage(output_format)}
showLineNumbers
readOnly
/>
)} )}
</SheetContent> </SheetContent>
</Sheet> </Sheet>
</>
); );
} }
@@ -1,7 +1,7 @@
'use client'; 'use client';
import { getSubscribeList } from '@/services/admin/subscribe';
import { getRegisterConfig, updateRegisterConfig } from '@/services/admin/system'; import { getRegisterConfig, updateRegisterConfig } from '@/services/admin/system';
import { useSubscribe } from '@/store/subscribe';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
@@ -61,17 +61,7 @@ export default function RegisterConfig() {
enabled: open, enabled: open,
}); });
const { data: subscribe } = useQuery({ const { subscribes } = useSubscribe();
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 9999,
});
return data.data?.list as API.Subscribe[];
},
enabled: open,
});
const form = useForm<RegisterFormData>({ const form = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema), resolver: zodResolver(registerSchema),
@@ -268,12 +258,10 @@ export default function RegisterConfig() {
field.onChange(value); field.onChange(value);
} }
}} }}
options={ options={subscribes?.map((item) => ({
subscribe?.map((item) => ({ label: item.name!,
label: item.name, value: item.id!,
value: item.id, }))}
})) || []
}
className='bg-secondary w-32 rounded-r-none' className='bg-secondary w-32 rounded-r-none'
/> />
)} )}
+164 -27
View File
@@ -1,8 +1,6 @@
'use client'; 'use client';
import { Display } from '@/components/display';
import { ProTable, ProTableActions } from '@/components/pro-table'; import { ProTable, ProTableActions } from '@/components/pro-table';
import { getSubscribeList } from '@/services/admin/subscribe';
import { import {
createUser, createUser,
deleteUser, deleteUser,
@@ -10,9 +8,9 @@ import {
getUserList, getUserList,
updateUserBasicInfo, updateUserBasicInfo,
} from '@/services/admin/user'; } from '@/services/admin/user';
import { useSubscribe } from '@/store/subscribe';
import { formatDate } from '@/utils/common'; import { formatDate } from '@/utils/common';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { import {
DropdownMenu, DropdownMenu,
@@ -20,6 +18,13 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@workspace/ui/components/dropdown-menu'; } from '@workspace/ui/components/dropdown-menu';
import { Input } from '@workspace/ui/components/input';
import {
Popover,
PopoverClose,
PopoverContent,
PopoverTrigger,
} from '@workspace/ui/components/popover';
import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { ScrollArea } from '@workspace/ui/components/scroll-area';
import { import {
Sheet, Sheet,
@@ -31,6 +36,7 @@ import {
import { Switch } from '@workspace/ui/components/switch'; import { Switch } from '@workspace/ui/components/switch';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button'; import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
import { FilePenLine } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import Link from 'next/link'; import Link from 'next/link';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
@@ -38,37 +44,88 @@ import { useRef, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { UserDetail } from './user-detail'; import { UserDetail } from './user-detail';
import UserForm from './user-form'; import UserForm from './user-form';
import { AuthMethodsForm } from './user-profile/auth-methods-form';
import { BasicInfoForm } from './user-profile/basic-info-form'; import { BasicInfoForm } from './user-profile/basic-info-form';
import { NotifySettingsForm } from './user-profile/notify-settings-form'; import { NotifySettingsForm } from './user-profile/notify-settings-form';
import UserSubscription from './user-subscription'; import UserSubscription from './user-subscription';
function getDeviceTypeInfo(userAgent = '') {
let deviceType = 'Unknown';
const ua = userAgent.toLowerCase();
if (ua.includes('android')) {
deviceType = 'Android';
} else if (ua.includes('iphone') || ua.includes('ios')) {
deviceType = 'iPhone';
} else if (ua.includes('ipad')) {
deviceType = 'iPad';
} else if (ua.includes('mac os') || ua.includes('mac')) {
deviceType = 'Mac';
} else if (ua.includes('windows')) {
deviceType = 'Windows';
} else if (ua.includes('linux')) {
deviceType = 'Linux';
}
return { deviceType };
}
// äļš RemarkForm įŧ„äŧķåۚäđ‰ props įąŧ型
interface RemarkFormProps {
initialRemark?: string | null;
onSave: (remark: string) => void;
CloseComponent: React.ComponentType<{ asChild?: boolean; children: React.ReactNode }>;
}
// æ–°įš„å­įŧ„äŧķåœĻįŪĄį†åŪƒč‡Šå·ąįš„å·æģĻįŠķ态
const RemarkForm: React.FC<RemarkFormProps> = ({ onSave, initialRemark, CloseComponent }) => {
const [remark, setRemark] = useState<string>(initialRemark ?? '');
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setRemark(event.target.value);
};
const handleSaveClick = () => {
onSave(remark);
};
return (
<>
<div className='mb-2 text-sm font-semibold'>å·æģĻ</div>
<Input
type='text'
value={remark}
onChange={handleInputChange}
placeholder='åœĻæ­Īčū“å…Ĩå·æģĻ...'
className='w-full'
/>
<CloseComponent asChild>
<Button onClick={handleSaveClick} variant='default' size={'sm'} className={'mt-2'}>
äŋå­˜
</Button>
</CloseComponent>
</>
);
};
export default function Page() { export default function Page() {
const t = useTranslations('user'); const t = useTranslations('user');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
const sp = useSearchParams(); const sp = useSearchParams();
const { data: subscribeList } = useQuery({ const { subscribes } = useSubscribe();
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 9999,
});
return data.data?.list as API.SubscribeGroup[];
},
});
const initialFilters = { const initialFilters = {
search: sp.get('search') || undefined, search: sp.get('search') || undefined,
user_id: sp.get('user_id') || undefined, user_id: sp.get('user_id') || undefined,
subscribe_id: sp.get('subscribe_id') || undefined, subscribe_id: sp.get('subscribe_id') || undefined,
user_subscribe_id: sp.get('user_subscribe_id') || undefined, user_subscribe_id: sp.get('user_subscribe_id') || undefined,
device_id: sp.get('device_id') || undefined,
}; };
return ( return (
<ProTable<API.User, API.GetUserListParams> <ProTable<API.User, API.GetUserListParams>
key={initialFilters.user_id}
action={ref} action={ref}
initialFilters={initialFilters} initialFilters={initialFilters}
header={{ header={{
@@ -136,20 +193,96 @@ export default function Page() {
}, },
{ {
accessorKey: 'auth_methods', accessorKey: 'auth_methods',
header: t('userName'), header: 'įŧ‘åۚé‚ŪįŪą',
cell: ({ row }) => { cell: ({ row }) => {
const method = row.original.auth_methods?.[0]; const method = row.original.auth_methods;
return ( return (
<div> <div>
<Badge className='mr-1 uppercase' title={method?.verified ? t('verified') : ''}> <Popover>
{method?.auth_type} <PopoverTrigger>
</Badge> <div className={'flex items-center'}>
{method?.auth_identifier} {method?.find((v) => v.auth_type === 'email')?.auth_identifier || 'åū…įŧ‘åۚ'}
{row.original?.remark ? `${row.original.remark}` : ''}
<FilePenLine size={14} className={'text-primary ml-2'} />
</div>
</PopoverTrigger>
<PopoverContent className={'w-64'}>
<RemarkForm
initialRemark={row.original.remark}
CloseComponent={PopoverClose}
onSave={async (remark) => {
const {
auth_methods,
user_devices,
enable_balance_notify,
enable_login_notify,
enable_subscribe_notify,
enable_trade_notify,
updated_at,
created_at,
id,
...rest
} = row.original;
await updateUserBasicInfo({
user_id: id,
...rest,
remark,
} as unknown as API.UpdateUserBasiceInfoRequest);
toast.success(t('updateSuccess'));
ref.current?.refresh();
}}
/>
</PopoverContent>
</Popover>
</div> </div>
); );
}, },
}, },
{ {
accessorKey: 'user_devices',
header: 'įŧ‘åۚčŪūå·',
cell: ({ row }) => {
const devices = row?.original.user_devices ?? [];
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
{devices.map((v, index) => {
const { deviceType } = getDeviceTypeInfo(v.user_agent);
return (
<div key={v.id + '_wrapper'}>
<div
style={{
padding: '4px 6px',
background: '#f8f8f8',
borderRadius: '4px',
border: '1px solid #e0e0e0',
fontSize: '12px',
lineHeight: '16px',
}}
>
<div style={{ fontWeight: 500 }}>
IDïžš{v.id}{deviceType}
</div>
</div>
{index !== devices.length - 1 && (
<div
style={{
height: '1px',
background: '#eee',
margin: '4px 0',
}}
></div>
)}
</div>
);
})}
</div>
);
},
},
/*{
accessorKey: 'balance', accessorKey: 'balance',
header: t('balance'), header: t('balance'),
cell: ({ row }) => <Display type='currency' value={row.getValue('balance')} />, cell: ({ row }) => <Display type='currency' value={row.getValue('balance')} />,
@@ -163,7 +296,7 @@ export default function Page() {
accessorKey: 'commission', accessorKey: 'commission',
header: t('commission'), header: t('commission'),
cell: ({ row }) => <Display type='currency' value={row.getValue('commission')} />, cell: ({ row }) => <Display type='currency' value={row.getValue('commission')} />,
}, },*/
{ {
accessorKey: 'refer_code', accessorKey: 'refer_code',
header: t('inviteCode'), header: t('inviteCode'),
@@ -194,9 +327,9 @@ export default function Page() {
{ {
key: 'subscribe_id', key: 'subscribe_id',
placeholder: t('subscription'), placeholder: t('subscription'),
options: subscribeList?.map((item) => ({ options: subscribes?.map((item) => ({
label: item.name, label: item.name!,
value: String(item.id), value: String(item.id!),
})), })),
}, },
{ {
@@ -211,6 +344,10 @@ export default function Page() {
key: 'user_subscribe_id', key: 'user_subscribe_id',
placeholder: t('subscriptionId'), placeholder: t('subscriptionId'),
}, },
{
key: 'device_id',
placeholder: 'čŪūå·id',
},
]} ]}
actions={{ actions={{
render: (row) => { render: (row) => {
@@ -289,7 +426,7 @@ function ProfileSheet({ userId }: { userId: number }) {
<TabsList className='mb-3'> <TabsList className='mb-3'>
<TabsTrigger value='basic'>{t('basicInfoTitle')}</TabsTrigger> <TabsTrigger value='basic'>{t('basicInfoTitle')}</TabsTrigger>
<TabsTrigger value='notify'>{t('notifySettingsTitle')}</TabsTrigger> <TabsTrigger value='notify'>{t('notifySettingsTitle')}</TabsTrigger>
<TabsTrigger value='auth'>{t('authMethodsTitle')}</TabsTrigger> {/*<TabsTrigger value='auth'>{t('authMethodsTitle')}</TabsTrigger>*/}
</TabsList> </TabsList>
<TabsContent value='basic' className='mt-0'> <TabsContent value='basic' className='mt-0'>
<BasicInfoForm user={user} refetch={refetch as any} /> <BasicInfoForm user={user} refetch={refetch as any} />
@@ -297,9 +434,9 @@ function ProfileSheet({ userId }: { userId: number }) {
<TabsContent value='notify' className='mt-0'> <TabsContent value='notify' className='mt-0'>
<NotifySettingsForm user={user} refetch={refetch as any} /> <NotifySettingsForm user={user} refetch={refetch as any} />
</TabsContent> </TabsContent>
<TabsContent value='auth' className='mt-0'> {/*<TabsContent value='auth' className='mt-0'>
<AuthMethodsForm user={user} refetch={refetch as any} /> <AuthMethodsForm user={user} refetch={refetch as any} />
</TabsContent> </TabsContent>*/}
</Tabs> </Tabs>
</ScrollArea> </ScrollArea>
)} )}
@@ -148,7 +148,8 @@ export function UserDetail({ id }: { id: number }) {
const identifier = const identifier =
data?.auth_methods.find((m) => m.auth_type === 'email')?.auth_identifier || data?.auth_methods.find((m) => m.auth_type === 'email')?.auth_identifier ||
data?.auth_methods[0]?.auth_identifier; `čŪūå·Idïžš${data?.user_devices[0]?.id}` ||
'čīĶ号äļå­˜åœĻ';
return ( return (
<HoverCard> <HoverCard>
@@ -170,6 +170,7 @@ export default function UserForm<T extends Record<string, any>>({
<FormLabel>{t('password')}</FormLabel> <FormLabel>{t('password')}</FormLabel>
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
autoComplete='new-password'
placeholder={t('passwordPlaceholder')} placeholder={t('passwordPlaceholder')}
{...field} {...field}
onValueChange={(value) => { onValueChange={(value) => {
@@ -2,6 +2,7 @@
import { Display } from '@/components/display'; import { Display } from '@/components/display';
import { ProTable, ProTableActions } from '@/components/pro-table'; import { ProTable, ProTableActions } from '@/components/pro-table';
import useGlobalStore from '@/config/use-global';
import { import {
createUserSubscribe, createUserSubscribe,
deleteUserSubscribe, deleteUserSubscribe,
@@ -28,6 +29,7 @@ export default function UserSubscription({ userId }: { userId: number }) {
const t = useTranslations('user'); const t = useTranslations('user');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null); const ref = useRef<ProTableActions>(null);
const { getUserSubscribeUrls } = useGlobalStore();
return ( return (
<ProTable<API.UserSubscribe, Record<string, unknown>> <ProTable<API.UserSubscribe, Record<string, unknown>>
@@ -146,6 +148,16 @@ export default function UserSubscription({ userId }: { userId: number }) {
return true; return true;
}} }}
/>, />,
<Button
key='copy'
variant='secondary'
onClick={async () => {
await navigator.clipboard.writeText(getUserSubscribeUrls(row.token)[0] || '');
toast.success(t('copySuccess'));
}}
>
{t('copySubscription')}
</Button>,
<ConfirmButton <ConfirmButton
key='delete' key='delete'
trigger={<Button variant='destructive'>{t('delete')}</Button>} trigger={<Button variant='destructive'>{t('delete')}</Button>}
@@ -1,8 +1,7 @@
'use client'; 'use client';
import { getSubscribeList } from '@/services/admin/subscribe'; import { useSubscribe } from '@/store/subscribe';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button'; import { Button } from '@workspace/ui/components/button';
import { import {
Form, Form,
@@ -74,16 +73,7 @@ export function SubscriptionForm({ trigger, title, loading, initialData, onSubmi
} }
}; };
const { data: subscribe } = useQuery({ const { subscribes } = useSubscribe();
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 9999,
});
return data.data?.list as API.Subscribe[];
},
});
return ( return (
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
@@ -117,9 +107,9 @@ export function SubscriptionForm({ trigger, title, loading, initialData, onSubmi
onChange={(value) => { onChange={(value) => {
form.setValue(field.name, value); form.setValue(field.name, value);
}} }}
options={subscribe?.map((item: API.Subscribe) => ({ options={subscribes?.map((item) => ({
value: item.id, value: item.id!,
label: item.name, label: item.name!,
}))} }))}
/> />
</FormControl> </FormControl>
@@ -86,7 +86,7 @@ export default function Statistics() {
value: ServerTotal?.online_users || 0, value: ServerTotal?.online_users || 0,
subtitle: t('currentlyOnline'), subtitle: t('currentlyOnline'),
icon: 'uil:users-alt', icon: 'uil:users-alt',
href: '/dashboard/users', href: '/dashboard/user',
color: 'text-blue-600 dark:text-blue-400', color: 'text-blue-600 dark:text-blue-400',
iconBg: 'bg-blue-100 dark:bg-blue-900/30', iconBg: 'bg-blue-100 dark:bg-blue-900/30',
}, },
+7
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import useGlobalStore, { GlobalStore } from '@/config/use-global'; import useGlobalStore, { GlobalStore } from '@/config/use-global';
import { useStatsStore } from '@/store/stats';
import { Logout } from '@/utils/common'; import { Logout } from '@/utils/common';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental';
@@ -42,6 +43,12 @@ export default function Providers({
setCommon(common); setCommon(common);
}, [setCommon, common]); }, [setCommon, common]);
const { stats } = useStatsStore();
useEffect(() => {
stats();
}, []);
return ( return (
<NextThemesProvider attribute='class' defaultTheme='system' enableSystem> <NextThemesProvider attribute='class' defaultTheme='system' enableSystem>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
+26 -1
View File
@@ -1,3 +1,5 @@
import { NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SITE_URL } from '@/config/constants';
import { extractDomain } from '@workspace/ui/utils';
import { create } from 'zustand'; import { create } from 'zustand';
export interface GlobalStore { export interface GlobalStore {
@@ -5,9 +7,10 @@ export interface GlobalStore {
user?: API.User; user?: API.User;
setCommon: (common: Partial<API.GetGlobalConfigResponse>) => void; setCommon: (common: Partial<API.GetGlobalConfigResponse>) => void;
setUser: (user?: API.User) => void; setUser: (user?: API.User) => void;
getUserSubscribeUrls: (uuid: string, type?: string) => string[];
} }
export const useGlobalStore = create<GlobalStore>((set) => ({ export const useGlobalStore = create<GlobalStore>((set, get) => ({
common: { common: {
site: { site: {
host: '', host: '',
@@ -42,6 +45,12 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
ip_register_limit: 0, ip_register_limit: 0,
ip_register_limit_duration: 0, ip_register_limit_duration: 0,
}, },
device: {
enable: false,
show_ads: false,
enable_security: false,
only_real_device: false,
},
}, },
invite: { invite: {
forced_invite: false, forced_invite: false,
@@ -77,6 +86,22 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
}, },
})), })),
setUser: (user) => set({ user }), setUser: (user) => set({ user }),
getUserSubscribeUrls: (uuid: string, type?: string) => {
const { pan_domain, subscribe_domain, subscribe_path } = get().common.subscribe || {};
const domains = subscribe_domain
? subscribe_domain.split('\n')
: [extractDomain(NEXT_PUBLIC_API_URL || NEXT_PUBLIC_SITE_URL || '', pan_domain)];
return domains.map((domain) => {
if (pan_domain) {
if (type) return `https://${uuid}.${type}.${domain}`;
return `https://${uuid}.${domain}`;
} else {
if (type) return `https://${domain}${subscribe_path}?token=${uuid}&type=${type}`;
return `https://${domain}${subscribe_path}?token=${uuid}`;
}
});
},
})); }));
export default useGlobalStore; export default useGlobalStore;
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Vyberte protokolâ€Ķ", "select_protocol": "Vyberte protokolâ€Ķ",
"select_server": "Vyberte serverâ€Ķ", "select_server": "Vyberte serverâ€Ķ",
"server": "Server", "server": "Server",
"sorted_success": "ÚspÄ›ÅĄně seřazeno",
"tags": "Štítky", "tags": "Štítky",
"tags_description": "Å títek pro skupinovÃĄní oprÃĄvnění (včetně vazby na plÃĄn a dodací politiky).", "tags_description": "Å títek pro skupinovÃĄní oprÃĄvnění (včetně vazby na plÃĄn a dodací politiky).",
"tags_placeholder": "PouÅūijte Enter nebo ÄÃĄrku (,) pro přidÃĄní více ÅĄtítkÅŊ", "tags_placeholder": "PouÅūijte Enter nebo ÄÃĄrku (,) pro přidÃĄní více ÅĄtítkÅŊ",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Adresa",
"address_placeholder": "Adresa serveru",
"bandwidth_placeholder": "Zadejte ÅĄÃ­Å™ku pÃĄsma, nechte prÃĄzdnÃĐ pro BBR",
"basic": "ZÃĄkladní konfigurace",
"cancel": "ZruÅĄit",
"city": "Město",
"config": {
"actions": { "actions": {
"cancel": "ZruÅĄit", "cancel": "ZruÅĄit",
"save": "UloÅūit" "save": "UloÅūit"
}, },
"communicationKey": "Komunikační klíč", "address": "Adresa",
"communicationKeyDescription": "PouÅūívÃĄ se pro autentizaci uzlu.", "address_placeholder": "Adresa serveru",
"description": "Spravujte klíče pro komunikaci uzlu, intervaly stahovÃĄní/odesílÃĄní a dynamickÃĐ multiplikÃĄtory.", "apiHost": "API hostitel",
"dynamicMultiplier": "DynamickÃ― multiplikÃĄtor", "apiHostPlaceholder": "http(s)://example.com",
"dynamicMultiplierDescription": "Definujte časovÃĐ sloty a multiplikÃĄtory pro Úpravu ÚčtovÃĄní provozu.", "bandwidth_placeholder": "Zadejte ÅĄÃ­Å™ku pÃĄsma, nechte prÃĄzdnÃĐ pro BBR",
"endTime": "Čas konce", "basic": "ZÃĄkladní konfigurace",
"inputPlaceholder": "Prosím zadejte", "cancel": "ZruÅĄit",
"multiplier": "MultiplikÃĄtor", "cert_dns_env": "DNS proměnnÃĐ prostředí",
"nodePullInterval": "Interval stahovÃĄní uzlu", "cert_dns_provider": "DNS poskytovatel",
"nodePullIntervalDescription": "Jak často uzel stahuje konfiguraci (vteřiny).", "cert_mode": "ReÅūim certifikÃĄtu",
"nodePushInterval": "Interval odesílÃĄní uzlu", "cipher": "Å ifrovací algoritmus",
"nodePushIntervalDescription": "Jak často uzel odesílÃĄ statistiky (vteřiny).", "city": "Město",
"reset": "Obnovit",
"save": "UloÅūit",
"saveSuccess": "ÚspÄ›ÅĄně uloÅūeno",
"startTime": "Čas zaÄÃĄtku",
"timeSlot": "ČasovÃ― slot",
"title": "Konfigurace uzlu"
},
"confirm": "Potvrdit", "confirm": "Potvrdit",
"confirmDeleteDesc": "Tuto akci nelze vrÃĄtit zpět.", "confirmDeleteDesc": "Tuto akci nelze vrÃĄtit zpět.",
"confirmDeleteTitle": "Smazat tento server?", "confirmDeleteTitle": "Smazat tento server?",
"congestion_controller": "Ovladač přetíÅūení", "congestion_controller": "Ovladač přetíÅūení",
"connect": "Připojit",
"copied": "ZkopírovÃĄno", "copied": "ZkopírovÃĄno",
"copy": "Kopírovat", "copy": "Kopírovat",
"country": "Země", "country": "Země",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Upravit server", "drawerEditTitle": "Upravit server",
"edit": "Upravit", "edit": "Upravit",
"enabled": "Povoleno", "enabled": "Povoleno",
"encryption_method": "Metoda ÅĄifrovÃĄní", "encryption": "Metoda ÅĄifrovÃĄní",
"encryption_client_padding": "Padding klienta",
"encryption_mode": "ReÅūim",
"encryption_password": "Heslo",
"encryption_password_placeholder": "Nechte prÃĄzdnÃĐ pro automatickou generaci, nahraďte ručně pro postkvantovÃĐ ÅĄifrovÃĄní",
"encryption_private_key": "SoukromÃ― klíč",
"encryption_private_key_placeholder": "Nechte prÃĄzdnÃĐ pro automatickou generaci, nahraďte ručně pro postkvantovÃĐ ÅĄifrovÃĄní",
"encryption_rtt": "RTT",
"encryption_server_padding": "Padding serveru",
"encryption_ticket": "Čas lístku",
"expireTime": "Čas vyprÅĄení", "expireTime": "Čas vyprÅĄení",
"expired": "VyprÅĄelo", "expired": "VyprÅĄelo",
"extra": "DalÅĄÃ­ konfigurace",
"flow": "Tok", "flow": "Tok",
"generate_quantum_resistant_key": "Generovat kvantově odolnÃ― klíč",
"generate_standard_encryption_key": "Generovat standardní ÅĄifrovací klíč",
"hop_interval": "Interval skoku", "hop_interval": "Interval skoku",
"hop_ports": "Porty skoku", "hop_ports": "Porty skoku",
"hop_ports_placeholder": "např. 1-65535", "hop_ports_placeholder": "např. 1-65535",
"host": "Hostitel", "host": "Hostitel",
"id": "ID", "id": "ID",
"installCommand": "Instalační příkaz",
"ipAddresses": "IP adresy", "ipAddresses": "IP adresy",
"memory": "PaměÅĨ", "memory": "PaměÅĨ",
"migrate": "Migrace dat", "migrate": "Migrace dat",
"migrateFailed": "Migrace dat se nezdařila", "migrateFailed": "Migrace dat se nezdařila",
"migrated": "Data byla ÚspÄ›ÅĄně migrovÃĄna", "migrated": "Data byla ÚspÄ›ÅĄně migrovÃĄna",
"migrating": "ProbíhÃĄ migrace...", "migrating": "ProbíhÃĄ migrace...",
"mode": "ReÅūim",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "NÃĄzev", "name": "NÃĄzev",
"noData": "Å―ÃĄdnÃĄ data", "noData": "Å―ÃĄdnÃĄ data",
"notAvailable": "Není k dispozici", "notAvailable": "Není k dispozici",
"obfs": "ObfuskovÃĄní",
"obfs_host": "Obfs hostitel",
"obfs_password": "Heslo pro obfuskaci", "obfs_password": "Heslo pro obfuskaci",
"obfs_password_placeholder": "Zadejte heslo pro obfuskaci", "obfs_password_placeholder": "Zadejte heslo pro obfuskaci",
"obfs_path": "Obfs cesta",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "Instalace jedním kliknutím",
"online": "Online", "online": "Online",
"onlineUsers": "Online uÅūivatelÃĐ", "onlineUsers": "Online uÅūivatelÃĐ",
"padding_scheme": "SchÃĐma vycpÃĄní", "padding_scheme": "SchÃĐma vycpÃĄní",
@@ -78,8 +83,6 @@
"pageTitle": "Servery", "pageTitle": "Servery",
"path": "Cesta", "path": "Cesta",
"please_select": "Prosím vyberte", "please_select": "Prosím vyberte",
"plugin": "Konfigurace pluginu",
"plugin_opts": "MoÅūnosti pluginu",
"port": "Port", "port": "Port",
"protocol_configurations": "Konfigurace protokolu", "protocol_configurations": "Konfigurace protokolu",
"protocol_configurations_desc": "Povolit a nakonfigurovat poÅūadovanÃĐ typy protokolÅŊ", "protocol_configurations_desc": "Povolit a nakonfigurovat poÅūadovanÃĐ typy protokolÅŊ",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "HexadecimÃĄlní řetězec (max. 16 znakÅŊ)", "security_short_id_placeholder": "HexadecimÃĄlní řetězec (max. 16 znakÅŊ)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Vyberte metodu ÅĄifrovÃĄní", "select_encryption_method": "Vyberte metodu ÅĄifrovÃĄní",
"server_config": {
"description": "Spravujte klíče pro komunikaci uzlu, intervaly stahovÃĄní/odesílÃĄní.",
"dynamic_multiplier": "DynamickÃ― multiplikÃĄtor",
"dynamic_multiplier_desc": "Definujte časovÃĐ sloty a multiplikÃĄtory pro Úpravu ÚčtovÃĄní provozu.",
"fields": {
"block_rules_placeholder": "Jedno pravidlo domÃĐny na Å™ÃĄdek, podporuje:\nkeyword:google (shoda podle klíčovÃĐho slova)\nsuffix:google.com (shoda podle přípony)\nregex:.*\\.example\\.com$ (shoda podle regulÃĄrního vÃ―razu)\nexample.com (přesnÃĄ shoda)",
"communication_key": "Klíč pro komunikaci",
"communication_key_desc": "PouÅūívÃĄ se pro autentizaci uzlu.",
"communication_key_placeholder": "Zadejte prosím",
"dns_config": "DNS konfigurace",
"dns_domains_placeholder": "Jedno pravidlo domÃĐny na Å™ÃĄdek, podporuje:\nkeyword:google (shoda podle klíčovÃĐho slova)\nsuffix:google.com (shoda podle přípony)\nregex:.*\\.example\\.com$ (shoda podle regulÃĄrního vÃ―razu)\nexample.com (přesnÃĄ shoda)",
"dns_proto_placeholder": "Vyberte typ",
"end_time": "Čas konce",
"ip_strategy": "IP strategie",
"ip_strategy_desc": "Vyberte preferenci verze IP pro síÅĨovÃĄ připojení",
"ip_strategy_ipv4": "Preferovat IPv4",
"ip_strategy_ipv6": "Preferovat IPv6",
"ip_strategy_placeholder": "Vyberte IP strategii",
"multiplier": "MultiplikÃĄtor",
"node_pull_interval": "Interval stahovÃĄní uzlu",
"node_pull_interval_desc": "Jak často uzel stahuje konfiguraci (v sekundÃĄch).",
"node_push_interval": "Interval odesílÃĄní uzlu",
"node_push_interval_desc": "Jak často uzel odesílÃĄ statistiky (v sekundÃĄch).",
"outbound_address_placeholder": "Adresa serveru",
"outbound_name_placeholder": "NÃĄzev konfigurace",
"outbound_password_placeholder": "Heslo (volitelnÃĐ)",
"outbound_port_placeholder": "Číslo portu",
"outbound_protocol_placeholder": "Vyberte protokol",
"outbound_rules_placeholder": "Jedno pravidlo na Å™ÃĄdek, podporuje:\nkeyword:google (shoda podle klíčovÃĐho slova)\nsuffix:google.com (shoda podle přípony)\nregex:.*\\.example\\.com$ (shoda podle regulÃĄrního vÃ―razu)\nexample.com (přesnÃĄ shoda)\nNechte prÃĄzdnÃĐ pro vÃ―chozí směrovÃĄní",
"reset": "Obnovit",
"save": "UloÅūit",
"start_time": "Čas zaÄÃĄtku",
"time_slot": "ČasovÃ― slot",
"traffic_report_threshold": "PrahovÃĄ hodnota zprÃĄvy o provozu",
"traffic_report_threshold_desc": "Nastavte minimÃĄlní prahovou hodnotu pro hlÃĄÅĄení o provozu. Provoz bude hlÃĄÅĄen pouze tehdy, kdyÅū překročí tuto hodnotu. Nastavte na 0 nebo nechte prÃĄzdnÃĐ pro hlÃĄÅĄení veÅĄkerÃĐho provozu."
},
"saveSuccess": "ÚspÄ›ÅĄně uloÅūeno",
"tabs": {
"basic": "ZÃĄkladní konfigurace",
"block": "Blokovací pravidla",
"dns": "DNS konfigurace",
"outbound": "Odchozí pravidla"
},
"title": "Konfigurace uzlu"
},
"server_key": "Klíč serveru", "server_key": "Klíč serveru",
"service_name": "NÃĄzev sluÅūby", "service_name": "NÃĄzev sluÅūby",
"sorted_success": "ÚspÄ›ÅĄně seřazeno",
"status": "Stav", "status": "Stav",
"subscribeId": "ID předplatnÃĐho", "subscribeId": "ID předplatnÃĐho",
"subscription": "PředplatnÃĐ", "subscription": "PředplatnÃĐ",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Potvrdit", "confirm": "Potvrdit",
"confirmDelete": "Opravdu chcete smazat?", "confirmDelete": "Opravdu chcete smazat?",
"confirmOffline": "Potvrdit offline", "confirmOffline": "Potvrdit offline",
"copySubscription": "Kopírovat předplatnÃĐ",
"copySuccess": "KopírovÃĄní bylo ÚspÄ›ÅĄnÃĐ",
"create": "Vytvořit", "create": "Vytvořit",
"createSubscription": "Vytvořit předplatnÃĐ", "createSubscription": "Vytvořit předplatnÃĐ",
"createSuccess": "Vytvoření ÚspÄ›ÅĄnÃĐ", "createSuccess": "Vytvoření ÚspÄ›ÅĄnÃĐ",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Protokoll auswÃĪhlenâ€Ķ", "select_protocol": "Protokoll auswÃĪhlenâ€Ķ",
"select_server": "Server auswÃĪhlenâ€Ķ", "select_server": "Server auswÃĪhlenâ€Ķ",
"server": "Server", "server": "Server",
"sorted_success": "Erfolgreich sortiert",
"tags": "Tags", "tags": "Tags",
"tags_description": "Berechtigungsgruppierungs-Tag (einschließlich Planbindung und Lieferrichtlinien).", "tags_description": "Berechtigungsgruppierungs-Tag (einschließlich Planbindung und Lieferrichtlinien).",
"tags_placeholder": "Verwenden Sie Enter oder Komma (,) um mehrere Tags hinzuzufÞgen", "tags_placeholder": "Verwenden Sie Enter oder Komma (,) um mehrere Tags hinzuzufÞgen",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Adresse",
"address_placeholder": "Serveradresse",
"bandwidth_placeholder": "Geben Sie die Bandbreite ein, lassen Sie das Feld leer fÞr BBR",
"basic": "Grundkonfiguration",
"cancel": "Abbrechen",
"city": "Stadt",
"config": {
"actions": { "actions": {
"cancel": "Abbrechen", "cancel": "Abbrechen",
"save": "Speichern" "save": "Speichern"
}, },
"communicationKey": "KommunikationsschlÞssel", "address": "Adresse",
"communicationKeyDescription": "Wird zur Authentifizierung des Knotens verwendet.", "address_placeholder": "Serveradresse",
"description": "Verwalten Sie die KommunikationsschlÞssel des Knotens, Pull/Push-Intervalle und dynamische Multiplikatoren.", "apiHost": "API-Host",
"dynamicMultiplier": "Dynamischer Multiplikator", "apiHostPlaceholder": "http(s)://beispiel.de",
"dynamicMultiplierDescription": "Definieren Sie Zeitfenster und Multiplikatoren zur Anpassung der Verkehrsabrechnung.", "bandwidth_placeholder": "Geben Sie die Bandbreite ein, lassen Sie das Feld leer fÞr BBR",
"endTime": "Endzeit", "basic": "Grundkonfiguration",
"inputPlaceholder": "Bitte eingeben", "cancel": "Abbrechen",
"multiplier": "Multiplikator", "cert_dns_env": "DNS-Umgebungsvariablen",
"nodePullInterval": "Knoten-Pull-Intervall", "cert_dns_provider": "DNS-Anbieter",
"nodePullIntervalDescription": "Wie oft der Knoten die Konfiguration abruft (Sekunden).", "cert_mode": "Zertifikatsmodus",
"nodePushInterval": "Knoten-Push-Intervall", "cipher": "VerschlÞsselungsalgorithmus",
"nodePushIntervalDescription": "Wie oft der Knoten Statistiken sendet (Sekunden).", "city": "Stadt",
"reset": "ZurÞcksetzen",
"save": "Speichern",
"saveSuccess": "Erfolgreich gespeichert",
"startTime": "Startzeit",
"timeSlot": "Zeitfenster",
"title": "Knoten-Konfiguration"
},
"confirm": "BestÃĪtigen", "confirm": "BestÃĪtigen",
"confirmDeleteDesc": "Diese Aktion kann nicht rÞckgÃĪngig gemacht werden.", "confirmDeleteDesc": "Diese Aktion kann nicht rÞckgÃĪngig gemacht werden.",
"confirmDeleteTitle": "Diesen Server lÃķschen?", "confirmDeleteTitle": "Diesen Server lÃķschen?",
"congestion_controller": "Staukontroller", "congestion_controller": "Staukontroller",
"connect": "Verbinden",
"copied": "Kopiert", "copied": "Kopiert",
"copy": "Kopieren", "copy": "Kopieren",
"country": "Land", "country": "Land",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Server bearbeiten", "drawerEditTitle": "Server bearbeiten",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"enabled": "Aktiviert", "enabled": "Aktiviert",
"encryption_method": "VerschlÞsselungsmethode", "encryption": "VerschlÞsselungsmethode",
"encryption_client_padding": "Client-Padding",
"encryption_mode": "Modus",
"encryption_password": "Passwort",
"encryption_password_placeholder": "Leer lassen fÞr automatische Generierung, manuell ersetzen fÞr post-quanten VerschlÞsselung",
"encryption_private_key": "Privater SchlÞssel",
"encryption_private_key_placeholder": "Leer lassen fÞr automatische Generierung, manuell ersetzen fÞr post-quanten VerschlÞsselung",
"encryption_rtt": "RTT",
"encryption_server_padding": "Server-Padding",
"encryption_ticket": "Ticketzeit",
"expireTime": "Ablaufzeit", "expireTime": "Ablaufzeit",
"expired": "Abgelaufen", "expired": "Abgelaufen",
"extra": "ZusÃĪtzliche Konfiguration",
"flow": "Fluss", "flow": "Fluss",
"generate_quantum_resistant_key": "Quantenresistenten SchlÞssel generieren",
"generate_standard_encryption_key": "Standard-VerschlÞsselungsschlÞssel generieren",
"hop_interval": "Hop-Intervall", "hop_interval": "Hop-Intervall",
"hop_ports": "Hop-Ports", "hop_ports": "Hop-Ports",
"hop_ports_placeholder": "z.B. 1-65535", "hop_ports_placeholder": "z.B. 1-65535",
"host": "Host", "host": "Host",
"id": "ID", "id": "ID",
"installCommand": "Installationsbefehl",
"ipAddresses": "IP-Adressen", "ipAddresses": "IP-Adressen",
"memory": "Speicher", "memory": "Speicher",
"migrate": "Daten migrieren", "migrate": "Daten migrieren",
"migrateFailed": "Datenmigration fehlgeschlagen", "migrateFailed": "Datenmigration fehlgeschlagen",
"migrated": "Daten erfolgreich migriert", "migrated": "Daten erfolgreich migriert",
"migrating": "Wird migriert...", "migrating": "Wird migriert...",
"mode": "Modus",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Name", "name": "Name",
"noData": "Keine Daten", "noData": "Keine Daten",
"notAvailable": "Nicht verfÞgbar", "notAvailable": "Nicht verfÞgbar",
"obfs": "Obfuskation",
"obfs_host": "Obfs-Host",
"obfs_password": "Obfuskationspasswort", "obfs_password": "Obfuskationspasswort",
"obfs_password_placeholder": "Obfuskationspasswort eingeben", "obfs_password_placeholder": "Obfuskationspasswort eingeben",
"obfs_path": "Obfs-Pfad",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "Ein-Klick-Installation",
"online": "Online", "online": "Online",
"onlineUsers": "Online-Benutzer", "onlineUsers": "Online-Benutzer",
"padding_scheme": "Polsterungsschema", "padding_scheme": "Polsterungsschema",
@@ -78,8 +83,6 @@
"pageTitle": "Server", "pageTitle": "Server",
"path": "Pfad", "path": "Pfad",
"please_select": "Bitte auswÃĪhlen", "please_select": "Bitte auswÃĪhlen",
"plugin": "Plugin-Konfiguration",
"plugin_opts": "Plugin-Optionen",
"port": "Port", "port": "Port",
"protocol_configurations": "Protokollkonfigurationen", "protocol_configurations": "Protokollkonfigurationen",
"protocol_configurations_desc": "Aktivieren und konfigurieren Sie die erforderlichen Protokolltypen", "protocol_configurations_desc": "Aktivieren und konfigurieren Sie die erforderlichen Protokolltypen",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Hex-String (bis zu 16 Zeichen)", "security_short_id_placeholder": "Hex-String (bis zu 16 Zeichen)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "VerschlÞsselungsmethode auswÃĪhlen", "select_encryption_method": "VerschlÞsselungsmethode auswÃĪhlen",
"server_config": {
"description": "Verwalten Sie die KommunikationsschlÞssel des Knotens, Pull/Push-Intervalle.",
"dynamic_multiplier": "Dynamischer Multiplikator",
"dynamic_multiplier_desc": "Definieren Sie Zeitfenster und Multiplikatoren zur Anpassung der Verkehrserfassung.",
"fields": {
"block_rules_placeholder": "Eine Domainregel pro Zeile, unterstÞtzt:\nkeyword:google (SchlÞsselwortÞbereinstimmung)\nsuffix:google.com (SuffixÞbereinstimmung)\nregex:.*\\.example\\.com$ (Regex-Übereinstimmung)\nexample.com (exakte Übereinstimmung)",
"communication_key": "KommunikationsschlÞssel",
"communication_key_desc": "Wird zur Authentifizierung des Knotens verwendet.",
"communication_key_placeholder": "Bitte eingeben",
"dns_config": "DNS-Konfiguration",
"dns_domains_placeholder": "Eine Domainregel pro Zeile, unterstÞtzt:\nkeyword:google (SchlÞsselwortÞbereinstimmung)\nsuffix:google.com (SuffixÞbereinstimmung)\nregex:.*\\.example\\.com$ (Regex-Übereinstimmung)\nexample.com (exakte Übereinstimmung)",
"dns_proto_placeholder": "Typ auswÃĪhlen",
"end_time": "Endzeit",
"ip_strategy": "IP-Strategie",
"ip_strategy_desc": "WÃĪhlen Sie die bevorzugte IP-Version fÞr Netzwerkverbindungen",
"ip_strategy_ipv4": "Bevorzuge IPv4",
"ip_strategy_ipv6": "Bevorzuge IPv6",
"ip_strategy_placeholder": "IP-Strategie auswÃĪhlen",
"multiplier": "Multiplikator",
"node_pull_interval": "Knoten-Pull-Intervall",
"node_pull_interval_desc": "Wie oft der Knoten die Konfiguration abruft (Sekunden).",
"node_push_interval": "Knoten-Push-Intervall",
"node_push_interval_desc": "Wie oft der Knoten Statistiken sendet (Sekunden).",
"outbound_address_placeholder": "Serveradresse",
"outbound_name_placeholder": "Konfigurationsname",
"outbound_password_placeholder": "Passwort (optional)",
"outbound_port_placeholder": "Portnummer",
"outbound_protocol_placeholder": "Protokoll auswÃĪhlen",
"outbound_rules_placeholder": "Eine Regel pro Zeile, unterstÞtzt:\nkeyword:google (SchlÞsselwortÞbereinstimmung)\nsuffix:google.com (SuffixÞbereinstimmung)\nregex:.*\\.example\\.com$ (Regex-Übereinstimmung)\nexample.com (exakte Übereinstimmung)\nLeer lassen fÞr Standardrouting",
"reset": "ZurÞcksetzen",
"save": "Speichern",
"start_time": "Startzeit",
"time_slot": "Zeitfenster",
"traffic_report_threshold": "Schwellenwert fÞr Verkehrsberichte",
"traffic_report_threshold_desc": "Legen Sie den Mindestschwellenwert fÞr die Verkehrsmeldung fest. Verkehr wird nur gemeldet, wenn er diesen Wert Þberschreitet. Auf 0 setzen oder leer lassen, um gesamten Verkehr zu melden."
},
"saveSuccess": "Erfolgreich gespeichert",
"tabs": {
"basic": "Grundkonfiguration",
"block": "Blockierungsregeln",
"dns": "DNS-Konfiguration",
"outbound": "Ausgehende Regeln"
},
"title": "Knoten-Konfiguration"
},
"server_key": "Server-SchlÞssel", "server_key": "Server-SchlÞssel",
"service_name": "Dienstname", "service_name": "Dienstname",
"sorted_success": "Erfolgreich sortiert",
"status": "Status", "status": "Status",
"subscribeId": "Abonnement-ID", "subscribeId": "Abonnement-ID",
"subscription": "Abonnement", "subscription": "Abonnement",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "BestÃĪtigen", "confirm": "BestÃĪtigen",
"confirmDelete": "Sind Sie sicher, dass Sie lÃķschen mÃķchten?", "confirmDelete": "Sind Sie sicher, dass Sie lÃķschen mÃķchten?",
"confirmOffline": "Offline bestÃĪtigen", "confirmOffline": "Offline bestÃĪtigen",
"copySubscription": "Abonnement kopieren",
"copySuccess": "Kopie erfolgreich",
"create": "Erstellen", "create": "Erstellen",
"createSubscription": "Abonnement erstellen", "createSubscription": "Abonnement erstellen",
"createSuccess": "Erstellung erfolgreich", "createSuccess": "Erstellung erfolgreich",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Select protocolâ€Ķ", "select_protocol": "Select protocolâ€Ķ",
"select_server": "Select serverâ€Ķ", "select_server": "Select serverâ€Ķ",
"server": "Server", "server": "Server",
"sorted_success": "Sorted successfully",
"tags": "Tags", "tags": "Tags",
"tags_description": "Permission grouping tag (incl. plan binding and delivery policies).", "tags_description": "Permission grouping tag (incl. plan binding and delivery policies).",
"tags_placeholder": "Use Enter or comma (,) to add multiple tags", "tags_placeholder": "Use Enter or comma (,) to add multiple tags",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Address",
"address_placeholder": "Server address",
"bandwidth_placeholder": "Enter bandwidth, leave empty for BBR",
"basic": "Basic Configuration",
"cancel": "Cancel",
"city": "City",
"config": {
"title": "Node configuration",
"description": "Manage node communication keys, pull/push intervals, and dynamic multipliers.",
"saveSuccess": "Saved successfully",
"communicationKey": "Communication key",
"inputPlaceholder": "Please enter",
"communicationKeyDescription": "Used for node authentication.",
"nodePullInterval": "Node pull interval",
"nodePullIntervalDescription": "How often the node pulls configuration (seconds).",
"nodePushInterval": "Node push interval",
"nodePushIntervalDescription": "How often the node pushes stats (seconds).",
"dynamicMultiplier": "Dynamic multiplier",
"dynamicMultiplierDescription": "Define time slots and multipliers to adjust traffic accounting.",
"startTime": "Start time",
"endTime": "End time",
"multiplier": "Multiplier",
"reset": "Reset",
"save": "Save",
"timeSlot": "Time slot",
"actions": { "actions": {
"cancel": "Cancel", "cancel": "Cancel",
"save": "Save" "save": "Save"
}
}, },
"address": "Address",
"address_placeholder": "Server address",
"apiHost": "API Host",
"apiHostPlaceholder": "http(s)://example.com",
"bandwidth_placeholder": "Enter bandwidth, leave empty for BBR",
"basic": "Basic Configuration",
"cancel": "Cancel",
"cert_dns_env": "DNS Environment Variables",
"cert_dns_provider": "DNS Provider",
"cert_mode": "Certificate Mode",
"cipher": "Encryption Algorithm",
"city": "City",
"confirm": "Confirm", "confirm": "Confirm",
"confirmDeleteDesc": "This action cannot be undone.", "confirmDeleteDesc": "This action cannot be undone.",
"confirmDeleteTitle": "Delete this server?", "confirmDeleteTitle": "Delete this server?",
"congestion_controller": "Congestion controller", "congestion_controller": "Congestion controller",
"connect": "Connect",
"copied": "Copied", "copied": "Copied",
"copy": "Copy", "copy": "Copy",
"country": "Country", "country": "Country",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Edit Server", "drawerEditTitle": "Edit Server",
"edit": "Edit", "edit": "Edit",
"enabled": "Enabled", "enabled": "Enabled",
"encryption_method": "Encryption method", "encryption": "Encryption Method",
"encryption_client_padding": "Client Padding",
"encryption_mode": "Mode",
"encryption_password": "Password",
"encryption_password_placeholder": "Leave empty for auto-generation, replace manually for post-quantum encryption",
"encryption_private_key": "Private Key",
"encryption_private_key_placeholder": "Leave empty for auto-generation, replace manually for post-quantum encryption",
"encryption_rtt": "RTT",
"encryption_server_padding": "Server Padding",
"encryption_ticket": "Ticket time",
"expireTime": "Expire time", "expireTime": "Expire time",
"expired": "Expired", "expired": "Expired",
"extra": "Extra Configuration",
"flow": "Flow", "flow": "Flow",
"generate_quantum_resistant_key": "Generate Quantum-Resistant Key",
"generate_standard_encryption_key": "Generate Standard Encryption Key",
"hop_interval": "Hop interval", "hop_interval": "Hop interval",
"hop_ports": "Hop ports", "hop_ports": "Hop ports",
"hop_ports_placeholder": "e.g. 1-65535", "hop_ports_placeholder": "e.g. 1-65535",
"host": "Host", "host": "Host",
"id": "ID", "id": "ID",
"installCommand": "Install command",
"ipAddresses": "IP addresses", "ipAddresses": "IP addresses",
"memory": "Memory", "memory": "Memory",
"migrate": "Migrate Data", "migrate": "Migrate Data",
"migrateFailed": "Data migration failed", "migrateFailed": "Data migration failed",
"migrated": "Data migrated successfully", "migrated": "Data migrated successfully",
"migrating": "Migrating...", "migrating": "Migrating...",
"mode": "Mode",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Name", "name": "Name",
"noData": "No data", "noData": "No data",
"notAvailable": "N/A", "notAvailable": "N/A",
"obfs": "Obfuscation",
"obfs_host": "Obfs Host",
"obfs_password": "Obfuscation password", "obfs_password": "Obfuscation password",
"obfs_password_placeholder": "Enter obfuscation password", "obfs_password_placeholder": "Enter obfuscation password",
"obfs_path": "Obfs Path",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "One-click Install",
"online": "Online", "online": "Online",
"onlineUsers": "Online users", "onlineUsers": "Online users",
"padding_scheme": "Padding Scheme", "padding_scheme": "Padding Scheme",
@@ -78,8 +83,6 @@
"pageTitle": "Servers", "pageTitle": "Servers",
"path": "Path", "path": "Path",
"please_select": "Please select", "please_select": "Please select",
"plugin": "Plugin Configuration",
"plugin_opts": "Plugin Options",
"port": "Port", "port": "Port",
"protocol_configurations": "Protocol Configurations", "protocol_configurations": "Protocol Configurations",
"protocol_configurations_desc": "Enable and configure the required protocol types", "protocol_configurations_desc": "Enable and configure the required protocol types",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Hex string (up to 16 chars)", "security_short_id_placeholder": "Hex string (up to 16 chars)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Select encryption method", "select_encryption_method": "Select encryption method",
"server_config": {
"title": "Node configuration",
"description": "Manage node communication keys, pull/push intervals.",
"saveSuccess": "Saved successfully",
"dynamic_multiplier": "Dynamic multiplier",
"dynamic_multiplier_desc": "Define time slots and multipliers to adjust traffic accounting.",
"tabs": {
"basic": "Basic Configuration",
"dns": "DNS Configuration",
"outbound": "Outbound Rules",
"block": "Block Rules"
},
"fields": {
"communication_key": "Communication key",
"communication_key_placeholder": "Please enter",
"communication_key_desc": "Used for node authentication.",
"node_pull_interval": "Node pull interval",
"node_pull_interval_desc": "How often the node pulls configuration (seconds).",
"node_push_interval": "Node push interval",
"node_push_interval_desc": "How often the node pushes stats (seconds).",
"start_time": "Start time",
"end_time": "End time",
"multiplier": "Multiplier",
"reset": "Reset",
"save": "Save",
"time_slot": "Time slot",
"traffic_report_threshold": "Traffic Report Threshold",
"traffic_report_threshold_desc": "Set the minimum threshold for traffic reporting. Traffic will only be reported when it exceeds this value. Set to 0 or leave empty to report all traffic.",
"ip_strategy": "IP Strategy",
"ip_strategy_desc": "Choose IP version preference for network connections",
"ip_strategy_placeholder": "Select IP strategy",
"ip_strategy_ipv4": "Prefer IPv4",
"ip_strategy_ipv6": "Prefer IPv6",
"dns_config": "DNS Configuration",
"dns_proto_placeholder": "Select type",
"dns_domains_placeholder": "One domain rule per line, supports:\nkeyword:google (keyword matching)\nsuffix:google.com (suffix matching)\nregex:.*\\.example\\.com$ (regex matching)\nexample.com (exact matching)",
"outbound_protocol_placeholder": "Select protocol",
"outbound_name_placeholder": "Configuration name",
"outbound_address_placeholder": "Server address",
"outbound_port_placeholder": "Port number",
"outbound_password_placeholder": "Password (optional)",
"outbound_rules_placeholder": "One rule per line, supports:\nkeyword:google (keyword matching)\nsuffix:google.com (suffix matching)\nregex:.*\\.example\\.com$ (regex matching)\nexample.com (exact matching)\nLeave empty for default routing",
"block_rules_placeholder": "One domain rule per line, supports:\nkeyword:google (keyword matching)\nsuffix:google.com (suffix matching)\nregex:.*\\.example\\.com$ (regex matching)\nexample.com (exact matching)"
}
},
"server_key": "Server key", "server_key": "Server key",
"service_name": "Service name", "service_name": "Service name",
"sorted_success": "Sorted successfully",
"status": "Status", "status": "Status",
"subscribeId": "Subscription ID", "subscribeId": "Subscription ID",
"subscription": "Subscription", "subscription": "Subscription",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Confirm", "confirm": "Confirm",
"confirmDelete": "Confirm Delete", "confirmDelete": "Confirm Delete",
"confirmOffline": "Confirm Offline", "confirmOffline": "Confirm Offline",
"copySubscription": "Copy Subscription",
"copySuccess": "Copy successful",
"create": "Create", "create": "Create",
"createSubscription": "Create Subscription", "createSubscription": "Create Subscription",
"createSuccess": "Create successful", "createSuccess": "Create successful",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Seleccionar protocoloâ€Ķ", "select_protocol": "Seleccionar protocoloâ€Ķ",
"select_server": "Seleccionar servidorâ€Ķ", "select_server": "Seleccionar servidorâ€Ķ",
"server": "Servidor", "server": "Servidor",
"sorted_success": "Ordenado con ÃĐxito",
"tags": "Etiquetas", "tags": "Etiquetas",
"tags_description": "Etiqueta de agrupaciÃģn de permisos (incl. vinculaciÃģn de planes y políticas de entrega).", "tags_description": "Etiqueta de agrupaciÃģn de permisos (incl. vinculaciÃģn de planes y políticas de entrega).",
"tags_placeholder": "Usa Enter o coma (,) para aÃąadir mÚltiples etiquetas", "tags_placeholder": "Usa Enter o coma (,) para aÃąadir mÚltiples etiquetas",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "DirecciÃģn",
"address_placeholder": "DirecciÃģn del servidor",
"bandwidth_placeholder": "Introduce el ancho de banda, deja vacío para BBR",
"basic": "ConfiguraciÃģn BÃĄsica",
"cancel": "Cancelar",
"city": "Ciudad",
"config": {
"actions": { "actions": {
"cancel": "Cancelar", "cancel": "Cancelar",
"save": "Guardar" "save": "Guardar"
}, },
"communicationKey": "Clave de comunicaciÃģn", "address": "DirecciÃģn",
"communicationKeyDescription": "Utilizado para la autenticaciÃģn del nodo.", "address_placeholder": "DirecciÃģn del servidor",
"description": "Gestionar las claves de comunicaciÃģn del nodo, intervalos de extracciÃģn/empuje y multiplicadores dinÃĄmicos.", "apiHost": "Host de API",
"dynamicMultiplier": "Multiplicador dinÃĄmico", "apiHostPlaceholder": "http(s)://ejemplo.com",
"dynamicMultiplierDescription": "Definir intervalos de tiempo y multiplicadores para ajustar la contabilidad del trÃĄfico.", "bandwidth_placeholder": "Introduce el ancho de banda, deja vacío para BBR",
"endTime": "Hora de finalizaciÃģn", "basic": "ConfiguraciÃģn BÃĄsica",
"inputPlaceholder": "Por favor ingrese", "cancel": "Cancelar",
"multiplier": "Multiplicador", "cert_dns_env": "Variables de Entorno DNS",
"nodePullInterval": "Intervalo de extracciÃģn del nodo", "cert_dns_provider": "Proveedor de DNS",
"nodePullIntervalDescription": "Con quÃĐ frecuencia el nodo extrae la configuraciÃģn (segundos).", "cert_mode": "Modo de Certificado",
"nodePushInterval": "Intervalo de empuje del nodo", "cipher": "Algoritmo de Cifrado",
"nodePushIntervalDescription": "Con quÃĐ frecuencia el nodo envía estadísticas (segundos).", "city": "Ciudad",
"reset": "Restablecer",
"save": "Guardar",
"saveSuccess": "Guardado con ÃĐxito",
"startTime": "Hora de inicio",
"timeSlot": "Intervalo de tiempo",
"title": "ConfiguraciÃģn del nodo"
},
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDeleteDesc": "Esta acciÃģn no se puede deshacer.", "confirmDeleteDesc": "Esta acciÃģn no se puede deshacer.",
"confirmDeleteTitle": "ÂŋEliminar este servidor?", "confirmDeleteTitle": "ÂŋEliminar este servidor?",
"congestion_controller": "Controlador de congestiÃģn", "congestion_controller": "Controlador de congestiÃģn",
"connect": "Conectar",
"copied": "Copiado", "copied": "Copiado",
"copy": "Copiar", "copy": "Copiar",
"country": "País", "country": "País",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Editar Servidor", "drawerEditTitle": "Editar Servidor",
"edit": "Editar", "edit": "Editar",
"enabled": "Habilitado", "enabled": "Habilitado",
"encryption_method": "MÃĐtodo de cifrado", "encryption": "MÃĐtodo de Cifrado",
"encryption_client_padding": "Relleno del Cliente",
"encryption_mode": "Modo",
"encryption_password": "ContraseÃąa",
"encryption_password_placeholder": "Dejar vacío para auto-generaciÃģn, reemplazar manualmente para cifrado post-cuÃĄntico",
"encryption_private_key": "Clave Privada",
"encryption_private_key_placeholder": "Dejar vacío para auto-generaciÃģn, reemplazar manualmente para cifrado post-cuÃĄntico",
"encryption_rtt": "RTT",
"encryption_server_padding": "Relleno del Servidor",
"encryption_ticket": "Tiempo de Ticket",
"expireTime": "Tiempo de expiraciÃģn", "expireTime": "Tiempo de expiraciÃģn",
"expired": "Expirado", "expired": "Expirado",
"extra": "ConfiguraciÃģn Extra",
"flow": "Flujo", "flow": "Flujo",
"generate_quantum_resistant_key": "Generar clave resistente a cuÃĄnticos",
"generate_standard_encryption_key": "Generar clave de cifrado estÃĄndar",
"hop_interval": "Intervalo de salto", "hop_interval": "Intervalo de salto",
"hop_ports": "Puertos de salto", "hop_ports": "Puertos de salto",
"hop_ports_placeholder": "p. ej. 1-65535", "hop_ports_placeholder": "p. ej. 1-65535",
"host": "Host", "host": "Host",
"id": "ID", "id": "ID",
"installCommand": "Comando de instalaciÃģn",
"ipAddresses": "Direcciones IP", "ipAddresses": "Direcciones IP",
"memory": "Memoria", "memory": "Memoria",
"migrate": "Migrar datos", "migrate": "Migrar datos",
"migrateFailed": "La migraciÃģn de datos fallÃģ", "migrateFailed": "La migraciÃģn de datos fallÃģ",
"migrated": "Datos migrados con ÃĐxito", "migrated": "Datos migrados con ÃĐxito",
"migrating": "Migrando...", "migrating": "Migrando...",
"mode": "Modo",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Nombre", "name": "Nombre",
"noData": "Sin datos", "noData": "Sin datos",
"notAvailable": "N/A", "notAvailable": "N/A",
"obfs": "OfuscaciÃģn",
"obfs_host": "Host de OfuscaciÃģn",
"obfs_password": "ContraseÃąa de ofuscaciÃģn", "obfs_password": "ContraseÃąa de ofuscaciÃģn",
"obfs_password_placeholder": "Ingrese la contraseÃąa de ofuscaciÃģn", "obfs_password_placeholder": "Ingrese la contraseÃąa de ofuscaciÃģn",
"obfs_path": "Ruta de OfuscaciÃģn",
"offline": "Desconectado", "offline": "Desconectado",
"oneClickInstall": "InstalaciÃģn con un clic",
"online": "Conectado", "online": "Conectado",
"onlineUsers": "Usuarios en línea", "onlineUsers": "Usuarios en línea",
"padding_scheme": "Esquema de Relleno", "padding_scheme": "Esquema de Relleno",
@@ -78,8 +83,6 @@
"pageTitle": "Servidores", "pageTitle": "Servidores",
"path": "Ruta", "path": "Ruta",
"please_select": "Por favor seleccione", "please_select": "Por favor seleccione",
"plugin": "ConfiguraciÃģn del Plugin",
"plugin_opts": "Opciones del Plugin",
"port": "Puerto", "port": "Puerto",
"protocol_configurations": "Configuraciones de Protocolo", "protocol_configurations": "Configuraciones de Protocolo",
"protocol_configurations_desc": "Habilitar y configurar los tipos de protocolo requeridos", "protocol_configurations_desc": "Habilitar y configurar los tipos de protocolo requeridos",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Cadena hexadecimal (hasta 16 caracteres)", "security_short_id_placeholder": "Cadena hexadecimal (hasta 16 caracteres)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Seleccionar mÃĐtodo de cifrado", "select_encryption_method": "Seleccionar mÃĐtodo de cifrado",
"server_config": {
"description": "Gestionar claves de comunicaciÃģn del nodo, intervalos de extracciÃģn/push.",
"dynamic_multiplier": "Multiplicador dinÃĄmico",
"dynamic_multiplier_desc": "Definir intervalos de tiempo y multiplicadores para ajustar la contabilidad del trÃĄfico.",
"fields": {
"block_rules_placeholder": "Una regla de dominio por línea, soporta:\nkeyword:google (coincidencia de palabras clave)\nsuffix:google.com (coincidencia de sufijo)\nregex:.*\\.example\\.com$ (coincidencia de regex)\nexample.com (coincidencia exacta)",
"communication_key": "Clave de comunicaciÃģn",
"communication_key_desc": "Utilizado para la autenticaciÃģn del nodo.",
"communication_key_placeholder": "Por favor ingrese",
"dns_config": "ConfiguraciÃģn de DNS",
"dns_domains_placeholder": "Una regla de dominio por línea, soporta:\nkeyword:google (coincidencia de palabras clave)\nsuffix:google.com (coincidencia de sufijo)\nregex:.*\\.example\\.com$ (coincidencia de regex)\nexample.com (coincidencia exacta)",
"dns_proto_placeholder": "Seleccionar tipo",
"end_time": "Hora de finalizaciÃģn",
"ip_strategy": "Estrategia de IP",
"ip_strategy_desc": "Elija la preferencia de versiÃģn de IP para conexiones de red",
"ip_strategy_ipv4": "Preferir IPv4",
"ip_strategy_ipv6": "Preferir IPv6",
"ip_strategy_placeholder": "Seleccionar estrategia de IP",
"multiplier": "Multiplicador",
"node_pull_interval": "Intervalo de extracciÃģn del nodo",
"node_pull_interval_desc": "Con quÃĐ frecuencia el nodo extrae la configuraciÃģn (segundos).",
"node_push_interval": "Intervalo de push del nodo",
"node_push_interval_desc": "Con quÃĐ frecuencia el nodo envía estadísticas (segundos).",
"outbound_address_placeholder": "DirecciÃģn del servidor",
"outbound_name_placeholder": "Nombre de la configuraciÃģn",
"outbound_password_placeholder": "ContraseÃąa (opcional)",
"outbound_port_placeholder": "NÚmero de puerto",
"outbound_protocol_placeholder": "Seleccionar protocolo",
"outbound_rules_placeholder": "Una regla por línea, soporta:\nkeyword:google (coincidencia de palabras clave)\nsuffix:google.com (coincidencia de sufijo)\nregex:.*\\.example\\.com$ (coincidencia de regex)\nexample.com (coincidencia exacta)\nDejar vacío para enrutamiento por defecto",
"reset": "Restablecer",
"save": "Guardar",
"start_time": "Hora de inicio",
"time_slot": "Intervalo de tiempo",
"traffic_report_threshold": "Umbral de Informe de TrÃĄfico",
"traffic_report_threshold_desc": "Establecer el umbral mínimo para el informe de trÃĄfico. El trÃĄfico solo se informarÃĄ cuando supere este valor. Establezca en 0 o deje vacío para informar todo el trÃĄfico."
},
"saveSuccess": "Guardado con ÃĐxito",
"tabs": {
"basic": "ConfiguraciÃģn BÃĄsica",
"block": "Reglas de Bloqueo",
"dns": "ConfiguraciÃģn de DNS",
"outbound": "Reglas Salientes"
},
"title": "ConfiguraciÃģn del nodo"
},
"server_key": "Clave del servidor", "server_key": "Clave del servidor",
"service_name": "Nombre del servicio", "service_name": "Nombre del servicio",
"sorted_success": "Ordenado con ÃĐxito",
"status": "Estado", "status": "Estado",
"subscribeId": "ID de suscripciÃģn", "subscribeId": "ID de suscripciÃģn",
"subscription": "SuscripciÃģn", "subscription": "SuscripciÃģn",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDelete": "ÂŋEstÃĄ seguro de que desea eliminar?", "confirmDelete": "ÂŋEstÃĄ seguro de que desea eliminar?",
"confirmOffline": "Confirmar sin conexiÃģn", "confirmOffline": "Confirmar sin conexiÃģn",
"copySubscription": "Copiar SuscripciÃģn",
"copySuccess": "Copia exitosa",
"create": "Crear", "create": "Crear",
"createSubscription": "Crear suscripciÃģn", "createSubscription": "Crear suscripciÃģn",
"createSuccess": "CreaciÃģn exitosa", "createSuccess": "CreaciÃģn exitosa",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Seleccionar protocoloâ€Ķ", "select_protocol": "Seleccionar protocoloâ€Ķ",
"select_server": "Seleccionar servidorâ€Ķ", "select_server": "Seleccionar servidorâ€Ķ",
"server": "Servidor", "server": "Servidor",
"sorted_success": "Ordenado con ÃĐxito",
"tags": "Etiquetas", "tags": "Etiquetas",
"tags_description": "Etiqueta de agrupaciÃģn de permisos (incl. vinculaciÃģn de planes y políticas de entrega).", "tags_description": "Etiqueta de agrupaciÃģn de permisos (incl. vinculaciÃģn de planes y políticas de entrega).",
"tags_placeholder": "Usa Enter o coma (,) para agregar mÚltiples etiquetas", "tags_placeholder": "Usa Enter o coma (,) para agregar mÚltiples etiquetas",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "DirecciÃģn",
"address_placeholder": "DirecciÃģn del servidor",
"bandwidth_placeholder": "Ingresa el ancho de banda, deja vacío para BBR",
"basic": "ConfiguraciÃģn BÃĄsica",
"cancel": "Cancelar",
"city": "Ciudad",
"config": {
"actions": { "actions": {
"cancel": "Cancelar", "cancel": "Cancelar",
"save": "Guardar" "save": "Guardar"
}, },
"communicationKey": "Clave de comunicaciÃģn", "address": "DirecciÃģn",
"communicationKeyDescription": "Utilizado para la autenticaciÃģn del nodo.", "address_placeholder": "DirecciÃģn del servidor",
"description": "Gestiona las claves de comunicaciÃģn del nodo, intervalos de extracciÃģn/empuje y multiplicadores dinÃĄmicos.", "apiHost": "Host de API",
"dynamicMultiplier": "Multiplicador dinÃĄmico", "apiHostPlaceholder": "http(s)://ejemplo.com",
"dynamicMultiplierDescription": "Define intervalos de tiempo y multiplicadores para ajustar la contabilidad del trÃĄfico.", "bandwidth_placeholder": "Ingresa el ancho de banda, deja vacío para BBR",
"endTime": "Hora de finalizaciÃģn", "basic": "ConfiguraciÃģn BÃĄsica",
"inputPlaceholder": "Por favor ingresa", "cancel": "Cancelar",
"multiplier": "Multiplicador", "cert_dns_env": "Variables de Entorno DNS",
"nodePullInterval": "Intervalo de extracciÃģn del nodo", "cert_dns_provider": "Proveedor de DNS",
"nodePullIntervalDescription": "Con quÃĐ frecuencia el nodo extrae la configuraciÃģn (segundos).", "cert_mode": "Modo de Certificado",
"nodePushInterval": "Intervalo de empuje del nodo", "cipher": "Algoritmo de Cifrado",
"nodePushIntervalDescription": "Con quÃĐ frecuencia el nodo envía estadísticas (segundos).", "city": "Ciudad",
"reset": "Restablecer",
"save": "Guardar",
"saveSuccess": "Guardado con ÃĐxito",
"startTime": "Hora de inicio",
"timeSlot": "Intervalo de tiempo",
"title": "ConfiguraciÃģn del nodo"
},
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDeleteDesc": "Esta acciÃģn no se puede deshacer.", "confirmDeleteDesc": "Esta acciÃģn no se puede deshacer.",
"confirmDeleteTitle": "ÂŋEliminar este servidor?", "confirmDeleteTitle": "ÂŋEliminar este servidor?",
"congestion_controller": "Controlador de congestiÃģn", "congestion_controller": "Controlador de congestiÃģn",
"connect": "Conectar",
"copied": "Copiado", "copied": "Copiado",
"copy": "Copiar", "copy": "Copiar",
"country": "País", "country": "País",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Editar Servidor", "drawerEditTitle": "Editar Servidor",
"edit": "Editar", "edit": "Editar",
"enabled": "Habilitado", "enabled": "Habilitado",
"encryption_method": "MÃĐtodo de encriptaciÃģn", "encryption": "MÃĐtodo de Cifrado",
"encryption_client_padding": "Relleno del Cliente",
"encryption_mode": "Modo",
"encryption_password": "ContraseÃąa",
"encryption_password_placeholder": "Dejar vacío para auto-generaciÃģn, reemplazar manualmente para cifrado post-cuÃĄntico",
"encryption_private_key": "Clave Privada",
"encryption_private_key_placeholder": "Dejar vacío para auto-generaciÃģn, reemplazar manualmente para cifrado post-cuÃĄntico",
"encryption_rtt": "RTT",
"encryption_server_padding": "Relleno del Servidor",
"encryption_ticket": "Tiempo de Ticket",
"expireTime": "Tiempo de expiraciÃģn", "expireTime": "Tiempo de expiraciÃģn",
"expired": "Expirado", "expired": "Expirado",
"extra": "ConfiguraciÃģn Extra",
"flow": "Flujo", "flow": "Flujo",
"generate_quantum_resistant_key": "Generar clave resistente a cuÃĄnticos",
"generate_standard_encryption_key": "Generar clave de cifrado estÃĄndar",
"hop_interval": "Intervalo de salto", "hop_interval": "Intervalo de salto",
"hop_ports": "Puertos de salto", "hop_ports": "Puertos de salto",
"hop_ports_placeholder": "p. ej. 1-65535", "hop_ports_placeholder": "p. ej. 1-65535",
"host": "Host", "host": "Host",
"id": "ID", "id": "ID",
"installCommand": "Comando de instalaciÃģn",
"ipAddresses": "Direcciones IP", "ipAddresses": "Direcciones IP",
"memory": "Memoria", "memory": "Memoria",
"migrate": "Migrar datos", "migrate": "Migrar datos",
"migrateFailed": "La migraciÃģn de datos fallÃģ", "migrateFailed": "La migraciÃģn de datos fallÃģ",
"migrated": "Datos migrados con ÃĐxito", "migrated": "Datos migrados con ÃĐxito",
"migrating": "Migrando...", "migrating": "Migrando...",
"mode": "Modo",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Nombre", "name": "Nombre",
"noData": "Sin datos", "noData": "Sin datos",
"notAvailable": "N/A", "notAvailable": "N/A",
"obfs": "OfuscaciÃģn",
"obfs_host": "Host de OfuscaciÃģn",
"obfs_password": "ContraseÃąa de ofuscaciÃģn", "obfs_password": "ContraseÃąa de ofuscaciÃģn",
"obfs_password_placeholder": "Ingresa la contraseÃąa de ofuscaciÃģn", "obfs_password_placeholder": "Ingresa la contraseÃąa de ofuscaciÃģn",
"obfs_path": "Ruta de OfuscaciÃģn",
"offline": "Desconectado", "offline": "Desconectado",
"oneClickInstall": "InstalaciÃģn con un clic",
"online": "Conectado", "online": "Conectado",
"onlineUsers": "Usuarios en línea", "onlineUsers": "Usuarios en línea",
"padding_scheme": "Esquema de Relleno", "padding_scheme": "Esquema de Relleno",
@@ -78,8 +83,6 @@
"pageTitle": "Servidores", "pageTitle": "Servidores",
"path": "Ruta", "path": "Ruta",
"please_select": "Por favor selecciona", "please_select": "Por favor selecciona",
"plugin": "ConfiguraciÃģn del Plugin",
"plugin_opts": "Opciones del Plugin",
"port": "Puerto", "port": "Puerto",
"protocol_configurations": "Configuraciones de Protocolo", "protocol_configurations": "Configuraciones de Protocolo",
"protocol_configurations_desc": "Habilitar y configurar los tipos de protocolo requeridos", "protocol_configurations_desc": "Habilitar y configurar los tipos de protocolo requeridos",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Cadena hexadecimal (hasta 16 caracteres)", "security_short_id_placeholder": "Cadena hexadecimal (hasta 16 caracteres)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Selecciona el mÃĐtodo de encriptaciÃģn", "select_encryption_method": "Selecciona el mÃĐtodo de encriptaciÃģn",
"server_config": {
"description": "Gestionar claves de comunicaciÃģn del nodo, intervalos de extracciÃģn/push.",
"dynamic_multiplier": "Multiplicador DinÃĄmico",
"dynamic_multiplier_desc": "Definir intervalos de tiempo y multiplicadores para ajustar la contabilidad del trÃĄfico.",
"fields": {
"block_rules_placeholder": "Una regla de dominio por línea, soporta:\nkeyword:google (coincidencia de palabra clave)\nsuffix:google.com (coincidencia de sufijo)\nregex:.*\\.example\\.com$ (coincidencia de regex)\nexample.com (coincidencia exacta)",
"communication_key": "Clave de ComunicaciÃģn",
"communication_key_desc": "Utilizado para la autenticaciÃģn del nodo.",
"communication_key_placeholder": "Por favor ingrese",
"dns_config": "ConfiguraciÃģn de DNS",
"dns_domains_placeholder": "Una regla de dominio por línea, soporta:\nkeyword:google (coincidencia de palabra clave)\nsuffix:google.com (coincidencia de sufijo)\nregex:.*\\.example\\.com$ (coincidencia de regex)\nexample.com (coincidencia exacta)",
"dns_proto_placeholder": "Seleccionar tipo",
"end_time": "Hora de Fin",
"ip_strategy": "Estrategia de IP",
"ip_strategy_desc": "Elija la preferencia de versiÃģn de IP para conexiones de red",
"ip_strategy_ipv4": "Preferir IPv4",
"ip_strategy_ipv6": "Preferir IPv6",
"ip_strategy_placeholder": "Seleccionar estrategia de IP",
"multiplier": "Multiplicador",
"node_pull_interval": "Intervalo de ExtracciÃģn del Nodo",
"node_pull_interval_desc": "Con quÃĐ frecuencia el nodo extrae la configuraciÃģn (segundos).",
"node_push_interval": "Intervalo de Push del Nodo",
"node_push_interval_desc": "Con quÃĐ frecuencia el nodo envía estadísticas (segundos).",
"outbound_address_placeholder": "DirecciÃģn del servidor",
"outbound_name_placeholder": "Nombre de la configuraciÃģn",
"outbound_password_placeholder": "ContraseÃąa (opcional)",
"outbound_port_placeholder": "NÚmero de puerto",
"outbound_protocol_placeholder": "Seleccionar protocolo",
"outbound_rules_placeholder": "Una regla por línea, soporta:\nkeyword:google (coincidencia de palabra clave)\nsuffix:google.com (coincidencia de sufijo)\nregex:.*\\.example\\.com$ (coincidencia de regex)\nexample.com (coincidencia exacta)\nDejar vacío para enrutamiento por defecto",
"reset": "Restablecer",
"save": "Guardar",
"start_time": "Hora de Inicio",
"time_slot": "Intervalo de Tiempo",
"traffic_report_threshold": "Umbral de Informe de TrÃĄfico",
"traffic_report_threshold_desc": "Establecer el umbral mínimo para el informe de trÃĄfico. El trÃĄfico solo se informarÃĄ cuando supere este valor. Establezca en 0 o deje vacío para informar todo el trÃĄfico."
},
"saveSuccess": "Guardado exitosamente",
"tabs": {
"basic": "ConfiguraciÃģn BÃĄsica",
"block": "Reglas de Bloqueo",
"dns": "ConfiguraciÃģn de DNS",
"outbound": "Reglas Salientes"
},
"title": "ConfiguraciÃģn del Nodo"
},
"server_key": "Clave del servidor", "server_key": "Clave del servidor",
"service_name": "Nombre del servicio", "service_name": "Nombre del servicio",
"sorted_success": "Ordenado con ÃĐxito",
"status": "Estado", "status": "Estado",
"subscribeId": "ID de suscripciÃģn", "subscribeId": "ID de suscripciÃģn",
"subscription": "SuscripciÃģn", "subscription": "SuscripciÃģn",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDelete": "ÂŋEstÃĄ seguro de que desea eliminar?", "confirmDelete": "ÂŋEstÃĄ seguro de que desea eliminar?",
"confirmOffline": "Confirmar sin conexiÃģn", "confirmOffline": "Confirmar sin conexiÃģn",
"copySubscription": "Copiar SuscripciÃģn",
"copySuccess": "Copia exitosa",
"create": "Crear", "create": "Crear",
"createSubscription": "Crear SuscripciÃģn", "createSubscription": "Crear SuscripciÃģn",
"createSuccess": "CreaciÃģn exitosa", "createSuccess": "CreaciÃģn exitosa",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "ŲūØąŲˆØŠÚĐŲ„ ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊâ€Ķ", "select_protocol": "ŲūØąŲˆØŠÚĐŲ„ ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊâ€Ķ",
"select_server": "ØģØąŲˆØą ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊâ€Ķ", "select_server": "ØģØąŲˆØą ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊâ€Ķ",
"server": "ØģØąŲˆØą", "server": "ØģØąŲˆØą",
"sorted_success": "ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ų…ØąØŠØĻ ØīØŊ",
"tags": "ØĻØąÚ†ØģØĻâ€ŒŲ‡Ø§", "tags": "ØĻØąÚ†ØģØĻâ€ŒŲ‡Ø§",
"tags_description": "ØĻØąÚ†ØģØĻ ÚŊØąŲˆŲ‡â€ŒØĻŲ†ØŊی Ų…ØŽŲˆØē (ØīØ§Ų…Ų„ ŲūÛŒŲˆŲ†ØŊ ØĻØąŲ†Ø§Ų…Ų‡ ؈ ØģیاØģØŠâ€ŒŲ‡Ø§ÛŒ ØŠØ­ŲˆÛŒŲ„).", "tags_description": "ØĻØąÚ†ØģØĻ ÚŊØąŲˆŲ‡â€ŒØĻŲ†ØŊی Ų…ØŽŲˆØē (ØīØ§Ų…Ų„ ŲūÛŒŲˆŲ†ØŊ ØĻØąŲ†Ø§Ų…Ų‡ ؈ ØģیاØģØŠâ€ŒŲ‡Ø§ÛŒ ØŠØ­ŲˆÛŒŲ„).",
"tags_placeholder": "ØĻØąØ§ÛŒ Ø§ŲØē؈ØŊŲ† Ú†Ų†ØŊÛŒŲ† ØĻØąÚ†ØģØĻ اØē Enter یا ŲˆÛŒØąÚŊŲˆŲ„ (,) اØģØŠŲØ§ØŊŲ‡ ÚĐŲ†ÛŒØŊ", "tags_placeholder": "ØĻØąØ§ÛŒ Ø§ŲØē؈ØŊŲ† Ú†Ų†ØŊÛŒŲ† ØĻØąÚ†ØģØĻ اØē Enter یا ŲˆÛŒØąÚŊŲˆŲ„ (,) اØģØŠŲØ§ØŊŲ‡ ÚĐŲ†ÛŒØŊ",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "ØĒØŊØąØģ",
"address_placeholder": "ØĒØŊØąØģ ØģØąŲˆØą",
"bandwidth_placeholder": "ØđØąØķ ŲūŲ‡Ų†Ø§ÛŒ ØĻØ§Ų†ØŊ، ØĻØąØ§ÛŒ BBR ØŪØ§Ų„ÛŒ ØĻÚŊØ°Ø§ØąÛŒØŊ",
"basic": "ŲūیÚĐØąØĻŲ†ØŊی ŲūØ§ÛŒŲ‡",
"cancel": "Ų„ØšŲˆ",
"city": "ØīŲ‡Øą",
"config": {
"actions": { "actions": {
"cancel": "Ų„ØšŲˆ", "cancel": "Ų„ØšŲˆ",
"save": "ذØŪÛŒØąŲ‡" "save": "ذØŪÛŒØąŲ‡"
}, },
"communicationKey": "ÚĐŲ„ÛŒØŊ Ø§ØąØŠØĻاطی", "address": "ØĒØŊØąØģ",
"communicationKeyDescription": "ØĻØąØ§ÛŒ Ø§Ø­ØąØ§Øē Ų‡ŲˆÛŒØŠ Ų†ŲˆØŊ اØģØŠŲØ§ØŊŲ‡ Ų…ÛŒâ€ŒØī؈ØŊ.", "address_placeholder": "ØĒØŊØąØģ ØģØąŲˆØą",
"description": "Ų…ØŊÛŒØąÛŒØŠ ÚĐŲ„ÛŒØŊŲ‡Ø§ÛŒ Ø§ØąØŠØĻاطی Ų†ŲˆØŊ، ŲŲˆØ§ØĩŲ„ ÚĐØīیØŊŲ†/؁ØīØąØŊŲ† ؈ ØķØąÛŒØĻâ€ŒŲ‡Ø§ÛŒ ØŊÛŒŲ†Ø§Ų…ÛŒÚĐ.", "apiHost": "Ų…ÛŒØēØĻØ§Ų† API",
"dynamicMultiplier": "ØķØąÛŒØĻ ØŊÛŒŲ†Ø§Ų…ÛŒÚĐ", "apiHostPlaceholder": "http(s)://example.com",
"dynamicMultiplierDescription": "ØŠØđØąÛŒŲ ØēŲ…Ø§Ų†â€ŒŲ‡Ø§ÛŒ Ų…ØīØŪØĩ ؈ ØķØąÛŒØĻâ€ŒŲ‡Ø§ ØĻØąØ§ÛŒ ØŠŲ†ØļÛŒŲ… Ø­ØģاØĻØŊØ§ØąÛŒ ØŠØąØ§ŲÛŒÚĐ.", "bandwidth_placeholder": "ØđØąØķ ŲūŲ‡Ų†Ø§ÛŒ ØĻØ§Ų†ØŊ، ØĻØąØ§ÛŒ BBR ØŪØ§Ų„ÛŒ ØĻÚŊØ°Ø§ØąÛŒØŊ",
"endTime": "ØēŲ…Ø§Ų† ŲūØ§ÛŒØ§Ų†", "basic": "ŲūیÚĐØąØĻŲ†ØŊی ŲūØ§ÛŒŲ‡",
"inputPlaceholder": "Ų„Ø·ŲØ§Ų‹ ŲˆØ§ØąØŊ ÚĐŲ†ÛŒØŊ", "cancel": "Ų„ØšŲˆ",
"multiplier": "ØķØąÛŒØĻ", "cert_dns_env": "Ų…ØŠØšÛŒØąŲ‡Ø§ÛŒ Ų…Ø­ÛŒØ·ÛŒ DNS",
"nodePullInterval": "ŲØ§ØĩŲ„Ų‡ ÚĐØīیØŊŲ† Ų†ŲˆØŊ", "cert_dns_provider": "Ø§ØąØ§ØĶŲ‡â€ŒØŊŲ‡Ų†ØŊŲ‡ DNS",
"nodePullIntervalDescription": "Ú†Ų†ØŊ ŲˆŲ‚ØŠ یÚĐØĻØ§Øą Ų†ŲˆØŊ ŲūیÚĐØąØĻŲ†ØŊی ØąØ§ Ų…ÛŒâ€ŒÚĐØīØŊ (ØŦØ§Ų†ÛŒŲ‡).", "cert_mode": "Ø­Ø§Ų„ØŠ ÚŊŲˆØ§Ų‡ÛŒ",
"nodePushInterval": "ŲØ§ØĩŲ„Ų‡ ؁ØīØąØŊŲ† Ų†ŲˆØŊ", "cipher": "Ø§Ų„ÚŊŲˆØąÛŒØŠŲ… ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ",
"nodePushIntervalDescription": "Ú†Ų†ØŊ ŲˆŲ‚ØŠ یÚĐØĻØ§Øą Ų†ŲˆØŊ ØĒŲ…Ø§Øą ØąØ§ ؁ØīØąØŊŲ‡ Ų…ÛŒâ€ŒÚĐŲ†ØŊ (ØŦØ§Ų†ÛŒŲ‡).", "city": "ØīŲ‡Øą",
"reset": "ØĻاØēŲ†ØīØ§Ų†ÛŒ",
"save": "ذØŪÛŒØąŲ‡",
"saveSuccess": "ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ ذØŪÛŒØąŲ‡ ØīØŊ",
"startTime": "ØēŲ…Ø§Ų† ØīØąŲˆØđ",
"timeSlot": "ØēŲ…Ø§Ų†â€ŒØĻŲ†ØŊی",
"title": "ŲūیÚĐØąØĻŲ†ØŊی Ų†ŲˆØŊ"
},
"confirm": "ØŠØĢییØŊ", "confirm": "ØŠØĢییØŊ",
"confirmDeleteDesc": "Ø§ÛŒŲ† ØđŲ…Ų„ Ų‚Ø§ØĻŲ„ ØĻاØēÚŊØīØŠ Ų†ÛŒØģØŠ.", "confirmDeleteDesc": "Ø§ÛŒŲ† ØđŲ…Ų„ Ų‚Ø§ØĻŲ„ ØĻاØēÚŊØīØŠ Ų†ÛŒØģØŠ.",
"confirmDeleteTitle": "ØĒیا Ø§ÛŒŲ† ØģØąŲˆØą ØąØ§ Ø­Ø°Ų ÚĐŲ†ÛŒØŊ؟", "confirmDeleteTitle": "ØĒیا Ø§ÛŒŲ† ØģØąŲˆØą ØąØ§ Ø­Ø°Ų ÚĐŲ†ÛŒØŊ؟",
"congestion_controller": "ÚĐŲ†ØŠØąŲ„â€ŒÚĐŲ†Ų†ØŊŲ‡ ØŠØąØ§ŲÛŒÚĐ", "congestion_controller": "ÚĐŲ†ØŠØąŲ„â€ŒÚĐŲ†Ų†ØŊŲ‡ ØŠØąØ§ŲÛŒÚĐ",
"connect": "ا؊ØĩØ§Ų„",
"copied": "ÚĐŲūی ØīØŊ", "copied": "ÚĐŲūی ØīØŊ",
"copy": "ÚĐŲūی", "copy": "ÚĐŲūی",
"country": "ÚĐØīŲˆØą", "country": "ÚĐØīŲˆØą",
@@ -49,28 +36,46 @@
"drawerEditTitle": "ŲˆÛŒØąØ§ÛŒØī ØģØąŲˆØą", "drawerEditTitle": "ŲˆÛŒØąØ§ÛŒØī ØģØąŲˆØą",
"edit": "ŲˆÛŒØąØ§ÛŒØī", "edit": "ŲˆÛŒØąØ§ÛŒØī",
"enabled": "؁ØđØ§Ų„", "enabled": "؁ØđØ§Ų„",
"encryption_method": "ØąŲˆØī ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ", "encryption": "ØąŲˆØī ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ",
"encryption_client_padding": "ŲūØŊÛŒŲ†ÚŊ ÚĐŲ„Ø§ÛŒŲ†ØŠ",
"encryption_mode": "Ø­Ø§Ų„ØŠ",
"encryption_password": "ÚŊØ°ØąŲˆØ§Ú˜Ų‡",
"encryption_password_placeholder": "ØĻØąØ§ÛŒ ØŠŲˆŲ„ÛŒØŊ ØŪ؈ØŊÚĐØ§Øą ØŪØ§Ų„ÛŒ ØĻÚŊØ°Ø§ØąÛŒØŊ، ØĻŲ‡ ØĩŲˆØąØŠ ØŊØģØŠÛŒ ØĻØąØ§ÛŒ ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ ŲūØģاÚĐŲˆØ§Ų†ØŠŲˆŲ…ÛŒ ؎ایÚŊØēÛŒŲ† ÚĐŲ†ÛŒØŊ",
"encryption_private_key": "ÚĐŲ„ÛŒØŊ ØŪØĩ؈Øĩی",
"encryption_private_key_placeholder": "ØĻØąØ§ÛŒ ØŠŲˆŲ„ÛŒØŊ ØŪ؈ØŊÚĐØ§Øą ØŪØ§Ų„ÛŒ ØĻÚŊØ°Ø§ØąÛŒØŊ، ØĻŲ‡ ØĩŲˆØąØŠ ØŊØģØŠÛŒ ØĻØąØ§ÛŒ ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ ŲūØģاÚĐŲˆØ§Ų†ØŠŲˆŲ…ÛŒ ؎ایÚŊØēÛŒŲ† ÚĐŲ†ÛŒØŊ",
"encryption_rtt": "RTT",
"encryption_server_padding": "ŲūØŊÛŒŲ†ÚŊ ØģØąŲˆØą",
"encryption_ticket": "ØēŲ…Ø§Ų† ØĻŲ„ÛŒØ·",
"expireTime": "ØēŲ…Ø§Ų† Ø§Ų†Ų‚Øķا", "expireTime": "ØēŲ…Ø§Ų† Ø§Ų†Ų‚Øķا",
"expired": "Ų…Ų†Ų‚Øķی ØīØŊŲ‡", "expired": "Ų…Ų†Ų‚Øķی ØīØŊŲ‡",
"extra": "ŲūیÚĐØąØĻŲ†ØŊی اØķØ§ŲÛŒ",
"flow": "ØŽØąÛŒØ§Ų†", "flow": "ØŽØąÛŒØ§Ų†",
"generate_quantum_resistant_key": "ØŠŲˆŲ„ÛŒØŊ ÚĐŲ„ÛŒØŊ Ų…Ų‚Ø§ŲˆŲ… ØŊØą ØĻØąØ§ØĻØą ÚĐŲˆØ§Ų†ØŠŲˆŲ…",
"generate_standard_encryption_key": "ØŠŲˆŲ„ÛŒØŊ ÚĐŲ„ÛŒØŊ ØąŲ…ØēÚŊØ°Ø§ØąÛŒ اØģØŠØ§Ų†ØŊØ§ØąØŊ",
"hop_interval": "ŲØ§ØĩŲ„Ų‡ ŲūØąØī", "hop_interval": "ŲØ§ØĩŲ„Ų‡ ŲūØąØī",
"hop_ports": "ŲūŲˆØąØŠâ€ŒŲ‡Ø§ÛŒ ŲūØąØī", "hop_ports": "ŲūŲˆØąØŠâ€ŒŲ‡Ø§ÛŒ ŲūØąØī",
"hop_ports_placeholder": "Ų…ØŦŲ„Ø§Ų‹ 1-65535", "hop_ports_placeholder": "Ų…ØŦŲ„Ø§Ų‹ 1-65535",
"host": "Ų…ÛŒØēØĻØ§Ų†", "host": "Ų…ÛŒØēØĻØ§Ų†",
"id": "ØīŲ†Ø§ØģŲ‡", "id": "ØīŲ†Ø§ØģŲ‡",
"installCommand": "ØŊØģØŠŲˆØą Ų†ØĩØĻ",
"ipAddresses": "ØĒØŊØąØģâ€ŒŲ‡Ø§ÛŒ IP", "ipAddresses": "ØĒØŊØąØģâ€ŒŲ‡Ø§ÛŒ IP",
"memory": "Ø­Ø§ŲØļŲ‡", "memory": "Ø­Ø§ŲØļŲ‡",
"migrate": "Ø§Ų†ØŠŲ‚Ø§Ų„ ØŊاØŊŲ‡", "migrate": "Ø§Ų†ØŠŲ‚Ø§Ų„ ØŊاØŊŲ‡",
"migrateFailed": "Ø§Ų†ØŠŲ‚Ø§Ų„ ØŊاØŊŲ‡ Ų†Ø§Ų…ŲˆŲŲ‚ ØĻ؈ØŊ", "migrateFailed": "Ø§Ų†ØŠŲ‚Ø§Ų„ ØŊاØŊŲ‡ Ų†Ø§Ų…ŲˆŲŲ‚ ØĻ؈ØŊ",
"migrated": "ØŊاØŊŲ‡ ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ų…Ų†ØŠŲ‚Ų„ ØīØŊ", "migrated": "ØŊاØŊŲ‡ ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ų…Ų†ØŠŲ‚Ų„ ØīØŊ",
"migrating": "ØŊØą Ø­Ø§Ų„ Ø§Ų†ØŠŲ‚Ø§Ų„...", "migrating": "ØŊØą Ø­Ø§Ų„ Ø§Ų†ØŠŲ‚Ø§Ų„...",
"mode": "Ø­Ø§Ų„ØŠ",
"multiplex": "Ú†Ų†ØŊŲ…Ų†ØļŲˆØąŲ‡", "multiplex": "Ú†Ų†ØŊŲ…Ų†ØļŲˆØąŲ‡",
"name": "Ų†Ø§Ų…", "name": "Ų†Ø§Ų…",
"noData": "Ų‡ÛŒÚ† ØŊاØŊŲ‡â€ŒØ§ÛŒ", "noData": "Ų‡ÛŒÚ† ØŊاØŊŲ‡â€ŒØ§ÛŒ",
"notAvailable": "ØšÛŒØąŲ‚Ø§ØĻŲ„ ØŊØģØŠØąØģی", "notAvailable": "ØšÛŒØąŲ‚Ø§ØĻŲ„ ØŊØģØŠØąØģی",
"obfs": "ŲūŲ†Ų‡Ø§Ų†â€ŒØģاØēی",
"obfs_host": "Ų…ÛŒØēØĻØ§Ų† ŲūŲ†Ų‡Ø§Ų†â€ŒØģاØēی",
"obfs_password": "ØąŲ…Øē ØđØĻŲˆØą اØŪØŠŲØ§", "obfs_password": "ØąŲ…Øē ØđØĻŲˆØą اØŪØŠŲØ§",
"obfs_password_placeholder": "ØąŲ…Øē ØđØĻŲˆØą اØŪØŠŲØ§ ØąØ§ ŲˆØ§ØąØŊ ÚĐŲ†ÛŒØŊ", "obfs_password_placeholder": "ØąŲ…Øē ØđØĻŲˆØą اØŪØŠŲØ§ ØąØ§ ŲˆØ§ØąØŊ ÚĐŲ†ÛŒØŊ",
"obfs_path": "Ų…ØģÛŒØą ŲūŲ†Ų‡Ø§Ų†â€ŒØģاØēی",
"offline": "ØĒŲŲ„Ø§ÛŒŲ†", "offline": "ØĒŲŲ„Ø§ÛŒŲ†",
"oneClickInstall": "Ų†ØĩØĻ ØĻا یÚĐ ÚĐŲ„ÛŒÚĐ",
"online": "ØĒŲ†Ų„Ø§ÛŒŲ†", "online": "ØĒŲ†Ų„Ø§ÛŒŲ†",
"onlineUsers": "ÚĐØ§ØąØĻØąØ§Ų† ØĒŲ†Ų„Ø§ÛŒŲ†", "onlineUsers": "ÚĐØ§ØąØĻØąØ§Ų† ØĒŲ†Ų„Ø§ÛŒŲ†",
"padding_scheme": "Ø·ØąØ­ ŲūØŊÛŒŲ†ÚŊ", "padding_scheme": "Ø·ØąØ­ ŲūØŊÛŒŲ†ÚŊ",
@@ -78,8 +83,6 @@
"pageTitle": "ØģØąŲˆØąŲ‡Ø§", "pageTitle": "ØģØąŲˆØąŲ‡Ø§",
"path": "Ų…ØģÛŒØą", "path": "Ų…ØģÛŒØą",
"please_select": "Ų„Ø·ŲØ§Ų‹ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ", "please_select": "Ų„Ø·ŲØ§Ų‹ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ",
"plugin": "ŲūیÚĐØąØĻŲ†ØŊی ŲūŲ„Ø§ÚŊÛŒŲ†",
"plugin_opts": "ÚŊØēÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒ ŲūŲ„Ø§ÚŊÛŒŲ†",
"port": "ŲūŲˆØąØŠ", "port": "ŲūŲˆØąØŠ",
"protocol_configurations": "ŲūیÚĐØąØĻŲ†ØŊÛŒâ€ŒŲ‡Ø§ÛŒ ŲūØąŲˆØŠÚĐŲ„", "protocol_configurations": "ŲūیÚĐØąØĻŲ†ØŊÛŒâ€ŒŲ‡Ø§ÛŒ ŲūØąŲˆØŠÚĐŲ„",
"protocol_configurations_desc": "ŲūØąŲˆØŠÚĐŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲˆØąØŊ Ų†ÛŒØ§Øē ØąØ§ ؁ØđØ§Ų„ ؈ ŲūیÚĐØąØĻŲ†ØŊی ÚĐŲ†ÛŒØŊ", "protocol_configurations_desc": "ŲūØąŲˆØŠÚĐŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲˆØąØŊ Ų†ÛŒØ§Øē ØąØ§ ؁ØđØ§Ų„ ؈ ŲūیÚĐØąØĻŲ†ØŊی ÚĐŲ†ÛŒØŊ",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "ØąØīØŠŲ‡ Ų‡ÚŊØē (Ø­ØŊاÚĐØŦØą 16 ÚĐØ§ØąØ§ÚĐØŠØą)", "security_short_id_placeholder": "ØąØīØŠŲ‡ Ų‡ÚŊØē (Ø­ØŊاÚĐØŦØą 16 ÚĐØ§ØąØ§ÚĐØŠØą)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "ØąŲˆØī ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ", "select_encryption_method": "ØąŲˆØī ØąŲ…ØēŲ†ÚŊØ§ØąÛŒ ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ",
"server_config": {
"description": "Ų…ØŊÛŒØąÛŒØŠ ÚĐŲ„ÛŒØŊŲ‡Ø§ÛŒ Ø§ØąØŠØĻاط Ų†ŲˆØŊ، ŲŲˆØ§ØĩŲ„ ÚĐØīیØŊŲ†/؁ØīØąØŊŲ†.",
"dynamic_multiplier": "ØķØąÛŒØĻ ØŊÛŒŲ†Ø§Ų…ÛŒÚĐ",
"dynamic_multiplier_desc": "ØēŲ…Ø§Ų†â€ŒŲ‡Ø§ÛŒ Ų…ØīØŪØĩ ؈ ØķØąØ§ÛŒØĻ ØąØ§ ØĻØąØ§ÛŒ ØŠŲ†ØļÛŒŲ… Ø­ØģاØĻØŊØ§ØąÛŒ ØŠØąØ§ŲÛŒÚĐ ØŠØđØąÛŒŲ ÚĐŲ†ÛŒØŊ.",
"fields": {
"block_rules_placeholder": "یÚĐ Ų‚Ø§Ų†ŲˆŲ† ØŊØ§Ų…Ų†Ų‡ ØŊØą Ų‡Øą ØŪط، ŲūØīØŠÛŒØĻØ§Ų†ÛŒ Ų…ÛŒâ€ŒÚĐŲ†ØŊ:\nkeyword:google (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا ÚĐŲ„Ų…Ų‡ ÚĐŲ„ÛŒØŊی)\nsuffix:google.com (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا ŲūØģŲˆŲ†ØŊ)\nregex:.*\\.example\\.com$ (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا regex)\nexample.com (Ų…Ø·Ø§ØĻŲ‚ØŠ ØŊŲ‚ÛŒŲ‚)",
"communication_key": "ÚĐŲ„ÛŒØŊ Ø§ØąØŠØĻاطی",
"communication_key_desc": "ØĻØąØ§ÛŒ Ø§Ø­ØąØ§Øē Ų‡ŲˆÛŒØŠ Ų†ŲˆØŊ اØģØŠŲØ§ØŊŲ‡ Ų…ÛŒâ€ŒØī؈ØŊ.",
"communication_key_placeholder": "Ų„Ø·ŲØ§Ų‹ ŲˆØ§ØąØŊ ÚĐŲ†ÛŒØŊ",
"dns_config": "ŲūیÚĐØąØĻŲ†ØŊی DNS",
"dns_domains_placeholder": "یÚĐ Ų‚Ø§Ų†ŲˆŲ† ØŊØ§Ų…Ų†Ų‡ ØŊØą Ų‡Øą ØŪط، ŲūØīØŠÛŒØĻØ§Ų†ÛŒ Ų…ÛŒâ€ŒÚĐŲ†ØŊ:\nkeyword:google (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا ÚĐŲ„Ų…Ų‡ ÚĐŲ„ÛŒØŊی)\nsuffix:google.com (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا ŲūØģŲˆŲ†ØŊ)\nregex:.*\\.example\\.com$ (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا regex)\nexample.com (Ų…Ø·Ø§ØĻŲ‚ØŠ ØŊŲ‚ÛŒŲ‚)",
"dns_proto_placeholder": "Ų†ŲˆØđ ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ",
"end_time": "ØēŲ…Ø§Ų† ŲūØ§ÛŒØ§Ų†",
"ip_strategy": "اØģØŠØąØ§ØŠÚ˜ÛŒ IP",
"ip_strategy_desc": "ØŠØąØŽÛŒØ­ Ų†ØģØŪŲ‡ IP ØąØ§ ØĻØąØ§ÛŒ ا؊ØĩØ§Ų„Ø§ØŠ ØīØĻÚĐŲ‡ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ",
"ip_strategy_ipv4": "ØŠØąØŽÛŒØ­ IPv4",
"ip_strategy_ipv6": "ØŠØąØŽÛŒØ­ IPv6",
"ip_strategy_placeholder": "اØģØŠØąØ§ØŠÚ˜ÛŒ IP ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ",
"multiplier": "ØķØąÛŒØĻ",
"node_pull_interval": "ŲØ§ØĩŲ„Ų‡ ÚĐØīیØŊŲ† Ų†ŲˆØŊ",
"node_pull_interval_desc": "Ú†Ų†ØŊ ŲˆŲ‚ØŠ یÚĐØĻØ§Øą Ų†ŲˆØŊ ŲūیÚĐØąØĻŲ†ØŊی ØąØ§ Ų…ÛŒâ€ŒÚĐØīØŊ (ØŦØ§Ų†ÛŒŲ‡).",
"node_push_interval": "ŲØ§ØĩŲ„Ų‡ ؁ØīØąØŊŲ† Ų†ŲˆØŊ",
"node_push_interval_desc": "Ú†Ų†ØŊ ŲˆŲ‚ØŠ یÚĐØĻØ§Øą Ų†ŲˆØŊ ØĒŲ…Ø§Øą ØąØ§ ؁ØīØąØŊŲ‡ Ų…ÛŒâ€ŒÚĐŲ†ØŊ (ØŦØ§Ų†ÛŒŲ‡).",
"outbound_address_placeholder": "ØĒØŊØąØģ ØģØąŲˆØą",
"outbound_name_placeholder": "Ų†Ø§Ų… ŲūیÚĐØąØĻŲ†ØŊی",
"outbound_password_placeholder": "ØąŲ…Øē ØđØĻŲˆØą (اØŪØŠÛŒØ§ØąÛŒ)",
"outbound_port_placeholder": "ØīŲ…Ø§ØąŲ‡ ŲūŲˆØąØŠ",
"outbound_protocol_placeholder": "ŲūØąŲˆØŠÚĐŲ„ ØąØ§ Ø§Ų†ØŠØŪاØĻ ÚĐŲ†ÛŒØŊ",
"outbound_rules_placeholder": "یÚĐ Ų‚Ø§Ų†ŲˆŲ† ØŊØą Ų‡Øą ØŪط، ŲūØīØŠÛŒØĻØ§Ų†ÛŒ Ų…ÛŒâ€ŒÚĐŲ†ØŊ:\nkeyword:google (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا ÚĐŲ„Ų…Ų‡ ÚĐŲ„ÛŒØŊی)\nsuffix:google.com (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا ŲūØģŲˆŲ†ØŊ)\nregex:.*\\.example\\.com$ (Ų…Ø·Ø§ØĻŲ‚ØŠ ØĻا regex)\nexample.com (Ų…Ø·Ø§ØĻŲ‚ØŠ ØŊŲ‚ÛŒŲ‚)\nØĻØąØ§ÛŒ Ų…ØģÛŒØąÛŒØ§ØĻی ŲūیØīâ€ŒŲØąØķ ØŪØ§Ų„ÛŒ ØĻÚŊØ°Ø§ØąÛŒØŊ",
"reset": "ØŠŲ†ØļÛŒŲ… Ų…ØŽØŊØŊ",
"save": "ذØŪÛŒØąŲ‡",
"start_time": "ØēŲ…Ø§Ų† ØīØąŲˆØđ",
"time_slot": "ØĻاØēŲ‡ ØēŲ…Ø§Ų†ÛŒ",
"traffic_report_threshold": "ØĒØģØŠØ§Ų†Ų‡ ÚŊØēØ§ØąØī ØŠØąØ§ŲÛŒÚĐ",
"traffic_report_threshold_desc": "Ø­ØŊØ§Ų‚Ų„ ØĒØģØŠØ§Ų†Ų‡ ØĻØąØ§ÛŒ ÚŊØēØ§ØąØī ØŠØąØ§ŲÛŒÚĐ ØąØ§ ØŠŲ†ØļÛŒŲ… ÚĐŲ†ÛŒØŊ. ØŠØąØ§ŲÛŒÚĐ ŲŲ‚Ø· ØēŲ…Ø§Ų†ÛŒ ÚŊØēØ§ØąØī Ų…ÛŒâ€ŒØī؈ØŊ ÚĐŲ‡ اØē Ø§ÛŒŲ† Ų…Ų‚ØŊØ§Øą ŲØąØ§ØŠØą ØąŲˆØŊ. ØĻØąØ§ÛŒ ÚŊØēØ§ØąØī Ų‡Ų…Ų‡ ØŠØąØ§ŲÛŒÚĐØŒ Ų…Ų‚ØŊØ§Øą ØąØ§ 0 ØŠŲ†ØļÛŒŲ… ÚĐŲ†ÛŒØŊ یا ØŪØ§Ų„ÛŒ ØĻÚŊØ°Ø§ØąÛŒØŊ."
},
"saveSuccess": "ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ ذØŪÛŒØąŲ‡ ØīØŊ",
"tabs": {
"basic": "ŲūیÚĐØąØĻŲ†ØŊی ŲūØ§ÛŒŲ‡",
"block": "Ų‚ŲˆØ§Ų†ÛŒŲ† Ų…ØģØŊ؈ØŊ",
"dns": "ŲūیÚĐØąØĻŲ†ØŊی DNS",
"outbound": "Ų‚ŲˆØ§Ų†ÛŒŲ† ØŪØąŲˆØŽÛŒ"
},
"title": "ŲūیÚĐØąØĻŲ†ØŊی Ų†ŲˆØŊ"
},
"server_key": "ÚĐŲ„ÛŒØŊ ØģØąŲˆØą", "server_key": "ÚĐŲ„ÛŒØŊ ØģØąŲˆØą",
"service_name": "Ų†Ø§Ų… ØģØąŲˆÛŒØģ", "service_name": "Ų†Ø§Ų… ØģØąŲˆÛŒØģ",
"sorted_success": "ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ų…ØąØŠØĻ ØīØŊ",
"status": "؈ØķØđی؊", "status": "؈ØķØđی؊",
"subscribeId": "ØīŲ†Ø§ØģŲ‡ اØīØŠØąØ§ÚĐ", "subscribeId": "ØīŲ†Ø§ØģŲ‡ اØīØŠØąØ§ÚĐ",
"subscription": "اØīØŠØąØ§ÚĐ", "subscription": "اØīØŠØąØ§ÚĐ",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "ØŠØĢییØŊ", "confirm": "ØŠØĢییØŊ",
"confirmDelete": "ØĒیا Ų…Ø·Ų…ØĶŲ† Ų‡ØģØŠÛŒØŊ ÚĐŲ‡ Ų…ÛŒâ€ŒØŪŲˆØ§Ų‡ÛŒØŊ Ø­Ø°Ų ÚĐŲ†ÛŒØŊ؟", "confirmDelete": "ØĒیا Ų…Ø·Ų…ØĶŲ† Ų‡ØģØŠÛŒØŊ ÚĐŲ‡ Ų…ÛŒâ€ŒØŪŲˆØ§Ų‡ÛŒØŊ Ø­Ø°Ų ÚĐŲ†ÛŒØŊ؟",
"confirmOffline": "ØŠØĢییØŊ ØĒŲŲ„Ø§ÛŒŲ†", "confirmOffline": "ØŠØĢییØŊ ØĒŲŲ„Ø§ÛŒŲ†",
"copySubscription": "ÚĐŲūی اØīØŠØąØ§ÚĐ",
"copySuccess": "ÚĐŲūی ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ø§Ų†ØŽØ§Ų… ØīØŊ",
"create": "ای؎اØŊ", "create": "ای؎اØŊ",
"createSubscription": "ای؎اØŊ اØīØŠØąØ§ÚĐ", "createSubscription": "ای؎اØŊ اØīØŠØąØ§ÚĐ",
"createSuccess": "ای؎اØŊ ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ø§Ų†ØŽØ§Ų… ØīØŊ", "createSuccess": "ای؎اØŊ ØĻا Ų…ŲˆŲŲ‚ÛŒØŠ Ø§Ų†ØŽØ§Ų… ØīØŊ",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Valitse protokollaâ€Ķ", "select_protocol": "Valitse protokollaâ€Ķ",
"select_server": "Valitse palvelinâ€Ķ", "select_server": "Valitse palvelinâ€Ķ",
"server": "Palvelin", "server": "Palvelin",
"sorted_success": "Lajiteltu onnistuneesti",
"tags": "Tunnisteet", "tags": "Tunnisteet",
"tags_description": "Oikeuksien ryhmittelytunniste (mukaan lukien suunnitelman sitominen ja toimituskÃĪytÃĪnnÃķt).", "tags_description": "Oikeuksien ryhmittelytunniste (mukaan lukien suunnitelman sitominen ja toimituskÃĪytÃĪnnÃķt).",
"tags_placeholder": "KÃĪytÃĪ EnteriÃĪ tai pilkkua (,) lisÃĪtÃĪksesi useita tunnisteita", "tags_placeholder": "KÃĪytÃĪ EnteriÃĪ tai pilkkua (,) lisÃĪtÃĪksesi useita tunnisteita",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Osoite",
"address_placeholder": "Palvelimen osoite",
"bandwidth_placeholder": "SyÃķtÃĪ kaistanleveys, jÃĪtÃĪ tyhjÃĪksi BBR:lle",
"basic": "Perusasetukset",
"cancel": "Peruuta",
"city": "Kaupunki",
"config": {
"actions": { "actions": {
"cancel": "Peruuta", "cancel": "Peruuta",
"save": "Tallenna" "save": "Tallenna"
}, },
"communicationKey": "ViestintÃĪavain", "address": "Osoite",
"communicationKeyDescription": "KÃĪytetÃĪÃĪn solmun todennukseen.", "address_placeholder": "Palvelimen osoite",
"description": "Hallitse solmun viestintÃĪavaimia, vetovÃĪlejÃĪ ja dynaamisia kertoimia.", "apiHost": "API-isÃĪntÃĪ",
"dynamicMultiplier": "Dynaaminen kerroin", "apiHostPlaceholder": "http(s)://esimerkki.com",
"dynamicMultiplierDescription": "MÃĪÃĪritÃĪ aikavÃĪli ja kertoimet liikenteen laskentaa varten.", "bandwidth_placeholder": "SyÃķtÃĪ kaistanleveys, jÃĪtÃĪ tyhjÃĪksi BBR:lle",
"endTime": "Lopetusaika", "basic": "Perusasetukset",
"inputPlaceholder": "Ole hyvÃĪ ja syÃķtÃĪ", "cancel": "Peruuta",
"multiplier": "Kerroin", "cert_dns_env": "DNS-ympÃĪristÃķmuuttujat",
"nodePullInterval": "Solmun vetovÃĪli", "cert_dns_provider": "DNS-toimittaja",
"nodePullIntervalDescription": "Kuinka usein solmu vetÃĪÃĪ konfiguraation (sekunteina).", "cert_mode": "Sertifikaattitila",
"nodePushInterval": "Solmun tyÃķntÃķvÃĪli", "cipher": "Salausalgoritmi",
"nodePushIntervalDescription": "Kuinka usein solmu tyÃķntÃĪÃĪ tilastoja (sekunteina).", "city": "Kaupunki",
"reset": "Nollaa",
"save": "Tallenna",
"saveSuccess": "Tallennus onnistui",
"startTime": "Aloitusaika",
"timeSlot": "AikavÃĪli",
"title": "Solmun konfigurointi"
},
"confirm": "Vahvista", "confirm": "Vahvista",
"confirmDeleteDesc": "TÃĪtÃĪ toimintoa ei voi peruuttaa.", "confirmDeleteDesc": "TÃĪtÃĪ toimintoa ei voi peruuttaa.",
"confirmDeleteTitle": "Poista tÃĪmÃĪ palvelin?", "confirmDeleteTitle": "Poista tÃĪmÃĪ palvelin?",
"congestion_controller": "RuuhkansÃĪÃĪtÃķ", "congestion_controller": "RuuhkansÃĪÃĪtÃķ",
"connect": "YhdistÃĪ",
"copied": "Kopioitu", "copied": "Kopioitu",
"copy": "Kopioi", "copy": "Kopioi",
"country": "Maa", "country": "Maa",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Muokkaa palvelinta", "drawerEditTitle": "Muokkaa palvelinta",
"edit": "Muokkaa", "edit": "Muokkaa",
"enabled": "KÃĪytÃķssÃĪ", "enabled": "KÃĪytÃķssÃĪ",
"encryption_method": "SalausmenetelmÃĪ", "encryption": "SalausmenetelmÃĪ",
"encryption_client_padding": "Asiakkaan tÃĪyte",
"encryption_mode": "Tila",
"encryption_password": "Salasana",
"encryption_password_placeholder": "JÃĪtÃĪ tyhjÃĪksi automaattista generointia varten, vaihda manuaalisesti post-kvanttisalausta varten",
"encryption_private_key": "Yksityinen avain",
"encryption_private_key_placeholder": "JÃĪtÃĪ tyhjÃĪksi automaattista generointia varten, vaihda manuaalisesti post-kvanttisalausta varten",
"encryption_rtt": "RTT",
"encryption_server_padding": "Palvelimen tÃĪyte",
"encryption_ticket": "Lippuaika",
"expireTime": "Voimassaoloaika", "expireTime": "Voimassaoloaika",
"expired": "Vanhentunut", "expired": "Vanhentunut",
"extra": "LisÃĪasetukset",
"flow": "Virta", "flow": "Virta",
"generate_quantum_resistant_key": "Luo kvanttikestÃĪvÃĪ avain",
"generate_standard_encryption_key": "Luo standardi salausavain",
"hop_interval": "HyppyvÃĪlit", "hop_interval": "HyppyvÃĪlit",
"hop_ports": "Hyppysatamat", "hop_ports": "Hyppysatamat",
"hop_ports_placeholder": "esim. 1-65535", "hop_ports_placeholder": "esim. 1-65535",
"host": "IsÃĪntÃĪ", "host": "IsÃĪntÃĪ",
"id": "ID", "id": "ID",
"installCommand": "Asennuskomento",
"ipAddresses": "IP-osoitteet", "ipAddresses": "IP-osoitteet",
"memory": "Muisti", "memory": "Muisti",
"migrate": "SiirrÃĪ tiedot", "migrate": "SiirrÃĪ tiedot",
"migrateFailed": "Tietojen siirto epÃĪonnistui", "migrateFailed": "Tietojen siirto epÃĪonnistui",
"migrated": "Tiedot siirretty onnistuneesti", "migrated": "Tiedot siirretty onnistuneesti",
"migrating": "SiirretÃĪÃĪn...", "migrating": "SiirretÃĪÃĪn...",
"mode": "Tila",
"multiplex": "Monistamo", "multiplex": "Monistamo",
"name": "Nimi", "name": "Nimi",
"noData": "Ei tietoja", "noData": "Ei tietoja",
"notAvailable": "Ei saatavilla", "notAvailable": "Ei saatavilla",
"obfs": "HÃĪilytys",
"obfs_host": "HÃĪilytys isÃĪntÃĪ",
"obfs_password": "HÃĪilytyssalasana", "obfs_password": "HÃĪilytyssalasana",
"obfs_password_placeholder": "SyÃķtÃĪ hÃĪilytyssalasana", "obfs_password_placeholder": "SyÃķtÃĪ hÃĪilytyssalasana",
"obfs_path": "HÃĪilytys polku",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "Yhden napsautuksen asennus",
"online": "Online", "online": "Online",
"onlineUsers": "Verkossa olevat kÃĪyttÃĪjÃĪt", "onlineUsers": "Verkossa olevat kÃĪyttÃĪjÃĪt",
"padding_scheme": "TÃĪyttÃķkaavio", "padding_scheme": "TÃĪyttÃķkaavio",
@@ -78,8 +83,6 @@
"pageTitle": "Palvelimet", "pageTitle": "Palvelimet",
"path": "Polku", "path": "Polku",
"please_select": "Ole hyvÃĪ ja valitse", "please_select": "Ole hyvÃĪ ja valitse",
"plugin": "Laajennuksen asetukset",
"plugin_opts": "Laajennusvaihtoehdot",
"port": "Portti", "port": "Portti",
"protocol_configurations": "Protokolla-asetukset", "protocol_configurations": "Protokolla-asetukset",
"protocol_configurations_desc": "Ota kÃĪyttÃķÃķn ja mÃĪÃĪritÃĪ tarvittavat protokollatyypit", "protocol_configurations_desc": "Ota kÃĪyttÃķÃķn ja mÃĪÃĪritÃĪ tarvittavat protokollatyypit",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Hex-merkkijono (enintÃĪÃĪn 16 merkkiÃĪ)", "security_short_id_placeholder": "Hex-merkkijono (enintÃĪÃĪn 16 merkkiÃĪ)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Valitse salausmenetelmÃĪ", "select_encryption_method": "Valitse salausmenetelmÃĪ",
"server_config": {
"description": "Hallitse solmun viestintÃĪavaimia, vetÃĪmis-/tyÃķntÃķvÃĪlejÃĪ.",
"dynamic_multiplier": "Dynaaminen kerroin",
"dynamic_multiplier_desc": "MÃĪÃĪritÃĪ aikavÃĪlit ja kertoimet liikenteen laskentaa varten.",
"fields": {
"block_rules_placeholder": "Yksi domain-sÃĪÃĪntÃķ per rivi, tukee:\nkeyword:google (avainsanan vastaavuus)\nsuffix:google.com (pÃĪÃĪtteiden vastaavuus)\nregex:.*\\.example\\.com$ (regex-vastaavuus)\nexample.com (tÃĪsmÃĪllinen vastaavuus)",
"communication_key": "ViestintÃĪavain",
"communication_key_desc": "KÃĪytetÃĪÃĪn solmun todennukseen.",
"communication_key_placeholder": "Ole hyvÃĪ ja syÃķtÃĪ",
"dns_config": "DNS-asetukset",
"dns_domains_placeholder": "Yksi domain-sÃĪÃĪntÃķ per rivi, tukee:\nkeyword:google (avainsanan vastaavuus)\nsuffix:google.com (pÃĪÃĪtteiden vastaavuus)\nregex:.*\\.example\\.com$ (regex-vastaavuus)\nexample.com (tÃĪsmÃĪllinen vastaavuus)",
"dns_proto_placeholder": "Valitse tyyppi",
"end_time": "Lopetusaika",
"ip_strategy": "IP-strategia",
"ip_strategy_desc": "Valitse IP-version mieltymys verkkoyhteyksille",
"ip_strategy_ipv4": "Suosi IPv4:ÃĪÃĪ",
"ip_strategy_ipv6": "Suosi IPv6:ta",
"ip_strategy_placeholder": "Valitse IP-strategia",
"multiplier": "Kerroin",
"node_pull_interval": "Solmun vetovÃĪli",
"node_pull_interval_desc": "Kuinka usein solmu vetÃĪÃĪ konfiguraation (sekunteina).",
"node_push_interval": "Solmun tyÃķntÃķvÃĪli",
"node_push_interval_desc": "Kuinka usein solmu tyÃķntÃĪÃĪ tilastoja (sekunteina).",
"outbound_address_placeholder": "Palvelimen osoite",
"outbound_name_placeholder": "Konfiguraation nimi",
"outbound_password_placeholder": "Salasana (valinnainen)",
"outbound_port_placeholder": "Porttinumero",
"outbound_protocol_placeholder": "Valitse protokolla",
"outbound_rules_placeholder": "Yksi sÃĪÃĪntÃķ per rivi, tukee:\nkeyword:google (avainsanan vastaavuus)\nsuffix:google.com (pÃĪÃĪtteiden vastaavuus)\nregex:.*\\.example\\.com$ (regex-vastaavuus)\nexample.com (tÃĪsmÃĪllinen vastaavuus)\nJÃĪtÃĪ tyhjÃĪksi oletusreititykselle",
"reset": "Nollaa",
"save": "Tallenna",
"start_time": "Aloitusaika",
"time_slot": "AikavÃĪli",
"traffic_report_threshold": "Liikennetiedotuksen kynnysarvo",
"traffic_report_threshold_desc": "Aseta liikennetiedotuksen vÃĪhimmÃĪiskynnys. LiikennettÃĪ raportoidaan vain, kun se ylittÃĪÃĪ tÃĪmÃĪn arvon. Aseta 0 tai jÃĪtÃĪ tyhjÃĪksi, jotta kaikki liikenne raportoidaan."
},
"saveSuccess": "Tallennus onnistui",
"tabs": {
"basic": "Perusasetukset",
"block": "EstosÃĪÃĪnnÃķt",
"dns": "DNS-asetukset",
"outbound": "UlkosÃĪÃĪnnÃķt"
},
"title": "Solmun konfiguraatio"
},
"server_key": "Palvelimen avain", "server_key": "Palvelimen avain",
"service_name": "Palvelun nimi", "service_name": "Palvelun nimi",
"sorted_success": "Lajiteltu onnistuneesti",
"status": "Tila", "status": "Tila",
"subscribeId": "Tilauksen ID", "subscribeId": "Tilauksen ID",
"subscription": "Tilauksen", "subscription": "Tilauksen",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Vahvista", "confirm": "Vahvista",
"confirmDelete": "Oletko varma, ettÃĪ haluat poistaa?", "confirmDelete": "Oletko varma, ettÃĪ haluat poistaa?",
"confirmOffline": "Vahvista offline-tila", "confirmOffline": "Vahvista offline-tila",
"copySubscription": "Kopioi tilaus",
"copySuccess": "Kopiointi onnistui",
"create": "Luo", "create": "Luo",
"createSubscription": "Luo tilaus", "createSubscription": "Luo tilaus",
"createSuccess": "Luonti onnistui", "createSuccess": "Luonti onnistui",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "SÃĐlectionner un protocoleâ€Ķ", "select_protocol": "SÃĐlectionner un protocoleâ€Ķ",
"select_server": "SÃĐlectionner un serveurâ€Ķ", "select_server": "SÃĐlectionner un serveurâ€Ķ",
"server": "Serveur", "server": "Serveur",
"sorted_success": "TriÃĐ avec succÃĻs",
"tags": "Étiquettes", "tags": "Étiquettes",
"tags_description": "Étiquette de regroupement de permissions (y compris l'association de plan et les politiques de livraison).", "tags_description": "Étiquette de regroupement de permissions (y compris l'association de plan et les politiques de livraison).",
"tags_placeholder": "Utilisez EntrÃĐe ou une virgule (,) pour ajouter plusieurs ÃĐtiquettes", "tags_placeholder": "Utilisez EntrÃĐe ou une virgule (,) pour ajouter plusieurs ÃĐtiquettes",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Adresse",
"address_placeholder": "Adresse du serveur",
"bandwidth_placeholder": "Entrez la bande passante, laissez vide pour BBR",
"basic": "Configuration de base",
"cancel": "Annuler",
"city": "Ville",
"config": {
"actions": { "actions": {
"cancel": "Annuler", "cancel": "Annuler",
"save": "Enregistrer" "save": "Enregistrer"
}, },
"communicationKey": "ClÃĐ de communication", "address": "Adresse",
"communicationKeyDescription": "UtilisÃĐ pour l'authentification du nœud.", "address_placeholder": "Adresse du serveur",
"description": "GÃĐrer les clÃĐs de communication du nœud, les intervalles de tirage/poussÃĐe et les multiplicateurs dynamiques.", "apiHost": "HÃīte API",
"dynamicMultiplier": "Multiplicateur dynamique", "apiHostPlaceholder": "http(s)://exemple.com",
"dynamicMultiplierDescription": "DÃĐfinir des crÃĐneaux horaires et des multiplicateurs pour ajuster le comptage du trafic.", "bandwidth_placeholder": "Entrez la bande passante, laissez vide pour BBR",
"endTime": "Heure de fin", "basic": "Configuration de base",
"inputPlaceholder": "Veuillez entrer", "cancel": "Annuler",
"multiplier": "Multiplicateur", "cert_dns_env": "Variables d'environnement DNS",
"nodePullInterval": "Intervalle de tirage du nœud", "cert_dns_provider": "Fournisseur DNS",
"nodePullIntervalDescription": "À quelle frÃĐquence le nœud tire la configuration (secondes).", "cert_mode": "Mode de certificat",
"nodePushInterval": "Intervalle de poussÃĐe du nœud", "cipher": "Algorithme de chiffrement",
"nodePushIntervalDescription": "À quelle frÃĐquence le nœud pousse les statistiques (secondes).", "city": "Ville",
"reset": "RÃĐinitialiser",
"save": "Enregistrer",
"saveSuccess": "EnregistrÃĐ avec succÃĻs",
"startTime": "Heure de dÃĐbut",
"timeSlot": "CrÃĐneau horaire",
"title": "Configuration du nœud"
},
"confirm": "Confirmer", "confirm": "Confirmer",
"confirmDeleteDesc": "Cette action ne peut pas Être annulÃĐe.", "confirmDeleteDesc": "Cette action ne peut pas Être annulÃĐe.",
"confirmDeleteTitle": "Supprimer ce serveur ?", "confirmDeleteTitle": "Supprimer ce serveur ?",
"congestion_controller": "ContrÃīleur de congestion", "congestion_controller": "ContrÃīleur de congestion",
"connect": "Se connecter",
"copied": "CopiÃĐ", "copied": "CopiÃĐ",
"copy": "Copier", "copy": "Copier",
"country": "Pays", "country": "Pays",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Modifier le serveur", "drawerEditTitle": "Modifier le serveur",
"edit": "Modifier", "edit": "Modifier",
"enabled": "ActivÃĐ", "enabled": "ActivÃĐ",
"encryption_method": "MÃĐthode de chiffrement", "encryption": "MÃĐthode de chiffrement",
"encryption_client_padding": "Remplissage client",
"encryption_mode": "Mode",
"encryption_password": "Mot de passe",
"encryption_password_placeholder": "Laissez vide pour une gÃĐnÃĐration automatique, remplacez manuellement pour un chiffrement post-quantique",
"encryption_private_key": "ClÃĐ privÃĐe",
"encryption_private_key_placeholder": "Laissez vide pour une gÃĐnÃĐration automatique, remplacez manuellement pour un chiffrement post-quantique",
"encryption_rtt": "RTT",
"encryption_server_padding": "Remplissage serveur",
"encryption_ticket": "Temps de ticket",
"expireTime": "Temps d'expiration", "expireTime": "Temps d'expiration",
"expired": "ExpirÃĐ", "expired": "ExpirÃĐ",
"extra": "Configuration supplÃĐmentaire",
"flow": "Flux", "flow": "Flux",
"generate_quantum_resistant_key": "GÃĐnÃĐrer une clÃĐ rÃĐsistante aux quantiques",
"generate_standard_encryption_key": "GÃĐnÃĐrer une clÃĐ de chiffrement standard",
"hop_interval": "Intervalle de saut", "hop_interval": "Intervalle de saut",
"hop_ports": "Ports de saut", "hop_ports": "Ports de saut",
"hop_ports_placeholder": "ex. 1-65535", "hop_ports_placeholder": "ex. 1-65535",
"host": "HÃīte", "host": "HÃīte",
"id": "ID", "id": "ID",
"installCommand": "Commande d'installation",
"ipAddresses": "Adresses IP", "ipAddresses": "Adresses IP",
"memory": "MÃĐmoire", "memory": "MÃĐmoire",
"migrate": "Migrer les donnÃĐes", "migrate": "Migrer les donnÃĐes",
"migrateFailed": "Échec de la migration des donnÃĐes", "migrateFailed": "Échec de la migration des donnÃĐes",
"migrated": "DonnÃĐes migrÃĐes avec succÃĻs", "migrated": "DonnÃĐes migrÃĐes avec succÃĻs",
"migrating": "Migration en cours...", "migrating": "Migration en cours...",
"mode": "Mode",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Nom", "name": "Nom",
"noData": "Aucune donnÃĐe", "noData": "Aucune donnÃĐe",
"notAvailable": "N/A", "notAvailable": "N/A",
"obfs": "Obfuscation",
"obfs_host": "HÃīte Obfs",
"obfs_password": "Mot de passe d'obfuscation", "obfs_password": "Mot de passe d'obfuscation",
"obfs_password_placeholder": "Entrez le mot de passe d'obfuscation", "obfs_password_placeholder": "Entrez le mot de passe d'obfuscation",
"obfs_path": "Chemin Obfs",
"offline": "Hors ligne", "offline": "Hors ligne",
"oneClickInstall": "Installation en un clic",
"online": "En ligne", "online": "En ligne",
"onlineUsers": "Utilisateurs en ligne", "onlineUsers": "Utilisateurs en ligne",
"padding_scheme": "SchÃĐma de remplissage", "padding_scheme": "SchÃĐma de remplissage",
@@ -78,8 +83,6 @@
"pageTitle": "Serveurs", "pageTitle": "Serveurs",
"path": "Chemin", "path": "Chemin",
"please_select": "Veuillez sÃĐlectionner", "please_select": "Veuillez sÃĐlectionner",
"plugin": "Configuration du plugin",
"plugin_opts": "Options du plugin",
"port": "Port", "port": "Port",
"protocol_configurations": "Configurations de protocole", "protocol_configurations": "Configurations de protocole",
"protocol_configurations_desc": "Activer et configurer les types de protocole requis", "protocol_configurations_desc": "Activer et configurer les types de protocole requis",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "ChaÃŪne hexadÃĐcimale (jusqu'à 16 caractÃĻres)", "security_short_id_placeholder": "ChaÃŪne hexadÃĐcimale (jusqu'à 16 caractÃĻres)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "SÃĐlectionner la mÃĐthode de chiffrement", "select_encryption_method": "SÃĐlectionner la mÃĐthode de chiffrement",
"server_config": {
"description": "GÃĐrer les clÃĐs de communication du nœud, les intervalles de pull/push.",
"dynamic_multiplier": "Multiplicateur dynamique",
"dynamic_multiplier_desc": "DÃĐfinir des crÃĐneaux horaires et des multiplicateurs pour ajuster le comptage du trafic.",
"fields": {
"block_rules_placeholder": "Une rÃĻgle de domaine par ligne, prend en charge :\nkeyword:google (correspondance par mot-clÃĐ)\nsuffix:google.com (correspondance par suffixe)\nregex:.*\\.example\\.com$ (correspondance regex)\nexample.com (correspondance exacte)",
"communication_key": "ClÃĐ de communication",
"communication_key_desc": "UtilisÃĐ pour l'authentification du nœud.",
"communication_key_placeholder": "Veuillez entrer",
"dns_config": "Configuration DNS",
"dns_domains_placeholder": "Une rÃĻgle de domaine par ligne, prend en charge :\nkeyword:google (correspondance par mot-clÃĐ)\nsuffix:google.com (correspondance par suffixe)\nregex:.*\\.example\\.com$ (correspondance regex)\nexample.com (correspondance exacte)",
"dns_proto_placeholder": "SÃĐlectionnez le type",
"end_time": "Heure de fin",
"ip_strategy": "StratÃĐgie IP",
"ip_strategy_desc": "Choisissez la prÃĐfÃĐrence de version IP pour les connexions rÃĐseau",
"ip_strategy_ipv4": "PrÃĐfÃĐrer IPv4",
"ip_strategy_ipv6": "PrÃĐfÃĐrer IPv6",
"ip_strategy_placeholder": "SÃĐlectionnez la stratÃĐgie IP",
"multiplier": "Multiplicateur",
"node_pull_interval": "Intervalle de pull du nœud",
"node_pull_interval_desc": "À quelle frÃĐquence le nœud rÃĐcupÃĻre la configuration (secondes).",
"node_push_interval": "Intervalle de push du nœud",
"node_push_interval_desc": "À quelle frÃĐquence le nœud envoie des statistiques (secondes).",
"outbound_address_placeholder": "Adresse du serveur",
"outbound_name_placeholder": "Nom de la configuration",
"outbound_password_placeholder": "Mot de passe (optionnel)",
"outbound_port_placeholder": "NumÃĐro de port",
"outbound_protocol_placeholder": "SÃĐlectionnez le protocole",
"outbound_rules_placeholder": "Une rÃĻgle par ligne, prend en charge :\nkeyword:google (correspondance par mot-clÃĐ)\nsuffix:google.com (correspondance par suffixe)\nregex:.*\\.example\\.com$ (correspondance regex)\nexample.com (correspondance exacte)\nLaisser vide pour le routage par dÃĐfaut",
"reset": "RÃĐinitialiser",
"save": "Enregistrer",
"start_time": "Heure de dÃĐbut",
"time_slot": "CrÃĐneau horaire",
"traffic_report_threshold": "Seuil de rapport de trafic",
"traffic_report_threshold_desc": "DÃĐfinir le seuil minimum pour le rapport de trafic. Le trafic ne sera rapportÃĐ que s'il dÃĐpasse cette valeur. Mettre à 0 ou laisser vide pour rapporter tout le trafic."
},
"saveSuccess": "EnregistrÃĐ avec succÃĻs",
"tabs": {
"basic": "Configuration de base",
"block": "RÃĻgles de blocage",
"dns": "Configuration DNS",
"outbound": "RÃĻgles sortantes"
},
"title": "Configuration du nœud"
},
"server_key": "ClÃĐ du serveur", "server_key": "ClÃĐ du serveur",
"service_name": "Nom du service", "service_name": "Nom du service",
"sorted_success": "TriÃĐ avec succÃĻs",
"status": "Statut", "status": "Statut",
"subscribeId": "ID d'abonnement", "subscribeId": "ID d'abonnement",
"subscription": "Abonnement", "subscription": "Abonnement",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Confirmer", "confirm": "Confirmer",
"confirmDelete": "Êtes-vous sÃŧr de vouloir supprimer ?", "confirmDelete": "Êtes-vous sÃŧr de vouloir supprimer ?",
"confirmOffline": "Confirmer hors ligne", "confirmOffline": "Confirmer hors ligne",
"copySubscription": "Copier l'abonnement",
"copySuccess": "Copie rÃĐussie",
"create": "CrÃĐer", "create": "CrÃĐer",
"createSubscription": "CrÃĐer un abonnement", "createSubscription": "CrÃĐer un abonnement",
"createSuccess": "CrÃĐation rÃĐussie", "createSuccess": "CrÃĐation rÃĐussie",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āΚāĨāĪĻāĨ‡āĪ‚â€Ķ", "select_protocol": "āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āΚāĨāĪĻāĨ‡āĪ‚â€Ķ",
"select_server": "āĪļāΰāĨāĪĩāΰ āΚāĨāĪĻāĨ‡āĪ‚â€Ķ", "select_server": "āĪļāΰāĨāĪĩāΰ āΚāĨāĪĻāĨ‡āĪ‚â€Ķ",
"server": "āĪļāΰāĨāĪĩāΰ", "server": "āĪļāΰāĨāĪĩāΰ",
"sorted_success": "āĪļāĪŦāĪēāĪĪāĪūāĪŠāĨ‚āΰāĨāĪĩāĪ• āĪ•āĨāΰāĪŪāĪŽāĪĶāĨāΧ āĪ•āĪŋāĪŊāĪū āĪ—āĪŊāĪū",
"tags": "āΟāĨˆāĪ—", "tags": "āΟāĨˆāĪ—",
"tags_description": "āĪ…āĪĻāĨāĪŪāĪĪāĪŋ āĪļāĪŪāĨ‚āĪđ āΟāĨˆāĪ— (āĪŊāĨ‹āΜāĪĻāĪū āĪŽāĪūā·āĪ‚āĪĄāĪŋāĪ‚āĪ— āĪ”āΰ āĪĩāĪŋāĪĪāΰāĪĢ āĪĻāĨ€āĪĪāĪŋāĪŊāĨ‹āĪ‚ āĪļāĪđāĪŋāĪĪ)āĨĪ", "tags_description": "āĪ…āĪĻāĨāĪŪāĪĪāĪŋ āĪļāĪŪāĨ‚āĪđ āΟāĨˆāĪ— (āĪŊāĨ‹āΜāĪĻāĪū āĪŽāĪūā·āĪ‚āĪĄāĪŋāĪ‚āĪ— āĪ”āΰ āĪĩāĪŋāĪĪāΰāĪĢ āĪĻāĨ€āĪĪāĪŋāĪŊāĨ‹āĪ‚ āĪļāĪđāĪŋāĪĪ)āĨĪ",
"tags_placeholder": "āĪāĪ•āĪūāΧāĪŋāĪ• āΟāĨˆāĪ— āΜāĨ‹āĪĄāĪžāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ Enter āĪŊāĪū āĪ•āĨ‰āĪŪāĪū (,) āĪ•āĪū āΉāĪŠāĪŊāĨ‹āĪ— āĪ•āΰāĨ‡āĪ‚", "tags_placeholder": "āĪāĪ•āĪūāΧāĪŋāĪ• āΟāĨˆāĪ— āΜāĨ‹āĪĄāĪžāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ Enter āĪŊāĪū āĪ•āĨ‰āĪŪāĪū (,) āĪ•āĪū āΉāĪŠāĪŊāĨ‹āĪ— āĪ•āΰāĨ‡āĪ‚",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "āĪŠāĪĪāĪū",
"address_placeholder": "āĪļāΰāĨāĪĩāΰ āĪ•āĪū āĪŠāĪĪāĪū",
"bandwidth_placeholder": "āĪŽāĨˆāĪ‚āĪĄāĪĩāĪŋāĪĄāĨāĪĨ āĪĶāΰāĨāΜ āĪ•āΰāĨ‡āĪ‚, BBR āĪ•āĨ‡ āĪēāĪŋāĪ āĪ–āĪūāĪēāĨ€ āĪ›āĨ‹āĪĄāĪžāĨ‡āĪ‚",
"basic": "āĪŽāĨāĪĻāĪŋāĪŊāĪūāĪĶāĨ€ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"cancel": "āΰāĪĶāĨāĪĶ āĪ•āΰāĨ‡āĪ‚",
"city": "āĪķāĪđāΰ",
"config": {
"actions": { "actions": {
"cancel": "āΰāĪĶāĨāĪĶ āĪ•āΰāĨ‡āĪ‚", "cancel": "āΰāĪĶāĨāĪĶ āĪ•āΰāĨ‡āĪ‚",
"save": "āĪļāĪđāĨ‡āΜāĨ‡āĪ‚" "save": "āĪļāĪđāĨ‡āΜāĨ‡āĪ‚"
}, },
"communicationKey": "āĪļāĪ‚āΚāĪūāΰ āĪ•āĨāĪ‚āΜāĨ€", "address": "āĪŠāĪĪāĪū",
"communicationKeyDescription": "āĪĻāĨ‹āĪĄ āĪŠāĨāΰāĪŪāĪūāĪĢāĨ€āĪ•āΰāĪĢ āĪ•āĨ‡ āĪēāĪŋāĪ āΉāĪŠāĪŊāĨ‹āĪ— āĪ•āĪŋāĪŊāĪū āΜāĪūāĪĪāĪū āĪđāĨˆāĨĪ", "address_placeholder": "āĪļāΰāĨāĪĩāΰ āĪ•āĪū āĪŠāĪĪāĪū",
"description": "āĪĻāĨ‹āĪĄ āĪļāĪ‚āΚāĪūāΰ āĪ•āĨāĪ‚āΜāĨ€, āĪŠāĨāĪē/āΧāĪ•āĨāĪ•āĪū āĪ…āĪ‚āĪĪāΰāĪūāĪē, āĪ”āΰ āĪ—āĪĪāĪŋāĪķāĨ€āĪē āĪ—āĨāĪĢāĪūāĪ‚āĪ• āĪŠāĨāΰāĪŽāĪ‚āΧāĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚āĨĪ", "apiHost": "āĪāĪŠāĨ€āΆāΈ āĪđāĨ‹āĪļāĨāΟ",
"dynamicMultiplier": "āĪ—āĪĪāĪŋāĪķāĨ€āĪē āĪ—āĨāĪĢāĪūāĪ‚āĪ•", "apiHostPlaceholder": "http(s)://example.com",
"dynamicMultiplierDescription": "āĪŊāĪūāĪĪāĪūāĪŊāĪūāĪĪ āĪēāĨ‡āĪ–āĪūāĪ‚āĪ•āĪĻ āĪ•āĨ‹ āĪļāĪŪāĪūāĪŊāĨ‹āΜāĪŋāĪĪ āĪ•āΰāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ āĪļāĪŪāĪŊ āĪļāĨāĪēāĨ‰āΟ āĪ”āΰ āĪ—āĨāĪĢāĪūāĪ‚āĪ• āĪŠāΰāĪŋāĪ­āĪūāĪ·āĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚āĨĪ", "bandwidth_placeholder": "āĪŽāĨˆāĪ‚āĪĄāĪĩāĪŋāĪĄāĨāĪĨ āĪĶāΰāĨāΜ āĪ•āΰāĨ‡āĪ‚, BBR āĪ•āĨ‡ āĪēāĪŋāĪ āĪ–āĪūāĪēāĨ€ āĪ›āĨ‹āĪĄāĪžāĨ‡āĪ‚",
"endTime": "āĪļāĪŪāĪūāĪŠāĨāĪĪāĪŋ āĪ•āĪū āĪļāĪŪāĪŊ", "basic": "āĪŽāĨāĪĻāĪŋāĪŊāĪūāĪĶāĨ€ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"inputPlaceholder": "āĪ•āĨƒāĪŠāĪŊāĪū āĪĶāΰāĨāΜ āĪ•āΰāĨ‡āĪ‚", "cancel": "āΰāĪĶāĨāĪĶ āĪ•āΰāĨ‡āĪ‚",
"multiplier": "āĪ—āĨāĪĢāĪūāĪ‚āĪ•", "cert_dns_env": "DNS āĪŠāΰāĨāĪŊāĪūāĪĩāΰāĪĢ āΚāΰ",
"nodePullInterval": "āĪĻāĨ‹āĪĄ āĪŠāĨāĪē āĪ…āĪ‚āĪĪāΰāĪūāĪē", "cert_dns_provider": "DNS āĪŠāĨāΰāĪĶāĪūāĪĪāĪū",
"nodePullIntervalDescription": "āĪĻāĨ‹āĪĄ āĪ•āĪŋāĪĪāĪĻāĨ€ āĪŽāĪūāΰ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ āĪ–āĨ€āĪ‚āΚāĪĪāĪū āĪđāĨˆ (āĪļāĨ‡āĪ•āĪ‚āĪĄ āĪŪāĨ‡āĪ‚)āĨĪ", "cert_mode": "āĪŠāĨāΰāĪŪāĪūāĪĢāĪŠāĪĪāĨāΰ āĪŪāĨ‹āĪĄ",
"nodePushInterval": "āĪĻāĨ‹āĪĄ āΧāĪ•āĨāĪ•āĪū āĪ…āĪ‚āĪĪāΰāĪūāĪē", "cipher": "āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪāĪēāĨāĪ—āĨ‹āΰāĪŋāĪĶāĪŪ",
"nodePushIntervalDescription": "āĪĻāĨ‹āĪĄ āĪ•āĪŋāĪĪāĪĻāĨ€ āĪŽāĪūāΰ āΆā΁āĪ•āĪĄāĪžāĨ‡ āΧāĪ•āĨ‡āĪēāĪĪāĪū āĪđāĨˆ (āĪļāĨ‡āĪ•āĪ‚āĪĄ āĪŪāĨ‡āĪ‚)āĨĪ", "city": "āĪķāĪđāΰ",
"reset": "āΰāĨ€āĪļāĨ‡āΟ āĪ•āΰāĨ‡āĪ‚",
"save": "āĪļāĪđāĨ‡āΜāĨ‡āĪ‚",
"saveSuccess": "āĪļāĪŦāĪēāĪĪāĪū āĪļāĨ‡ āĪļāĪđāĨ‡āΜāĪū āĪ—āĪŊāĪū",
"startTime": "āĪķāĨāΰāĨāΆāĪĪ āĪ•āĪū āĪļāĪŪāĪŊ",
"timeSlot": "āĪļāĪŪāĪŊ āĪļāĨāĪēāĨ‰āΟ",
"title": "āĪĻāĨ‹āĪĄ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ"
},
"confirm": "āĪŠāĨāĪ·āĨāΟāĪŋ āĪ•āΰāĨ‡āĪ‚", "confirm": "āĪŠāĨāĪ·āĨāΟāĪŋ āĪ•āΰāĨ‡āĪ‚",
"confirmDeleteDesc": "āĪŊāĪđ āĪ•āĨāΰāĪŋāĪŊāĪū āĪŠāĨ‚āΰāĨāĪĩāĪĩāĪĪ āĪĻāĪđāĨ€āĪ‚ āĪ•āĨ€ āΜāĪū āĪļāĪ•āĪĪāĨ€āĨĪ", "confirmDeleteDesc": "āĪŊāĪđ āĪ•āĨāΰāĪŋāĪŊāĪū āĪŠāĨ‚āΰāĨāĪĩāĪĩāĪĪ āĪĻāĪđāĨ€āĪ‚ āĪ•āĨ€ āΜāĪū āĪļāĪ•āĪĪāĨ€āĨĪ",
"confirmDeleteTitle": "āĪ•āĨāĪŊāĪū ā·āĪļ āĪļāΰāĨāĪĩāΰ āĪ•āĨ‹ āĪđāΟāĪūāĪāĪ‚?", "confirmDeleteTitle": "āĪ•āĨāĪŊāĪū ā·āĪļ āĪļāΰāĨāĪĩāΰ āĪ•āĨ‹ āĪđāΟāĪūāĪāĪ‚?",
"congestion_controller": "āĪ­āĨ€āĪĄāĪž āĪĻāĪŋāĪŊāĪ‚āĪĪāĨāΰāĪĢ", "congestion_controller": "āĪ­āĨ€āĪĄāĪž āĪĻāĪŋāĪŊāĪ‚āĪĪāĨāΰāĪĢ",
"connect": "āΜāĨ‹āĪĄāĪžāĨ‡āĪ‚",
"copied": "āĪ•āĨ‰āĪŠāĨ€ āĪ•āĪŋāĪŊāĪū āĪ—āĪŊāĪū", "copied": "āĪ•āĨ‰āĪŠāĨ€ āĪ•āĪŋāĪŊāĪū āĪ—āĪŊāĪū",
"copy": "āĪ•āĨ‰āĪŠāĨ€ āĪ•āΰāĨ‡āĪ‚", "copy": "āĪ•āĨ‰āĪŠāĨ€ āĪ•āΰāĨ‡āĪ‚",
"country": "āĪĶāĨ‡āĪķ", "country": "āĪĶāĨ‡āĪķ",
@@ -49,28 +36,46 @@
"drawerEditTitle": "āĪļāΰāĨāĪĩāΰ āĪļāĪ‚āĪŠāĪūāĪĶāĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚", "drawerEditTitle": "āĪļāΰāĨāĪĩāΰ āĪļāĪ‚āĪŠāĪūāĪĶāĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚",
"edit": "āĪļāĪ‚āĪŠāĪūāĪĶāĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚", "edit": "āĪļāĪ‚āĪŠāĪūāĪĶāĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚",
"enabled": "āĪļāĪ•āĨāĪ·āĪŪ", "enabled": "āĪļāĪ•āĨāĪ·āĪŪ",
"encryption_method": "āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪĩāĪŋāΧāĪŋ", "encryption": "āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪĩāĪŋāΧāĪŋ",
"encryption_client_padding": "āĪ•āĨāĪēāĪūā·āĪ‚āΟ āĪŠāĨˆāĪĄāĪŋāĪ‚āĪ—",
"encryption_mode": "āĪŪāĨ‹āĪĄ",
"encryption_password": "āĪŠāĪūāĪļāĪĩāΰāĨāĪĄ",
"encryption_password_placeholder": "āĪļāĨāĪĩāĪĪā΃ āΉāĪĪāĨāĪŠāĪĻāĨāĪĻ āĪ•āΰāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ āĪ–āĪūāĪēāĨ€ āĪ›āĨ‹āĪĄāĪžāĨ‡āĪ‚, āĪŠāĨ‹āĪļāĨāΟ-āĪ•āĨāĪĩāĪūāĪ‚āΟāĪŪ āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪ•āĨ‡ āĪēāĪŋāĪ āĪŪāĨˆāĪĻāĨāĪŊāĨāĪ…āĪē āΰāĨ‚āĪŠ āĪļāĨ‡ āĪŽāĪĶāĪēāĨ‡āĪ‚",
"encryption_private_key": "āĪĻāĪŋāΜāĨ€ āĪ•āĨāĪ‚āΜāĨ€",
"encryption_private_key_placeholder": "āĪļāĨāĪĩāĪĪā΃ āΉāĪĪāĨāĪŠāĪĻāĨāĪĻ āĪ•āΰāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ āĪ–āĪūāĪēāĨ€ āĪ›āĨ‹āĪĄāĪžāĨ‡āĪ‚, āĪŠāĨ‹āĪļāĨāΟ-āĪ•āĨāĪĩāĪūāĪ‚āΟāĪŪ āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪ•āĨ‡ āĪēāĪŋāĪ āĪŪāĨˆāĪĻāĨāĪŊāĨāĪ…āĪē āΰāĨ‚āĪŠ āĪļāĨ‡ āĪŽāĪĶāĪēāĨ‡āĪ‚",
"encryption_rtt": "RTT",
"encryption_server_padding": "āĪļāΰāĨāĪĩāΰ āĪŠāĨˆāĪĄāĪŋāĪ‚āĪ—",
"encryption_ticket": "āΟāĪŋāĪ•āΟ āĪļāĪŪāĪŊ",
"expireTime": "āĪļāĪŪāĪūāĪŠāĨāĪĪāĪŋ āĪļāĪŪāĪŊ", "expireTime": "āĪļāĪŪāĪūāĪŠāĨāĪĪāĪŋ āĪļāĪŪāĪŊ",
"expired": "āĪļāĪŪāĪŊ āĪļāĪŪāĪūāĪŠāĨāĪĪ", "expired": "āĪļāĪŪāĪŊ āĪļāĪŪāĪūāĪŠāĨāĪĪ",
"extra": "āĪ…āĪĪāĪŋāΰāĪŋāĪ•āĨāĪĪ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"flow": "āĪŠāĨāΰāĪĩāĪūāĪđ", "flow": "āĪŠāĨāΰāĪĩāĪūāĪđ",
"generate_quantum_resistant_key": "āĪ•āĨāĪĩāĪūāĪ‚āΟāĪŪ-āĪŠāĨāΰāĪĪāĪŋāΰāĨ‹āΧāĨ€ āĪ•āĨāĪ‚āΜāĨ€ āΉāĪĪāĨāĪŠāĪĻāĨāĪĻ āĪ•āΰāĨ‡āĪ‚",
"generate_standard_encryption_key": "āĪŪāĪūāĪĻāĪ• āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪ•āĨāĪ‚āΜāĨ€ āΉāĪĪāĨāĪŠāĪĻāĨāĪĻ āĪ•āΰāĨ‡āĪ‚",
"hop_interval": "āĪđāĨ‰āĪŠ āĪ…āĪ‚āĪĪāΰāĪūāĪē", "hop_interval": "āĪđāĨ‰āĪŠ āĪ…āĪ‚āĪĪāΰāĪūāĪē",
"hop_ports": "āĪđāĨ‰āĪŠ āĪŠāĨ‹āΰāĨāΟ", "hop_ports": "āĪđāĨ‰āĪŠ āĪŠāĨ‹āΰāĨāΟ",
"hop_ports_placeholder": "āΜāĨˆāĪļāĨ‡ 1-65535", "hop_ports_placeholder": "āΜāĨˆāĪļāĨ‡ 1-65535",
"host": "āĪđāĨ‹āĪļāĨāΟ", "host": "āĪđāĨ‹āĪļāĨāΟ",
"id": "āΆāΈāĪĄāĨ€", "id": "āΆāΈāĪĄāĨ€",
"installCommand": "ā·āĪ‚āĪļāĨāΟāĨ‰āĪē āĪ•āĪŪāĪūāĪ‚āĪĄ",
"ipAddresses": "āΆāΈāĪŠāĨ€ āĪŠāĪĪāĨ‡", "ipAddresses": "āΆāΈāĪŠāĨ€ āĪŠāĪĪāĨ‡",
"memory": "āĪŪāĨ‡āĪŪāĨ‹āΰāĨ€", "memory": "āĪŪāĨ‡āĪŪāĨ‹āΰāĨ€",
"migrate": "āĪĄāĨ‡āΟāĪū āĪŪāĪūā·āĪ—āĨāΰāĨ‡āΟ āĪ•āΰāĨ‡āĪ‚", "migrate": "āĪĄāĨ‡āΟāĪū āĪŪāĪūā·āĪ—āĨāΰāĨ‡āΟ āĪ•āΰāĨ‡āĪ‚",
"migrateFailed": "āĪĄāĨ‡āΟāĪū āĪŪāĪūā·āĪ—āĨāΰāĨ‡āĪķāĪĻ āĪĩāĪŋāĪŦāĪē", "migrateFailed": "āĪĄāĨ‡āΟāĪū āĪŪāĪūā·āĪ—āĨāΰāĨ‡āĪķāĪĻ āĪĩāĪŋāĪŦāĪē",
"migrated": "āĪĄāĨ‡āΟāĪū āĪļāĪŦāĪēāĪĪāĪūāĪŠāĨ‚āΰāĨāĪĩāĪ• āĪŪāĪūā·āĪ—āĨāΰāĨ‡āΟ āĪ•āĪŋāĪŊāĪū āĪ—āĪŊāĪū", "migrated": "āĪĄāĨ‡āΟāĪū āĪļāĪŦāĪēāĪĪāĪūāĪŠāĨ‚āΰāĨāĪĩāĪ• āĪŪāĪūā·āĪ—āĨāΰāĨ‡āΟ āĪ•āĪŋāĪŊāĪū āĪ—āĪŊāĪū",
"migrating": "āĪŪāĪūā·āĪ—āĨāΰāĨ‡āΟ āĪ•āĪŋāĪŊāĪū āΜāĪū āΰāĪđāĪū āĪđāĨˆ...", "migrating": "āĪŪāĪūā·āĪ—āĨāΰāĨ‡āΟ āĪ•āĪŋāĪŊāĪū āΜāĪū āΰāĪđāĪū āĪđāĨˆ...",
"mode": "āĪŪāĨ‹āĪĄ",
"multiplex": "āĪŪāĪēāĨāΟāĨ€āĪŠāĨāĪēāĨ‡āĪ•āĨāĪļ", "multiplex": "āĪŪāĪēāĨāΟāĨ€āĪŠāĨāĪēāĨ‡āĪ•āĨāĪļ",
"name": "āĪĻāĪūāĪŪ", "name": "āĪĻāĪūāĪŪ",
"noData": "āĪ•āĨ‹āΈ āĪĄāĨ‡āΟāĪū āĪĻāĪđāĨ€āĪ‚", "noData": "āĪ•āĨ‹āΈ āĪĄāĨ‡āΟāĪū āĪĻāĪđāĨ€āĪ‚",
"notAvailable": "āΉāĪŠāĪēāĪŽāĨāΧ āĪĻāĪđāĨ€āĪ‚", "notAvailable": "āΉāĪŠāĪēāĪŽāĨāΧ āĪĻāĪđāĨ€āĪ‚",
"obfs": "āĪ“āĪŽāĪŦāĪļāĨāĪ•āĨ‡āĪķāĪĻ",
"obfs_host": "āĪ“āĪŽāĪŦāĨāĪļ āĪđāĨ‹āĪļāĨāΟ",
"obfs_password": "āĪ…āĪĩāĪķāĨ‹āĪ·āĪĢ āĪŠāĪūāĪļāĪĩāΰāĨāĪĄ", "obfs_password": "āĪ…āĪĩāĪķāĨ‹āĪ·āĪĢ āĪŠāĪūāĪļāĪĩāΰāĨāĪĄ",
"obfs_password_placeholder": "āĪ…āĪĩāĪķāĨ‹āĪ·āĪĢ āĪŠāĪūāĪļāĪĩāΰāĨāĪĄ āĪĶāΰāĨāΜ āĪ•āΰāĨ‡āĪ‚", "obfs_password_placeholder": "āĪ…āĪĩāĪķāĨ‹āĪ·āĪĢ āĪŠāĪūāĪļāĪĩāΰāĨāĪĄ āĪĶāΰāĨāΜ āĪ•āΰāĨ‡āĪ‚",
"obfs_path": "āĪ“āĪŽāĪŦāĨāĪļ āĪŠāĪĨ",
"offline": "āĪ‘āĪŦāĪēāĪūā·āĪĻ", "offline": "āĪ‘āĪŦāĪēāĪūā·āĪĻ",
"oneClickInstall": "āĪāĪ•-āĪ•āĨāĪēāĪŋāĪ• ā·āĪ‚āĪļāĨāΟāĨ‰āĪēāĨ‡āĪķāĪĻ",
"online": "āĪ‘āĪĻāĪēāĪūā·āĪĻ", "online": "āĪ‘āĪĻāĪēāĪūā·āĪĻ",
"onlineUsers": "āĪ‘āĪĻāĪēāĪūā·āĪĻ āΉāĪŠāĪŊāĨ‹āĪ—āĪ•āΰāĨāĪĪāĪū", "onlineUsers": "āĪ‘āĪĻāĪēāĪūā·āĪĻ āΉāĪŠāĪŊāĨ‹āĪ—āĪ•āΰāĨāĪĪāĪū",
"padding_scheme": "āĪŠāĨˆāĪĄāĪŋāĪ‚āĪ— āĪŊāĨ‹āΜāĪĻāĪū", "padding_scheme": "āĪŠāĨˆāĪĄāĪŋāĪ‚āĪ— āĪŊāĨ‹āΜāĪĻāĪū",
@@ -78,8 +83,6 @@
"pageTitle": "āĪļāΰāĨāĪĩāΰ", "pageTitle": "āĪļāΰāĨāĪĩāΰ",
"path": "āĪŠāĪĨ", "path": "āĪŠāĪĨ",
"please_select": "āĪ•āĨƒāĪŠāĪŊāĪū āΚāĨāĪĻāĨ‡āĪ‚", "please_select": "āĪ•āĨƒāĪŠāĪŊāĪū āΚāĨāĪĻāĨ‡āĪ‚",
"plugin": "āĪŠāĨāĪēāĪ—ā·āĪĻ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"plugin_opts": "āĪŠāĨāĪēāĪ—ā·āĪĻ āĪĩāĪŋāĪ•āĪēāĨāĪŠ",
"port": "āĪŠāĨ‹āΰāĨāΟ", "port": "āĪŠāĨ‹āΰāĨāΟ",
"protocol_configurations": "āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ", "protocol_configurations": "āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"protocol_configurations_desc": "āΆāĪĩāĪķāĨāĪŊāĪ• āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āĪŠāĨāΰāĪ•āĪūāΰāĨ‹āĪ‚ āĪ•āĨ‹ āĪļāĪ•āĨāĪ·āĪŪ āĪ”āΰ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰ āĪ•āΰāĨ‡āĪ‚", "protocol_configurations_desc": "āΆāĪĩāĪķāĨāĪŊāĪ• āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āĪŠāĨāΰāĪ•āĪūāΰāĨ‹āĪ‚ āĪ•āĨ‹ āĪļāĪ•āĨāĪ·āĪŪ āĪ”āΰ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰ āĪ•āΰāĨ‡āĪ‚",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "āĪđāĨ‡āĪ•āĨāĪļ āĪļāĨāΟāĨāΰāĪŋāĪ‚āĪ— (16 āĪ…āĪ•āĨāĪ·āΰāĨ‹āĪ‚ āĪĪāĪ•)", "security_short_id_placeholder": "āĪđāĨ‡āĪ•āĨāĪļ āĪļāĨāΟāĨāΰāĪŋāĪ‚āĪ— (16 āĪ…āĪ•āĨāĪ·āΰāĨ‹āĪ‚ āĪĪāĪ•)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪĩāĪŋāΧāĪŋ āΚāĨāĪĻāĨ‡āĪ‚", "select_encryption_method": "āĪāĪĻāĨāĪ•āĨāΰāĪŋāĪŠāĨāĪķāĪĻ āĪĩāĪŋāΧāĪŋ āΚāĨāĪĻāĨ‡āĪ‚",
"server_config": {
"description": "āĪĻāĨ‹āĪĄ āĪļāĪ‚āΚāĪūāΰ āĪ•āĨāĪ‚āΜāĨ€, āĪ–āĨ€āĪ‚āΚāĪĻāĨ‡/āΧāĪ•āĨ‡āĪēāĪĻāĨ‡ āĪ•āĨ‡ āĪ…āĪ‚āĪĪāΰāĪūāĪē āĪŠāĨāΰāĪŽāĪ‚āΧāĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚āĨĪ",
"dynamic_multiplier": "āĪ—āĪĪāĪŋāĪķāĨ€āĪē āĪ—āĨāĪĢāĪūāĪ‚āĪ•",
"dynamic_multiplier_desc": "āĪŊāĪūāĪĪāĪūāĪŊāĪūāĪĪ āĪēāĨ‡āĪ–āĪūāĪ‚āĪ•āĪĻ āĪ•āĨ‹ āĪļāĪŪāĪūāĪŊāĨ‹āΜāĪŋāĪĪ āĪ•āΰāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ āĪļāĪŪāĪŊ āĪļāĨāĪēāĨ‰āΟ āĪ”āΰ āĪ—āĨāĪĢāĪūāĪ‚āĪ• āĪŠāΰāĪŋāĪ­āĪūāĪ·āĪŋāĪĪ āĪ•āΰāĨ‡āĪ‚āĨĪ",
"fields": {
"block_rules_placeholder": "āĪŠāĨāΰāĪĪāĪŋ āĪŠāĪ‚āĪ•āĨāĪĪāĪŋ āĪāĪ• āĪĄāĨ‹āĪŪāĨ‡āĪĻ āĪĻāĪŋāĪŊāĪŪ, āĪļāĪŪāΰāĨāĪĨāĪĻ:\nāĪ•āĨ€āĪĩāΰāĨāĪĄ:google (āĪ•āĨ€āĪĩāΰāĨāĪĄ āĪŪāĪŋāĪēāĪūāĪĻ)\nsuffix:google.com (āĪļāĪŦāĪŋāĪ•āĨāĪļ āĪŪāĪŋāĪēāĪūāĪĻ)\nregex:.*\\.example\\.com$ (regex āĪŪāĪŋāĪēāĪūāĪĻ)\nexample.com (āĪļāΟāĨ€āĪ• āĪŪāĪŋāĪēāĪūāĪĻ)",
"communication_key": "āĪļāĪ‚āΚāĪūāΰ āĪ•āĨāĪ‚āΜāĨ€",
"communication_key_desc": "āĪĻāĨ‹āĪĄ āĪŠāĨāΰāĪŪāĪūāĪĢāĨ€āĪ•āΰāĪĢ āĪ•āĨ‡ āĪēāĪŋāĪ āΉāĪŠāĪŊāĨ‹āĪ— āĪ•āĪŋāĪŊāĪū āΜāĪūāĪĪāĪū āĪđāĨˆāĨĪ",
"communication_key_placeholder": "āĪ•āĨƒāĪŠāĪŊāĪū āĪĶāΰāĨāΜ āĪ•āΰāĨ‡āĪ‚",
"dns_config": "DNS āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"dns_domains_placeholder": "āĪŠāĨāΰāĪĪāĪŋ āĪŠāĪ‚āĪ•āĨāĪĪāĪŋ āĪāĪ• āĪĄāĨ‹āĪŪāĨ‡āĪĻ āĪĻāĪŋāĪŊāĪŪ, āĪļāĪŪāΰāĨāĪĨāĪĻ:\nāĪ•āĨ€āĪĩāΰāĨāĪĄ:google (āĪ•āĨ€āĪĩāΰāĨāĪĄ āĪŪāĪŋāĪēāĪūāĪĻ)\nsuffix:google.com (āĪļāĪŦāĪŋāĪ•āĨāĪļ āĪŪāĪŋāĪēāĪūāĪĻ)\nregex:.*\\.example\\.com$ (regex āĪŪāĪŋāĪēāĪūāĪĻ)\nexample.com (āĪļāΟāĨ€āĪ• āĪŪāĪŋāĪēāĪūāĪĻ)",
"dns_proto_placeholder": "āĪŠāĨāΰāĪ•āĪūāΰ āΚāĨāĪĻāĨ‡āĪ‚",
"end_time": "āĪļāĪŪāĪūāĪŠāĨāĪĪāĪŋ āĪ•āĪū āĪļāĪŪāĪŊ",
"ip_strategy": "IP āΰāĪĢāĪĻāĨ€āĪĪāĪŋ",
"ip_strategy_desc": "āĪĻāĨ‡āΟāĪĩāΰāĨāĪ• āĪ•āĪĻāĨ‡āĪ•āĨāĪķāĪĻāĨ‹āĪ‚ āĪ•āĨ‡ āĪēāĪŋāĪ IP āĪļāĪ‚āĪļāĨāĪ•āΰāĪĢ āĪŠāĨāΰāĪūāĪĨāĪŪāĪŋāĪ•āĪĪāĪū āΚāĨāĪĻāĨ‡āĪ‚",
"ip_strategy_ipv4": "IPv4 āĪ•āĨ‹ āĪŠāĨāΰāĪūāĪĨāĪŪāĪŋāĪ•āĪĪāĪū āĪĶāĨ‡āĪ‚",
"ip_strategy_ipv6": "IPv6 āĪ•āĨ‹ āĪŠāĨāΰāĪūāĪĨāĪŪāĪŋāĪ•āĪĪāĪū āĪĶāĨ‡āĪ‚",
"ip_strategy_placeholder": "IP āΰāĪĢāĪĻāĨ€āĪĪāĪŋ āΚāĨāĪĻāĨ‡āĪ‚",
"multiplier": "āĪ—āĨāĪĢāĪūāĪ‚āĪ•",
"node_pull_interval": "āĪĻāĨ‹āĪĄ āĪ–āĨ€āĪ‚āΚāĪĻāĨ‡ āĪ•āĪū āĪ…āĪ‚āĪĪāΰāĪūāĪē",
"node_pull_interval_desc": "āĪĻāĨ‹āĪĄ āĪ•āĪŋāĪĪāĪĻāĨ€ āĪŽāĪūāΰ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ āĪ–āĨ€āĪ‚āΚāĪĪāĪū āĪđāĨˆ (āĪļāĨ‡āĪ•āĪ‚āĪĄ āĪŪāĨ‡āĪ‚)āĨĪ",
"node_push_interval": "āĪĻāĨ‹āĪĄ āΧāĪ•āĨ‡āĪēāĪĻāĨ‡ āĪ•āĪū āĪ…āĪ‚āĪĪāΰāĪūāĪē",
"node_push_interval_desc": "āĪĻāĨ‹āĪĄ āĪ•āĪŋāĪĪāĪĻāĨ€ āĪŽāĪūāΰ āΆā΁āĪ•āĪĄāĪžāĨ‡ āΧāĪ•āĨ‡āĪēāĪĪāĪū āĪđāĨˆ (āĪļāĨ‡āĪ•āĪ‚āĪĄ āĪŪāĨ‡āĪ‚)āĨĪ",
"outbound_address_placeholder": "āĪļāΰāĨāĪĩāΰ āĪŠāĪĪāĪū",
"outbound_name_placeholder": "āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ āĪĻāĪūāĪŪ",
"outbound_password_placeholder": "āĪŠāĪūāĪļāĪĩāΰāĨāĪĄ (āĪĩāĨˆāĪ•āĪēāĨāĪŠāĪŋāĪ•)",
"outbound_port_placeholder": "āĪŠāĨ‹āΰāĨāΟ āĪļāĪ‚āĪ–āĨāĪŊāĪū",
"outbound_protocol_placeholder": "āĪŠāĨāΰāĨ‹āΟāĨ‹āĪ•āĨ‰āĪē āΚāĨāĪĻāĨ‡āĪ‚",
"outbound_rules_placeholder": "āĪŠāĨāΰāĪĪāĪŋ āĪŠāĪ‚āĪ•āĨāĪĪāĪŋ āĪāĪ• āĪĻāĪŋāĪŊāĪŪ, āĪļāĪŪāΰāĨāĪĨāĪĻ:\nāĪ•āĨ€āĪĩāΰāĨāĪĄ:google (āĪ•āĨ€āĪĩāΰāĨāĪĄ āĪŪāĪŋāĪēāĪūāĪĻ)\nsuffix:google.com (āĪļāĪŦāĪŋāĪ•āĨāĪļ āĪŪāĪŋāĪēāĪūāĪĻ)\nregex:.*\\.example\\.com$ (regex āĪŪāĪŋāĪēāĪūāĪĻ)\nexample.com (āĪļāΟāĨ€āĪ• āĪŪāĪŋāĪēāĪūāĪĻ)\nāĪĄāĪŋāĪŦāĪžāĨ‰āĪēāĨāΟ āΰāĨ‚āΟāĪŋāĪ‚āĪ— āĪ•āĨ‡ āĪēāĪŋāĪ āĪ–āĪūāĪēāĨ€ āĪ›āĨ‹āĪĄāĪžāĨ‡āĪ‚",
"reset": "āΰāĨ€āĪļāĨ‡āΟ āĪ•āΰāĨ‡āĪ‚",
"save": "āĪļāĪđāĨ‡āΜāĨ‡āĪ‚",
"start_time": "āĪķāĨāΰāĨāΆāĪĪ āĪ•āĪū āĪļāĪŪāĪŊ",
"time_slot": "āĪļāĪŪāĪŊ āĪļāĨāĪēāĨ‰āΟ",
"traffic_report_threshold": "āĪŊāĪūāĪĪāĪūāĪŊāĪūāĪĪ āΰāĪŋāĪŠāĨ‹āΰāĨāΟ āĪĨāĨāΰāĨ‡āĪķāĨ‹āĪēāĨāĪĄ",
"traffic_report_threshold_desc": "āĪŊāĪūāĪĪāĪūāĪŊāĪūāĪĪ āΰāĪŋāĪŠāĨ‹āΰāĨāΟāĪŋāĪ‚āĪ— āĪ•āĨ‡ āĪēāĪŋāĪ āĪĻāĨāĪŊāĨ‚āĪĻāĪĪāĪŪ āĪĨāĨāΰāĨ‡āĪķāĨ‹āĪēāĨāĪĄ āĪļāĨ‡āΟ āĪ•āΰāĨ‡āĪ‚āĨĪ āΜāĪŽ āĪŊāĪūāĪĪāĪūāĪŊāĪūāĪĪ ā·āĪļ āĪŪāĪūāĪĻ āĪ•āĨ‹ āĪŠāĪūāΰ āĪ•āΰāĨ‡āĪ—āĪū āĪĪāĪ­āĨ€ āΰāĪŋāĪŠāĨ‹āΰāĨāΟ āĪ•āĪŋāĪŊāĪū āΜāĪūāĪāĪ—āĪūāĨĪ āĪļāĪ­āĨ€ āĪŊāĪūāĪĪāĪūāĪŊāĪūāĪĪ āΰāĪŋāĪŠāĨ‹āΰāĨāΟ āĪ•āΰāĪĻāĨ‡ āĪ•āĨ‡ āĪēāĪŋāĪ 0 āĪŠāΰ āĪļāĨ‡āΟ āĪ•āΰāĨ‡āĪ‚ āĪŊāĪū āĪ–āĪūāĪēāĨ€ āĪ›āĨ‹āĪĄāĪžāĨ‡āĪ‚āĨĪ"
},
"saveSuccess": "āĪļāĪŦāĪēāĪĪāĪū āĪļāĨ‡ āĪļāĪđāĨ‡āΜāĪū āĪ—āĪŊāĪū",
"tabs": {
"basic": "āĪŽāĨāĪĻāĪŋāĪŊāĪūāĪĶāĨ€ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"block": "āĪŽāĨāĪēāĨ‰āĪ• āĪĻāĪŋāĪŊāĪŪ",
"dns": "DNS āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ",
"outbound": "āΆāΉāΟāĪŽāĪūāΉāĪ‚āĪĄ āĪĻāĪŋāĪŊāĪŪ"
},
"title": "āĪĻāĨ‹āĪĄ āĪ•āĨ‰āĪĻāĨāĪŦāĪžāĪŋāĪ—āΰāĨ‡āĪķāĪĻ"
},
"server_key": "āĪļāΰāĨāĪĩāΰ āĪ•āĨāĪ‚āΜāĨ€", "server_key": "āĪļāΰāĨāĪĩāΰ āĪ•āĨāĪ‚āΜāĨ€",
"service_name": "āĪļāĨ‡āĪĩāĪū āĪ•āĪū āĪĻāĪūāĪŪ", "service_name": "āĪļāĨ‡āĪĩāĪū āĪ•āĪū āĪĻāĪūāĪŪ",
"sorted_success": "āĪļāĪŦāĪēāĪĪāĪū āĪļāĨ‡ āĪ•āĨāΰāĪŪāĪŽāĪĶāĨāΧ āĪ•āĪŋāĪŊāĪū āĪ—āĪŊāĪū",
"status": "āĪļāĨāĪĨāĪŋāĪĪāĪŋ", "status": "āĪļāĨāĪĨāĪŋāĪĪāĪŋ",
"subscribeId": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū āΆāΈāĪĄāĨ€", "subscribeId": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū āΆāΈāĪĄāĨ€",
"subscription": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū", "subscription": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "āĪŠāĨāĪ·āĨāΟāĪŋ āĪ•āΰāĨ‡āĪ‚", "confirm": "āĪŠāĨāĪ·āĨāΟāĪŋ āĪ•āΰāĨ‡āĪ‚",
"confirmDelete": "āĪ•āĨāĪŊāĪū āΆāĪŠ āĪĩāĪūāĪ•āΈ āĪđāΟāĪūāĪĻāĪū āΚāĪūāĪđāĪĪāĨ‡ āĪđāĨˆāĪ‚?", "confirmDelete": "āĪ•āĨāĪŊāĪū āΆāĪŠ āĪĩāĪūāĪ•āΈ āĪđāΟāĪūāĪĻāĪū āΚāĪūāĪđāĪĪāĨ‡ āĪđāĨˆāĪ‚?",
"confirmOffline": "āĪ‘āĪŦāĪžāĪēāĪūā·āĪĻ āĪ•āĨ€ āĪŠāĨāĪ·āĨāΟāĪŋ āĪ•āΰāĨ‡āĪ‚", "confirmOffline": "āĪ‘āĪŦāĪžāĪēāĪūā·āĪĻ āĪ•āĨ€ āĪŠāĨāĪ·āĨāΟāĪŋ āĪ•āΰāĨ‡āĪ‚",
"copySubscription": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū āĪ•āĨ‰āĪŠāĨ€ āĪ•āΰāĨ‡āĪ‚",
"copySuccess": "āĪ•āĨ‰āĪŠāĨ€ āĪļāĪŦāĪē",
"create": "āĪļāĨƒāΜāĪĻ", "create": "āĪļāĨƒāΜāĪĻ",
"createSubscription": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū āĪŽāĪĻāĪūāĪāĪ‚", "createSubscription": "āĪļāĪĶāĪļāĨāĪŊāĪĪāĪū āĪŽāĪĻāĪūāĪāĪ‚",
"createSuccess": "āĪļāĨƒāΜāĪĻ āĪļāĪŦāĪē", "createSuccess": "āĪļāĨƒāΜāĪĻ āĪļāĪŦāĪē",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "VÃĄlassza ki a protokolltâ€Ķ", "select_protocol": "VÃĄlassza ki a protokolltâ€Ķ",
"select_server": "VÃĄlassza ki a szervertâ€Ķ", "select_server": "VÃĄlassza ki a szervertâ€Ķ",
"server": "Szerver", "server": "Szerver",
"sorted_success": "Sikeresen rendezve",
"tags": "CímkÃĐk", "tags": "CímkÃĐk",
"tags_description": "EngedÃĐlyezÃĐsi csoportosítÃģ címke (beleÃĐrtve a tervkÃķtÃĐst ÃĐs a szÃĄllítÃĄsi irÃĄnyelveket).", "tags_description": "EngedÃĐlyezÃĐsi csoportosítÃģ címke (beleÃĐrtve a tervkÃķtÃĐst ÃĐs a szÃĄllítÃĄsi irÃĄnyelveket).",
"tags_placeholder": "TÃķbb címke hozzÃĄadÃĄsÃĄhoz hasznÃĄlja az Entert vagy a vesszőt (,)", "tags_placeholder": "TÃķbb címke hozzÃĄadÃĄsÃĄhoz hasznÃĄlja az Entert vagy a vesszőt (,)",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Cím",
"address_placeholder": "Szerver cím",
"bandwidth_placeholder": "Adja meg a sÃĄvszÃĐlessÃĐget, hagyja Þresen a BBR-hez",
"basic": "AlapÃĐrtelmezett BeÃĄllítÃĄsok",
"cancel": "MÃĐgse",
"city": "VÃĄros",
"config": {
"actions": { "actions": {
"cancel": "MÃĐgse", "cancel": "MÃĐgse",
"save": "MentÃĐs" "save": "MentÃĐs"
}, },
"communicationKey": "KommunikÃĄciÃģs kulcs", "address": "Cím",
"communicationKeyDescription": "A node hitelesítÃĐsÃĐhez hasznÃĄlatos.", "address_placeholder": "Szerver cím",
"description": "A node kommunikÃĄciÃģs kulcsainak, pull/push időkÃķzeinek ÃĐs dinamikus szorzÃģinak kezelÃĐse.", "apiHost": "API gazda",
"dynamicMultiplier": "Dinamikus szorzÃģ", "apiHostPlaceholder": "http(s)://pelda.com",
"dynamicMultiplierDescription": "Időszakok ÃĐs szorzÃģk meghatÃĄrozÃĄsa a forgalom elszÃĄmolÃĄsÃĄnak mÃģdosítÃĄsÃĄhoz.", "bandwidth_placeholder": "Adja meg a sÃĄvszÃĐlessÃĐget, hagyja Þresen a BBR-hez",
"endTime": "BefejezÃĐsi idő", "basic": "AlapÃĐrtelmezett BeÃĄllítÃĄsok",
"inputPlaceholder": "KÃĐrjÞk, adja meg", "cancel": "MÃĐgse",
"multiplier": "SzorzÃģ", "cert_dns_env": "DNS KÃķrnyezeti VÃĄltozÃģk",
"nodePullInterval": "Node pull időkÃķz", "cert_dns_provider": "DNS SzolgÃĄltatÃģ",
"nodePullIntervalDescription": "Milyen gyakran hÚzza a node a konfigurÃĄciÃģt (mÃĄsodperc).", "cert_mode": "TanÚsítvÃĄny MÃģd",
"nodePushInterval": "Node push időkÃķz", "cipher": "TitkosítÃĄsi algoritmus",
"nodePushIntervalDescription": "Milyen gyakran tolja a node a statisztikÃĄkat (mÃĄsodperc).", "city": "VÃĄros",
"reset": "VisszaÃĄllítÃĄs",
"save": "MentÃĐs",
"saveSuccess": "Sikeresen mentve",
"startTime": "KezdÃĐsi idő",
"timeSlot": "Időszak",
"title": "Node konfigurÃĄciÃģ"
},
"confirm": "MegerősítÃĐs", "confirm": "MegerősítÃĐs",
"confirmDeleteDesc": "Ez a mÅąvelet nem vonhatÃģ vissza.", "confirmDeleteDesc": "Ez a mÅąvelet nem vonhatÃģ vissza.",
"confirmDeleteTitle": "TÃķrÃķlni szeretnÃĐ ezt a szervert?", "confirmDeleteTitle": "TÃķrÃķlni szeretnÃĐ ezt a szervert?",
"congestion_controller": "Torlaszkezelő", "congestion_controller": "Torlaszkezelő",
"connect": "CsatlakozÃĄs",
"copied": "MÃĄsolva", "copied": "MÃĄsolva",
"copy": "MÃĄsolÃĄs", "copy": "MÃĄsolÃĄs",
"country": "OrszÃĄg", "country": "OrszÃĄg",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Szerver szerkesztÃĐse", "drawerEditTitle": "Szerver szerkesztÃĐse",
"edit": "SzerkesztÃĐs", "edit": "SzerkesztÃĐs",
"enabled": "EngedÃĐlyezve", "enabled": "EngedÃĐlyezve",
"encryption_method": "TitkosítÃĄsi mÃģdszer", "encryption": "TitkosítÃĄsi mÃģdszer",
"encryption_client_padding": "Kliens kitÃķltÃĐs",
"encryption_mode": "MÃģd",
"encryption_password": "JelszÃģ",
"encryption_password_placeholder": "Hagyja Þresen az automatikus generÃĄlÃĄshoz, cserÃĐlje ki kÃĐzzel a poszt-kvantum titkosítÃĄshoz",
"encryption_private_key": "PrivÃĄt kulcs",
"encryption_private_key_placeholder": "Hagyja Þresen az automatikus generÃĄlÃĄshoz, cserÃĐlje ki kÃĐzzel a poszt-kvantum titkosítÃĄshoz",
"encryption_rtt": "RTT",
"encryption_server_padding": "Szerver kitÃķltÃĐs",
"encryption_ticket": "Jegy idő",
"expireTime": "LejÃĄrati idő", "expireTime": "LejÃĄrati idő",
"expired": "LejÃĄrt", "expired": "LejÃĄrt",
"extra": "TovÃĄbbi konfigurÃĄciÃģ",
"flow": "Forgalom", "flow": "Forgalom",
"generate_quantum_resistant_key": "KvantumÃĄllÃģ kulcs generÃĄlÃĄsa",
"generate_standard_encryption_key": "SzabvÃĄnyos titkosítÃĄsi kulcs generÃĄlÃĄsa",
"hop_interval": "UgrÃĄs időkÃķz", "hop_interval": "UgrÃĄs időkÃķz",
"hop_ports": "UgrÃĄs portok", "hop_ports": "UgrÃĄs portok",
"hop_ports_placeholder": "pl. 1-65535", "hop_ports_placeholder": "pl. 1-65535",
"host": "Gazda", "host": "Gazda",
"id": "ID", "id": "ID",
"installCommand": "TelepítÃĐsi parancs",
"ipAddresses": "IP címek", "ipAddresses": "IP címek",
"memory": "MemÃģria", "memory": "MemÃģria",
"migrate": "Adatok migrÃĄlÃĄsa", "migrate": "Adatok migrÃĄlÃĄsa",
"migrateFailed": "Az adatok migrÃĄlÃĄsa sikertelen", "migrateFailed": "Az adatok migrÃĄlÃĄsa sikertelen",
"migrated": "Az adatok sikeresen migrÃĄlva", "migrated": "Az adatok sikeresen migrÃĄlva",
"migrating": "MigrÃĄlÃĄs...", "migrating": "MigrÃĄlÃĄs...",
"mode": "MÃģd",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "NÃĐv", "name": "NÃĐv",
"noData": "Nincs adat", "noData": "Nincs adat",
"notAvailable": "N/A", "notAvailable": "N/A",
"obfs": "ObfuszkÃĄlÃĄs",
"obfs_host": "Obfs gazda",
"obfs_password": "ObfuszkÃĄlÃĄs jelszÃģ", "obfs_password": "ObfuszkÃĄlÃĄs jelszÃģ",
"obfs_password_placeholder": "Adja meg az obfuszkÃĄlÃĄs jelszÃģt", "obfs_password_placeholder": "Adja meg az obfuszkÃĄlÃĄs jelszÃģt",
"obfs_path": "Obfs Útvonal",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "EgylÃĐpÃĐses telepítÃĐs",
"online": "Online", "online": "Online",
"onlineUsers": "Online felhasznÃĄlÃģk", "onlineUsers": "Online felhasznÃĄlÃģk",
"padding_scheme": "KitÃķltÃĐsi SÃĐmÃĄk", "padding_scheme": "KitÃķltÃĐsi SÃĐmÃĄk",
@@ -78,8 +83,6 @@
"pageTitle": "Szerverek", "pageTitle": "Szerverek",
"path": "Útvonal", "path": "Útvonal",
"please_select": "KÃĐrjÞk, vÃĄlasszon", "please_select": "KÃĐrjÞk, vÃĄlasszon",
"plugin": "BővítmÃĐny BeÃĄllítÃĄsok",
"plugin_opts": "BővítmÃĐny OpciÃģk",
"port": "Port", "port": "Port",
"protocol_configurations": "Protokoll BeÃĄllítÃĄsok", "protocol_configurations": "Protokoll BeÃĄllítÃĄsok",
"protocol_configurations_desc": "EngedÃĐlyezze ÃĐs konfigurÃĄlja a szÞksÃĐges protokolltípusokat", "protocol_configurations_desc": "EngedÃĐlyezze ÃĐs konfigurÃĄlja a szÞksÃĐges protokolltípusokat",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "HexadecimÃĄlis karakterlÃĄnc (legfeljebb 16 karakter)", "security_short_id_placeholder": "HexadecimÃĄlis karakterlÃĄnc (legfeljebb 16 karakter)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "VÃĄlassza ki a titkosítÃĄsi mÃģdszert", "select_encryption_method": "VÃĄlassza ki a titkosítÃĄsi mÃģdszert",
"server_config": {
"description": "Node kommunikÃĄciÃģs kulcsok kezelÃĐse, pull/push időkÃķzÃķk.",
"dynamic_multiplier": "Dinamikus szorzÃģ",
"dynamic_multiplier_desc": "Időszakok ÃĐs szorzÃģk meghatÃĄrozÃĄsa a forgalom elszÃĄmolÃĄsÃĄnak mÃģdosítÃĄsÃĄhoz.",
"fields": {
"block_rules_placeholder": "Egy domain szabÃĄly soronkÃĐnt, tÃĄmogatja:\nkeyword:google (kulcsszÃģ egyezÃĐs)\nsuffix:google.com (vÃĐgződÃĐs egyezÃĐs)\nregex:.*\\.example\\.com$ (regex egyezÃĐs)\nexample.com (pontos egyezÃĐs)",
"communication_key": "KommunikÃĄciÃģs kulcs",
"communication_key_desc": "A node hitelesítÃĐsÃĐhez hasznÃĄlatos.",
"communication_key_placeholder": "KÃĐrjÞk, adja meg",
"dns_config": "DNS BeÃĄllítÃĄsok",
"dns_domains_placeholder": "Egy domain szabÃĄly soronkÃĐnt, tÃĄmogatja:\nkeyword:google (kulcsszÃģ egyezÃĐs)\nsuffix:google.com (vÃĐgződÃĐs egyezÃĐs)\nregex:.*\\.example\\.com$ (regex egyezÃĐs)\nexample.com (pontos egyezÃĐs)",
"dns_proto_placeholder": "VÃĄlassza ki a típust",
"end_time": "BefejezÃĐsi idő",
"ip_strategy": "IP StratÃĐgia",
"ip_strategy_desc": "VÃĄlassza ki az IP verziÃģ preferenciÃĄt a hÃĄlÃģzati kapcsolatokhoz",
"ip_strategy_ipv4": "IPv4 előnyben",
"ip_strategy_ipv6": "IPv6 előnyben",
"ip_strategy_placeholder": "VÃĄlassza ki az IP stratÃĐgiÃĄt",
"multiplier": "SzorzÃģ",
"node_pull_interval": "Node lehÚzÃĄsi időkÃķz",
"node_pull_interval_desc": "Milyen gyakran hÚzza le a node a konfigurÃĄciÃģt (mÃĄsodperc).",
"node_push_interval": "Node feltÃķltÃĐsi időkÃķz",
"node_push_interval_desc": "Milyen gyakran tÃķlti fel a node a statisztikÃĄkat (mÃĄsodperc).",
"outbound_address_placeholder": "Szerver címe",
"outbound_name_placeholder": "KonfigurÃĄciÃģ neve",
"outbound_password_placeholder": "JelszÃģ (opcionÃĄlis)",
"outbound_port_placeholder": "Port szÃĄm",
"outbound_protocol_placeholder": "VÃĄlassza ki a protokollt",
"outbound_rules_placeholder": "Egy szabÃĄly soronkÃĐnt, tÃĄmogatja:\nkeyword:google (kulcsszÃģ egyezÃĐs)\nsuffix:google.com (vÃĐgződÃĐs egyezÃĐs)\nregex:.*\\.example\\.com$ (regex egyezÃĐs)\nexample.com (pontos egyezÃĐs)\nHagyja Þresen az alapÃĐrtelmezett Útvonalhoz",
"reset": "VisszaÃĄllítÃĄs",
"save": "MentÃĐs",
"start_time": "KezdÃĐsi idő",
"time_slot": "Időszak",
"traffic_report_threshold": "Forgalom JelentÃĐsi KÞszÃķb",
"traffic_report_threshold_desc": "Állítsa be a forgalom jelentÃĐsÃĐnek minimÃĄlis kÞszÃķbÃĐt. A forgalmat csak akkor jelentjÞk, ha meghaladja ezt az ÃĐrtÃĐket. Állítsa 0-ra vagy hagyja Þresen, hogy minden forgalmat jelenteni tudjon."
},
"saveSuccess": "Sikeresen mentve",
"tabs": {
"basic": "AlapÃĐrtelmezett BeÃĄllítÃĄsok",
"block": "BlokkolÃĄsi SzabÃĄlyok",
"dns": "DNS BeÃĄllítÃĄsok",
"outbound": "Kimenő SzabÃĄlyok"
},
"title": "Node konfigurÃĄciÃģ"
},
"server_key": "Szerver kulcs", "server_key": "Szerver kulcs",
"service_name": "SzolgÃĄltatÃĄs neve", "service_name": "SzolgÃĄltatÃĄs neve",
"sorted_success": "Sikeresen rendezve",
"status": "Állapot", "status": "Állapot",
"subscribeId": "ElőfizetÃĐsi ID", "subscribeId": "ElőfizetÃĐsi ID",
"subscription": "ElőfizetÃĐs", "subscription": "ElőfizetÃĐs",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "MegerősítÃĐs", "confirm": "MegerősítÃĐs",
"confirmDelete": "Biztosan tÃķrÃķlni szeretnÃĐ?", "confirmDelete": "Biztosan tÃķrÃķlni szeretnÃĐ?",
"confirmOffline": "MegerősítÃĐs offline", "confirmOffline": "MegerősítÃĐs offline",
"copySubscription": "ElőfizetÃĐs mÃĄsolÃĄsa",
"copySuccess": "MÃĄsolÃĄs sikeres",
"create": "LÃĐtrehozÃĄs", "create": "LÃĐtrehozÃĄs",
"createSubscription": "ElőfizetÃĐs lÃĐtrehozÃĄsa", "createSubscription": "ElőfizetÃĐs lÃĐtrehozÃĄsa",
"createSuccess": "Sikeres lÃĐtrehozÃĄs", "createSuccess": "Sikeres lÃĐtrehozÃĄs",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "プロトã‚ģãƒŦをéļ択â€Ķ", "select_protocol": "プロトã‚ģãƒŦをéļ択â€Ķ",
"select_server": "ã‚ĩマバマをéļ択â€Ķ", "select_server": "ã‚ĩマバマをéļ択â€Ķ",
"server": "ã‚ĩマバマ", "server": "ã‚ĩマバマ",
"sorted_success": "æ­ĢåļļãŦã‚―ãƒžãƒˆã•ã‚Œãūした",
"tags": "ã‚ŋグ", "tags": "ã‚ŋグ",
"tags_description": "æĻĐ限グãƒŦマプ化ã‚ŋグプãƒĐãƒģバã‚Īãƒģデã‚Ģãƒģグおよãģ配äŋĄãƒãƒŠã‚·ãƒžã‚’åŦむ。", "tags_description": "æĻĐ限グãƒŦマプ化ã‚ŋグプãƒĐãƒģバã‚Īãƒģデã‚Ģãƒģグおよãģ配äŋĄãƒãƒŠã‚·ãƒžã‚’åŦむ。",
"tags_placeholder": "čĪ‡æ•°ãŪã‚ŋグをčŋ―加するãŦãŊEnterãūたãŊã‚Ŧãƒģマ(,)をä―ŋį”ĻしãĶください", "tags_placeholder": "čĪ‡æ•°ãŪã‚ŋグをčŋ―加するãŦãŊEnterãūたãŊã‚Ŧãƒģマ(,)をä―ŋį”ĻしãĶください",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "ã‚Ēドノã‚đ",
"address_placeholder": "ã‚ĩマバマã‚Ēドノã‚đ",
"bandwidth_placeholder": "åļŊ域åđ…ã‚’å…Ĩ力しãĶください。BBRãŪå ī合ãŊįĐšį™―ãŪãūãūãŦしãĶください。",
"basic": "埚朎čĻ­åۚ",
"cancel": "キãƒĢãƒģã‚ŧãƒŦ",
"city": "éƒ―åļ‚",
"config": {
"actions": { "actions": {
"cancel": "キãƒĢãƒģã‚ŧãƒŦ", "cancel": "キãƒĢãƒģã‚ŧãƒŦ",
"save": "äŋå­˜" "save": "äŋå­˜"
}, },
"communicationKey": "通äŋĄã‚­ãƒž", "address": "ã‚Ēドノã‚đ",
"communicationKeyDescription": "ãƒŽãƒžãƒ‰čŠčĻžãŦä―ŋį”Ļされãūす。", "address_placeholder": "ã‚ĩマバマã‚Ēドノã‚đ",
"description": "ノマドãŪ通äŋĄã‚­ãƒžã€ãƒ—ãƒŦ/プッシãƒĨé–“éš”ã€å‹•įš„å€įŽ‡ã‚’įŪĄį†ã—ãūす。", "apiHost": "APIホã‚đト",
"dynamicMultiplier": "å‹•įš„å€įŽ‡", "apiHostPlaceholder": "http(s)://example.com",
"dynamicMultiplierDescription": "トãƒĐフã‚Ģッã‚ŊčψįŪ—ã‚’čŠŋæ•īするためãŪ時間ã‚đロットãĻå€įŽ‡ã‚’åۚįūĐしãūす。", "bandwidth_placeholder": "åļŊ域åđ…ã‚’å…Ĩ力しãĶください。BBRãŪå ī合ãŊįĐšį™―ãŪãūãūãŦしãĶください。",
"endTime": "įĩ‚䚆時間", "basic": "埚朎čĻ­åۚ",
"inputPlaceholder": "å…Ĩ力しãĶください", "cancel": "キãƒĢãƒģã‚ŧãƒŦ",
"multiplier": "å€įŽ‡", "cert_dns_env": "DNSį’°åǃåĪ‰æ•°",
"nodePullInterval": "ノマドプãƒŦ間隔", "cert_dns_provider": "DNSプロバã‚Īダマ",
"nodePullIntervalDescription": "ノマドがčĻ­åŪšã‚’ãƒ—ãƒŦするé ŧåšĶïžˆį§’ïž‰ã€‚", "cert_mode": "čĻžæ˜Žæ›ļãƒĒマド",
"nodePushInterval": "ノマドプッシãƒĨ間隔", "cipher": "暗号化ã‚ĒãƒŦã‚īナ゚ム",
"nodePushIntervalDescription": "ノマドがįĩąčĻˆã‚’ãƒ—ãƒƒã‚·ãƒĨするé ŧåšĶïžˆį§’ïž‰ã€‚", "city": "éƒ―åļ‚",
"reset": "ナã‚ŧット",
"save": "äŋå­˜",
"saveSuccess": "äŋå­˜ãŦ成功しãūした",
"startTime": "開始時間",
"timeSlot": "時間ã‚đロット",
"title": "ノマドčĻ­åۚ"
},
"confirm": "įĒščŠ", "confirm": "įĒščŠ",
"confirmDeleteDesc": "こãŪæ“ä―œãŊ元ãŦæˆŧせãūせん。", "confirmDeleteDesc": "こãŪæ“ä―œãŊ元ãŦæˆŧせãūせん。",
"confirmDeleteTitle": "こãŪã‚ĩマバマを削é™Īしãūすか", "confirmDeleteTitle": "こãŪã‚ĩマバマを削é™Īしãūすか",
"congestion_controller": "混雑åˆķåūĄ", "congestion_controller": "混雑åˆķåūĄ",
"connect": "æŽĨįķš",
"copied": "ã‚ģピマしãūした", "copied": "ã‚ģピマしãūした",
"copy": "ã‚ģピマ", "copy": "ã‚ģピマ",
"country": "å›―", "country": "å›―",
@@ -49,28 +36,46 @@
"drawerEditTitle": "ã‚ĩマバマをį·Ļ集", "drawerEditTitle": "ã‚ĩマバマをį·Ļ集",
"edit": "į·Ļ集", "edit": "į·Ļ集",
"enabled": "有åŠđ", "enabled": "有åŠđ",
"encryption_method": "暗号化æ–đ垏", "encryption": "暗号化æ–đ垏",
"encryption_client_padding": "ã‚ŊãƒĐã‚Īã‚Ēãƒģトパデã‚Ģãƒģグ",
"encryption_mode": "ãƒĒマド",
"encryption_password": "パã‚đãƒŊマド",
"encryption_password_placeholder": "č‡Šå‹•į”ŸæˆãŪためãŦįĐšį™―ãŪãūãūãŦし、ポã‚đト量子暗号化ãŪためãŦ手動でį―Ūき換えãĶください",
"encryption_private_key": "᧘åŊ†éĩ",
"encryption_private_key_placeholder": "č‡Šå‹•į”ŸæˆãŪためãŦįĐšį™―ãŪãūãūãŦし、ポã‚đト量子暗号化ãŪためãŦ手動でį―Ūき換えãĶください",
"encryption_rtt": "RTT",
"encryption_server_padding": "ã‚ĩマバマパデã‚Ģãƒģグ",
"encryption_ticket": "ãƒã‚ąãƒƒãƒˆæ™‚é–“",
"expireTime": "有åŠđ期限", "expireTime": "有åŠđ期限",
"expired": "期限切れ", "expired": "期限切れ",
"extra": "čŋ―加čĻ­åۚ",
"flow": "フロマ", "flow": "フロマ",
"generate_quantum_resistant_key": "é‡å­č€æ€§ã‚­ãƒžã‚’į”Ÿæˆ",
"generate_standard_encryption_key": "æĻ™æš–æš—å·åŒ–ã‚­ãƒžã‚’į”Ÿæˆ",
"hop_interval": "ホップ間隔", "hop_interval": "ホップ間隔",
"hop_ports": "ホップポマト", "hop_ports": "ホップポマト",
"hop_ports_placeholder": "äū‹: 1-65535", "hop_ports_placeholder": "äū‹: 1-65535",
"host": "ホã‚đト", "host": "ホã‚đト",
"id": "ID", "id": "ID",
"installCommand": "ã‚Īãƒģã‚đトマãƒŦã‚ģマãƒģド",
"ipAddresses": "IPã‚Ēドノã‚đ", "ipAddresses": "IPã‚Ēドノã‚đ",
"memory": "ãƒĄãƒĒナ", "memory": "ãƒĄãƒĒナ",
"migrate": "デマã‚ŋをį§ŧčĄŒã™ã‚‹", "migrate": "デマã‚ŋをį§ŧčĄŒã™ã‚‹",
"migrateFailed": "デマã‚ŋãŪį§ŧ行ãŦåĪąæ•—ã—ãūした", "migrateFailed": "デマã‚ŋãŪį§ŧ行ãŦåĪąæ•—ã—ãūした",
"migrated": "デマã‚ŋがæ­ĢåļļãŦį§ŧčĄŒã•ã‚Œãūした", "migrated": "デマã‚ŋがæ­ĢåļļãŦį§ŧčĄŒã•ã‚Œãūした",
"migrating": "į§ŧ行äļ­...", "migrating": "į§ŧ行äļ­...",
"mode": "ãƒĒマド",
"multiplex": "マãƒŦチプノッã‚Ŋã‚đ", "multiplex": "マãƒŦチプノッã‚Ŋã‚đ",
"name": "名前", "name": "名前",
"noData": "デマã‚ŋおし", "noData": "デマã‚ŋおし",
"notAvailable": "åˆĐį”ĻäļåŊ", "notAvailable": "åˆĐį”ĻäļåŊ",
"obfs": "é›ĢčŠ­åŒ–",
"obfs_host": "é›ĢčŠ­åŒ–ãƒ›ã‚đト",
"obfs_password": "é›ĢčŠ­åŒ–ãƒ‘ã‚đãƒŊマド", "obfs_password": "é›ĢčŠ­åŒ–ãƒ‘ã‚đãƒŊマド",
"obfs_password_placeholder": "é›ĢčŠ­åŒ–ãƒ‘ã‚đãƒŊマドをå…Ĩ力しãĶください", "obfs_password_placeholder": "é›ĢčŠ­åŒ–ãƒ‘ã‚đãƒŊマドをå…Ĩ力しãĶください",
"obfs_path": "é›ĢčŠ­åŒ–ãƒ‘ã‚đ",
"offline": "りフãƒĐã‚Īãƒģ", "offline": "りフãƒĐã‚Īãƒģ",
"oneClickInstall": "ãƒŊãƒģã‚Ŋナッã‚Ŋã‚Īãƒģã‚đトマãƒŦ",
"online": "りãƒģãƒĐã‚Īãƒģ", "online": "りãƒģãƒĐã‚Īãƒģ",
"onlineUsers": "りãƒģãƒĐã‚ĪãƒģãƒĶマã‚ķマ", "onlineUsers": "りãƒģãƒĐã‚ĪãƒģãƒĶマã‚ķマ",
"padding_scheme": "パデã‚Ģãƒģグã‚đキマム", "padding_scheme": "パデã‚Ģãƒģグã‚đキマム",
@@ -78,8 +83,6 @@
"pageTitle": "ã‚ĩマバマ", "pageTitle": "ã‚ĩマバマ",
"path": "パã‚đ", "path": "パã‚đ",
"please_select": "éļ択しãĶください", "please_select": "éļ択しãĶください",
"plugin": "プãƒĐグã‚ĪãƒģčĻ­åۚ",
"plugin_opts": "プãƒĐグã‚Īãƒģりプショãƒģ",
"port": "ポマト", "port": "ポマト",
"protocol_configurations": "プロトã‚ģãƒŦčĻ­åۚ", "protocol_configurations": "プロトã‚ģãƒŦčĻ­åۚ",
"protocol_configurations_desc": "åŋ…čĶãŠãƒ—ãƒ­ãƒˆã‚ģãƒŦã‚ŋã‚Īプを有åŠđãŦしãĶčĻ­åŪšã—ãūす", "protocol_configurations_desc": "åŋ…čĶãŠãƒ—ãƒ­ãƒˆã‚ģãƒŦã‚ŋã‚Īプを有åŠđãŦしãĶčĻ­åŪšã—ãūす",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "16文字äŧĨ内ãŪ16é€ē数文字列", "security_short_id_placeholder": "16文字äŧĨ内ãŪ16é€ē数文字列",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "暗号化æ–đ垏をéļ択", "select_encryption_method": "暗号化æ–đ垏をéļ択",
"server_config": {
"description": "ノマド通äŋĄã‚­ãƒžã€ãƒ—ãƒŦ/プッシãƒĨ間隔をįŪĄį†ã—ãūす。",
"dynamic_multiplier": "å‹•įš„äđ—æ•°",
"dynamic_multiplier_desc": "トãƒĐフã‚Ģッã‚ŊčψįŪ—ã‚’čŠŋæ•īするためãŪ時間ã‚đロットãĻäđ—æ•°ã‚’åۚįūĐしãūす。",
"fields": {
"block_rules_placeholder": "1行ãŦ1ãĪãŪãƒ‰ãƒĄã‚ĪãƒģãƒŦマãƒŦ、ã‚ĩポマト:\nkeyword:googleキマãƒŊマドマッチãƒģグ\nsuffix:google.comã‚ĩフã‚Ģッã‚Ŋã‚đマッチãƒģグ\nregex:.*\\.example\\.com$æ­ĢčĶčĄĻįūマッチãƒģグ\nexample.comåیå…Ļäļ€č‡ī",
"communication_key": "通äŋĄã‚­ãƒž",
"communication_key_desc": "ãƒŽãƒžãƒ‰čŠčĻžãŦä―ŋį”Ļされãūす。",
"communication_key_placeholder": "å…Ĩ力しãĶください",
"dns_config": "DNSčĻ­åۚ",
"dns_domains_placeholder": "1行ãŦ1ãĪãŪãƒ‰ãƒĄã‚ĪãƒģãƒŦマãƒŦ、ã‚ĩポマト:\nkeyword:googleキマãƒŊマドマッチãƒģグ\nsuffix:google.comã‚ĩフã‚Ģッã‚Ŋã‚đマッチãƒģグ\nregex:.*\\.example\\.com$æ­ĢčĶčĄĻįūマッチãƒģグ\nexample.comåیå…Ļäļ€č‡ī",
"dns_proto_placeholder": "ã‚ŋã‚Īプをéļ択",
"end_time": "įĩ‚䚆時間",
"ip_strategy": "IPæˆĶį•Ĩ",
"ip_strategy_desc": "ネットãƒŊマã‚ŊæŽĨįķšãŪためãŪIPバマã‚ļョãƒģãŪ儊先åšĶをéļ択しãūす。",
"ip_strategy_ipv4": "IPv4を儊先",
"ip_strategy_ipv6": "IPv6を儊先",
"ip_strategy_placeholder": "IPæˆĶį•Ĩをéļ択",
"multiplier": "äđ—æ•°",
"node_pull_interval": "ノマドプãƒŦ間隔",
"node_pull_interval_desc": "ノマドがčĻ­åŪšã‚’ãƒ—ãƒŦするé ŧåšĶïžˆį§’ïž‰ã€‚",
"node_push_interval": "ノマドプッシãƒĨ間隔",
"node_push_interval_desc": "ノマドがįĩąčĻˆã‚’ãƒ—ãƒƒã‚·ãƒĨするé ŧåšĶïžˆį§’ïž‰ã€‚",
"outbound_address_placeholder": "ã‚ĩマバマã‚Ēドノã‚đ",
"outbound_name_placeholder": "čĻ­åŪšå",
"outbound_password_placeholder": "パã‚đãƒŊマドりプショãƒģ",
"outbound_port_placeholder": "ãƒãƒžãƒˆį•Šå·",
"outbound_protocol_placeholder": "プロトã‚ģãƒŦをéļ択",
"outbound_rules_placeholder": "1行ãŦ1ãĪãŪãƒŦマãƒŦ、ã‚ĩポマト:\nkeyword:googleキマãƒŊマドマッチãƒģグ\nsuffix:google.comã‚ĩフã‚Ģッã‚Ŋã‚đマッチãƒģグ\nregex:.*\\.example\\.com$æ­ĢčĶčĄĻįūマッチãƒģグ\nexample.comåیå…Ļäļ€č‡ī\nデフã‚ĐãƒŦトãƒŦマテã‚ĢãƒģグãŦãŊįĐšį™―ãŪãūãūãŦしãĶください",
"reset": "ナã‚ŧット",
"save": "äŋå­˜",
"start_time": "開始時間",
"time_slot": "時間ã‚đロット",
"traffic_report_threshold": "トãƒĐフã‚Ģッã‚ŊノポマトãŪé–ūå€Ī",
"traffic_report_threshold_desc": "トãƒĐフã‚Ģッã‚Ŋå ąå‘ŠãŪ最小é–ūå€ĪをčĻ­åŪšã—ãūす。こãŪå€Īをčķ…えたå ī合ãŪãŋトãƒĐフã‚Ģッã‚ŊãŒå ąå‘Šã•ã‚Œãūす。すãđãĶãŪトãƒĐフã‚Ģッã‚Ŋã‚’å ąå‘Šã™ã‚‹ãŦãŊ0ãŦčĻ­åŪšã™ã‚‹ã‹ã€įĐšį™―ãŪãūãūãŦしãĶください。"
},
"saveSuccess": "æ­ĢåļļãŦäŋå­˜ã•れãūした",
"tabs": {
"basic": "埚朎čĻ­åۚ",
"block": "ブロッã‚ŊãƒŦマãƒŦ",
"dns": "DNSčĻ­åۚ",
"outbound": "ã‚Ēã‚Ķトバã‚ĶãƒģドãƒŦマãƒŦ"
},
"title": "ノマドčĻ­åۚ"
},
"server_key": "ã‚ĩマバマキマ", "server_key": "ã‚ĩマバマキマ",
"service_name": "ã‚ĩマビã‚đ名", "service_name": "ã‚ĩマビã‚đ名",
"sorted_success": "æ­ĢåļļãŦã‚―ãƒžãƒˆã•ã‚Œãūした",
"status": "ã‚đテマã‚ŋã‚đ", "status": "ã‚đテマã‚ŋã‚đ",
"subscribeId": "ã‚ĩブã‚đã‚ŊナプショãƒģID", "subscribeId": "ã‚ĩブã‚đã‚ŊナプショãƒģID",
"subscription": "ã‚ĩブã‚đã‚Ŋナプショãƒģ", "subscription": "ã‚ĩブã‚đã‚Ŋナプショãƒģ",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "įĒščŠ", "confirm": "įĒščŠ",
"confirmDelete": "削é™ĪしãĶもよろしいですか", "confirmDelete": "削é™ĪしãĶもよろしいですか",
"confirmOffline": "りフãƒĐã‚ĪãƒģをįĒščŠ", "confirmOffline": "りフãƒĐã‚ĪãƒģをįĒščŠ",
"copySubscription": "ã‚ĩブã‚đã‚Ŋナプショãƒģをã‚ģピマ",
"copySuccess": "ã‚ģピマが成功しãūした",
"create": "ä―œæˆ", "create": "ä―œæˆ",
"createSubscription": "ã‚ĩブã‚đã‚Ŋナプショãƒģã‚’ä―œæˆ", "createSubscription": "ã‚ĩブã‚đã‚Ŋナプショãƒģã‚’ä―œæˆ",
"createSuccess": "ä―œæˆæˆåŠŸ", "createSuccess": "ä―œæˆæˆåŠŸ",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "í”„ëĄœí† ė―œ ė„ íƒâ€Ķ", "select_protocol": "í”„ëĄœí† ė―œ ė„ íƒâ€Ķ",
"select_server": "ė„œëē„ ė„ íƒâ€Ķ", "select_server": "ė„œëē„ ė„ íƒâ€Ķ",
"server": "ė„œëē„", "server": "ė„œëē„",
"sorted_success": "ė •ë Žėī 뙄ëĢŒë˜ė—ˆėŠĩ니ë‹Ī.",
"tags": "태ę·ļ", "tags": "태ę·ļ",
"tags_description": "ęķŒí•œ ę·ļëĢđ화 태ę·ļ(ęģ„획 바ėļë”Đ 및 배폎 ė •ėą… 폎í•Ļ).", "tags_description": "ęķŒí•œ ę·ļëĢđ화 태ę·ļ(ęģ„획 바ėļë”Đ 및 배폎 ė •ėą… 폎í•Ļ).",
"tags_placeholder": "ė—ŽëŸŽ 태ę·ļëĨž ėķ”ę°€í•˜ë ĪëĐī Enter 또는 ė‰ží‘œ(,)ëĨž ė‚ŽėšĐ하ė„ļėš”", "tags_placeholder": "ė—ŽëŸŽ 태ę·ļëĨž ėķ”ę°€í•˜ë ĪëĐī Enter 또는 ė‰ží‘œ(,)ëĨž ė‚ŽėšĐ하ė„ļėš”",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "ėĢžė†Œ",
"address_placeholder": "ė„œëē„ ėĢžė†Œ",
"bandwidth_placeholder": "ëŒ€ė—­í­ė„ ėž…ë Ĩ하ė„ļėš”. BBRė„ ė‚ŽėšĐ하ë ĪëĐī ëđ„ė›Œ 두ė„ļėš”.",
"basic": "ęļ°ëģļ ė„Īė •",
"cancel": "ė·Ļė†Œ",
"city": "ë„ė‹œ",
"config": {
"actions": { "actions": {
"cancel": "ė·Ļė†Œ", "cancel": "ė·Ļė†Œ",
"save": "ė €ėžĨ" "save": "ė €ėžĨ"
}, },
"communicationKey": "í†ĩė‹  í‚Ī", "address": "ėĢžė†Œ",
"communicationKeyDescription": "ë…ļ드 ėļėĶė— ė‚ŽėšĐëĐ니ë‹Ī.", "address_placeholder": "ė„œëē„ ėĢžė†Œ",
"description": "ë…ļ드 í†ĩė‹  í‚Ī, 풀/í‘ļė‹œ 간ęēР및 ë™ė  ë°°ėˆ˜ëĨž ęī€ëĶŽí•Đ니ë‹Ī.", "apiHost": "API í˜ļėŠĪíŠļ",
"dynamicMultiplier": "ë™ė  ë°°ėˆ˜", "apiHostPlaceholder": "http(s)://example.com",
"dynamicMultiplierDescription": "íŠļëž˜í”― 회ęģ„ëĨž ėĄ°ė •í•˜ęļ° ėœ„í•œ ė‹œę°„ ėŠŽëĄŊ 및 ë°°ėˆ˜ëĨž ė •ė˜í•Đ니ë‹Ī.", "bandwidth_placeholder": "ëŒ€ė—­í­ė„ ėž…ë Ĩ하ė„ļėš”. BBRė„ ė‚ŽėšĐ하ë ĪëĐī ëđ„ė›Œ 두ė„ļėš”.",
"endTime": "ėĒ…ë̌ ė‹œę°„", "basic": "ęļ°ëģļ ė„Īė •",
"inputPlaceholder": "ėž…ë Ĩí•ī ėĢžė„ļėš”", "cancel": "ė·Ļė†Œ",
"multiplier": "ë°°ėˆ˜", "cert_dns_env": "DNS 환ęē― ëģ€ėˆ˜",
"nodePullInterval": "ë…ļ드 풀 간ęēĐ", "cert_dns_provider": "DNS 렜ęģĩėž",
"nodePullIntervalDescription": "ë…ļ드가 ęĩŽė„ąė„ 가ė ļė˜Ī는 ëđˆë„(ėīˆ ë‹Ļėœ„).", "cert_mode": "ėļėĶė„œ ëŠĻ드",
"nodePushInterval": "ë…ļ드 í‘ļė‹œ 간ęēĐ", "cipher": "ė•”í˜ļ화 ė•Œęģ ëĶŽė͘",
"nodePushIntervalDescription": "ë…ļ드가 í†ĩęģ„ëĨž í‘ļė‹œí•˜ëŠ” ëđˆë„(ėīˆ ë‹Ļėœ„).", "city": "ë„ė‹œ",
"reset": "ėīˆęļ°í™”",
"save": "ė €ėžĨ",
"saveSuccess": "ė €ėžĨ ė„ąęģĩ",
"startTime": "ė‹œėž‘ ė‹œę°„",
"timeSlot": "ė‹œę°„ ėŠŽëĄŊ",
"title": "ë…ļ드 ęĩŽė„ą"
},
"confirm": "확ėļ", "confirm": "확ėļ",
"confirmDeleteDesc": "ėī ėž‘ė—…ė€ ė‹Ī행 ė·Ļė†Œí•  눘 ė—†ėŠĩ니ë‹Ī.", "confirmDeleteDesc": "ėī ėž‘ė—…ė€ ė‹Ī행 ė·Ļė†Œí•  눘 ė—†ėŠĩ니ë‹Ī.",
"confirmDeleteTitle": "ėī ė„œëē„ëĨž ė‚­ė œí•˜ė‹œęē ėŠĩ니ęđŒ?", "confirmDeleteTitle": "ėī ė„œëē„ëĨž ė‚­ė œí•˜ė‹œęē ėŠĩ니ęđŒ?",
"congestion_controller": "í˜žėžĄ 렜ė–īęļ°", "congestion_controller": "í˜žėžĄ 렜ė–īęļ°",
"connect": "ė—°ęē°",
"copied": "ëģĩė‚ŽëĻ", "copied": "ëģĩė‚ŽëĻ",
"copy": "ëģĩė‚Ž", "copy": "ëģĩė‚Ž",
"country": "ęĩ­ę°€", "country": "ęĩ­ę°€",
@@ -49,28 +36,46 @@
"drawerEditTitle": "ė„œëē„ íŽļė§‘", "drawerEditTitle": "ė„œëē„ íŽļė§‘",
"edit": "íŽļė§‘", "edit": "íŽļė§‘",
"enabled": "í™œė„ąí™”ëĻ", "enabled": "í™œė„ąí™”ëĻ",
"encryption_method": "ė•”í˜ļ화 ë°Đëē•", "encryption": "ė•”í˜ļ화 ë°Đëē•",
"encryption_client_padding": "íī띞ėīė–ļíŠļ íŒĻë”Đ",
"encryption_mode": "ëŠĻ드",
"encryption_password": "ëđ„ë°€ëēˆí˜ļ",
"encryption_password_placeholder": "ėžë™ ėƒė„ąí•˜ë ĪëĐī ëđ„ė›Œ 두ęģ , 폎ėŠĪíŠļ ė–‘ėž ė•”í˜ļ화ëĨž ėœ„í•ī ėˆ˜ë™ėœžëĄœ ęĩėēī하ė„ļėš”",
"encryption_private_key": "氜ėļ í‚Ī",
"encryption_private_key_placeholder": "ėžë™ ėƒė„ąí•˜ë ĪëĐī ëđ„ė›Œ 두ęģ , 폎ėŠĪíŠļ ė–‘ėž ė•”í˜ļ화ëĨž ėœ„í•ī ėˆ˜ë™ėœžëĄœ ęĩėēī하ė„ļėš”",
"encryption_rtt": "RTT",
"encryption_server_padding": "ė„œëē„ íŒĻë”Đ",
"encryption_ticket": "í‹°ėž“ ė‹œę°„",
"expireTime": "만ë̌ ė‹œę°„", "expireTime": "만ë̌ ė‹œę°„",
"expired": "만ë̌ëĻ", "expired": "만ë̌ëĻ",
"extra": "ėķ”ę°€ ęĩŽė„ą",
"flow": "흐ëĶ„", "flow": "흐ëĶ„",
"generate_quantum_resistant_key": "ė–‘ėž ė €í•­ í‚Ī ėƒė„ą",
"generate_standard_encryption_key": "표ėĪ€ ė•”í˜ļ화 í‚Ī ėƒė„ą",
"hop_interval": "홉 간ęēĐ", "hop_interval": "홉 간ęēĐ",
"hop_ports": "홉 폎íŠļ", "hop_ports": "홉 폎íŠļ",
"hop_ports_placeholder": "똈: 1-65535", "hop_ports_placeholder": "똈: 1-65535",
"host": "í˜ļėŠĪíŠļ", "host": "í˜ļėŠĪíŠļ",
"id": "ID", "id": "ID",
"installCommand": "ė„Īėđ˜ 멅ë đ",
"ipAddresses": "IP ėĢžė†Œ", "ipAddresses": "IP ėĢžė†Œ",
"memory": "ëДëŠĻëĶŽ", "memory": "ëДëŠĻëĶŽ",
"migrate": "데ėī터 마ėīę·ļ레ėīė…˜", "migrate": "데ėī터 마ėīę·ļ레ėīė…˜",
"migrateFailed": "데ėī터 마ėīę·ļ레ėīė…˜ ė‹ĪíŒĻ", "migrateFailed": "데ėī터 마ėīę·ļ레ėīė…˜ ė‹ĪíŒĻ",
"migrated": "데ėī터가 ė„ąęģĩė ėœžëĄœ 마ėīę·ļ레ėīė…˜ë˜ė—ˆėŠĩ니ë‹Ī", "migrated": "데ėī터가 ė„ąęģĩė ėœžëĄœ 마ėīę·ļ레ėīė…˜ë˜ė—ˆėŠĩ니ë‹Ī",
"migrating": "마ėīę·ļ레ėīė…˜ ėĪ‘...", "migrating": "마ėīę·ļ레ėīė…˜ ėĪ‘...",
"mode": "ëŠĻ드",
"multiplex": "ëЀ티플렉ėŠĪ", "multiplex": "ëЀ티플렉ėŠĪ",
"name": "ėīëĶ„", "name": "ėīëĶ„",
"noData": "데ėī터 ė—†ėŒ", "noData": "데ėī터 ė—†ėŒ",
"notAvailable": "ė‚ŽėšĐ ëķˆę°€", "notAvailable": "ė‚ŽėšĐ ëķˆę°€",
"obfs": "난독화",
"obfs_host": "난독화 í˜ļėŠĪíŠļ",
"obfs_password": "난독화 ëđ„ë°€ëēˆí˜ļ", "obfs_password": "난독화 ëđ„ë°€ëēˆí˜ļ",
"obfs_password_placeholder": "난독화 ëđ„ë°€ëēˆí˜ļëĨž ėž…ë Ĩ하ė„ļėš”", "obfs_password_placeholder": "난독화 ëđ„ë°€ëēˆí˜ļëĨž ėž…ë Ĩ하ė„ļėš”",
"obfs_path": "난독화 ęē―로",
"offline": "ė˜Ī프띾ėļ", "offline": "ė˜Ī프띾ėļ",
"oneClickInstall": "뛐íīëĶ­ ė„Īėđ˜",
"online": "ė˜Ļ띞ėļ", "online": "ė˜Ļ띞ėļ",
"onlineUsers": "ė˜Ļ띞ėļ ė‚ŽėšĐėž", "onlineUsers": "ė˜Ļ띞ėļ ė‚ŽėšĐėž",
"padding_scheme": "íŒĻë”Đ 淜ėđ™", "padding_scheme": "íŒĻë”Đ 淜ėđ™",
@@ -78,8 +83,6 @@
"pageTitle": "ė„œëē„", "pageTitle": "ė„œëē„",
"path": "ęē―로", "path": "ęē―로",
"please_select": "ė„ íƒí•ī ėĢžė„ļėš”", "please_select": "ė„ íƒí•ī ėĢžė„ļėš”",
"plugin": "플럮ę·ļėļ ė„Īė •",
"plugin_opts": "플럮ę·ļėļ ė˜ĩė…˜",
"port": "폎íŠļ", "port": "폎íŠļ",
"protocol_configurations": "í”„ëĄœí† ė―œ ė„Īė •", "protocol_configurations": "í”„ëĄœí† ė―œ ė„Īė •",
"protocol_configurations_desc": "í•„ėš”í•œ í”„ëĄœí† ė―œ ėœ í˜•ė„ í™œė„ąí™”í•˜ęģ  ęĩŽė„ąí•Đ니ë‹Ī", "protocol_configurations_desc": "í•„ėš”í•œ í”„ëĄœí† ė―œ ėœ í˜•ė„ í™œė„ąí™”í•˜ęģ  ęĩŽė„ąí•Đ니ë‹Ī",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "16ėž ėīë‚īė˜ í—ĨėŠĪ ëŽļėžė—ī", "security_short_id_placeholder": "16ėž ėīë‚īė˜ í—ĨėŠĪ ëŽļėžė—ī",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "ė•”í˜ļ화 ë°Đëē• ė„ íƒ", "select_encryption_method": "ė•”í˜ļ화 ë°Đëē• ė„ íƒ",
"server_config": {
"description": "ë…ļ드 í†ĩė‹  í‚Ī, 풀/í‘ļė‹œ 간ęēĐ ęī€ëĶŽ.",
"dynamic_multiplier": "ë™ė  ë°°ėˆ˜",
"dynamic_multiplier_desc": "íŠļëž˜í”― 회ęģ„ëĨž ėĄ°ė •í•˜ęļ° ėœ„í•œ ė‹œę°„ ėŠŽëĄŊęģž ë°°ėˆ˜ëĨž ė •ė˜í•Đ니ë‹Ī.",
"fields": {
"block_rules_placeholder": "한 ė΄뗐 í•˜ë‚˜ė˜ 도ëДėļ 淜ėđ™, 맀뛐:\nkeyword:google (í‚Īė›Œë“œ ėžėđ˜)\nsuffix:google.com (ė ‘ëŊļė‚Ž ėžėđ˜)\nregex:.*\\.example\\.com$ (ė •ę·œ í‘œí˜„ė‹ ėžėđ˜)\nexample.com (ė •í™•í•œ ėžėđ˜)",
"communication_key": "í†ĩė‹  í‚Ī",
"communication_key_desc": "ë…ļ드 ėļėĶė— ė‚ŽėšĐëĐ니ë‹Ī.",
"communication_key_placeholder": "ėž…ë Ĩí•ī ėĢžė„ļėš”",
"dns_config": "DNS ęĩŽė„ą",
"dns_domains_placeholder": "한 ė΄뗐 í•˜ë‚˜ė˜ 도ëДėļ 淜ėđ™, 맀뛐:\nkeyword:google (í‚Īė›Œë“œ ėžėđ˜)\nsuffix:google.com (ė ‘ëŊļė‚Ž ėžėđ˜)\nregex:.*\\.example\\.com$ (ė •ę·œ í‘œí˜„ė‹ ėžėđ˜)\nexample.com (ė •í™•í•œ ėžėđ˜)",
"dns_proto_placeholder": "ėœ í˜• ė„ íƒ",
"end_time": "ėĒ…ë̌ ė‹œę°„",
"ip_strategy": "IP ė „ëžĩ",
"ip_strategy_desc": "ë„ĪíŠļė›ŒíŽ ė—°ęē°ė„ ėœ„í•œ IP ëē„ė „ ė„ í˜ļ도ëĨž ė„ íƒí•Đ니ë‹Ī.",
"ip_strategy_ipv4": "IPv4 ėš°ė„ ",
"ip_strategy_ipv6": "IPv6 ėš°ė„ ",
"ip_strategy_placeholder": "IP ė „ëžĩ ė„ íƒ",
"multiplier": "ë°°ėˆ˜",
"node_pull_interval": "ë…ļ드 풀 간ęēĐ",
"node_pull_interval_desc": "ë…ļ드가 ęĩŽė„ąė„ 가ė ļė˜Ī는 ëđˆë„(ėīˆ).",
"node_push_interval": "ë…ļ드 í‘ļė‹œ 간ęēĐ",
"node_push_interval_desc": "ë…ļ드가 í†ĩęģ„ëĨž í‘ļė‹œí•˜ëŠ” ëđˆë„(ėīˆ).",
"outbound_address_placeholder": "ė„œëē„ ėĢžė†Œ",
"outbound_name_placeholder": "ęĩŽė„ą ėīëĶ„",
"outbound_password_placeholder": "ëđ„ë°€ëēˆí˜ļ (ė„ íƒ ė‚Ží•­)",
"outbound_port_placeholder": "폎íŠļ ëēˆí˜ļ",
"outbound_protocol_placeholder": "í”„ëĄœí† ė―œ ė„ íƒ",
"outbound_rules_placeholder": "한 ė΄뗐 í•˜ë‚˜ė˜ 淜ėđ™, 맀뛐:\nkeyword:google (í‚Īė›Œë“œ ėžėđ˜)\nsuffix:google.com (ė ‘ëŊļė‚Ž ėžėđ˜)\nregex:.*\\.example\\.com$ (ė •ę·œ í‘œí˜„ė‹ ėžėđ˜)\nexample.com (ė •í™•í•œ ėžėđ˜)\nęļ°ëģļ ëžėš°íŒ…ė„ ėœ„í•ī ëđ„ė›Œë‘ęļ°",
"reset": "ėīˆęļ°í™”",
"save": "ė €ėžĨ",
"start_time": "ė‹œėž‘ ė‹œę°„",
"time_slot": "ė‹œę°„ ėŠŽëĄŊ",
"traffic_report_threshold": "íŠļëž˜í”― ëģīęģ  ėž„ęģ„ę°’",
"traffic_report_threshold_desc": "íŠļëž˜í”― ëģīęģ ëĨž ėœ„í•œ ėĩœė†Œ ėž„ęģ„ę°’ė„ ė„Īė •í•Đ니ë‹Ī. ėī ę°’ė„ ėīˆęģží•  때만 íŠļëž˜í”―ėī ëģīęģ ëĐ니ë‹Ī. 0ėœžëĄœ ė„Īė •í•˜ęą°ë‚˜ ëđ„ė›Œë‘ëĐī ëŠĻ든 íŠļëž˜í”―ėī ëģīęģ ëĐ니ë‹Ī."
},
"saveSuccess": "ė„ąęģĩė ėœžëĄœ ė €ėžĨë˜ė—ˆėŠĩ니ë‹Ī.",
"tabs": {
"basic": "ęļ°ëģļ ęĩŽė„ą",
"block": "ė°Ļë‹Ļ 淜ėđ™",
"dns": "DNS ęĩŽė„ą",
"outbound": "ė•„ė›ƒë°”ėšī드 淜ėđ™"
},
"title": "ë…ļ드 ęĩŽė„ą"
},
"server_key": "ė„œëē„ í‚Ī", "server_key": "ė„œëē„ í‚Ī",
"service_name": "ė„œëđ„ėŠĪ ėīëĶ„", "service_name": "ė„œëđ„ėŠĪ ėīëĶ„",
"sorted_success": "ė •ë Žėī 뙄ëĢŒë˜ė—ˆėŠĩ니ë‹Ī.",
"status": "ėƒíƒœ", "status": "ėƒíƒœ",
"subscribeId": "ęĩŽë… ID", "subscribeId": "ęĩŽë… ID",
"subscription": "ęĩŽë…", "subscription": "ęĩŽë…",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "확ėļ", "confirm": "확ėļ",
"confirmDelete": "ė‚­ė œí•˜ė‹œęē ėŠĩ니ęđŒ?", "confirmDelete": "ė‚­ė œí•˜ė‹œęē ėŠĩ니ęđŒ?",
"confirmOffline": "ė˜Ī프띾ėļ 확ėļ", "confirmOffline": "ė˜Ī프띾ėļ 확ėļ",
"copySubscription": "ęĩŽë… ëģĩė‚Ž",
"copySuccess": "ëģĩė‚Ž ė„ąęģĩ",
"create": "ėƒė„ą", "create": "ėƒė„ą",
"createSubscription": "ęĩŽë… ėƒė„ą", "createSubscription": "ęĩŽë… ėƒė„ą",
"createSuccess": "ėƒė„ą ė„ąęģĩ", "createSuccess": "ėƒė„ą ė„ąęģĩ",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Velg protokollâ€Ķ", "select_protocol": "Velg protokollâ€Ķ",
"select_server": "Velg serverâ€Ķ", "select_server": "Velg serverâ€Ķ",
"server": "Server", "server": "Server",
"sorted_success": "Sortert med suksess",
"tags": "Tagger", "tags": "Tagger",
"tags_description": "Tillatelsesgrupperingstagg (inkl. planbinding og leveringspolicyer).", "tags_description": "Tillatelsesgrupperingstagg (inkl. planbinding og leveringspolicyer).",
"tags_placeholder": "Bruk Enter eller komma (,) for ÃĨ legge til flere tagger", "tags_placeholder": "Bruk Enter eller komma (,) for ÃĨ legge til flere tagger",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Adresse",
"address_placeholder": "Serveradresse",
"bandwidth_placeholder": "Skriv inn bÃĨndbredde, la stÃĨ tomt for BBR",
"basic": "Grunnleggende Konfigurasjon",
"cancel": "Avbryt",
"city": "By",
"config": {
"actions": { "actions": {
"cancel": "Avbryt", "cancel": "Avbryt",
"save": "Lagre" "save": "Lagre"
}, },
"communicationKey": "KommunikasjonsnÃļkkel", "address": "Adresse",
"communicationKeyDescription": "Brukes for nodeautentisering.", "address_placeholder": "Serveradresse",
"description": "Administrer nodekommunikasjonsnÃļkler, pull/push-intervaller og dynamiske multiplikatorer.", "apiHost": "API-vert",
"dynamicMultiplier": "Dynamisk multiplikator", "apiHostPlaceholder": "http(s)://eksempel.com",
"dynamicMultiplierDescription": "Definer tidsluker og multiplikatorer for ÃĨ justere trafikkregnskap.", "bandwidth_placeholder": "Skriv inn bÃĨndbredde, la stÃĨ tomt for BBR",
"endTime": "Sluttid", "basic": "Grunnleggende Konfigurasjon",
"inputPlaceholder": "Vennligst skriv inn", "cancel": "Avbryt",
"multiplier": "Multiplikator", "cert_dns_env": "DNS-miljÃļvariabler",
"nodePullInterval": "Node pull-intervall", "cert_dns_provider": "DNS-leverandÃļr",
"nodePullIntervalDescription": "Hvor ofte noden henter konfigurasjon (sekunder).", "cert_mode": "Sertifikatmodus",
"nodePushInterval": "Node push-intervall", "cipher": "Krypteringsalgoritme",
"nodePushIntervalDescription": "Hvor ofte noden sender statistikk (sekunder).", "city": "By",
"reset": "Tilbakestill",
"save": "Lagre",
"saveSuccess": "Lagring vellykket",
"startTime": "Starttid",
"timeSlot": "Tidsluke",
"title": "Nodekonfigurasjon"
},
"confirm": "Bekreft", "confirm": "Bekreft",
"confirmDeleteDesc": "Denne handlingen kan ikke angres.", "confirmDeleteDesc": "Denne handlingen kan ikke angres.",
"confirmDeleteTitle": "Slette denne serveren?", "confirmDeleteTitle": "Slette denne serveren?",
"congestion_controller": "Kongestjonskontroller", "congestion_controller": "Kongestjonskontroller",
"connect": "Koble til",
"copied": "Kopiert", "copied": "Kopiert",
"copy": "Kopier", "copy": "Kopier",
"country": "Land", "country": "Land",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Rediger server", "drawerEditTitle": "Rediger server",
"edit": "Rediger", "edit": "Rediger",
"enabled": "Aktivert", "enabled": "Aktivert",
"encryption_method": "Krypteringsmetode", "encryption": "Krypteringsmetode",
"encryption_client_padding": "Klient Padding",
"encryption_mode": "Modus",
"encryption_password": "Passord",
"encryption_password_placeholder": "La stÃĨ tom for automatisk generering, erstatt manuelt for post-kvantum kryptering",
"encryption_private_key": "Privat nÃļkkel",
"encryption_private_key_placeholder": "La stÃĨ tom for automatisk generering, erstatt manuelt for post-kvantum kryptering",
"encryption_rtt": "RTT",
"encryption_server_padding": "Server Padding",
"encryption_ticket": "Billettid",
"expireTime": "UtlÃļpstid", "expireTime": "UtlÃļpstid",
"expired": "UtlÃļpt", "expired": "UtlÃļpt",
"extra": "Ekstra konfigurasjon",
"flow": "Flyt", "flow": "Flyt",
"generate_quantum_resistant_key": "Generer kvantumresistent nÃļkkel",
"generate_standard_encryption_key": "Generer standard krypteringsnÃļkkel",
"hop_interval": "Hoppintervall", "hop_interval": "Hoppintervall",
"hop_ports": "Hoppporter", "hop_ports": "Hoppporter",
"hop_ports_placeholder": "f.eks. 1-65535", "hop_ports_placeholder": "f.eks. 1-65535",
"host": "Vert", "host": "Vert",
"id": "ID", "id": "ID",
"installCommand": "Installasjonskommando",
"ipAddresses": "IP-adresser", "ipAddresses": "IP-adresser",
"memory": "Minne", "memory": "Minne",
"migrate": "Migrer data", "migrate": "Migrer data",
"migrateFailed": "Datamigrering mislyktes", "migrateFailed": "Datamigrering mislyktes",
"migrated": "Data migrert med suksess", "migrated": "Data migrert med suksess",
"migrating": "Migrerer...", "migrating": "Migrerer...",
"mode": "Modus",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Navn", "name": "Navn",
"noData": "Ingen data", "noData": "Ingen data",
"notAvailable": "Ikke tilgjengelig", "notAvailable": "Ikke tilgjengelig",
"obfs": "Obfuskering",
"obfs_host": "Obfs Vert",
"obfs_password": "Obfuskasjonspassord", "obfs_password": "Obfuskasjonspassord",
"obfs_password_placeholder": "Skriv inn obfuskasjonspassord", "obfs_password_placeholder": "Skriv inn obfuskasjonspassord",
"obfs_path": "Obfs Sti",
"offline": "Frakoblet", "offline": "Frakoblet",
"oneClickInstall": "Én-klikk installasjon",
"online": "PÃĨ nett", "online": "PÃĨ nett",
"onlineUsers": "Brukere pÃĨ nett", "onlineUsers": "Brukere pÃĨ nett",
"padding_scheme": "Polstring Skjema", "padding_scheme": "Polstring Skjema",
@@ -78,8 +83,6 @@
"pageTitle": "Servere", "pageTitle": "Servere",
"path": "Sti", "path": "Sti",
"please_select": "Vennligst velg", "please_select": "Vennligst velg",
"plugin": "Plugin Konfigurasjon",
"plugin_opts": "Plugin Alternativer",
"port": "Port", "port": "Port",
"protocol_configurations": "Protokoll Konfigurasjoner", "protocol_configurations": "Protokoll Konfigurasjoner",
"protocol_configurations_desc": "Aktiver og konfigurer de nÃļdvendige protokolltypene", "protocol_configurations_desc": "Aktiver og konfigurer de nÃļdvendige protokolltypene",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Hex-streng (opptil 16 tegn)", "security_short_id_placeholder": "Hex-streng (opptil 16 tegn)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Velg krypteringsmetode", "select_encryption_method": "Velg krypteringsmetode",
"server_config": {
"description": "Administrer nodekommunikasjonsnÃļkler, pull/push-intervaller.",
"dynamic_multiplier": "Dynamisk multiplikator",
"dynamic_multiplier_desc": "Definer tidsluker og multiplikatorer for ÃĨ justere trafikkregnskap.",
"fields": {
"block_rules_placeholder": "Én domene regel per linje, stÃļtter:\nkeyword:google (nÃļkkelordmatching)\nsuffix:google.com (suffixmatching)\nregex:.*\\.example\\.com$ (regexmatching)\nexample.com (nÃļyaktig matching)",
"communication_key": "KommunikasjonsnÃļkkel",
"communication_key_desc": "Brukes for nodeautentisering.",
"communication_key_placeholder": "Vennligst skriv inn",
"dns_config": "DNS-konfigurasjon",
"dns_domains_placeholder": "Én domene regel per linje, stÃļtter:\nkeyword:google (nÃļkkelordmatching)\nsuffix:google.com (suffixmatching)\nregex:.*\\.example\\.com$ (regexmatching)\nexample.com (nÃļyaktig matching)",
"dns_proto_placeholder": "Velg type",
"end_time": "Sluttid",
"ip_strategy": "IP-strategi",
"ip_strategy_desc": "Velg IP-versjon preferanse for nettverksforbindelser",
"ip_strategy_ipv4": "Foretrekk IPv4",
"ip_strategy_ipv6": "Foretrekk IPv6",
"ip_strategy_placeholder": "Velg IP-strategi",
"multiplier": "Multiplikator",
"node_pull_interval": "Node pull-intervall",
"node_pull_interval_desc": "Hvor ofte noden henter konfigurasjon (sekunder).",
"node_push_interval": "Node push-intervall",
"node_push_interval_desc": "Hvor ofte noden sender statistikk (sekunder).",
"outbound_address_placeholder": "Serveradresse",
"outbound_name_placeholder": "Konfigurasjonsnavn",
"outbound_password_placeholder": "Passord (valgfritt)",
"outbound_port_placeholder": "Portnummer",
"outbound_protocol_placeholder": "Velg protokoll",
"outbound_rules_placeholder": "Én regel per linje, stÃļtter:\nkeyword:google (nÃļkkelordmatching)\nsuffix:google.com (suffixmatching)\nregex:.*\\.example\\.com$ (regexmatching)\nexample.com (nÃļyaktig matching)\nLa stÃĨ tomt for standard ruting",
"reset": "Tilbakestill",
"save": "Lagre",
"start_time": "Starttid",
"time_slot": "Tidsluke",
"traffic_report_threshold": "Trafikkrapportgrense",
"traffic_report_threshold_desc": "Sett minimumsgrensen for trafikkrapportering. Trafikk vil kun bli rapportert nÃĨr den overstiger denne verdien. Sett til 0 eller la stÃĨ tomt for ÃĨ rapportere all trafikk."
},
"saveSuccess": "Lagring vellykket",
"tabs": {
"basic": "Grunnleggende konfigurasjon",
"block": "Blokker regler",
"dns": "DNS-konfigurasjon",
"outbound": "UtgÃĨende regler"
},
"title": "Nodekonfigurasjon"
},
"server_key": "ServernÃļkkel", "server_key": "ServernÃļkkel",
"service_name": "Tjenestenavn", "service_name": "Tjenestenavn",
"sorted_success": "Sortert med suksess",
"status": "Status", "status": "Status",
"subscribeId": "Abonnements-ID", "subscribeId": "Abonnements-ID",
"subscription": "Abonnement", "subscription": "Abonnement",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "bekreft", "confirm": "bekreft",
"confirmDelete": "Er du sikker pÃĨ at du vil slette?", "confirmDelete": "Er du sikker pÃĨ at du vil slette?",
"confirmOffline": "Bekreft frakoblet", "confirmOffline": "Bekreft frakoblet",
"copySubscription": "Kopier abonnement",
"copySuccess": "Kopiering vellykket",
"create": "opprett", "create": "opprett",
"createSubscription": "Opprett abonnement", "createSubscription": "Opprett abonnement",
"createSuccess": "Opprettelse vellykket", "createSuccess": "Opprettelse vellykket",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Wybierz protokÃģłâ€Ķ", "select_protocol": "Wybierz protokÃģłâ€Ķ",
"select_server": "Wybierz serwerâ€Ķ", "select_server": "Wybierz serwerâ€Ķ",
"server": "Serwer", "server": "Serwer",
"sorted_success": "Posortowano pomyślnie",
"tags": "Tagi", "tags": "Tagi",
"tags_description": "Tag grupujący uprawnienia (w tym powiązania planu i polityki dostarczania).", "tags_description": "Tag grupujący uprawnienia (w tym powiązania planu i polityki dostarczania).",
"tags_placeholder": "UÅžyj Enter lub przecinka (,) aby dodać wiele tagÃģw", "tags_placeholder": "UÅžyj Enter lub przecinka (,) aby dodać wiele tagÃģw",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Adres",
"address_placeholder": "Adres serwera",
"bandwidth_placeholder": "WprowadÅš przepustowość, pozostaw puste dla BBR",
"basic": "Podstawowa konfiguracja",
"cancel": "Anuluj",
"city": "Miasto",
"config": {
"actions": { "actions": {
"cancel": "Anuluj", "cancel": "Anuluj",
"save": "Zapisz" "save": "Zapisz"
}, },
"communicationKey": "Klucz komunikacyjny", "address": "Adres",
"communicationKeyDescription": "UÅžywany do uwierzytelniania węzła.", "address_placeholder": "Adres serwera",
"description": "Zarządzaj kluczami komunikacyjnymi węzła, interwałami pobierania/wysyłania oraz dynamicznymi mnoÅžnikami.", "apiHost": "Host API",
"dynamicMultiplier": "Dynamiczny mnoÅžnik", "apiHostPlaceholder": "http(s)://przyklad.com",
"dynamicMultiplierDescription": "Zdefiniuj przedziały czasowe i mnoÅžniki, aby dostosować rozliczanie ruchu.", "bandwidth_placeholder": "WprowadÅš przepustowość, pozostaw puste dla BBR",
"endTime": "Czas zakończenia", "basic": "Podstawowa konfiguracja",
"inputPlaceholder": "Proszę wpisać", "cancel": "Anuluj",
"multiplier": "MnoÅžnik", "cert_dns_env": "Zmienne środowiskowe DNS",
"nodePullInterval": "Interwał pobierania węzła", "cert_dns_provider": "Dostawca DNS",
"nodePullIntervalDescription": "Jak często węzeł pobiera konfigurację (sekundy).", "cert_mode": "Tryb certyfikatu",
"nodePushInterval": "Interwał wysyłania węzła", "cipher": "Algorytm szyfrowania",
"nodePushIntervalDescription": "Jak często węzeł wysyła statystyki (sekundy).", "city": "Miasto",
"reset": "Resetuj",
"save": "Zapisz",
"saveSuccess": "Zapisano pomyślnie",
"startTime": "Czas rozpoczęcia",
"timeSlot": "Przedział czasowy",
"title": "Konfiguracja węzła"
},
"confirm": "PotwierdÅš", "confirm": "PotwierdÅš",
"confirmDeleteDesc": "Ta akcja nie moÅže być cofnięta.", "confirmDeleteDesc": "Ta akcja nie moÅže być cofnięta.",
"confirmDeleteTitle": "Usunąć ten serwer?", "confirmDeleteTitle": "Usunąć ten serwer?",
"congestion_controller": "Kontroler przeciÄ…Åženia", "congestion_controller": "Kontroler przeciÄ…Åženia",
"connect": "Połącz",
"copied": "Skopiowano", "copied": "Skopiowano",
"copy": "Kopiuj", "copy": "Kopiuj",
"country": "Kraj", "country": "Kraj",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Edytuj serwer", "drawerEditTitle": "Edytuj serwer",
"edit": "Edytuj", "edit": "Edytuj",
"enabled": "Włączony", "enabled": "Włączony",
"encryption_method": "Metoda szyfrowania", "encryption": "Metoda szyfrowania",
"encryption_client_padding": "Padding klienta",
"encryption_mode": "Tryb",
"encryption_password": "Hasło",
"encryption_password_placeholder": "Pozostaw puste dla automatycznego generowania, zastąp ręcznie dla szyfrowania post-kwantowego",
"encryption_private_key": "Klucz prywatny",
"encryption_private_key_placeholder": "Pozostaw puste dla automatycznego generowania, zastąp ręcznie dla szyfrowania post-kwantowego",
"encryption_rtt": "RTT",
"encryption_server_padding": "Padding serwera",
"encryption_ticket": "Czas biletu",
"expireTime": "Czas wygaśnięcia", "expireTime": "Czas wygaśnięcia",
"expired": "Wygasł", "expired": "Wygasł",
"extra": "Dodatkowa konfiguracja",
"flow": "Przepływ", "flow": "Przepływ",
"generate_quantum_resistant_key": "Generuj klucz odporny na kwanty",
"generate_standard_encryption_key": "Generuj standardowy klucz szyfrowania",
"hop_interval": "Interwał skoku", "hop_interval": "Interwał skoku",
"hop_ports": "Porty skoku", "hop_ports": "Porty skoku",
"hop_ports_placeholder": "np. 1-65535", "hop_ports_placeholder": "np. 1-65535",
"host": "Host", "host": "Host",
"id": "ID", "id": "ID",
"installCommand": "Polecenie instalacji",
"ipAddresses": "Adresy IP", "ipAddresses": "Adresy IP",
"memory": "Pamięć", "memory": "Pamięć",
"migrate": "Migracja danych", "migrate": "Migracja danych",
"migrateFailed": "Migracja danych nie powiodła się", "migrateFailed": "Migracja danych nie powiodła się",
"migrated": "Dane zostały pomyślnie zmigrowane", "migrated": "Dane zostały pomyślnie zmigrowane",
"migrating": "Migracja...", "migrating": "Migracja...",
"mode": "Tryb",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Nazwa", "name": "Nazwa",
"noData": "Brak danych", "noData": "Brak danych",
"notAvailable": "N/D", "notAvailable": "N/D",
"obfs": "Obfuskacja",
"obfs_host": "Host obfuskacji",
"obfs_password": "Hasło obfuskacji", "obfs_password": "Hasło obfuskacji",
"obfs_password_placeholder": "WprowadÅš hasło obfuskacji", "obfs_password_placeholder": "WprowadÅš hasło obfuskacji",
"obfs_path": "ŚcieÅžka obfuskacji",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "Instalacja jednym kliknięciem",
"online": "Online", "online": "Online",
"onlineUsers": "UÅžytkownicy online", "onlineUsers": "UÅžytkownicy online",
"padding_scheme": "Schemat wypełnienia", "padding_scheme": "Schemat wypełnienia",
@@ -78,8 +83,6 @@
"pageTitle": "Serwery", "pageTitle": "Serwery",
"path": "ŚcieÅžka", "path": "ŚcieÅžka",
"please_select": "Proszę wybrać", "please_select": "Proszę wybrać",
"plugin": "Konfiguracja wtyczki",
"plugin_opts": "Opcje wtyczki",
"port": "Port", "port": "Port",
"protocol_configurations": "Konfiguracje protokołÃģw", "protocol_configurations": "Konfiguracje protokołÃģw",
"protocol_configurations_desc": "Włącz i skonfiguruj wymagane typy protokołÃģw", "protocol_configurations_desc": "Włącz i skonfiguruj wymagane typy protokołÃģw",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Ciąg szesnastkowy (do 16 znakÃģw)", "security_short_id_placeholder": "Ciąg szesnastkowy (do 16 znakÃģw)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Wybierz metodę szyfrowania", "select_encryption_method": "Wybierz metodę szyfrowania",
"server_config": {
"description": "Zarządzaj kluczami komunikacyjnymi węzła, interwałami pobierania/wysyłania.",
"dynamic_multiplier": "Dynamiczny mnoÅžnik",
"dynamic_multiplier_desc": "Zdefiniuj przedziały czasowe i mnoÅžniki do dostosowania rozliczania ruchu.",
"fields": {
"block_rules_placeholder": "Jedna reguła domeny na linię, wspiera:\nkeyword:google (dopasowanie słÃģw kluczowych)\nsuffix:google.com (dopasowanie sufiksu)\nregex:.*\\.example\\.com$ (dopasowanie regex)\nexample.com (dokładne dopasowanie)",
"communication_key": "Klucz komunikacyjny",
"communication_key_desc": "UÅžywany do uwierzytelniania węzła.",
"communication_key_placeholder": "Proszę wpisać",
"dns_config": "Konfiguracja DNS",
"dns_domains_placeholder": "Jedna reguła domeny na linię, wspiera:\nkeyword:google (dopasowanie słÃģw kluczowych)\nsuffix:google.com (dopasowanie sufiksu)\nregex:.*\\.example\\.com$ (dopasowanie regex)\nexample.com (dokładne dopasowanie)",
"dns_proto_placeholder": "Wybierz typ",
"end_time": "Czas zakończenia",
"ip_strategy": "Strategia IP",
"ip_strategy_desc": "Wybierz preferencje wersji IP dla połączeń sieciowych",
"ip_strategy_ipv4": "Preferuj IPv4",
"ip_strategy_ipv6": "Preferuj IPv6",
"ip_strategy_placeholder": "Wybierz strategię IP",
"multiplier": "MnoÅžnik",
"node_pull_interval": "Interwał pobierania węzła",
"node_pull_interval_desc": "Jak często węzeł pobiera konfigurację (sekundy).",
"node_push_interval": "Interwał wysyłania węzła",
"node_push_interval_desc": "Jak często węzeł wysyła statystyki (sekundy).",
"outbound_address_placeholder": "Adres serwera",
"outbound_name_placeholder": "Nazwa konfiguracji",
"outbound_password_placeholder": "Hasło (opcjonalnie)",
"outbound_port_placeholder": "Numer portu",
"outbound_protocol_placeholder": "Wybierz protokÃģł",
"outbound_rules_placeholder": "Jedna reguła na linię, wspiera:\nkeyword:google (dopasowanie słÃģw kluczowych)\nsuffix:google.com (dopasowanie sufiksu)\nregex:.*\\.example\\.com$ (dopasowanie regex)\nexample.com (dokładne dopasowanie)\nPozostaw puste dla domyślnego routingu",
"reset": "Resetuj",
"save": "Zapisz",
"start_time": "Czas rozpoczęcia",
"time_slot": "Przedział czasowy",
"traffic_report_threshold": "PrÃģg raportu ruchu",
"traffic_report_threshold_desc": "Ustaw minimalny prÃģg dla raportowania ruchu. Ruch będzie raportowany tylko wtedy, gdy przekroczy tę wartość. Ustaw na 0 lub pozostaw puste, aby raportować cały ruch."
},
"saveSuccess": "Zapisano pomyślnie",
"tabs": {
"basic": "Podstawowa konfiguracja",
"block": "Reguły blokowania",
"dns": "Konfiguracja DNS",
"outbound": "Reguły wychodzące"
},
"title": "Konfiguracja węzła"
},
"server_key": "Klucz serwera", "server_key": "Klucz serwera",
"service_name": "Nazwa usługi", "service_name": "Nazwa usługi",
"sorted_success": "Posortowano pomyślnie",
"status": "Status", "status": "Status",
"subscribeId": "ID subskrypcji", "subscribeId": "ID subskrypcji",
"subscription": "Subskrypcja", "subscription": "Subskrypcja",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "PotwierdÅš", "confirm": "PotwierdÅš",
"confirmDelete": "Czy na pewno chcesz usunąć?", "confirmDelete": "Czy na pewno chcesz usunąć?",
"confirmOffline": "PotwierdÅš tryb offline", "confirmOffline": "PotwierdÅš tryb offline",
"copySubscription": "Kopiuj subskrypcję",
"copySuccess": "Kopiowanie zakończone sukcesem",
"create": "UtwÃģrz", "create": "UtwÃģrz",
"createSubscription": "UtwÃģrz subskrypcję", "createSubscription": "UtwÃģrz subskrypcję",
"createSuccess": "Utworzono pomyślnie", "createSuccess": "Utworzono pomyślnie",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Selecionar protocoloâ€Ķ", "select_protocol": "Selecionar protocoloâ€Ķ",
"select_server": "Selecionar servidorâ€Ķ", "select_server": "Selecionar servidorâ€Ķ",
"server": "Servidor", "server": "Servidor",
"sorted_success": "Ordenado com sucesso",
"tags": "Tags", "tags": "Tags",
"tags_description": "Tag de agrupamento de permissÃĩes (incl. vinculaçÃĢo de plano e políticas de entrega).", "tags_description": "Tag de agrupamento de permissÃĩes (incl. vinculaçÃĢo de plano e políticas de entrega).",
"tags_placeholder": "Use Enter ou vírgula (,) para adicionar vÃĄrias tags", "tags_placeholder": "Use Enter ou vírgula (,) para adicionar vÃĄrias tags",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Endereço",
"address_placeholder": "Endereço do servidor",
"bandwidth_placeholder": "Insira a largura de banda, deixe em branco para BBR",
"basic": "ConfiguraçÃĢo BÃĄsica",
"cancel": "Cancelar",
"city": "Cidade",
"config": {
"actions": { "actions": {
"cancel": "Cancelar", "cancel": "Cancelar",
"save": "Salvar" "save": "Salvar"
}, },
"communicationKey": "Chave de comunicaçÃĢo", "address": "Endereço",
"communicationKeyDescription": "Usado para autenticaçÃĢo do nÃģ.", "address_placeholder": "Endereço do servidor",
"description": "Gerenciar chaves de comunicaçÃĢo do nÃģ, intervalos de pull/push e multiplicadores dinÃĒmicos.", "apiHost": "Host da API",
"dynamicMultiplier": "Multiplicador dinÃĒmico", "apiHostPlaceholder": "http(s)://exemplo.com",
"dynamicMultiplierDescription": "Defina intervalos de tempo e multiplicadores para ajustar a contagem de trÃĄfego.", "bandwidth_placeholder": "Insira a largura de banda, deixe em branco para BBR",
"endTime": "Hora de tÃĐrmino", "basic": "ConfiguraçÃĢo BÃĄsica",
"inputPlaceholder": "Por favor, insira", "cancel": "Cancelar",
"multiplier": "Multiplicador", "cert_dns_env": "VariÃĄveis de Ambiente DNS",
"nodePullInterval": "Intervalo de pull do nÃģ", "cert_dns_provider": "Provedor DNS",
"nodePullIntervalDescription": "Com que frequÊncia o nÃģ puxa a configuraçÃĢo (segundos).", "cert_mode": "Modo de Certificado",
"nodePushInterval": "Intervalo de push do nÃģ", "cipher": "Algoritmo de Criptografia",
"nodePushIntervalDescription": "Com que frequÊncia o nÃģ envia estatísticas (segundos).", "city": "Cidade",
"reset": "Redefinir",
"save": "Salvar",
"saveSuccess": "Salvo com sucesso",
"startTime": "Hora de início",
"timeSlot": "Intervalo de tempo",
"title": "ConfiguraçÃĢo do nÃģ"
},
"confirm": "Confirmar", "confirm": "Confirmar",
"confirmDeleteDesc": "Esta açÃĢo nÃĢo pode ser desfeita.", "confirmDeleteDesc": "Esta açÃĢo nÃĢo pode ser desfeita.",
"confirmDeleteTitle": "Excluir este servidor?", "confirmDeleteTitle": "Excluir este servidor?",
"congestion_controller": "Controlador de congestionamento", "congestion_controller": "Controlador de congestionamento",
"connect": "Conectar",
"copied": "Copiado", "copied": "Copiado",
"copy": "Copiar", "copy": "Copiar",
"country": "País", "country": "País",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Editar Servidor", "drawerEditTitle": "Editar Servidor",
"edit": "Editar", "edit": "Editar",
"enabled": "Ativado", "enabled": "Ativado",
"encryption_method": "MÃĐtodo de criptografia", "encryption": "MÃĐtodo de Criptografia",
"encryption_client_padding": "Preenchimento do Cliente",
"encryption_mode": "Modo",
"encryption_password": "Senha",
"encryption_password_placeholder": "Deixe em branco para auto-geraçÃĢo, substitua manualmente para criptografia pÃģs-quÃĒntica",
"encryption_private_key": "Chave Privada",
"encryption_private_key_placeholder": "Deixe em branco para auto-geraçÃĢo, substitua manualmente para criptografia pÃģs-quÃĒntica",
"encryption_rtt": "RTT",
"encryption_server_padding": "Preenchimento do Servidor",
"encryption_ticket": "Tempo do Ticket",
"expireTime": "Tempo de expiraçÃĢo", "expireTime": "Tempo de expiraçÃĢo",
"expired": "Expirado", "expired": "Expirado",
"extra": "ConfiguraçÃĢo Extra",
"flow": "Fluxo", "flow": "Fluxo",
"generate_quantum_resistant_key": "Gerar chave resistente a quÃĒnticos",
"generate_standard_encryption_key": "Gerar chave de criptografia padrÃĢo",
"hop_interval": "Intervalo de salto", "hop_interval": "Intervalo de salto",
"hop_ports": "Portas de salto", "hop_ports": "Portas de salto",
"hop_ports_placeholder": "ex. 1-65535", "hop_ports_placeholder": "ex. 1-65535",
"host": "Host", "host": "Host",
"id": "ID", "id": "ID",
"installCommand": "Comando de instalaçÃĢo",
"ipAddresses": "Endereços IP", "ipAddresses": "Endereços IP",
"memory": "MemÃģria", "memory": "MemÃģria",
"migrate": "Migrar Dados", "migrate": "Migrar Dados",
"migrateFailed": "A migraçÃĢo de dados falhou", "migrateFailed": "A migraçÃĢo de dados falhou",
"migrated": "Dados migrados com sucesso", "migrated": "Dados migrados com sucesso",
"migrating": "Migrando...", "migrating": "Migrando...",
"mode": "Modo",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Nome", "name": "Nome",
"noData": "Sem dados", "noData": "Sem dados",
"notAvailable": "N/D", "notAvailable": "N/D",
"obfs": "OfuscaçÃĢo",
"obfs_host": "Host de OfuscaçÃĢo",
"obfs_password": "Senha de ofuscaçÃĢo", "obfs_password": "Senha de ofuscaçÃĢo",
"obfs_password_placeholder": "Insira a senha de ofuscaçÃĢo", "obfs_password_placeholder": "Insira a senha de ofuscaçÃĢo",
"obfs_path": "Caminho de OfuscaçÃĢo",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "InstalaçÃĢo com um clique",
"online": "Online", "online": "Online",
"onlineUsers": "UsuÃĄrios online", "onlineUsers": "UsuÃĄrios online",
"padding_scheme": "Esquema de Preenchimento", "padding_scheme": "Esquema de Preenchimento",
@@ -78,8 +83,6 @@
"pageTitle": "Servidores", "pageTitle": "Servidores",
"path": "Caminho", "path": "Caminho",
"please_select": "Por favor, selecione", "please_select": "Por favor, selecione",
"plugin": "ConfiguraçÃĢo do Plugin",
"plugin_opts": "OpçÃĩes do Plugin",
"port": "Porta", "port": "Porta",
"protocol_configurations": "ConfiguraçÃĩes de Protocolo", "protocol_configurations": "ConfiguraçÃĩes de Protocolo",
"protocol_configurations_desc": "Ative e configure os tipos de protocolo necessÃĄrios", "protocol_configurations_desc": "Ative e configure os tipos de protocolo necessÃĄrios",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "String hexadecimal (atÃĐ 16 caracteres)", "security_short_id_placeholder": "String hexadecimal (atÃĐ 16 caracteres)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Selecionar mÃĐtodo de criptografia", "select_encryption_method": "Selecionar mÃĐtodo de criptografia",
"server_config": {
"description": "Gerenciar chaves de comunicaçÃĢo do nÃģ, intervalos de pull/push.",
"dynamic_multiplier": "Multiplicador DinÃĒmico",
"dynamic_multiplier_desc": "Defina intervalos de tempo e multiplicadores para ajustar a contagem de trÃĄfego.",
"fields": {
"block_rules_placeholder": "Uma regra de domínio por linha, suporta:\nkeyword:google (correspondÊncia de palavra-chave)\nsuffix:google.com (correspondÊncia de sufixo)\nregex:.*\\.example\\.com$ (correspondÊncia regex)\nexample.com (correspondÊncia exata)",
"communication_key": "Chave de ComunicaçÃĢo",
"communication_key_desc": "Usado para autenticaçÃĢo do nÃģ.",
"communication_key_placeholder": "Por favor, insira",
"dns_config": "ConfiguraçÃĢo DNS",
"dns_domains_placeholder": "Uma regra de domínio por linha, suporta:\nkeyword:google (correspondÊncia de palavra-chave)\nsuffix:google.com (correspondÊncia de sufixo)\nregex:.*\\.example\\.com$ (correspondÊncia regex)\nexample.com (correspondÊncia exata)",
"dns_proto_placeholder": "Selecione o tipo",
"end_time": "Hora de TÃĐrmino",
"ip_strategy": "EstratÃĐgia de IP",
"ip_strategy_desc": "Escolha a preferÊncia de versÃĢo de IP para conexÃĩes de rede",
"ip_strategy_ipv4": "Preferir IPv4",
"ip_strategy_ipv6": "Preferir IPv6",
"ip_strategy_placeholder": "Selecione a estratÃĐgia de IP",
"multiplier": "Multiplicador",
"node_pull_interval": "Intervalo de Pull do NÃģ",
"node_pull_interval_desc": "Com que frequÊncia o nÃģ puxa a configuraçÃĢo (segundos).",
"node_push_interval": "Intervalo de Push do NÃģ",
"node_push_interval_desc": "Com que frequÊncia o nÃģ envia estatísticas (segundos).",
"outbound_address_placeholder": "Endereço do servidor",
"outbound_name_placeholder": "Nome da configuraçÃĢo",
"outbound_password_placeholder": "Senha (opcional)",
"outbound_port_placeholder": "NÚmero da porta",
"outbound_protocol_placeholder": "Selecione o protocolo",
"outbound_rules_placeholder": "Uma regra por linha, suporta:\nkeyword:google (correspondÊncia de palavra-chave)\nsuffix:google.com (correspondÊncia de sufixo)\nregex:.*\\.example\\.com$ (correspondÊncia regex)\nexample.com (correspondÊncia exata)\nDeixe em branco para roteamento padrÃĢo",
"reset": "Redefinir",
"save": "Salvar",
"start_time": "Hora de Início",
"time_slot": "Intervalo de Tempo",
"traffic_report_threshold": "Limite de RelatÃģrio de TrÃĄfego",
"traffic_report_threshold_desc": "Defina o limite mínimo para o relatÃģrio de trÃĄfego. O trÃĄfego sÃģ serÃĄ relatado quando exceder este valor. Defina como 0 ou deixe em branco para relatar todo o trÃĄfego."
},
"saveSuccess": "Salvo com sucesso",
"tabs": {
"basic": "ConfiguraçÃĢo BÃĄsica",
"block": "Regras de Bloqueio",
"dns": "ConfiguraçÃĢo DNS",
"outbound": "Regras de Saída"
},
"title": "ConfiguraçÃĢo do NÃģ"
},
"server_key": "Chave do servidor", "server_key": "Chave do servidor",
"service_name": "Nome do serviço", "service_name": "Nome do serviço",
"sorted_success": "Ordenado com sucesso",
"status": "Status", "status": "Status",
"subscribeId": "ID de assinatura", "subscribeId": "ID de assinatura",
"subscription": "Assinatura", "subscription": "Assinatura",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "confirmar", "confirm": "confirmar",
"confirmDelete": "VocÊ tem certeza de que deseja excluir?", "confirmDelete": "VocÊ tem certeza de que deseja excluir?",
"confirmOffline": "Confirmar Offline", "confirmOffline": "Confirmar Offline",
"copySubscription": "Copiar Assinatura",
"copySuccess": "CÃģpia bem-sucedida",
"create": "Criar", "create": "Criar",
"createSubscription": "Criar Assinatura", "createSubscription": "Criar Assinatura",
"createSuccess": "CriaçÃĢo bem-sucedida", "createSuccess": "CriaçÃĢo bem-sucedida",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Selectează protocolulâ€Ķ", "select_protocol": "Selectează protocolulâ€Ķ",
"select_server": "Selectează serverulâ€Ķ", "select_server": "Selectează serverulâ€Ķ",
"server": "Server", "server": "Server",
"sorted_success": "Sortat cu succes",
"tags": "Etichete", "tags": "Etichete",
"tags_description": "Etichetă de grupare a permisiunilor (incluzÃĒnd legarea planului și politicile de livrare).", "tags_description": "Etichetă de grupare a permisiunilor (incluzÃĒnd legarea planului și politicile de livrare).",
"tags_placeholder": "Folosește Enter sau virgulă (,) pentru a adăuga mai multe etichete", "tags_placeholder": "Folosește Enter sau virgulă (,) pentru a adăuga mai multe etichete",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "Adresă",
"address_placeholder": "Adresă server",
"bandwidth_placeholder": "Introduceți lățimea de bandă, lăsați liber pentru BBR",
"basic": "Configurare de bază",
"cancel": "Anulează",
"city": "Oraș",
"config": {
"actions": { "actions": {
"cancel": "Anulează", "cancel": "Anulează",
"save": "Salvează" "save": "Salvează"
}, },
"communicationKey": "Cheie de comunicare", "address": "Adresă",
"communicationKeyDescription": "Utilizată pentru autentificarea nodului.", "address_placeholder": "Adresă server",
"description": "Gestionează cheile de comunicare ale nodului, intervalele de pull/push și multiplicatorii dinamici.", "apiHost": "Gazda API",
"dynamicMultiplier": "Multiplicator dinamic", "apiHostPlaceholder": "http(s)://exemplu.com",
"dynamicMultiplierDescription": "Definirea intervalelor de timp și a multiplicatorilor pentru ajustarea contabilizării traficului.", "bandwidth_placeholder": "Introduceți lățimea de bandă, lăsați liber pentru BBR",
"endTime": "Ora de sfÃĒrșit", "basic": "Configurare de bază",
"inputPlaceholder": "Te rog introdu", "cancel": "Anulează",
"multiplier": "Multiplicator", "cert_dns_env": "Variabile de mediu DNS",
"nodePullInterval": "Interval de pull al nodului", "cert_dns_provider": "Furnizor DNS",
"nodePullIntervalDescription": "CÃĒt de des nodul trage configurația (secunde).", "cert_mode": "Mod certificat",
"nodePushInterval": "Interval de push al nodului", "cipher": "Algoritm de criptare",
"nodePushIntervalDescription": "CÃĒt de des nodul trimite statistici (secunde).", "city": "Oraș",
"reset": "Resetare",
"save": "Salvează",
"saveSuccess": "Salvat cu succes",
"startTime": "Ora de ÃŪnceput",
"timeSlot": "Interval de timp",
"title": "Configurarea nodului"
},
"confirm": "Confirmă", "confirm": "Confirmă",
"confirmDeleteDesc": "Această acțiune nu poate fi anulată.", "confirmDeleteDesc": "Această acțiune nu poate fi anulată.",
"confirmDeleteTitle": "Șterge acest server?", "confirmDeleteTitle": "Șterge acest server?",
"congestion_controller": "Controler de congestie", "congestion_controller": "Controler de congestie",
"connect": "Conectare",
"copied": "Copiat", "copied": "Copiat",
"copy": "Copiază", "copy": "Copiază",
"country": "Țară", "country": "Țară",
@@ -49,28 +36,46 @@
"drawerEditTitle": "Editează Server", "drawerEditTitle": "Editează Server",
"edit": "Editează", "edit": "Editează",
"enabled": "Activat", "enabled": "Activat",
"encryption_method": "Metodă de criptare", "encryption": "Metodă de criptare",
"encryption_client_padding": "Padding client",
"encryption_mode": "Mod",
"encryption_password": "Parolă",
"encryption_password_placeholder": "Lăsați gol pentru generare automată, ÃŪnlocuiți manual pentru criptare post-cuantică",
"encryption_private_key": "Cheie privată",
"encryption_private_key_placeholder": "Lăsați gol pentru generare automată, ÃŪnlocuiți manual pentru criptare post-cuantică",
"encryption_rtt": "RTT",
"encryption_server_padding": "Padding server",
"encryption_ticket": "Timp bilet",
"expireTime": "Timp de expirare", "expireTime": "Timp de expirare",
"expired": "Expirat", "expired": "Expirat",
"extra": "Configurație suplimentară",
"flow": "Flux", "flow": "Flux",
"generate_quantum_resistant_key": "Generează cheie rezistentă la cuantică",
"generate_standard_encryption_key": "Generează cheie de criptare standard",
"hop_interval": "Interval de hop", "hop_interval": "Interval de hop",
"hop_ports": "Porturi hop", "hop_ports": "Porturi hop",
"hop_ports_placeholder": "de ex. 1-65535", "hop_ports_placeholder": "de ex. 1-65535",
"host": "Gazdă", "host": "Gazdă",
"id": "ID", "id": "ID",
"installCommand": "Comandă de instalare",
"ipAddresses": "Adrese IP", "ipAddresses": "Adrese IP",
"memory": "Memorie", "memory": "Memorie",
"migrate": "Migrați datele", "migrate": "Migrați datele",
"migrateFailed": "Migrarea datelor a eșuat", "migrateFailed": "Migrarea datelor a eșuat",
"migrated": "Datele au fost migrate cu succes", "migrated": "Datele au fost migrate cu succes",
"migrating": "Se migrează...", "migrating": "Se migrează...",
"mode": "Mod",
"multiplex": "Multiplex", "multiplex": "Multiplex",
"name": "Nume", "name": "Nume",
"noData": "Fără date", "noData": "Fără date",
"notAvailable": "N/A", "notAvailable": "N/A",
"obfs": "Obfuscare",
"obfs_host": "Host Obfs",
"obfs_password": "Parola de obfuscare", "obfs_password": "Parola de obfuscare",
"obfs_password_placeholder": "Introdu parola de obfuscare", "obfs_password_placeholder": "Introdu parola de obfuscare",
"obfs_path": "Cale Obfs",
"offline": "Offline", "offline": "Offline",
"oneClickInstall": "Instalare cu un singur clic",
"online": "Online", "online": "Online",
"onlineUsers": "Utilizatori online", "onlineUsers": "Utilizatori online",
"padding_scheme": "Schema de umplere", "padding_scheme": "Schema de umplere",
@@ -78,8 +83,6 @@
"pageTitle": "Servere", "pageTitle": "Servere",
"path": "Cale", "path": "Cale",
"please_select": "Te rog selectează", "please_select": "Te rog selectează",
"plugin": "Configurare plugin",
"plugin_opts": "Opțiuni plugin",
"port": "Port", "port": "Port",
"protocol_configurations": "Configurări protocol", "protocol_configurations": "Configurări protocol",
"protocol_configurations_desc": "Activează și configurează tipurile de protocol necesare", "protocol_configurations_desc": "Activează și configurează tipurile de protocol necesare",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "Șir hexazecimal (pÃĒnă la 16 caractere)", "security_short_id_placeholder": "Șir hexazecimal (pÃĒnă la 16 caractere)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Selectează metoda de criptare", "select_encryption_method": "Selectează metoda de criptare",
"server_config": {
"description": "Gestionează cheile de comunicare ale nodului, intervalele de pull/push.",
"dynamic_multiplier": "Înmulțitor dinamic",
"dynamic_multiplier_desc": "Definiți intervalele de timp și ÃŪnmulțitorii pentru a ajusta contabilizarea traficului.",
"fields": {
"block_rules_placeholder": "O regulă de domeniu pe linie, suportă:\nkeyword:google (potrivire cu cuvÃĒntul cheie)\nsuffix:google.com (potrivire cu sufixul)\nregex:.*\\.example\\.com$ (potrivire regex)\nexample.com (potrivire exactă)",
"communication_key": "Cheie de comunicare",
"communication_key_desc": "Utilizată pentru autentificarea nodului.",
"communication_key_placeholder": "Vă rugăm să introduceți",
"dns_config": "Configurare DNS",
"dns_domains_placeholder": "O regulă de domeniu pe linie, suportă:\nkeyword:google (potrivire cu cuvÃĒntul cheie)\nsuffix:google.com (potrivire cu sufixul)\nregex:.*\\.example\\.com$ (potrivire regex)\nexample.com (potrivire exactă)",
"dns_proto_placeholder": "Selectați tipul",
"end_time": "Ora de sfÃĒrșit",
"ip_strategy": "Strategie IP",
"ip_strategy_desc": "Alegeți preferința versiunii IP pentru conexiunile de rețea",
"ip_strategy_ipv4": "Preferă IPv4",
"ip_strategy_ipv6": "Preferă IPv6",
"ip_strategy_placeholder": "Selectați strategia IP",
"multiplier": "Înmulțitor",
"node_pull_interval": "Interval de pull al nodului",
"node_pull_interval_desc": "CÃĒt de des nodul trage configurația (secunde).",
"node_push_interval": "Interval de push al nodului",
"node_push_interval_desc": "CÃĒt de des nodul trimite statistici (secunde).",
"outbound_address_placeholder": "Adresa serverului",
"outbound_name_placeholder": "Numele configurației",
"outbound_password_placeholder": "Parola (opțional)",
"outbound_port_placeholder": "Numărul portului",
"outbound_protocol_placeholder": "Selectați protocolul",
"outbound_rules_placeholder": "O regulă pe linie, suportă:\nkeyword:google (potrivire cu cuvÃĒntul cheie)\nsuffix:google.com (potrivire cu sufixul)\nregex:.*\\.example\\.com$ (potrivire regex)\nexample.com (potrivire exactă)\nLăsați gol pentru rutare implicită",
"reset": "Resetare",
"save": "Salvează",
"start_time": "Ora de ÃŪnceput",
"time_slot": "Interval de timp",
"traffic_report_threshold": "Prag raportare trafic",
"traffic_report_threshold_desc": "Stabiliți pragul minim pentru raportarea traficului. Traficul va fi raportat doar cÃĒnd depășește această valoare. Setați la 0 sau lăsați gol pentru a raporta tot traficul."
},
"saveSuccess": "Salvat cu succes",
"tabs": {
"basic": "Configurare de bază",
"block": "Reguli de blocare",
"dns": "Configurare DNS",
"outbound": "Reguli de ieșire"
},
"title": "Configurarea nodului"
},
"server_key": "Cheie server", "server_key": "Cheie server",
"service_name": "Nume serviciu", "service_name": "Nume serviciu",
"sorted_success": "Sortat cu succes",
"status": "Stare", "status": "Stare",
"subscribeId": "ID abonament", "subscribeId": "ID abonament",
"subscription": "Abonament", "subscription": "Abonament",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "Confirmare", "confirm": "Confirmare",
"confirmDelete": "Sunteți sigur că doriți să ștergeți?", "confirmDelete": "Sunteți sigur că doriți să ștergeți?",
"confirmOffline": "Confirmare Offline", "confirmOffline": "Confirmare Offline",
"copySubscription": "Copiază Abonamentul",
"copySuccess": "Copiază cu succes",
"create": "crea", "create": "crea",
"createSubscription": "Creează Abonament", "createSubscription": "Creează Abonament",
"createSuccess": "Creat cu succes", "createSuccess": "Creat cu succes",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧâ€Ķ", "select_protocol": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧâ€Ķ",
"select_server": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ҁÐĩŅ€ÐēÐĩŅ€â€Ķ", "select_server": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ҁÐĩŅ€ÐēÐĩŅ€â€Ķ",
"server": "ÐĄÐĩŅ€ÐēÐĩŅ€", "server": "ÐĄÐĩŅ€ÐēÐĩŅ€",
"sorted_success": "ÐĢҁÐŋÐĩŅˆÐ―Ðū Ðū҂ҁÐūҀ҂ÐļŅ€ÐūÐēÐ°Ð―Ðū",
"tags": "ÐĒÐĩÐģÐļ", "tags": "ÐĒÐĩÐģÐļ",
"tags_description": "ÐĒÐĩÐģ ÐīÐŧŅ ÐģŅ€ŅƒÐŋÐŋÐļŅ€ÐūÐēКÐļ Ņ€Ð°Ð·Ņ€Ðĩ҈ÐĩÐ―ÐļÐđ (ÐēКÐŧŅŽŅ‡Ð°Ņ ÐŋŅ€ÐļÐēŅÐ·ÐšŅƒ ÐŋÐŧÐ°Ð―Ð° Ðļ ÐŋÐūÐŧÐļŅ‚ÐļКÐļ ÐīÐūŅŅ‚Ð°ÐēКÐļ).", "tags_description": "ÐĒÐĩÐģ ÐīÐŧŅ ÐģŅ€ŅƒÐŋÐŋÐļŅ€ÐūÐēКÐļ Ņ€Ð°Ð·Ņ€Ðĩ҈ÐĩÐ―ÐļÐđ (ÐēКÐŧŅŽŅ‡Ð°Ņ ÐŋŅ€ÐļÐēŅÐ·ÐšŅƒ ÐŋÐŧÐ°Ð―Ð° Ðļ ÐŋÐūÐŧÐļŅ‚ÐļКÐļ ÐīÐūŅŅ‚Ð°ÐēКÐļ).",
"tags_placeholder": "Ð˜ŅÐŋÐūÐŧŅŒÐ·ŅƒÐđŅ‚Ðĩ Enter ÐļÐŧÐļ заÐŋŅŅ‚ŅƒŅŽ (,) ÐīÐŧŅ ÐīÐūÐąÐ°ÐēÐŧÐĩÐ―ÐļŅ Ð―ÐĩŅÐšÐūÐŧŅŒÐšÐļŅ… Ņ‚ÐĩÐģÐūÐē", "tags_placeholder": "Ð˜ŅÐŋÐūÐŧŅŒÐ·ŅƒÐđŅ‚Ðĩ Enter ÐļÐŧÐļ заÐŋŅŅ‚ŅƒŅŽ (,) ÐīÐŧŅ ÐīÐūÐąÐ°ÐēÐŧÐĩÐ―ÐļŅ Ð―ÐĩŅÐšÐūÐŧŅŒÐšÐļŅ… Ņ‚ÐĩÐģÐūÐē",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "АÐīŅ€Ðĩҁ",
"address_placeholder": "АÐīŅ€Ðĩҁ ҁÐĩŅ€ÐēÐĩŅ€Ð°",
"bandwidth_placeholder": "ВÐēÐĩÐīÐļŅ‚Ðĩ ÐŋŅ€ÐūÐŋŅƒŅÐšÐ―ŅƒŅŽ ҁÐŋÐūҁÐūÐąÐ―ÐūŅŅ‚ŅŒ, ÐūŅŅ‚Ð°ÐēŅŒŅ‚Ðĩ ÐŋŅƒŅŅ‚Ņ‹Ðž ÐīÐŧŅ BBR",
"basic": "БазÐūÐēÐ°Ņ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ",
"cancel": "ÐžŅ‚ÐžÐĩÐ―Ð°",
"city": "ГÐūŅ€ÐūÐī",
"config": {
"actions": { "actions": {
"cancel": "ÐžŅ‚ÐžÐĩÐ―Ð°", "cancel": "ÐžŅ‚ÐžÐĩÐ―Ð°",
"save": "ÐĄÐūŅ…Ņ€Ð°Ð―ÐļŅ‚ŅŒ" "save": "ÐĄÐūŅ…Ņ€Ð°Ð―ÐļŅ‚ŅŒ"
}, },
"communicationKey": "КÐŧŅŽŅ‡ ҁÐēŅÐ·Ðļ", "address": "АÐīŅ€Ðĩҁ",
"communicationKeyDescription": "Ð˜ŅÐŋÐūÐŧŅŒÐ·ŅƒÐĩŅ‚ŅŅ ÐīÐŧŅ Ð°ŅƒŅ‚ÐĩÐ―Ņ‚ÐļŅ„ÐļÐšÐ°Ņ†ÐļÐļ ŅƒÐ·Ðŧа.", "address_placeholder": "АÐīŅ€Ðĩҁ ҁÐĩŅ€ÐēÐĩŅ€Ð°",
"description": "ÐĢÐŋŅ€Ð°ÐēÐŧÐĩÐ―ÐļÐĩ КÐŧŅŽŅ‡Ð°ÐžÐļ ҁÐēŅÐ·Ðļ ŅƒÐ·Ðŧа, ÐļÐ―Ņ‚ÐĩŅ€ÐēаÐŧаОÐļ ÐŋÐūÐŧŅƒŅ‡ÐĩÐ―ÐļŅ/ÐūŅ‚ÐŋŅ€Ð°ÐēКÐļ Ðļ ÐīÐļÐ―Ð°ÐžÐļ҇ÐĩŅÐšÐļОÐļ ÐžÐ―ÐūÐķÐļŅ‚ÐĩÐŧŅÐžÐļ.", "apiHost": "API Ņ…Ðūҁ҂",
"dynamicMultiplier": "ДÐļÐ―Ð°ÐžÐļ҇ÐĩŅÐšÐļÐđ ÐžÐ―ÐūÐķÐļŅ‚ÐĩÐŧҌ", "apiHostPlaceholder": "http(s)://ÐŋŅ€ÐļОÐĩŅ€.КÐūО",
"dynamicMultiplierDescription": "ОÐŋŅ€ÐĩÐīÐĩÐŧÐļŅ‚Ðĩ ÐēŅ€ÐĩОÐĩÐ―Ð―Ņ‹Ðĩ ҁÐŧÐū҂ҋ Ðļ ÐžÐ―ÐūÐķÐļŅ‚ÐĩÐŧÐļ ÐīÐŧŅ КÐūҀҀÐĩÐšŅ‚ÐļŅ€ÐūÐēКÐļ ŅƒŅ‡ÐĩŅ‚Ð° Ņ‚Ņ€Ð°Ņ„ÐļКа.", "bandwidth_placeholder": "ВÐēÐĩÐīÐļŅ‚Ðĩ ÐŋŅ€ÐūÐŋŅƒŅÐšÐ―ŅƒŅŽ ҁÐŋÐūҁÐūÐąÐ―ÐūŅŅ‚ŅŒ, ÐūŅŅ‚Ð°ÐēŅŒŅ‚Ðĩ ÐŋŅƒŅŅ‚Ņ‹Ðž ÐīÐŧŅ BBR",
"endTime": "Ð’Ņ€ÐĩÐžŅ ÐūКÐūÐ―Ņ‡Ð°Ð―ÐļŅ", "basic": "БазÐūÐēÐ°Ņ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ",
"inputPlaceholder": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēÐēÐĩÐīÐļŅ‚Ðĩ", "cancel": "ÐžŅ‚ÐžÐĩÐ―Ð°",
"multiplier": "ÐœÐ―ÐūÐķÐļŅ‚ÐĩÐŧҌ", "cert_dns_env": "ПÐĩŅ€ÐĩОÐĩÐ―Ð―Ņ‹Ðĩ ÐūÐšŅ€ŅƒÐķÐĩÐ―ÐļŅ DNS",
"nodePullInterval": "Ð˜Ð―Ņ‚ÐĩŅ€ÐēаÐŧ ÐŋÐūÐŧŅƒŅ‡ÐĩÐ―ÐļŅ ŅƒÐ·ÐŧÐūО", "cert_dns_provider": "ПÐūŅŅ‚Ð°Ðē҉ÐļК DNS",
"nodePullIntervalDescription": "КаК Ņ‡Ð°ŅŅ‚Ðū ŅƒÐ·ÐĩÐŧ ÐŋÐūÐŧŅƒŅ‡Ð°ÐĩŅ‚ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅŽ (Ðē ҁÐĩÐšŅƒÐ―ÐīÐ°Ņ…).", "cert_mode": "РÐĩÐķÐļО ҁÐĩҀ҂ÐļŅ„ÐļÐšÐ°Ņ‚Ð°",
"nodePushInterval": "Ð˜Ð―Ņ‚ÐĩŅ€ÐēаÐŧ ÐūŅ‚ÐŋŅ€Ð°ÐēКÐļ ŅƒÐ·ÐŧÐūО", "cipher": "АÐŧÐģÐūŅ€ÐļŅ‚Ðž ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ",
"nodePushIntervalDescription": "КаК Ņ‡Ð°ŅŅ‚Ðū ŅƒÐ·ÐĩÐŧ ÐūŅ‚ÐŋŅ€Ð°ÐēÐŧŅÐĩŅ‚ ŅŅ‚Ð°Ņ‚Ðļҁ҂ÐļÐšŅƒ (Ðē ҁÐĩÐšŅƒÐ―ÐīÐ°Ņ…).", "city": "ГÐūŅ€ÐūÐī",
"reset": "ÐĄÐąŅ€Ðūҁ",
"save": "ÐĄÐūŅ…Ņ€Ð°Ð―ÐļŅ‚ŅŒ",
"saveSuccess": "ÐĢҁÐŋÐĩŅˆÐ―Ðū ҁÐūŅ…Ņ€Ð°Ð―ÐĩÐ―Ðū",
"startTime": "Ð’Ņ€ÐĩÐžŅ Ð―Ð°Ņ‡Ð°Ðŧа",
"timeSlot": "Ð’Ņ€ÐĩОÐĩÐ―Ð―ÐūÐđ ҁÐŧÐūŅ‚",
"title": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ ŅƒÐ·Ðŧа"
},
"confirm": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚ŅŒ", "confirm": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚ŅŒ",
"confirmDeleteDesc": "Ð­Ņ‚Ðū ÐīÐĩÐđҁ҂ÐēÐļÐĩ Ð―ÐĩÐŧŅŒÐ·Ņ ÐūŅ‚ÐžÐĩÐ―ÐļŅ‚ŅŒ.", "confirmDeleteDesc": "Ð­Ņ‚Ðū ÐīÐĩÐđҁ҂ÐēÐļÐĩ Ð―ÐĩÐŧŅŒÐ·Ņ ÐūŅ‚ÐžÐĩÐ―ÐļŅ‚ŅŒ.",
"confirmDeleteTitle": "ÐĢÐīаÐŧÐļŅ‚ŅŒ ŅŅ‚ÐūŅ‚ ҁÐĩŅ€ÐēÐĩŅ€?", "confirmDeleteTitle": "ÐĢÐīаÐŧÐļŅ‚ŅŒ ŅŅ‚ÐūŅ‚ ҁÐĩŅ€ÐēÐĩŅ€?",
"congestion_controller": "КÐūÐ―Ņ‚Ņ€ÐūÐŧÐŧÐĩŅ€ ÐŋÐĩŅ€ÐĩÐģŅ€ŅƒÐ·ÐšÐļ", "congestion_controller": "КÐūÐ―Ņ‚Ņ€ÐūÐŧÐŧÐĩŅ€ ÐŋÐĩŅ€ÐĩÐģŅ€ŅƒÐ·ÐšÐļ",
"connect": "ПÐūÐīКÐŧŅŽŅ‡ÐļŅ‚ŅŒ",
"copied": "ÐĄÐšÐūÐŋÐļŅ€ÐūÐēÐ°Ð―Ðū", "copied": "ÐĄÐšÐūÐŋÐļŅ€ÐūÐēÐ°Ð―Ðū",
"copy": "КÐūÐŋÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ", "copy": "КÐūÐŋÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ",
"country": "ÐĄŅ‚Ņ€Ð°Ð―Ð°", "country": "ÐĄŅ‚Ņ€Ð°Ð―Ð°",
@@ -49,28 +36,46 @@
"drawerEditTitle": "РÐĩÐīÐ°ÐšŅ‚ÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ ҁÐĩŅ€ÐēÐĩŅ€", "drawerEditTitle": "РÐĩÐīÐ°ÐšŅ‚ÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ ҁÐĩŅ€ÐēÐĩŅ€",
"edit": "РÐĩÐīÐ°ÐšŅ‚ÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ", "edit": "РÐĩÐīÐ°ÐšŅ‚ÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ",
"enabled": "ВКÐŧŅŽŅ‡ÐĩÐ―Ðū", "enabled": "ВКÐŧŅŽŅ‡ÐĩÐ―Ðū",
"encryption_method": "МÐĩŅ‚ÐūÐī ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ", "encryption": "МÐĩŅ‚ÐūÐī ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ",
"encryption_client_padding": "ДÐūÐŋÐūÐŧÐ―ÐĩÐ―ÐļÐĩ КÐŧÐļÐĩÐ―Ņ‚Ð°",
"encryption_mode": "РÐĩÐķÐļО",
"encryption_password": "ÐŸÐ°Ņ€ÐūÐŧҌ",
"encryption_password_placeholder": "ÐžŅŅ‚Ð°ÐēŅŒŅ‚Ðĩ ÐŋŅƒŅŅ‚Ņ‹Ðž ÐīÐŧŅ аÐēŅ‚Ðū-ÐģÐĩÐ―ÐĩŅ€Ð°Ņ†ÐļÐļ, заОÐĩÐ―ÐļŅ‚Ðĩ ÐēŅ€ŅƒŅ‡Ð―ŅƒŅŽ ÐīÐŧŅ ÐŋÐūҁ҂-КÐēÐ°Ð―Ņ‚ÐūÐēÐūÐģÐū ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ",
"encryption_private_key": "Ð—Ð°ÐšŅ€Ņ‹Ņ‚Ņ‹Ðđ КÐŧŅŽŅ‡",
"encryption_private_key_placeholder": "ÐžŅŅ‚Ð°ÐēŅŒŅ‚Ðĩ ÐŋŅƒŅŅ‚Ņ‹Ðž ÐīÐŧŅ аÐēŅ‚Ðū-ÐģÐĩÐ―ÐĩŅ€Ð°Ņ†ÐļÐļ, заОÐĩÐ―ÐļŅ‚Ðĩ ÐēŅ€ŅƒŅ‡Ð―ŅƒŅŽ ÐīÐŧŅ ÐŋÐūҁ҂-КÐēÐ°Ð―Ņ‚ÐūÐēÐūÐģÐū ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ",
"encryption_rtt": "RTT",
"encryption_server_padding": "ДÐūÐŋÐūÐŧÐ―ÐĩÐ―ÐļÐĩ ҁÐĩŅ€ÐēÐĩŅ€Ð°",
"encryption_ticket": "Ð’Ņ€ÐĩÐžŅ ÐąÐļÐŧÐĩŅ‚Ð°",
"expireTime": "Ð’Ņ€ÐĩÐžŅ Ðļҁ҂Ðĩ҇ÐĩÐ―ÐļŅ", "expireTime": "Ð’Ņ€ÐĩÐžŅ Ðļҁ҂Ðĩ҇ÐĩÐ―ÐļŅ",
"expired": "Ð˜ŅŅ‚ÐĩКÐŧÐū", "expired": "Ð˜ŅŅ‚ÐĩКÐŧÐū",
"extra": "ДÐūÐŋÐūÐŧÐ―ÐļŅ‚ÐĩÐŧŅŒÐ―Ð°Ņ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ",
"flow": "ПÐūŅ‚ÐūК", "flow": "ПÐūŅ‚ÐūК",
"generate_quantum_resistant_key": "ГÐĩÐ―ÐĩŅ€ÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ КÐēÐ°Ð―Ņ‚ÐūÐēÐū-ŅƒŅŅ‚ÐūÐđ҇ÐļÐēŅ‹Ðđ КÐŧŅŽŅ‡",
"generate_standard_encryption_key": "ГÐĩÐ―ÐĩŅ€ÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ ŅŅ‚Ð°Ð―ÐīÐ°Ņ€Ņ‚Ð―Ņ‹Ðđ КÐŧŅŽŅ‡ ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ",
"hop_interval": "Ð˜Ð―Ņ‚ÐĩŅ€ÐēаÐŧ ÐŋÐĩŅ€ÐĩŅ…ÐūÐīа", "hop_interval": "Ð˜Ð―Ņ‚ÐĩŅ€ÐēаÐŧ ÐŋÐĩŅ€ÐĩŅ…ÐūÐīа",
"hop_ports": "ПÐūҀ҂ҋ ÐŋÐĩŅ€ÐĩŅ…ÐūÐīа", "hop_ports": "ПÐūҀ҂ҋ ÐŋÐĩŅ€ÐĩŅ…ÐūÐīа",
"hop_ports_placeholder": "Ð―Ð°ÐŋŅ€ÐļОÐĩŅ€, 1-65535", "hop_ports_placeholder": "Ð―Ð°ÐŋŅ€ÐļОÐĩŅ€, 1-65535",
"host": "ÐĨÐūҁ҂", "host": "ÐĨÐūҁ҂",
"id": "ID", "id": "ID",
"installCommand": "КÐūÐžÐ°Ð―Ðīа ŅƒŅŅ‚Ð°Ð―ÐūÐēКÐļ",
"ipAddresses": "IP-аÐīŅ€ÐĩŅÐ°", "ipAddresses": "IP-аÐīŅ€ÐĩŅÐ°",
"memory": "ÐŸÐ°ÐžŅŅ‚ŅŒ", "memory": "ÐŸÐ°ÐžŅŅ‚ŅŒ",
"migrate": "ПÐĩŅ€ÐĩÐ―Ðĩҁ҂Ðļ ÐīÐ°Ð―Ð―Ņ‹Ðĩ", "migrate": "ПÐĩŅ€ÐĩÐ―Ðĩҁ҂Ðļ ÐīÐ°Ð―Ð―Ņ‹Ðĩ",
"migrateFailed": "ÐžŅˆÐļÐąÐšÐ° ÐŋŅ€Ðļ ÐŋÐĩŅ€ÐĩÐ―ÐūҁÐĩ ÐīÐ°Ð―Ð―Ņ‹Ņ…", "migrateFailed": "ÐžŅˆÐļÐąÐšÐ° ÐŋŅ€Ðļ ÐŋÐĩŅ€ÐĩÐ―ÐūҁÐĩ ÐīÐ°Ð―Ð―Ņ‹Ņ…",
"migrated": "Ð”Ð°Ð―Ð―Ņ‹Ðĩ ҃ҁÐŋÐĩŅˆÐ―Ðū ÐŋÐĩŅ€ÐĩÐ―ÐĩҁÐĩÐ―Ņ‹", "migrated": "Ð”Ð°Ð―Ð―Ņ‹Ðĩ ҃ҁÐŋÐĩŅˆÐ―Ðū ÐŋÐĩŅ€ÐĩÐ―ÐĩҁÐĩÐ―Ņ‹",
"migrating": "ПÐĩŅ€ÐĩÐ―Ðūҁ ÐīÐ°Ð―Ð―Ņ‹Ņ…...", "migrating": "ПÐĩŅ€ÐĩÐ―Ðūҁ ÐīÐ°Ð―Ð―Ņ‹Ņ…...",
"mode": "РÐĩÐķÐļО",
"multiplex": "ÐœŅƒÐŧŅŒŅ‚ÐļÐŋÐŧÐĩÐšŅ", "multiplex": "ÐœŅƒÐŧŅŒŅ‚ÐļÐŋÐŧÐĩÐšŅ",
"name": "Ð˜ÐžŅ", "name": "Ð˜ÐžŅ",
"noData": "НÐĩŅ‚ ÐīÐ°Ð―Ð―Ņ‹Ņ…", "noData": "НÐĩŅ‚ ÐīÐ°Ð―Ð―Ņ‹Ņ…",
"notAvailable": "НÐĩÐīÐūŅŅ‚ŅƒÐŋÐ―Ðū", "notAvailable": "НÐĩÐīÐūŅŅ‚ŅƒÐŋÐ―Ðū",
"obfs": "ÐžÐąŅ„ŅƒŅÐšÐ°Ņ†ÐļŅ",
"obfs_host": "ÐžÐąŅ„ŅƒŅŅ†ÐļŅ€ÐūÐēÐ°Ð―Ð―Ņ‹Ðđ Ņ…Ðūҁ҂",
"obfs_password": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐūÐąŅ„ŅƒŅÐšÐ°Ņ†ÐļÐļ", "obfs_password": "ÐŸÐ°Ņ€ÐūÐŧҌ ÐūÐąŅ„ŅƒŅÐšÐ°Ņ†ÐļÐļ",
"obfs_password_placeholder": "ВÐēÐĩÐīÐļŅ‚Ðĩ ÐŋÐ°Ņ€ÐūÐŧҌ ÐūÐąŅ„ŅƒŅÐšÐ°Ņ†ÐļÐļ", "obfs_password_placeholder": "ВÐēÐĩÐīÐļŅ‚Ðĩ ÐŋÐ°Ņ€ÐūÐŧҌ ÐūÐąŅ„ŅƒŅÐšÐ°Ņ†ÐļÐļ",
"obfs_path": "ÐžÐąŅ„ŅƒŅŅ†ÐļŅ€ÐūÐēÐ°Ð―Ð―Ņ‹Ðđ ÐŋŅƒŅ‚ŅŒ",
"offline": "ÐžŅ„ÐŧаÐđÐ―", "offline": "ÐžŅ„ÐŧаÐđÐ―",
"oneClickInstall": "ÐĢŅŅ‚Ð°Ð―ÐūÐēКа Ðē ÐūÐīÐļÐ― КÐŧÐļК",
"online": "ÐžÐ―ÐŧаÐđÐ―", "online": "ÐžÐ―ÐŧаÐđÐ―",
"onlineUsers": "ÐžÐ―ÐŧаÐđÐ― ÐŋÐūÐŧŅŒÐ·ÐūÐēÐ°Ņ‚ÐĩÐŧÐļ", "onlineUsers": "ÐžÐ―ÐŧаÐđÐ― ÐŋÐūÐŧŅŒÐ·ÐūÐēÐ°Ņ‚ÐĩÐŧÐļ",
"padding_scheme": "ÐĄŅ…ÐĩОа ÐēŅ‹Ņ€Ð°ÐēÐ―ÐļÐēÐ°Ð―ÐļŅ", "padding_scheme": "ÐĄŅ…ÐĩОа ÐēŅ‹Ņ€Ð°ÐēÐ―ÐļÐēÐ°Ð―ÐļŅ",
@@ -78,8 +83,6 @@
"pageTitle": "ÐĄÐĩŅ€ÐēÐĩҀҋ", "pageTitle": "ÐĄÐĩŅ€ÐēÐĩҀҋ",
"path": "ÐŸŅƒŅ‚ŅŒ", "path": "ÐŸŅƒŅ‚ŅŒ",
"please_select": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēŅ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ", "please_select": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēŅ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ",
"plugin": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ ÐŋÐŧаÐģÐļÐ―Ð°",
"plugin_opts": "ОÐŋ҆ÐļÐļ ÐŋÐŧаÐģÐļÐ―Ð°",
"port": "ПÐūҀ҂", "port": "ПÐūҀ҂",
"protocol_configurations": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļÐļ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧÐūÐē", "protocol_configurations": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļÐļ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧÐūÐē",
"protocol_configurations_desc": "ВКÐŧŅŽŅ‡ÐļŅ‚Ðĩ Ðļ Ð―Ð°ŅŅ‚Ņ€ÐūÐđŅ‚Ðĩ Ð―ÐĩÐūÐąŅ…ÐūÐīÐļÐžŅ‹Ðĩ Ņ‚ÐļÐŋŅ‹ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧÐūÐē", "protocol_configurations_desc": "ВКÐŧŅŽŅ‡ÐļŅ‚Ðĩ Ðļ Ð―Ð°ŅŅ‚Ņ€ÐūÐđŅ‚Ðĩ Ð―ÐĩÐūÐąŅ…ÐūÐīÐļÐžŅ‹Ðĩ Ņ‚ÐļÐŋŅ‹ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧÐūÐē",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "ÐĻÐĩŅŅ‚Ð―Ð°ÐīŅ†Ð°Ņ‚ÐĩŅ€ÐļŅ‡Ð―Ð°Ņ ҁ҂ҀÐūКа (ÐīÐū 16 ҁÐļОÐēÐūÐŧÐūÐē)", "security_short_id_placeholder": "ÐĻÐĩŅŅ‚Ð―Ð°ÐīŅ†Ð°Ņ‚ÐĩŅ€ÐļŅ‡Ð―Ð°Ņ ҁ҂ҀÐūКа (ÐīÐū 16 ҁÐļОÐēÐūÐŧÐūÐē)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ОÐĩŅ‚ÐūÐī ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ", "select_encryption_method": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ОÐĩŅ‚ÐūÐī ҈Ðļ҄ҀÐūÐēÐ°Ð―ÐļŅ",
"server_config": {
"description": "ÐĢÐŋŅ€Ð°ÐēÐŧÐĩÐ―ÐļÐĩ КÐŧŅŽŅ‡Ð°ÐžÐļ ҁÐēŅÐ·Ðļ ŅƒÐ·Ðŧа, ÐļÐ―Ņ‚ÐĩŅ€ÐēаÐŧаОÐļ ÐŋÐūÐŧŅƒŅ‡ÐĩÐ―ÐļŅ/ÐūŅ‚ÐŋŅ€Ð°ÐēКÐļ.",
"dynamic_multiplier": "ДÐļÐ―Ð°ÐžÐļ҇ÐĩŅÐšÐļÐđ ÐžÐ―ÐūÐķÐļŅ‚ÐĩÐŧҌ",
"dynamic_multiplier_desc": "ОÐŋŅ€ÐĩÐīÐĩÐŧÐļŅ‚Ðĩ ÐēŅ€ÐĩОÐĩÐ―Ð―Ņ‹Ðĩ ҁÐŧÐū҂ҋ Ðļ ÐžÐ―ÐūÐķÐļŅ‚ÐĩÐŧÐļ ÐīÐŧŅ КÐūҀҀÐĩÐšŅ‚ÐļŅ€ÐūÐēКÐļ ŅƒŅ‡ÐĩŅ‚Ð° Ņ‚Ņ€Ð°Ņ„ÐļКа.",
"fields": {
"block_rules_placeholder": "ОÐīÐ―Ðū ÐŋŅ€Ð°ÐēÐļÐŧÐū ÐīÐūОÐĩÐ―Ð° Ð―Ð° ҁ҂ҀÐūÐšŅƒ, ÐŋÐūÐīÐīÐĩŅ€ÐķÐļÐēаÐĩŅ‚:\nkeyword:google (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū КÐŧŅŽŅ‡ÐĩÐēÐūÐžŅƒ ҁÐŧÐūÐē҃)\nsuffix:google.com (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū ŅŅƒŅ„Ņ„ÐļÐšŅŅƒ)\nregex:.*\\.example\\.com$ (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū Ņ€ÐĩÐģ҃ÐŧŅŅ€Ð―ÐūÐžŅƒ ÐēŅ‹Ņ€Ð°ÐķÐĩÐ―ÐļŅŽ)\nexample.com (Ņ‚ÐūŅ‡Ð―ÐūÐĩ ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ)",
"communication_key": "КÐŧŅŽŅ‡ ҁÐēŅÐ·Ðļ",
"communication_key_desc": "Ð˜ŅÐŋÐūÐŧŅŒÐ·ŅƒÐĩŅ‚ŅŅ ÐīÐŧŅ Ð°ŅƒŅ‚ÐĩÐ―Ņ‚ÐļŅ„ÐļÐšÐ°Ņ†ÐļÐļ ŅƒÐ·Ðŧа.",
"communication_key_placeholder": "ПÐūÐķаÐŧ҃ÐđŅŅ‚Ð°, ÐēÐēÐĩÐīÐļŅ‚Ðĩ",
"dns_config": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ DNS",
"dns_domains_placeholder": "ОÐīÐ―Ðū ÐŋŅ€Ð°ÐēÐļÐŧÐū ÐīÐūОÐĩÐ―Ð° Ð―Ð° ҁ҂ҀÐūÐšŅƒ, ÐŋÐūÐīÐīÐĩŅ€ÐķÐļÐēаÐĩŅ‚:\nkeyword:google (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū КÐŧŅŽŅ‡ÐĩÐēÐūÐžŅƒ ҁÐŧÐūÐē҃)\nsuffix:google.com (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū ŅŅƒŅ„Ņ„ÐļÐšŅŅƒ)\nregex:.*\\.example\\.com$ (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū Ņ€ÐĩÐģ҃ÐŧŅŅ€Ð―ÐūÐžŅƒ ÐēŅ‹Ņ€Ð°ÐķÐĩÐ―ÐļŅŽ)\nexample.com (Ņ‚ÐūŅ‡Ð―ÐūÐĩ ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ)",
"dns_proto_placeholder": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ Ņ‚ÐļÐŋ",
"end_time": "Ð’Ņ€ÐĩÐžŅ ÐūКÐūÐ―Ņ‡Ð°Ð―ÐļŅ",
"ip_strategy": "ÐĄŅ‚Ņ€Ð°Ņ‚ÐĩÐģÐļŅ IP",
"ip_strategy_desc": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ÐŋŅ€ÐĩÐīÐŋÐū҇҂ÐĩÐ―ÐļÐĩ ÐēÐĩҀҁÐļÐļ IP ÐīÐŧŅ ҁÐĩŅ‚ÐĩÐēҋ҅ ÐŋÐūÐīКÐŧŅŽŅ‡ÐĩÐ―ÐļÐđ",
"ip_strategy_ipv4": "ÐŸŅ€ÐĩÐīÐŋÐū҇ÐļŅ‚Ð°Ņ‚ŅŒ IPv4",
"ip_strategy_ipv6": "ÐŸŅ€ÐĩÐīÐŋÐū҇ÐļŅ‚Ð°Ņ‚ŅŒ IPv6",
"ip_strategy_placeholder": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ŅŅ‚Ņ€Ð°Ņ‚ÐĩÐģÐļŅŽ IP",
"multiplier": "ÐœÐ―ÐūÐķÐļŅ‚ÐĩÐŧҌ",
"node_pull_interval": "Ð˜Ð―Ņ‚ÐĩŅ€ÐēаÐŧ ÐŋÐūÐŧŅƒŅ‡ÐĩÐ―ÐļŅ ŅƒÐ·ÐŧÐūО",
"node_pull_interval_desc": "КаК Ņ‡Ð°ŅŅ‚Ðū ŅƒÐ·ÐĩÐŧ ÐŋÐūÐŧŅƒŅ‡Ð°ÐĩŅ‚ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅŽ (Ðē ҁÐĩÐšŅƒÐ―ÐīÐ°Ņ…).",
"node_push_interval": "Ð˜Ð―Ņ‚ÐĩŅ€ÐēаÐŧ ÐūŅ‚ÐŋŅ€Ð°ÐēКÐļ ŅƒÐ·ÐŧÐūО",
"node_push_interval_desc": "КаК Ņ‡Ð°ŅŅ‚Ðū ŅƒÐ·ÐĩÐŧ ÐūŅ‚ÐŋŅ€Ð°ÐēÐŧŅÐĩŅ‚ ŅŅ‚Ð°Ņ‚Ðļҁ҂ÐļÐšŅƒ (Ðē ҁÐĩÐšŅƒÐ―ÐīÐ°Ņ…).",
"outbound_address_placeholder": "АÐīŅ€Ðĩҁ ҁÐĩŅ€ÐēÐĩŅ€Ð°",
"outbound_name_placeholder": "Ð˜ÐžŅ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļÐļ",
"outbound_password_placeholder": "ÐŸÐ°Ņ€ÐūÐŧҌ (Ð―ÐĩÐūÐąŅÐ·Ð°Ņ‚ÐĩÐŧŅŒÐ―Ðū)",
"outbound_port_placeholder": "НÐūОÐĩŅ€ ÐŋÐūŅ€Ņ‚Ð°",
"outbound_protocol_placeholder": "Ð’Ņ‹ÐąÐĩŅ€ÐļŅ‚Ðĩ ÐŋŅ€ÐūŅ‚ÐūКÐūÐŧ",
"outbound_rules_placeholder": "ОÐīÐ―Ðū ÐŋŅ€Ð°ÐēÐļÐŧÐū Ð―Ð° ҁ҂ҀÐūÐšŅƒ, ÐŋÐūÐīÐīÐĩŅ€ÐķÐļÐēаÐĩŅ‚:\nkeyword:google (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū КÐŧŅŽŅ‡ÐĩÐēÐūÐžŅƒ ҁÐŧÐūÐē҃)\nsuffix:google.com (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū ŅŅƒŅ„Ņ„ÐļÐšŅŅƒ)\nregex:.*\\.example\\.com$ (ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ ÐŋÐū Ņ€ÐĩÐģ҃ÐŧŅŅ€Ð―ÐūÐžŅƒ ÐēŅ‹Ņ€Ð°ÐķÐĩÐ―ÐļŅŽ)\nexample.com (Ņ‚ÐūŅ‡Ð―ÐūÐĩ ҁÐūÐēÐŋаÐīÐĩÐ―ÐļÐĩ)\nÐžŅŅ‚Ð°ÐēŅŒŅ‚Ðĩ ÐŋŅƒŅŅ‚Ņ‹Ðž ÐīÐŧŅ ÐžÐ°Ņ€ŅˆŅ€ŅƒŅ‚ÐļÐ·Ð°Ņ†ÐļÐļ ÐŋÐū ŅƒÐžÐūÐŧŅ‡Ð°Ð―ÐļŅŽ",
"reset": "ÐĄÐąŅ€Ðūҁ",
"save": "ÐĄÐūŅ…Ņ€Ð°Ð―ÐļŅ‚ŅŒ",
"start_time": "Ð’Ņ€ÐĩÐžŅ Ð―Ð°Ņ‡Ð°Ðŧа",
"time_slot": "Ð’Ņ€ÐĩОÐĩÐ―Ð―ÐūÐđ ҁÐŧÐūŅ‚",
"traffic_report_threshold": "ПÐūŅ€ÐūÐģ Ðū҂҇ÐĩŅ‚Ð° Ðū Ņ‚Ņ€Ð°Ņ„ÐļКÐĩ",
"traffic_report_threshold_desc": "ÐĢŅŅ‚Ð°Ð―ÐūÐēÐļŅ‚Ðĩ ОÐļÐ―ÐļОаÐŧŅŒÐ―Ņ‹Ðđ ÐŋÐūŅ€ÐūÐģ ÐīÐŧŅ Ðū҂҇ÐĩŅ‚Ð° Ðū Ņ‚Ņ€Ð°Ņ„ÐļКÐĩ. ÐĒŅ€Ð°Ņ„ÐļК ÐąŅƒÐīÐĩŅ‚ ҁÐūÐūÐąŅ‰Ð°Ņ‚ŅŒŅŅ Ņ‚ÐūÐŧŅŒÐšÐū ÐŋŅ€Ðļ ÐŋŅ€ÐĩÐēŅ‹ŅˆÐĩÐ―ÐļÐļ ŅŅ‚ÐūÐģÐū Ð·Ð―Ð°Ņ‡ÐĩÐ―ÐļŅ. ÐĢŅŅ‚Ð°Ð―ÐūÐēÐļŅ‚Ðĩ 0 ÐļÐŧÐļ ÐūŅŅ‚Ð°ÐēŅŒŅ‚Ðĩ ÐŋŅƒŅŅ‚Ņ‹Ðž, ҇҂ÐūÐąŅ‹ ҁÐūÐūÐąŅ‰Ð°Ņ‚ŅŒ Ðū ÐēҁÐĩО Ņ‚Ņ€Ð°Ņ„ÐļКÐĩ."
},
"saveSuccess": "ÐĢҁÐŋÐĩŅˆÐ―Ðū ҁÐūŅ…Ņ€Ð°Ð―ÐĩÐ―Ðū",
"tabs": {
"basic": "ÐžŅÐ―ÐūÐēÐ―Ð°Ņ КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ",
"block": "ÐŸŅ€Ð°ÐēÐļÐŧа ÐąÐŧÐūКÐļŅ€ÐūÐēКÐļ",
"dns": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ DNS",
"outbound": "Ð˜ŅŅ…ÐūÐīŅŅ‰ÐļÐĩ ÐŋŅ€Ð°ÐēÐļÐŧа"
},
"title": "КÐūÐ―Ņ„ÐļÐģŅƒŅ€Ð°Ņ†ÐļŅ ŅƒÐ·Ðŧа"
},
"server_key": "КÐŧŅŽŅ‡ ҁÐĩŅ€ÐēÐĩŅ€Ð°", "server_key": "КÐŧŅŽŅ‡ ҁÐĩŅ€ÐēÐĩŅ€Ð°",
"service_name": "Ð˜ÐžŅ ҁÐŧ҃ÐķÐąŅ‹", "service_name": "Ð˜ÐžŅ ҁÐŧ҃ÐķÐąŅ‹",
"sorted_success": "ÐĢҁÐŋÐĩŅˆÐ―Ðū Ðū҂ҁÐūҀ҂ÐļŅ€ÐūÐēÐ°Ð―Ðū",
"status": "ÐĄŅ‚Ð°Ņ‚ŅƒŅ", "status": "ÐĄŅ‚Ð°Ņ‚ŅƒŅ",
"subscribeId": "ID ÐŋÐūÐīÐŋÐļŅÐšÐļ", "subscribeId": "ID ÐŋÐūÐīÐŋÐļŅÐšÐļ",
"subscription": "ПÐūÐīÐŋÐļŅÐšÐ°", "subscription": "ПÐūÐīÐŋÐļŅÐšÐ°",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚ŅŒ", "confirm": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚ŅŒ",
"confirmDelete": "Ð’Ņ‹ ҃ÐēÐĩŅ€ÐĩÐ―Ņ‹, ҇҂Ðū Ņ…ÐūŅ‚ÐļŅ‚Ðĩ ҃ÐīаÐŧÐļŅ‚ŅŒ?", "confirmDelete": "Ð’Ņ‹ ҃ÐēÐĩŅ€ÐĩÐ―Ņ‹, ҇҂Ðū Ņ…ÐūŅ‚ÐļŅ‚Ðĩ ҃ÐīаÐŧÐļŅ‚ŅŒ?",
"confirmOffline": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚ŅŒ ÐūŅ„ÐŧаÐđÐ―", "confirmOffline": "ПÐūÐīŅ‚ÐēÐĩŅ€ÐīÐļŅ‚ŅŒ ÐūŅ„ÐŧаÐđÐ―",
"copySubscription": "КÐūÐŋÐļŅ€ÐūÐēÐ°Ņ‚ŅŒ ÐŋÐūÐīÐŋÐļŅÐšŅƒ",
"copySuccess": "КÐūÐŋÐļŅ€ÐūÐēÐ°Ð―ÐļÐĩ ҃ҁÐŋÐĩŅˆÐ―Ðū",
"create": "ÐĄÐūзÐīÐ°Ņ‚ŅŒ", "create": "ÐĄÐūзÐīÐ°Ņ‚ŅŒ",
"createSubscription": "ÐĄÐūзÐīÐ°Ņ‚ŅŒ ÐŋÐūÐīÐŋÐļŅÐšŅƒ", "createSubscription": "ÐĄÐūзÐīÐ°Ņ‚ŅŒ ÐŋÐūÐīÐŋÐļŅÐšŅƒ",
"createSuccess": "ÐĄÐūзÐīÐ°Ð―ÐļÐĩ ҃ҁÐŋÐĩŅˆÐ―Ðū", "createSuccess": "ÐĄÐūзÐīÐ°Ð―ÐļÐĩ ҃ҁÐŋÐĩŅˆÐ―Ðū",
+1
View File
@@ -30,6 +30,7 @@
"select_protocol": "āđ€āļĨāļ·āļ­āļāđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨâ€Ķ", "select_protocol": "āđ€āļĨāļ·āļ­āļāđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨâ€Ķ",
"select_server": "āđ€āļĨāļ·āļ­āļāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒâ€Ķ", "select_server": "āđ€āļĨāļ·āļ­āļāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒâ€Ķ",
"server": "āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", "server": "āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"sorted_success": "āđ€āļĢāļĩāļĒāļ‡āļĨāļģāļ”āļąāļšāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§",
"tags": "āđāļ—āđ‡āļ", "tags": "āđāļ—āđ‡āļ",
"tags_description": "āđāļ—āđ‡āļāļāļēāļĢāļˆāļąāļ”āļāļĨāļļāđˆāļĄāļŠāļīāļ—āļ˜āļīāđŒ (āļĢāļ§āļĄāļ–āļķāļ‡āļāļēāļĢāļœāļđāļāđāļœāļ™āđāļĨāļ°āļ™āđ‚āļĒāļšāļēāļĒāļāļēāļĢāļˆāļąāļ”āļŠāđˆāļ‡)", "tags_description": "āđāļ—āđ‡āļāļāļēāļĢāļˆāļąāļ”āļāļĨāļļāđˆāļĄāļŠāļīāļ—āļ˜āļīāđŒ (āļĢāļ§āļĄāļ–āļķāļ‡āļāļēāļĢāļœāļđāļāđāļœāļ™āđāļĨāļ°āļ™āđ‚āļĒāļšāļēāļĒāļāļēāļĢāļˆāļąāļ”āļŠāđˆāļ‡)",
"tags_placeholder": "āđƒāļŠāđ‰ Enter āļŦāļĢāļ·āļ­āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāļˆāļļāļĨāļ āļēāļ„ (,) āđ€āļžāļ·āđˆāļ­āđ€āļžāļīāđˆāļĄāđāļ—āđ‡āļāļŦāļĨāļēāļĒāļĢāļēāļĒāļāļēāļĢ", "tags_placeholder": "āđƒāļŠāđ‰ Enter āļŦāļĢāļ·āļ­āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāļˆāļļāļĨāļ āļēāļ„ (,) āđ€āļžāļ·āđˆāļ­āđ€āļžāļīāđˆāļĄāđāļ—āđ‡āļāļŦāļĨāļēāļĒāļĢāļēāļĒāļāļēāļĢ",
+78 -29
View File
@@ -1,38 +1,25 @@
{ {
"address": "āļ—āļĩāđˆāļ­āļĒāļđāđˆ",
"address_placeholder": "āļ—āļĩāđˆāļ­āļĒāļđāđˆāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"bandwidth_placeholder": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāđāļšāļ™āļ”āđŒāļ§āļīāļ˜ āļ—āļīāđ‰āļ‡āļ§āđˆāļēāļ‡āđ„āļ§āđ‰āļŠāļģāļŦāļĢāļąāļš BBR",
"basic": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļžāļ·āđ‰āļ™āļāļēāļ™",
"cancel": "āļĒāļāđ€āļĨāļīāļ",
"city": "āđ€āļĄāļ·āļ­āļ‡",
"config": {
"actions": { "actions": {
"cancel": "āļĒāļāđ€āļĨāļīāļ", "cancel": "āļĒāļāđ€āļĨāļīāļ",
"save": "āļšāļąāļ™āļ—āļķāļ" "save": "āļšāļąāļ™āļ—āļķāļ"
}, },
"communicationKey": "āļ„āļĩāļĒāđŒāļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢ", "address": "āļ—āļĩāđˆāļ­āļĒāļđāđˆ",
"communicationKeyDescription": "āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒāđ‚āļŦāļ™āļ”", "address_placeholder": "āļ—āļĩāđˆāļ­āļĒāļđāđˆāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"description": "āļˆāļąāļ”āļāļēāļĢāļ„āļĩāļĒāđŒāļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢāļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”, āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāđƒāļ™āļāļēāļĢāļ”āļķāļ‡/āļŠāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ, āđāļĨāļ°āļ•āļąāļ§āļ„āļđāļ“āđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļ", "apiHost": "āđ‚āļŪāļŠāļ•āđŒ API",
"dynamicMultiplier": "āļ•āļąāļ§āļ„āļđāļ“āđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļ", "apiHostPlaceholder": "http(s)://example.com",
"dynamicMultiplierDescription": "āļāļģāļŦāļ™āļ”āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāđāļĨāļ°āļ•āļąāļ§āļ„āļđāļ“āđ€āļžāļ·āđˆāļ­āļ›āļĢāļąāļšāļāļēāļĢāļ„āļģāļ™āļ§āļ“āļāļēāļĢāļˆāļĢāļēāļˆāļĢ", "bandwidth_placeholder": "āļāļĢāļļāļ“āļēāļāļĢāļ­āļāđāļšāļ™āļ”āđŒāļ§āļīāļ˜ āļ—āļīāđ‰āļ‡āļ§āđˆāļēāļ‡āđ„āļ§āđ‰āļŠāļģāļŦāļĢāļąāļš BBR",
"endTime": "āđ€āļ§āļĨāļēāļŠāļīāđ‰āļ™āļŠāļļāļ”", "basic": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļžāļ·āđ‰āļ™āļāļēāļ™",
"inputPlaceholder": "āļāļĢāļļāļ“āļēāđƒāļŠāđˆ", "cancel": "āļĒāļāđ€āļĨāļīāļ",
"multiplier": "āļ•āļąāļ§āļ„āļđāļ“", "cert_dns_env": "āļ•āļąāļ§āđāļ›āļĢāļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄ DNS",
"nodePullInterval": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”", "cert_dns_provider": "āļœāļđāđ‰āđƒāļŦāđ‰āļšāļĢāļīāļāļēāļĢ DNS",
"nodePullIntervalDescription": "āļ„āļ§āļēāļĄāļ–āļĩāđˆāļ—āļĩāđˆāđ‚āļŦāļ™āļ”āļ”āļķāļ‡āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē (āļ§āļīāļ™āļēāļ—āļĩ)", "cert_mode": "āđ‚āļŦāļĄāļ”āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡",
"nodePushInterval": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļŠāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”", "cipher": "āļ­āļąāļĨāļāļ­āļĢāļīāļ˜āļķāļĄāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠ",
"nodePushIntervalDescription": "āļ„āļ§āļēāļĄāļ–āļĩāđˆāļ—āļĩāđˆāđ‚āļŦāļ™āļ”āļŠāđˆāļ‡āļŠāļ–āļīāļ•āļī (āļ§āļīāļ™āļēāļ—āļĩ)", "city": "āđ€āļĄāļ·āļ­āļ‡",
"reset": "āļĢāļĩāđ€āļ‹āđ‡āļ•",
"save": "āļšāļąāļ™āļ—āļķāļ",
"saveSuccess": "āļšāļąāļ™āļ—āļķāļāļŠāļģāđ€āļĢāđ‡āļˆ",
"startTime": "āđ€āļ§āļĨāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™",
"timeSlot": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļē",
"title": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ‚āļŦāļ™āļ”"
},
"confirm": "āļĒāļ·āļ™āļĒāļąāļ™", "confirm": "āļĒāļ·āļ™āļĒāļąāļ™",
"confirmDeleteDesc": "āļāļēāļĢāļāļĢāļ°āļ—āļģāļ™āļĩāđ‰āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĒāđ‰āļ­āļ™āļāļĨāļąāļšāđ„āļ”āđ‰", "confirmDeleteDesc": "āļāļēāļĢāļāļĢāļ°āļ—āļģāļ™āļĩāđ‰āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļĒāđ‰āļ­āļ™āļāļĨāļąāļšāđ„āļ”āđ‰",
"confirmDeleteTitle": "āļĨāļšāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ™āļĩāđ‰āļŦāļĢāļ·āļ­āđ„āļĄāđˆ?", "confirmDeleteTitle": "āļĨāļšāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ™āļĩāđ‰āļŦāļĢāļ·āļ­āđ„āļĄāđˆ?",
"congestion_controller": "āļ•āļąāļ§āļ„āļ§āļšāļ„āļļāļĄāļ„āļ§āļēāļĄāđāļ­āļ­āļąāļ”", "congestion_controller": "āļ•āļąāļ§āļ„āļ§āļšāļ„āļļāļĄāļ„āļ§āļēāļĄāđāļ­āļ­āļąāļ”",
"connect": "āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­",
"copied": "āļ„āļąāļ”āļĨāļ­āļāđāļĨāđ‰āļ§", "copied": "āļ„āļąāļ”āļĨāļ­āļāđāļĨāđ‰āļ§",
"copy": "āļ„āļąāļ”āļĨāļ­āļ", "copy": "āļ„āļąāļ”āļĨāļ­āļ",
"country": "āļ›āļĢāļ°āđ€āļ—āļĻ", "country": "āļ›āļĢāļ°āđ€āļ—āļĻ",
@@ -49,28 +36,46 @@
"drawerEditTitle": "āđāļāđ‰āđ„āļ‚āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", "drawerEditTitle": "āđāļāđ‰āđ„āļ‚āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"edit": "āđāļāđ‰āđ„āļ‚", "edit": "āđāļāđ‰āđ„āļ‚",
"enabled": "āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™", "enabled": "āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™",
"encryption_method": "āļ§āļīāļ˜āļĩāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠ", "encryption": "āļ§āļīāļ˜āļĩāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠ",
"encryption_client_padding": "āļāļēāļĢāđ€āļ•āļīāļĄāļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āļĨāļđāļāļ„āđ‰āļē",
"encryption_mode": "āđ‚āļŦāļĄāļ”",
"encryption_password": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™",
"encryption_password_placeholder": "āļ›āļĨāđˆāļ­āļĒāļ§āđˆāļēāļ‡āđ€āļžāļ·āđˆāļ­āļŠāļĢāđ‰āļēāļ‡āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ”āđ‰āļ§āļĒāļ•āļ™āđ€āļ­āļ‡āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļŦāļĨāļąāļ‡āļ„āļ§āļ­āļ™āļ•āļąāļĄ",
"encryption_private_key": "āļāļļāļāđāļˆāļŠāđˆāļ§āļ™āļ•āļąāļ§",
"encryption_private_key_placeholder": "āļ›āļĨāđˆāļ­āļĒāļ§āđˆāļēāļ‡āđ€āļžāļ·āđˆāļ­āļŠāļĢāđ‰āļēāļ‡āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ”āđ‰āļ§āļĒāļ•āļ™āđ€āļ­āļ‡āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļŦāļĨāļąāļ‡āļ„āļ§āļ­āļ™āļ•āļąāļĄ",
"encryption_rtt": "RTT",
"encryption_server_padding": "āļāļēāļĢāđ€āļ•āļīāļĄāļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ­āļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"encryption_ticket": "āđ€āļ§āļĨāļēāļ•āļąāđ‹āļ§",
"expireTime": "āđ€āļ§āļĨāļēāļŦāļĄāļ”āļ­āļēāļĒāļļ", "expireTime": "āđ€āļ§āļĨāļēāļŦāļĄāļ”āļ­āļēāļĒāļļ",
"expired": "āļŦāļĄāļ”āļ­āļēāļĒāļļ", "expired": "āļŦāļĄāļ”āļ­āļēāļĒāļļ",
"extra": "āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļēāļžāļīāđ€āļĻāļĐ",
"flow": "āļāļēāļĢāđ„āļŦāļĨ", "flow": "āļāļēāļĢāđ„āļŦāļĨ",
"generate_quantum_resistant_key": "āļŠāļĢāđ‰āļēāļ‡āļ„āļĩāļĒāđŒāļ•āđ‰āļēāļ™āļ—āļēāļ™āļ„āļ§āļ­āļ™āļ•āļąāļĄ",
"generate_standard_encryption_key": "āļŠāļĢāđ‰āļēāļ‡āļ„āļĩāļĒāđŒāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļĄāļēāļ•āļĢāļāļēāļ™",
"hop_interval": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļāļĢāļ°āđ‚āļ”āļ”", "hop_interval": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļāļĢāļ°āđ‚āļ”āļ”",
"hop_ports": "āļžāļ­āļĢāđŒāļ•āļāļēāļĢāļāļĢāļ°āđ‚āļ”āļ”", "hop_ports": "āļžāļ­āļĢāđŒāļ•āļāļēāļĢāļāļĢāļ°āđ‚āļ”āļ”",
"hop_ports_placeholder": "āđ€āļŠāđˆāļ™ 1-65535", "hop_ports_placeholder": "āđ€āļŠāđˆāļ™ 1-65535",
"host": "āđ‚āļŪāļŠāļ•āđŒ", "host": "āđ‚āļŪāļŠāļ•āđŒ",
"id": "ID", "id": "ID",
"installCommand": "āļ„āļģāļŠāļąāđˆāļ‡āļ•āļīāļ”āļ•āļąāđ‰āļ‡",
"ipAddresses": "āļ—āļĩāđˆāļ­āļĒāļđāđˆ IP", "ipAddresses": "āļ—āļĩāđˆāļ­āļĒāļđāđˆ IP",
"memory": "āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģ", "memory": "āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģ",
"migrate": "āļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ", "migrate": "āļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ",
"migrateFailed": "āļāļēāļĢāļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨāļĨāđ‰āļĄāđ€āļŦāļĨāļ§", "migrateFailed": "āļāļēāļĢāļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨāļĨāđ‰āļĄāđ€āļŦāļĨāļ§",
"migrated": "āļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļģāđ€āļĢāđ‡āļˆ", "migrated": "āļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļģāđ€āļĢāđ‡āļˆ",
"migrating": "āļāļģāļĨāļąāļ‡āļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ...", "migrating": "āļāļģāļĨāļąāļ‡āļĒāđ‰āļēāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ...",
"mode": "āđ‚āļŦāļĄāļ”",
"multiplex": "āļŦāļĨāļēāļĒāļŠāđˆāļ­āļ‡", "multiplex": "āļŦāļĨāļēāļĒāļŠāđˆāļ­āļ‡",
"name": "āļŠāļ·āđˆāļ­", "name": "āļŠāļ·āđˆāļ­",
"noData": "āđ„āļĄāđˆāļĄāļĩāļ‚āđ‰āļ­āļĄāļđāļĨ", "noData": "āđ„āļĄāđˆāļĄāļĩāļ‚āđ‰āļ­āļĄāļđāļĨ",
"notAvailable": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđƒāļŠāđ‰āđ„āļ”āđ‰", "notAvailable": "āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđƒāļŠāđ‰āđ„āļ”āđ‰",
"obfs": "āļāļēāļĢāļ—āļģāđƒāļŦāđ‰āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļ­āđˆāļēāļ™āđ„āļ”āđ‰",
"obfs_host": "āđ‚āļŪāļŠāļ•āđŒāļāļēāļĢāļ—āļģāđƒāļŦāđ‰āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļ­āđˆāļēāļ™āđ„āļ”āđ‰",
"obfs_password": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļāļēāļĢāļ›āļāļ›āļīāļ”", "obfs_password": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļāļēāļĢāļ›āļāļ›āļīāļ”",
"obfs_password_placeholder": "āļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļāļēāļĢāļ›āļāļ›āļīāļ”", "obfs_password_placeholder": "āļāļĢāļ­āļāļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļāļēāļĢāļ›āļāļ›āļīāļ”",
"obfs_path": "āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āļāļēāļĢāļ—āļģāđƒāļŦāđ‰āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļ­āđˆāļēāļ™āđ„āļ”āđ‰",
"offline": "āļ­āļ­āļŸāđ„āļĨāļ™āđŒ", "offline": "āļ­āļ­āļŸāđ„āļĨāļ™āđŒ",
"oneClickInstall": "āļ•āļīāļ”āļ•āļąāđ‰āļ‡āļ”āđ‰āļ§āļĒāļ„āļĨāļīāļāđ€āļ”āļĩāļĒāļ§",
"online": "āļ­āļ­āļ™āđ„āļĨāļ™āđŒ", "online": "āļ­āļ­āļ™āđ„āļĨāļ™āđŒ",
"onlineUsers": "āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™āļ­āļ­āļ™āđ„āļĨāļ™āđŒ", "onlineUsers": "āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™āļ­āļ­āļ™āđ„āļĨāļ™āđŒ",
"padding_scheme": "āļĢāļđāļ›āđāļšāļšāļāļēāļĢāđ€āļ•āļīāļĄ", "padding_scheme": "āļĢāļđāļ›āđāļšāļšāļāļēāļĢāđ€āļ•āļīāļĄ",
@@ -78,8 +83,6 @@
"pageTitle": "āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", "pageTitle": "āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"path": "āđ€āļŠāđ‰āļ™āļ—āļēāļ‡", "path": "āđ€āļŠāđ‰āļ™āļ—āļēāļ‡",
"please_select": "āļāļĢāļļāļ“āļēāđ€āļĨāļ·āļ­āļ", "please_select": "āļāļĢāļļāļ“āļēāđ€āļĨāļ·āļ­āļ",
"plugin": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļ›āļĨāļąāđŠāļāļ­āļīāļ™",
"plugin_opts": "āļ•āļąāļ§āđ€āļĨāļ·āļ­āļāļ›āļĨāļąāđŠāļāļ­āļīāļ™",
"port": "āļžāļ­āļĢāđŒāļ•", "port": "āļžāļ­āļĢāđŒāļ•",
"protocol_configurations": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨ", "protocol_configurations": "āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨ",
"protocol_configurations_desc": "āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āđāļĨāļ°āļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ›āļĢāļ°āđ€āļ āļ—āđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨāļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢ", "protocol_configurations_desc": "āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āđāļĨāļ°āļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ›āļĢāļ°āđ€āļ āļ—āđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨāļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢ",
@@ -101,8 +104,54 @@
"security_short_id_placeholder": "āļŠāļ•āļĢāļīāļ‡āļŪāļĩāļ‹ (āļŠāļđāļ‡āļŠāļļāļ” 16 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ)", "security_short_id_placeholder": "āļŠāļ•āļĢāļīāļ‡āļŪāļĩāļ‹ (āļŠāļđāļ‡āļŠāļļāļ” 16 āļ•āļąāļ§āļ­āļąāļāļĐāļĢ)",
"security_sni": "SNI", "security_sni": "SNI",
"select_encryption_method": "āđ€āļĨāļ·āļ­āļāļ§āļīāļ˜āļĩāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠ", "select_encryption_method": "āđ€āļĨāļ·āļ­āļāļ§āļīāļ˜āļĩāļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠ",
"server_config": {
"description": "āļˆāļąāļ”āļāļēāļĢāļāļļāļāđāļˆāļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢāļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”, āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāđƒāļ™āļāļēāļĢāļ”āļķāļ‡/āļŠāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ.",
"dynamic_multiplier": "āļ•āļąāļ§āļ„āļđāļ“āđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļ",
"dynamic_multiplier_desc": "āļāļģāļŦāļ™āļ”āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāđāļĨāļ°āļ•āļąāļ§āļ„āļđāļ“āđ€āļžāļ·āđˆāļ­āļ›āļĢāļąāļšāļāļēāļĢāļ„āļģāļ™āļ§āļ“āļāļēāļĢāļˆāļĢāļēāļˆāļĢ.",
"fields": {
"block_rules_placeholder": "āļāļŽāđ‚āļ”āđ€āļĄāļ™āļŦāļ™āļķāđˆāļ‡āļāļŽāļ•āđˆāļ­āļŦāļ™āļķāđˆāļ‡āļšāļĢāļĢāļ—āļąāļ”, āļĢāļ­āļ‡āļĢāļąāļš:\nkeyword:google (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ„āļģāļŠāļģāļ„āļąāļ)\nsuffix:google.com (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļŠāđˆāļ§āļ™āļ—āđ‰āļēāļĒ)\nregex:.*\\.example\\.com$ (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆ regex)\nexample.com (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ—āļĩāđˆāđāļ™āđˆāļ™āļ­āļ™)",
"communication_key": "āļāļļāļāđāļˆāļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢ",
"communication_key_desc": "āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒāļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”.",
"communication_key_placeholder": "āļāļĢāļļāļ“āļēāđƒāļŠāđˆ",
"dns_config": "āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļē DNS",
"dns_domains_placeholder": "āļāļŽāđ‚āļ”āđ€āļĄāļ™āļŦāļ™āļķāđˆāļ‡āļāļŽāļ•āđˆāļ­āļŦāļ™āļķāđˆāļ‡āļšāļĢāļĢāļ—āļąāļ”, āļĢāļ­āļ‡āļĢāļąāļš:\nkeyword:google (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ„āļģāļŠāļģāļ„āļąāļ)\nsuffix:google.com (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļŠāđˆāļ§āļ™āļ—āđ‰āļēāļĒ)\nregex:.*\\.example\\.com$ (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆ regex)\nexample.com (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ—āļĩāđˆāđāļ™āđˆāļ™āļ­āļ™)",
"dns_proto_placeholder": "āđ€āļĨāļ·āļ­āļāļ›āļĢāļ°āđ€āļ āļ—",
"end_time": "āđ€āļ§āļĨāļēāļŠāļīāđ‰āļ™āļŠāļļāļ”",
"ip_strategy": "āļāļĨāļĒāļļāļ—āļ˜āđŒ IP",
"ip_strategy_desc": "āđ€āļĨāļ·āļ­āļāļ„āļ§āļēāļĄāļŠāļ­āļšāđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™ IP āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒ",
"ip_strategy_ipv4": "āļŠāļ­āļš IPv4",
"ip_strategy_ipv6": "āļŠāļ­āļš IPv6",
"ip_strategy_placeholder": "āđ€āļĨāļ·āļ­āļāļāļĨāļĒāļļāļ—āļ˜āđŒ IP",
"multiplier": "āļ•āļąāļ§āļ„āļđāļ“",
"node_pull_interval": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļ”āļķāļ‡āļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”",
"node_pull_interval_desc": "āļ„āļ§āļēāļĄāļ–āļĩāđˆāļ—āļĩāđˆāđ‚āļŦāļ™āļ”āļ”āļķāļ‡āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļē (āļ§āļīāļ™āļēāļ—āļĩ).",
"node_push_interval": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļāļēāļĢāļŠāđˆāļ‡āļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”",
"node_push_interval_desc": "āļ„āļ§āļēāļĄāļ–āļĩāđˆāļ—āļĩāđˆāđ‚āļŦāļ™āļ”āļŠāđˆāļ‡āļŠāļ–āļīāļ•āļī (āļ§āļīāļ™āļēāļ—āļĩ).",
"outbound_address_placeholder": "āļ—āļĩāđˆāļ­āļĒāļđāđˆāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"outbound_name_placeholder": "āļŠāļ·āđˆāļ­āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļē",
"outbound_password_placeholder": "āļĢāļŦāļąāļŠāļœāđˆāļēāļ™ (āđ„āļĄāđˆāļšāļąāļ‡āļ„āļąāļš)",
"outbound_port_placeholder": "āļŦāļĄāļēāļĒāđ€āļĨāļ‚āļžāļ­āļĢāđŒāļ•",
"outbound_protocol_placeholder": "āđ€āļĨāļ·āļ­āļāđ‚āļ›āļĢāđ‚āļ•āļ„āļ­āļĨ",
"outbound_rules_placeholder": "āļāļŽāļŦāļ™āļķāđˆāļ‡āļāļŽāļ•āđˆāļ­āļŦāļ™āļķāđˆāļ‡āļšāļĢāļĢāļ—āļąāļ”, āļĢāļ­āļ‡āļĢāļąāļš:\nkeyword:google (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ„āļģāļŠāļģāļ„āļąāļ)\nsuffix:google.com (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļŠāđˆāļ§āļ™āļ—āđ‰āļēāļĒ)\nregex:.*\\.example\\.com$ (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆ regex)\nexample.com (āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ—āļĩāđˆāđāļ™āđˆāļ™āļ­āļ™)\nāđ€āļ§āđ‰āļ™āļ§āđˆāļēāļ‡āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļāļģāļŦāļ™āļ”āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™",
"reset": "āļĢāļĩāđ€āļ‹āđ‡āļ•",
"save": "āļšāļąāļ™āļ—āļķāļ",
"start_time": "āđ€āļ§āļĨāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™",
"time_slot": "āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļē",
"traffic_report_threshold": "āđ€āļāļ“āļ‘āđŒāļĢāļēāļĒāļ‡āļēāļ™āļāļēāļĢāļˆāļĢāļēāļˆāļĢ",
"traffic_report_threshold_desc": "āļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļāļ“āļ‘āđŒāļ‚āļąāđ‰āļ™āļ•āđˆāļģāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĢāļēāļĒāļ‡āļēāļ™āļāļēāļĢāļˆāļĢāļēāļˆāļĢ. āļāļēāļĢāļˆāļĢāļēāļˆāļĢāļˆāļ°āļ–āļđāļāļšāļąāļ™āļ—āļķāļāđ€āļĄāļ·āđˆāļ­āđ€āļāļīāļ™āļ„āđˆāļēāļ—āļĩāđˆāļ•āļąāđ‰āļ‡āđ„āļ§āđ‰. āļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļ›āđ‡āļ™ 0 āļŦāļĢāļ·āļ­āđ€āļ§āđ‰āļ™āļ§āđˆāļēāļ‡āđ€āļžāļ·āđˆāļ­āļĢāļēāļĒāļ‡āļēāļ™āļāļēāļĢāļˆāļĢāļēāļˆāļĢāļ—āļąāđ‰āļ‡āļŦāļĄāļ”."
},
"saveSuccess": "āļšāļąāļ™āļ—āļķāļāļŠāļģāđ€āļĢāđ‡āļˆ",
"tabs": {
"basic": "āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļēāļžāļ·āđ‰āļ™āļāļēāļ™",
"block": "āļāļŽāļāļēāļĢāļšāļĨāđ‡āļ­āļ",
"dns": "āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļē DNS",
"outbound": "āļāļŽāļāļēāļĢāļŠāđˆāļ‡āļ­āļ­āļ"
},
"title": "āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ‚āļ­āļ‡āđ‚āļŦāļ™āļ”"
},
"server_key": "āļ„āļĩāļĒāđŒāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ", "server_key": "āļ„āļĩāļĒāđŒāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ",
"service_name": "āļŠāļ·āđˆāļ­āļšāļĢāļīāļāļēāļĢ", "service_name": "āļŠāļ·āđˆāļ­āļšāļĢāļīāļāļēāļĢ",
"sorted_success": "āđ€āļĢāļĩāļĒāļ‡āļĨāļģāļ”āļąāļšāđ€āļĢāļĩāļĒāļšāļĢāđ‰āļ­āļĒāđāļĨāđ‰āļ§",
"status": "āļŠāļ–āļēāļ™āļ°", "status": "āļŠāļ–āļēāļ™āļ°",
"subscribeId": "ID āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ", "subscribeId": "ID āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ",
"subscription": "āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ", "subscription": "āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ",
+2
View File
@@ -17,6 +17,8 @@
"confirm": "āļĒāļ·āļ™āļĒāļąāļ™", "confirm": "āļĒāļ·āļ™āļĒāļąāļ™",
"confirmDelete": "āļ„āļļāļ“āđāļ™āđˆāđƒāļˆāļŦāļĢāļ·āļ­āļ§āđˆāļēāļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļš?", "confirmDelete": "āļ„āļļāļ“āđāļ™āđˆāđƒāļˆāļŦāļĢāļ·āļ­āļ§āđˆāļēāļ•āđ‰āļ­āļ‡āļāļēāļĢāļĨāļš?",
"confirmOffline": "āļĒāļ·āļ™āļĒāļąāļ™āļ­āļ­āļŸāđ„āļĨāļ™āđŒ", "confirmOffline": "āļĒāļ·āļ™āļĒāļąāļ™āļ­āļ­āļŸāđ„āļĨāļ™āđŒ",
"copySubscription": "āļ„āļąāļ”āļĨāļ­āļāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ",
"copySuccess": "āļ„āļąāļ”āļĨāļ­āļāļŠāļģāđ€āļĢāđ‡āļˆ",
"create": "āļŠāļĢāđ‰āļēāļ‡", "create": "āļŠāļĢāđ‰āļēāļ‡",
"createSubscription": "āļŠāļĢāđ‰āļēāļ‡āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ", "createSubscription": "āļŠāļĢāđ‰āļēāļ‡āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ",
"createSuccess": "āļŠāļĢāđ‰āļēāļ‡āļŠāļģāđ€āļĢāđ‡āļˆ", "createSuccess": "āļŠāļĢāđ‰āļēāļ‡āļŠāļģāđ€āļĢāđ‡āļˆ",

Some files were not shown because too many files have changed in this diff Show More