diff --git a/fe-app-podkop/src/clash/methods/createBaseApiRequest.ts b/fe-app-podkop/src/clash/methods/createBaseApiRequest.ts index b63516a..601a433 100644 --- a/fe-app-podkop/src/clash/methods/createBaseApiRequest.ts +++ b/fe-app-podkop/src/clash/methods/createBaseApiRequest.ts @@ -9,7 +9,7 @@ export async function createBaseApiRequest( if (!response.ok) { return { success: false as const, - message: `HTTP error ${response.status}: ${response.statusText}`, + message: `${_('HTTP error')} ${response.status}: ${response.statusText}`, }; } @@ -22,7 +22,7 @@ export async function createBaseApiRequest( } catch (e) { return { success: false as const, - message: e instanceof Error ? e.message : 'Unknown error', + message: e instanceof Error ? e.message : _('Unknown error'), }; } } diff --git a/fe-app-podkop/src/helpers/copyToClipboard.ts b/fe-app-podkop/src/helpers/copyToClipboard.ts deleted file mode 100644 index 154f4c5..0000000 --- a/fe-app-podkop/src/helpers/copyToClipboard.ts +++ /dev/null @@ -1,29 +0,0 @@ -interface CopyToClipboardResponse { - success: boolean; - message: string; -} - -export function copyToClipboard(text: string): CopyToClipboardResponse { - const textarea = document.createElement('textarea'); - textarea.value = text; - document.body.appendChild(textarea); - textarea.select(); - - try { - document.execCommand('copy'); - - return { - success: true, - message: 'Copied!', - }; - } catch (err) { - const error = err as Error; - - return { - success: false, - message: `Failed to copy: ${error.message}`, - }; - } finally { - document.body.removeChild(textarea); - } -} diff --git a/fe-app-podkop/src/helpers/index.ts b/fe-app-podkop/src/helpers/index.ts index a38f0b5..242f2e7 100644 --- a/fe-app-podkop/src/helpers/index.ts +++ b/fe-app-podkop/src/helpers/index.ts @@ -3,7 +3,6 @@ export * from './parseValueList'; export * from './injectGlobalStyles'; export * from './withTimeout'; export * from './executeShellCommand'; -export * from './copyToClipboard'; export * from './maskIP'; export * from './getProxyUrlName'; export * from './onMount'; diff --git a/fe-app-podkop/src/helpers/withTimeout.ts b/fe-app-podkop/src/helpers/withTimeout.ts index 4475a55..f06108a 100644 --- a/fe-app-podkop/src/helpers/withTimeout.ts +++ b/fe-app-podkop/src/helpers/withTimeout.ts @@ -2,7 +2,7 @@ export async function withTimeout( promise: Promise, timeoutMs: number, operationName: string, - timeoutMessage = 'Operation timed out', + timeoutMessage = _('Operation timed out'), ): Promise { let timeoutId; const start = performance.now(); diff --git a/fe-app-podkop/src/luci.d.ts b/fe-app-podkop/src/luci.d.ts index 9b6762e..01ff833 100644 --- a/fe-app-podkop/src/luci.d.ts +++ b/fe-app-podkop/src/luci.d.ts @@ -33,6 +33,8 @@ declare global { load: (packages: string | string[]) => Promise; sections: (conf: string, type?: string, cb?: () => void) => Promise; }; + + const _ = (_key: string) => string; } export {}; diff --git a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts index 931a4f5..c101926 100644 --- a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts +++ b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts @@ -106,7 +106,7 @@ export async function getDashboardSections(): Promise part.length > 63); if (atLeastOneInvalidPart) { - return { valid: false, message: 'Invalid domain address' }; + return { valid: false, message: _('Invalid domain address') }; } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } diff --git a/fe-app-podkop/src/validators/validateIp.ts b/fe-app-podkop/src/validators/validateIp.ts index 88ab1f0..78c154d 100644 --- a/fe-app-podkop/src/validators/validateIp.ts +++ b/fe-app-podkop/src/validators/validateIp.ts @@ -5,8 +5,8 @@ export function validateIPV4(ip: string): ValidationResult { /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/; if (ipRegex.test(ip)) { - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } - return { valid: false, message: 'Invalid IP address' }; + return { valid: false, message: _('Invalid IP address') }; } diff --git a/fe-app-podkop/src/validators/validateOutboundJson.ts b/fe-app-podkop/src/validators/validateOutboundJson.ts index c768543..822662b 100644 --- a/fe-app-podkop/src/validators/validateOutboundJson.ts +++ b/fe-app-podkop/src/validators/validateOutboundJson.ts @@ -8,13 +8,14 @@ export function validateOutboundJson(value: string): ValidationResult { if (!parsed.type || !parsed.server || !parsed.server_port) { return { valid: false, - message: + message: _( 'Outbound JSON must contain at least "type", "server" and "server_port" fields', + ), }; } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } catch { - return { valid: false, message: 'Invalid JSON format' }; + return { valid: false, message: _('Invalid JSON format') }; } } diff --git a/fe-app-podkop/src/validators/validatePath.ts b/fe-app-podkop/src/validators/validatePath.ts index 9da07ba..045601e 100644 --- a/fe-app-podkop/src/validators/validatePath.ts +++ b/fe-app-podkop/src/validators/validatePath.ts @@ -4,7 +4,7 @@ export function validatePath(value: string): ValidationResult { if (!value) { return { valid: false, - message: 'Path cannot be empty', + message: _('Path cannot be empty'), }; } @@ -19,7 +19,8 @@ export function validatePath(value: string): ValidationResult { return { valid: false, - message: + message: _( 'Invalid path format. Path must start with "/" and contain valid characters', + ), }; } diff --git a/fe-app-podkop/src/validators/validateProxyUrl.ts b/fe-app-podkop/src/validators/validateProxyUrl.ts index b9ef593..ec3fe47 100644 --- a/fe-app-podkop/src/validators/validateProxyUrl.ts +++ b/fe-app-podkop/src/validators/validateProxyUrl.ts @@ -19,6 +19,6 @@ export function validateProxyUrl(url: string): ValidationResult { return { valid: false, - message: 'URL must start with vless:// or ss:// or trojan://', + message: _('URL must start with vless:// or ss:// or trojan://'), }; } diff --git a/fe-app-podkop/src/validators/validateShadowsocksUrl.ts b/fe-app-podkop/src/validators/validateShadowsocksUrl.ts index 68081a7..29bd193 100644 --- a/fe-app-podkop/src/validators/validateShadowsocksUrl.ts +++ b/fe-app-podkop/src/validators/validateShadowsocksUrl.ts @@ -5,7 +5,7 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (!url.startsWith('ss://')) { return { valid: false, - message: 'Invalid Shadowsocks URL: must start with ss://', + message: _('Invalid Shadowsocks URL: must start with ss://'), }; } @@ -13,7 +13,7 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (!url || /\s/.test(url)) { return { valid: false, - message: 'Invalid Shadowsocks URL: must not contain spaces', + message: _('Invalid Shadowsocks URL: must not contain spaces'), }; } @@ -24,7 +24,7 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (!encryptedPart) { return { valid: false, - message: 'Invalid Shadowsocks URL: missing credentials', + message: _('Invalid Shadowsocks URL: missing credentials'), }; } @@ -34,16 +34,18 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (!decoded.includes(':')) { return { valid: false, - message: + message: _( 'Invalid Shadowsocks URL: decoded credentials must contain method:password', + ), }; } } catch (_e) { if (!encryptedPart.includes(':') && !encryptedPart.includes('-')) { return { valid: false, - message: + message: _( 'Invalid Shadowsocks URL: missing method and password separator ":"', + ), }; } } @@ -53,7 +55,7 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (!serverPart) { return { valid: false, - message: 'Invalid Shadowsocks URL: missing server address', + message: _('Invalid Shadowsocks URL: missing server address'), }; } @@ -62,14 +64,17 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (!server) { return { valid: false, - message: 'Invalid Shadowsocks URL: missing server', + message: _('Invalid Shadowsocks URL: missing server'), }; } const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null; if (!port) { - return { valid: false, message: 'Invalid Shadowsocks URL: missing port' }; + return { + valid: false, + message: _('Invalid Shadowsocks URL: missing port'), + }; } const portNum = parseInt(port, 10); @@ -77,12 +82,15 @@ export function validateShadowsocksUrl(url: string): ValidationResult { if (isNaN(portNum) || portNum < 1 || portNum > 65535) { return { valid: false, - message: 'Invalid port number. Must be between 1 and 65535', + message: _('Invalid port number. Must be between 1 and 65535'), }; } } catch (_e) { - return { valid: false, message: 'Invalid Shadowsocks URL: parsing failed' }; + return { + valid: false, + message: _('Invalid Shadowsocks URL: parsing failed'), + }; } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } diff --git a/fe-app-podkop/src/validators/validateSubnet.ts b/fe-app-podkop/src/validators/validateSubnet.ts index 6f3e2b9..e7974a2 100644 --- a/fe-app-podkop/src/validators/validateSubnet.ts +++ b/fe-app-podkop/src/validators/validateSubnet.ts @@ -8,14 +8,14 @@ export function validateSubnet(value: string): ValidationResult { if (!subnetRegex.test(value)) { return { valid: false, - message: 'Invalid format. Use X.X.X.X or X.X.X.X/Y', + message: _('Invalid format. Use X.X.X.X or X.X.X.X/Y'), }; } const [ip, cidr] = value.split('/'); if (ip === '0.0.0.0') { - return { valid: false, message: 'IP address 0.0.0.0 is not allowed' }; + return { valid: false, message: _('IP address 0.0.0.0 is not allowed') }; } const ipCheck = validateIPV4(ip); @@ -30,10 +30,10 @@ export function validateSubnet(value: string): ValidationResult { if (cidrNum < 0 || cidrNum > 32) { return { valid: false, - message: 'CIDR must be between 0 and 32', + message: _('CIDR must be between 0 and 32'), }; } } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } diff --git a/fe-app-podkop/src/validators/validateTrojanUrl.ts b/fe-app-podkop/src/validators/validateTrojanUrl.ts index f79536c..8e9e627 100644 --- a/fe-app-podkop/src/validators/validateTrojanUrl.ts +++ b/fe-app-podkop/src/validators/validateTrojanUrl.ts @@ -5,14 +5,14 @@ export function validateTrojanUrl(url: string): ValidationResult { if (!url.startsWith('trojan://')) { return { valid: false, - message: 'Invalid Trojan URL: must start with trojan://', + message: _('Invalid Trojan URL: must start with trojan://'), }; } if (!url || /\s/.test(url)) { return { valid: false, - message: 'Invalid Trojan URL: must not contain spaces', + message: _('Invalid Trojan URL: must not contain spaces'), }; } @@ -22,12 +22,14 @@ export function validateTrojanUrl(url: string): ValidationResult { if (!parsedUrl.username || !parsedUrl.hostname || !parsedUrl.port) { return { valid: false, - message: 'Invalid Trojan URL: must contain username, hostname and port', + message: _( + 'Invalid Trojan URL: must contain username, hostname and port', + ), }; } } catch (_e) { - return { valid: false, message: 'Invalid Trojan URL: parsing failed' }; + return { valid: false, message: _('Invalid Trojan URL: parsing failed') }; } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } diff --git a/fe-app-podkop/src/validators/validateUrl.ts b/fe-app-podkop/src/validators/validateUrl.ts index 5b6d522..dd2c88e 100644 --- a/fe-app-podkop/src/validators/validateUrl.ts +++ b/fe-app-podkop/src/validators/validateUrl.ts @@ -10,11 +10,11 @@ export function validateUrl( if (!protocols.includes(parsedUrl.protocol)) { return { valid: false, - message: `URL must use one of the following protocols: ${protocols.join(', ')}`, + message: `${_('URL must use one of the following protocols:')} ${protocols.join(', ')}`, }; } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } catch (_e) { - return { valid: false, message: 'Invalid URL format' }; + return { valid: false, message: _('Invalid URL format') }; } } diff --git a/fe-app-podkop/src/validators/validateVlessUrl.ts b/fe-app-podkop/src/validators/validateVlessUrl.ts index 45ea65a..73746e4 100644 --- a/fe-app-podkop/src/validators/validateVlessUrl.ts +++ b/fe-app-podkop/src/validators/validateVlessUrl.ts @@ -7,27 +7,27 @@ export function validateVlessUrl(url: string): ValidationResult { if (!url || /\s/.test(url)) { return { valid: false, - message: 'Invalid VLESS URL: must not contain spaces', + message: _('Invalid VLESS URL: must not contain spaces'), }; } if (parsedUrl.protocol !== 'vless:') { return { valid: false, - message: 'Invalid VLESS URL: must start with vless://', + message: _('Invalid VLESS URL: must start with vless://'), }; } if (!parsedUrl.username) { - return { valid: false, message: 'Invalid VLESS URL: missing UUID' }; + return { valid: false, message: _('Invalid VLESS URL: missing UUID') }; } if (!parsedUrl.hostname) { - return { valid: false, message: 'Invalid VLESS URL: missing server' }; + return { valid: false, message: _('Invalid VLESS URL: missing server') }; } if (!parsedUrl.port) { - return { valid: false, message: 'Invalid VLESS URL: missing port' }; + return { valid: false, message: _('Invalid VLESS URL: missing port') }; } if ( @@ -37,15 +37,16 @@ export function validateVlessUrl(url: string): ValidationResult { ) { return { valid: false, - message: + message: _( 'Invalid VLESS URL: invalid port number. Must be between 1 and 65535', + ), }; } if (!parsedUrl.search) { return { valid: false, - message: 'Invalid VLESS URL: missing query parameters', + message: _('Invalid VLESS URL: missing query parameters'), }; } @@ -67,8 +68,9 @@ export function validateVlessUrl(url: string): ValidationResult { if (!type || !validTypes.includes(type)) { return { valid: false, - message: + message: _( 'Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws', + ), }; } @@ -78,8 +80,9 @@ export function validateVlessUrl(url: string): ValidationResult { if (!security || !validSecurities.includes(security)) { return { valid: false, - message: + message: _( 'Invalid VLESS URL: security must be one of tls, reality, none', + ), }; } @@ -87,21 +90,23 @@ export function validateVlessUrl(url: string): ValidationResult { if (!params.get('pbk')) { return { valid: false, - message: + message: _( 'Invalid VLESS URL: missing pbk parameter for reality security', + ), }; } if (!params.get('fp')) { return { valid: false, - message: + message: _( 'Invalid VLESS URL: missing fp parameter for reality security', + ), }; } } - return { valid: true, message: 'Valid' }; + return { valid: true, message: _('Valid') }; } catch (_e) { - return { valid: false, message: 'Invalid VLESS URL: parsing failed' }; + return { valid: false, message: _('Invalid VLESS URL: parsing failed') }; } } diff --git a/fe-app-podkop/tests/setup/global-mocks.ts b/fe-app-podkop/tests/setup/global-mocks.ts new file mode 100644 index 0000000..8f93270 --- /dev/null +++ b/fe-app-podkop/tests/setup/global-mocks.ts @@ -0,0 +1,2 @@ +// tests/setup/global-mocks.ts +globalThis._ = (key: string) => key; diff --git a/fe-app-podkop/vitest.config.js b/fe-app-podkop/vitest.config.js index adbf725..a8eb868 100644 --- a/fe-app-podkop/vitest.config.js +++ b/fe-app-podkop/vitest.config.js @@ -4,5 +4,6 @@ export default defineConfig({ test: { globals: true, environment: 'node', + setupFiles: ['./tests/setup/global-mocks.ts'], }, }); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js index 2686fb0..d317048 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js @@ -88,7 +88,7 @@ function createAdditionalSection(mainSection) { return true; } - return _(validation.message); + return validation.message; }; o = mainSection.taboption( @@ -113,7 +113,7 @@ function createAdditionalSection(mainSection) { return true; } - return _(validation.message); + return validation.message; }; o = mainSection.taboption( @@ -342,7 +342,7 @@ function createAdditionalSection(mainSection) { return true; } - return _(validation.message); + return validation.message; }; o = mainSection.taboption( diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js index f1a225b..b4f3b17 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js @@ -152,7 +152,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; } catch (e) { return `${_('Invalid URL format:')} ${e?.message}`; } @@ -180,7 +180,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -204,7 +204,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -315,7 +315,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -459,7 +459,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -538,7 +538,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -575,7 +575,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -612,7 +612,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -654,7 +654,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -733,7 +733,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; o = s.taboption( @@ -772,7 +772,7 @@ function createConfigSection(section) { return true; } - return _(validation.message); + return validation.message; }; } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index a8f101b..34cde9e 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -8,40 +8,42 @@ function validateIPV4(ip) { const ipRegex = /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/; if (ipRegex.test(ip)) { - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } - return { valid: false, message: "Invalid IP address" }; + return { valid: false, message: _("Invalid IP address") }; } // src/validators/validateDomain.ts function validateDomain(domain) { const domainRegex = /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/; if (!domainRegex.test(domain)) { - return { valid: false, message: "Invalid domain address" }; + return { valid: false, message: _("Invalid domain address") }; } const hostname = domain.split("/")[0]; const parts = hostname.split("."); const atLeastOneInvalidPart = parts.some((part) => part.length > 63); if (atLeastOneInvalidPart) { - return { valid: false, message: "Invalid domain address" }; + return { valid: false, message: _("Invalid domain address") }; } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } // src/validators/validateDns.ts function validateDNS(value) { if (!value) { - return { valid: false, message: "DNS server address cannot be empty" }; + return { valid: false, message: _("DNS server address cannot be empty") }; } if (validateIPV4(value).valid) { - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } if (validateDomain(value).valid) { - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } return { valid: false, - message: "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" + message: _( + "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" + ) }; } @@ -52,12 +54,12 @@ function validateUrl(url, protocols = ["http:", "https:"]) { if (!protocols.includes(parsedUrl.protocol)) { return { valid: false, - message: `URL must use one of the following protocols: ${protocols.join(", ")}` + message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}` }; } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } catch (_e) { - return { valid: false, message: "Invalid URL format" }; + return { valid: false, message: _("Invalid URL format") }; } } @@ -66,7 +68,7 @@ function validatePath(value) { if (!value) { return { valid: false, - message: "Path cannot be empty" + message: _("Path cannot be empty") }; } const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/; @@ -78,7 +80,9 @@ function validatePath(value) { } return { valid: false, - message: 'Invalid path format. Path must start with "/" and contain valid characters' + message: _( + 'Invalid path format. Path must start with "/" and contain valid characters' + ) }; } @@ -88,12 +92,12 @@ function validateSubnet(value) { if (!subnetRegex.test(value)) { return { valid: false, - message: "Invalid format. Use X.X.X.X or X.X.X.X/Y" + message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y") }; } const [ip, cidr] = value.split("/"); if (ip === "0.0.0.0") { - return { valid: false, message: "IP address 0.0.0.0 is not allowed" }; + return { valid: false, message: _("IP address 0.0.0.0 is not allowed") }; } const ipCheck = validateIPV4(ip); if (!ipCheck.valid) { @@ -104,11 +108,11 @@ function validateSubnet(value) { if (cidrNum < 0 || cidrNum > 32) { return { valid: false, - message: "CIDR must be between 0 and 32" + message: _("CIDR must be between 0 and 32") }; } } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } // src/validators/bulkValidate.ts @@ -125,14 +129,14 @@ function validateShadowsocksUrl(url) { if (!url.startsWith("ss://")) { return { valid: false, - message: "Invalid Shadowsocks URL: must start with ss://" + message: _("Invalid Shadowsocks URL: must start with ss://") }; } try { if (!url || /\s/.test(url)) { return { valid: false, - message: "Invalid Shadowsocks URL: must not contain spaces" + message: _("Invalid Shadowsocks URL: must not contain spaces") }; } const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0]; @@ -140,7 +144,7 @@ function validateShadowsocksUrl(url) { if (!encryptedPart) { return { valid: false, - message: "Invalid Shadowsocks URL: missing credentials" + message: _("Invalid Shadowsocks URL: missing credentials") }; } try { @@ -148,14 +152,18 @@ function validateShadowsocksUrl(url) { if (!decoded.includes(":")) { return { valid: false, - message: "Invalid Shadowsocks URL: decoded credentials must contain method:password" + message: _( + "Invalid Shadowsocks URL: decoded credentials must contain method:password" + ) }; } } catch (_e) { if (!encryptedPart.includes(":") && !encryptedPart.includes("-")) { return { valid: false, - message: 'Invalid Shadowsocks URL: missing method and password separator ":"' + message: _( + 'Invalid Shadowsocks URL: missing method and password separator ":"' + ) }; } } @@ -163,31 +171,37 @@ function validateShadowsocksUrl(url) { if (!serverPart) { return { valid: false, - message: "Invalid Shadowsocks URL: missing server address" + message: _("Invalid Shadowsocks URL: missing server address") }; } const [server, portAndRest] = serverPart.split(":"); if (!server) { return { valid: false, - message: "Invalid Shadowsocks URL: missing server" + message: _("Invalid Shadowsocks URL: missing server") }; } const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null; if (!port) { - return { valid: false, message: "Invalid Shadowsocks URL: missing port" }; + return { + valid: false, + message: _("Invalid Shadowsocks URL: missing port") + }; } const portNum = parseInt(port, 10); if (isNaN(portNum) || portNum < 1 || portNum > 65535) { return { valid: false, - message: "Invalid port number. Must be between 1 and 65535" + message: _("Invalid port number. Must be between 1 and 65535") }; } } catch (_e) { - return { valid: false, message: "Invalid Shadowsocks URL: parsing failed" }; + return { + valid: false, + message: _("Invalid Shadowsocks URL: parsing failed") + }; } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } // src/validators/validateVlessUrl.ts @@ -197,34 +211,36 @@ function validateVlessUrl(url) { if (!url || /\s/.test(url)) { return { valid: false, - message: "Invalid VLESS URL: must not contain spaces" + message: _("Invalid VLESS URL: must not contain spaces") }; } if (parsedUrl.protocol !== "vless:") { return { valid: false, - message: "Invalid VLESS URL: must start with vless://" + message: _("Invalid VLESS URL: must start with vless://") }; } if (!parsedUrl.username) { - return { valid: false, message: "Invalid VLESS URL: missing UUID" }; + return { valid: false, message: _("Invalid VLESS URL: missing UUID") }; } if (!parsedUrl.hostname) { - return { valid: false, message: "Invalid VLESS URL: missing server" }; + return { valid: false, message: _("Invalid VLESS URL: missing server") }; } if (!parsedUrl.port) { - return { valid: false, message: "Invalid VLESS URL: missing port" }; + return { valid: false, message: _("Invalid VLESS URL: missing port") }; } if (isNaN(+parsedUrl.port) || +parsedUrl.port < 1 || +parsedUrl.port > 65535) { return { valid: false, - message: "Invalid VLESS URL: invalid port number. Must be between 1 and 65535" + message: _( + "Invalid VLESS URL: invalid port number. Must be between 1 and 65535" + ) }; } if (!parsedUrl.search) { return { valid: false, - message: "Invalid VLESS URL: missing query parameters" + message: _("Invalid VLESS URL: missing query parameters") }; } const params = new URLSearchParams(parsedUrl.search); @@ -243,7 +259,9 @@ function validateVlessUrl(url) { if (!type || !validTypes.includes(type)) { return { valid: false, - message: "Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws" + message: _( + "Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws" + ) }; } const security = params.get("security"); @@ -251,26 +269,32 @@ function validateVlessUrl(url) { if (!security || !validSecurities.includes(security)) { return { valid: false, - message: "Invalid VLESS URL: security must be one of tls, reality, none" + message: _( + "Invalid VLESS URL: security must be one of tls, reality, none" + ) }; } if (security === "reality") { if (!params.get("pbk")) { return { valid: false, - message: "Invalid VLESS URL: missing pbk parameter for reality security" + message: _( + "Invalid VLESS URL: missing pbk parameter for reality security" + ) }; } if (!params.get("fp")) { return { valid: false, - message: "Invalid VLESS URL: missing fp parameter for reality security" + message: _( + "Invalid VLESS URL: missing fp parameter for reality security" + ) }; } } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } catch (_e) { - return { valid: false, message: "Invalid VLESS URL: parsing failed" }; + return { valid: false, message: _("Invalid VLESS URL: parsing failed") }; } } @@ -281,12 +305,14 @@ function validateOutboundJson(value) { if (!parsed.type || !parsed.server || !parsed.server_port) { return { valid: false, - message: 'Outbound JSON must contain at least "type", "server" and "server_port" fields' + message: _( + 'Outbound JSON must contain at least "type", "server" and "server_port" fields' + ) }; } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } catch { - return { valid: false, message: "Invalid JSON format" }; + return { valid: false, message: _("Invalid JSON format") }; } } @@ -295,13 +321,13 @@ function validateTrojanUrl(url) { if (!url.startsWith("trojan://")) { return { valid: false, - message: "Invalid Trojan URL: must start with trojan://" + message: _("Invalid Trojan URL: must start with trojan://") }; } if (!url || /\s/.test(url)) { return { valid: false, - message: "Invalid Trojan URL: must not contain spaces" + message: _("Invalid Trojan URL: must not contain spaces") }; } try { @@ -309,13 +335,15 @@ function validateTrojanUrl(url) { if (!parsedUrl.username || !parsedUrl.hostname || !parsedUrl.port) { return { valid: false, - message: "Invalid Trojan URL: must contain username, hostname and port" + message: _( + "Invalid Trojan URL: must contain username, hostname and port" + ) }; } } catch (_e) { - return { valid: false, message: "Invalid Trojan URL: parsing failed" }; + return { valid: false, message: _("Invalid Trojan URL: parsing failed") }; } - return { valid: true, message: "Valid" }; + return { valid: true, message: _("Valid") }; } // src/validators/validateProxyUrl.ts @@ -331,7 +359,7 @@ function validateProxyUrl(url) { } return { valid: false, - message: "URL must start with vless:// or ss:// or trojan://" + message: _("URL must start with vless:// or ss:// or trojan://") }; } @@ -537,10 +565,10 @@ function injectGlobalStyles() { } // src/helpers/withTimeout.ts -async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = "Operation timed out") { +async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _("Operation timed out")) { let timeoutId; const start = performance.now(); - const timeoutPromise = new Promise((_, reject) => { + const timeoutPromise = new Promise((_2, reject) => { timeoutId = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); }); try { @@ -680,29 +708,6 @@ async function executeShellCommand({ } } -// src/helpers/copyToClipboard.ts -function copyToClipboard(text) { - const textarea = document.createElement("textarea"); - textarea.value = text; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand("copy"); - return { - success: true, - message: "Copied!" - }; - } catch (err) { - const error = err; - return { - success: false, - message: `Failed to copy: ${error.message}` - }; - } finally { - document.body.removeChild(textarea); - } -} - // src/helpers/maskIP.ts function maskIP(ip = "") { const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; @@ -767,7 +772,7 @@ async function createBaseApiRequest(fetchFn) { if (!response.ok) { return { success: false, - message: `HTTP error ${response.status}: ${response.statusText}` + message: `${_("HTTP error")} ${response.status}: ${response.statusText}` }; } const data = await response.json(); @@ -778,7 +783,7 @@ async function createBaseApiRequest(fetchFn) { } catch (e) { return { success: false, - message: e instanceof Error ? e.message : "Unknown error" + message: e instanceof Error ? e.message : _("Unknown error") }; } } @@ -943,7 +948,7 @@ async function getDashboardSections() { outbounds: [ { code: outbound?.code || "", - displayName: "Fastest", + displayName: _("Fastest"), latency: outbound?.value?.history?.[0]?.delay || 0, type: outbound?.value?.type || "", selected: selector?.value?.now === outbound?.code @@ -1080,7 +1085,7 @@ var TabServiceInstance = TabService.getInstance(); // src/store.ts function jsonStableStringify(obj) { - return JSON.stringify(obj, (_, value) => { + return JSON.stringify(obj, (_2, value) => { if (value && typeof value === "object" && !Array.isArray(value)) { return Object.keys(value).sort().reduce( (acc, key) => { @@ -1214,7 +1219,7 @@ function renderFailedState() { class: "pdk_dashboard-page__outbound-section centered", style: "height: 127px" }, - E("span", {}, "Dashboard currently unavailable") + E("span", {}, _("Dashboard currently unavailable")) ); } function renderLoadingState() { @@ -1318,7 +1323,7 @@ function renderFailedState2() { style: "height: 78px", class: "pdk_dashboard-page__widgets-section__item centered" }, - "Currently unavailable" + _("Currently unavailable") ); } function renderLoadingState2() { @@ -1713,10 +1718,10 @@ async function renderBandwidthWidget() { const renderedWidget = renderWidget({ loading: traffic.loading, failed: traffic.failed, - title: "Traffic", + title: _("Traffic"), items: [ - { key: "Uplink", value: `${prettyBytes(traffic.data.up)}/s` }, - { key: "Downlink", value: `${prettyBytes(traffic.data.down)}/s` } + { key: _("Uplink"), value: `${prettyBytes(traffic.data.up)}/s` }, + { key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` } ] }); container.replaceChildren(renderedWidget); @@ -1737,14 +1742,14 @@ async function renderTrafficTotalWidget() { const renderedWidget = renderWidget({ loading: trafficTotalWidget.loading, failed: trafficTotalWidget.failed, - title: "Traffic Total", + title: _("Traffic Total"), items: [ { - key: "Uplink", + key: _("Uplink"), value: String(prettyBytes(trafficTotalWidget.data.uploadTotal)) }, { - key: "Downlink", + key: _("Downlink"), value: String(prettyBytes(trafficTotalWidget.data.downloadTotal)) } ] @@ -1767,14 +1772,14 @@ async function renderSystemInfoWidget() { const renderedWidget = renderWidget({ loading: systemInfoWidget.loading, failed: systemInfoWidget.failed, - title: "System info", + title: _("System info"), items: [ { - key: "Active Connections", + key: _("Active Connections"), value: String(systemInfoWidget.data.connections) }, { - key: "Memory Usage", + key: _("Memory Usage"), value: String(prettyBytes(systemInfoWidget.data.memory)) } ] @@ -1797,18 +1802,18 @@ async function renderServicesInfoWidget() { const renderedWidget = renderWidget({ loading: servicesInfoWidget.loading, failed: servicesInfoWidget.failed, - title: "Services info", + title: _("Services info"), items: [ { - key: "Podkop", - value: servicesInfoWidget.data.podkop ? "\u2714 Enabled" : "\u2718 Disabled", + key: _("Podkop"), + value: servicesInfoWidget.data.podkop ? _("\u2714 Enabled") : _("\u2718 Disabled"), attributes: { class: servicesInfoWidget.data.podkop ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error" } }, { - key: "Sing-box", - value: servicesInfoWidget.data.singbox ? "\u2714 Running" : "\u2718 Stopped", + key: _("Sing-box"), + value: servicesInfoWidget.data.singbox ? _("\u2714 Running") : _("\u2718 Stopped"), attributes: { class: servicesInfoWidget.data.singbox ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error" } @@ -1865,7 +1870,6 @@ return baseclass.extend({ TabServiceInstance, UPDATE_INTERVAL_OPTIONS, bulkValidate, - copyToClipboard, coreService, createBaseApiRequest, executeShellCommand,