mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-12 14:37:03 +03:00
feat: migrate to _ locales handler
This commit is contained in:
@@ -9,7 +9,7 @@ export async function createBaseApiRequest<T>(
|
||||
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<T>(
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false as const,
|
||||
message: e instanceof Error ? e.message : 'Unknown error',
|
||||
message: e instanceof Error ? e.message : _('Unknown error'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -2,7 +2,7 @@ export async function withTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
operationName: string,
|
||||
timeoutMessage = 'Operation timed out',
|
||||
timeoutMessage = _('Operation timed out'),
|
||||
): Promise<T> {
|
||||
let timeoutId;
|
||||
const start = performance.now();
|
||||
|
||||
2
fe-app-podkop/src/luci.d.ts
vendored
2
fe-app-podkop/src/luci.d.ts
vendored
@@ -33,6 +33,8 @@ declare global {
|
||||
load: (packages: string | string[]) => Promise<string>;
|
||||
sections: (conf: string, type?: string, cb?: () => void) => Promise<T>;
|
||||
};
|
||||
|
||||
const _ = (_key: string) => string;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
|
||||
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,
|
||||
|
||||
@@ -217,10 +217,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` },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -247,14 +247,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)),
|
||||
},
|
||||
],
|
||||
@@ -283,14 +283,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)),
|
||||
},
|
||||
],
|
||||
@@ -319,11 +319,13 @@ 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 ? '✔ Enabled' : '✘ Disabled',
|
||||
key: _('Podkop'),
|
||||
value: servicesInfoWidget.data.podkop
|
||||
? _('✔ Enabled')
|
||||
: _('✘ Disabled'),
|
||||
attributes: {
|
||||
class: servicesInfoWidget.data.podkop
|
||||
? 'pdk_dashboard-page__widgets-section__item__row--success'
|
||||
@@ -331,8 +333,10 @@ async function renderServicesInfoWidget() {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Sing-box',
|
||||
value: servicesInfoWidget.data.singbox ? '✔ Running' : '✘ Stopped',
|
||||
key: _('Sing-box'),
|
||||
value: servicesInfoWidget.data.singbox
|
||||
? _('✔ Running')
|
||||
: _('✘ Stopped'),
|
||||
attributes: {
|
||||
class: servicesInfoWidget.data.singbox
|
||||
? 'pdk_dashboard-page__widgets-section__item__row--success'
|
||||
|
||||
@@ -15,7 +15,7 @@ function renderFailedState() {
|
||||
class: 'pdk_dashboard-page__outbound-section centered',
|
||||
style: 'height: 127px',
|
||||
},
|
||||
E('span', {}, 'Dashboard currently unavailable'),
|
||||
E('span', {}, _('Dashboard currently unavailable')),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ function renderFailedState() {
|
||||
style: 'height: 78px',
|
||||
class: 'pdk_dashboard-page__widgets-section__item centered',
|
||||
},
|
||||
'Currently unavailable',
|
||||
_('Currently unavailable'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,20 +4,21 @@ import { ValidationResult } from './types';
|
||||
|
||||
export function validateDNS(value: string): ValidationResult {
|
||||
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:
|
||||
message: _(
|
||||
'Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export function validateDomain(domain: string): ValidationResult {
|
||||
/^(?=.{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];
|
||||
@@ -14,8 +14,8 @@ export function validateDomain(domain: string): ValidationResult {
|
||||
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') };
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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://'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user