mirror of
https://github.com/itdoginfo/podkop.git
synced 2026-01-27 04:40:37 +03:00
@@ -7,3 +7,4 @@ export * from './maskIP';
|
|||||||
export * from './getProxyUrlName';
|
export * from './getProxyUrlName';
|
||||||
export * from './onMount';
|
export * from './onMount';
|
||||||
export * from './getClashApiUrl';
|
export * from './getClashApiUrl';
|
||||||
|
export * from './splitProxyString';
|
||||||
|
|||||||
7
fe-app-podkop/src/helpers/splitProxyString.ts
Normal file
7
fe-app-podkop/src/helpers/splitProxyString.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function splitProxyString(str: string) {
|
||||||
|
return str
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => !line.startsWith('//'))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Podkop } from '../types';
|
import { Podkop } from '../types';
|
||||||
import { getConfigSections } from './getConfigSections';
|
import { getConfigSections } from './getConfigSections';
|
||||||
import { getClashProxies } from '../../clash';
|
import { getClashProxies } from '../../clash';
|
||||||
import { getProxyUrlName } from '../../helpers';
|
import { getProxyUrlName, splitProxyString } from '../../helpers';
|
||||||
|
|
||||||
interface IGetDashboardSectionsResponse {
|
interface IGetDashboardSectionsResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -35,6 +35,11 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
|
|||||||
(proxy) => proxy.code === `${section['.name']}-out`,
|
(proxy) => proxy.code === `${section['.name']}-out`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeConfigs = splitProxyString(section.proxy_string);
|
||||||
|
|
||||||
|
const proxyDisplayName =
|
||||||
|
getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
withTagSelect: false,
|
withTagSelect: false,
|
||||||
code: outbound?.code || section['.name'],
|
code: outbound?.code || section['.name'],
|
||||||
@@ -42,10 +47,7 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
|
|||||||
outbounds: [
|
outbounds: [
|
||||||
{
|
{
|
||||||
code: outbound?.code || section['.name'],
|
code: outbound?.code || section['.name'],
|
||||||
displayName:
|
displayName: proxyDisplayName,
|
||||||
getProxyUrlName(section.proxy_string) ||
|
|
||||||
outbound?.value?.name ||
|
|
||||||
'',
|
|
||||||
latency: outbound?.value?.history?.[0]?.delay || 0,
|
latency: outbound?.value?.history?.[0]?.delay || 0,
|
||||||
type: outbound?.value?.type || '',
|
type: outbound?.value?.type || '',
|
||||||
selected: true,
|
selected: true,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function renderDefaultState({
|
|||||||
class: 'btn dashboard-sections-grid-item-test-latency',
|
class: 'btn dashboard-sections-grid-item-test-latency',
|
||||||
click: () => testLatency(),
|
click: () => testLatency(),
|
||||||
},
|
},
|
||||||
'Test latency',
|
_('Test latency'),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
E(
|
E(
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ export const invalidDomains = [
|
|||||||
['Too long domain (>253 chars)', Array(40).fill('abcdef').join('.') + '.com'],
|
['Too long domain (>253 chars)', Array(40).fill('abcdef').join('.') + '.com'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const dotTLDTests = [
|
||||||
|
['Dot TLD allowed (.net)', '.net', true, true],
|
||||||
|
['Dot TLD not allowed (.net)', '.net', false, false],
|
||||||
|
['Invalid with double dot', '..net', true, false],
|
||||||
|
['Invalid single word TLD (net)', 'net', true, false],
|
||||||
|
];
|
||||||
|
|
||||||
describe('validateDomain', () => {
|
describe('validateDomain', () => {
|
||||||
describe.each(validDomains)('Valid domain: %s', (_desc, domain) => {
|
describe.each(validDomains)('Valid domain: %s', (_desc, domain) => {
|
||||||
it(`returns valid=true for "${domain}"`, () => {
|
it(`returns valid=true for "${domain}"`, () => {
|
||||||
@@ -43,4 +50,14 @@ describe('validateDomain', () => {
|
|||||||
expect(res.valid).toBe(false);
|
expect(res.valid).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe.each(dotTLDTests)(
|
||||||
|
'Dot TLD toggle: %s',
|
||||||
|
(_desc, domain, allowDotTLD, expected) => {
|
||||||
|
it(`"${domain}" with allowDotTLD=${allowDotTLD} → valid=${expected}`, () => {
|
||||||
|
const res = validateDomain(domain, allowDotTLD);
|
||||||
|
expect(res.valid).toBe(expected);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
import { ValidationResult } from './types';
|
import { ValidationResult } from './types';
|
||||||
|
|
||||||
export function validateDomain(domain: string): ValidationResult {
|
export function validateDomain(
|
||||||
|
domain: string,
|
||||||
|
allowDotTLD = false,
|
||||||
|
): ValidationResult {
|
||||||
const domainRegex =
|
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]*)?$/;
|
/^(?=.{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 (allowDotTLD) {
|
||||||
|
const dotTLD = /^\.[a-zA-Z]{2,}$/;
|
||||||
|
if (dotTLD.test(domain)) {
|
||||||
|
return { valid: true, message: _('Valid') };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!domainRegex.test(domain)) {
|
if (!domainRegex.test(domain)) {
|
||||||
return { valid: false, message: _('Invalid domain address') };
|
return { valid: false, message: _('Invalid domain address') };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,11 +130,7 @@ function createConfigSection(section) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeConfigs = value
|
const activeConfigs = main.splitProxyString(value);
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter((line) => !line.startsWith('//'))
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
if (!activeConfigs.length) {
|
if (!activeConfigs.length) {
|
||||||
return _(
|
return _(
|
||||||
@@ -455,7 +451,7 @@ function createConfigSection(section) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validation = main.validateDomain(value);
|
const validation = main.validateDomain(value, true);
|
||||||
|
|
||||||
if (validation.valid) {
|
if (validation.valid) {
|
||||||
return true;
|
return true;
|
||||||
@@ -493,7 +489,7 @@ function createConfigSection(section) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { valid, results } = main.bulkValidate(domains, main.validateDomain);
|
const { valid, results } = main.bulkValidate(domains, row => main.validateDomain(row, true));
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
const errors = results
|
const errors = results
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ function validateIPV4(ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/validators/validateDomain.ts
|
// src/validators/validateDomain.ts
|
||||||
function validateDomain(domain) {
|
function validateDomain(domain, allowDotTLD = false) {
|
||||||
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]*)?$/;
|
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 (allowDotTLD) {
|
||||||
|
const dotTLD = /^\.[a-zA-Z]{2,}$/;
|
||||||
|
if (dotTLD.test(domain)) {
|
||||||
|
return { valid: true, message: _("Valid") };
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!domainRegex.test(domain)) {
|
if (!domainRegex.test(domain)) {
|
||||||
return { valid: false, message: _("Invalid domain address") };
|
return { valid: false, message: _("Invalid domain address") };
|
||||||
}
|
}
|
||||||
@@ -765,6 +771,11 @@ function getClashWsUrl() {
|
|||||||
return `ws://${hostname}:9090`;
|
return `ws://${hostname}:9090`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/helpers/splitProxyString.ts
|
||||||
|
function splitProxyString(str) {
|
||||||
|
return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
// src/clash/methods/createBaseApiRequest.ts
|
// src/clash/methods/createBaseApiRequest.ts
|
||||||
async function createBaseApiRequest(fetchFn) {
|
async function createBaseApiRequest(fetchFn) {
|
||||||
try {
|
try {
|
||||||
@@ -893,6 +904,8 @@ async function getDashboardSections() {
|
|||||||
const outbound = proxies.find(
|
const outbound = proxies.find(
|
||||||
(proxy) => proxy.code === `${section[".name"]}-out`
|
(proxy) => proxy.code === `${section[".name"]}-out`
|
||||||
);
|
);
|
||||||
|
const activeConfigs = splitProxyString(section.proxy_string);
|
||||||
|
const proxyDisplayName = getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || "";
|
||||||
return {
|
return {
|
||||||
withTagSelect: false,
|
withTagSelect: false,
|
||||||
code: outbound?.code || section[".name"],
|
code: outbound?.code || section[".name"],
|
||||||
@@ -900,7 +913,7 @@ async function getDashboardSections() {
|
|||||||
outbounds: [
|
outbounds: [
|
||||||
{
|
{
|
||||||
code: outbound?.code || section[".name"],
|
code: outbound?.code || section[".name"],
|
||||||
displayName: getProxyUrlName(section.proxy_string) || outbound?.value?.name || "",
|
displayName: proxyDisplayName,
|
||||||
latency: outbound?.value?.history?.[0]?.delay || 0,
|
latency: outbound?.value?.history?.[0]?.delay || 0,
|
||||||
type: outbound?.value?.type || "",
|
type: outbound?.value?.type || "",
|
||||||
selected: true
|
selected: true
|
||||||
@@ -1294,7 +1307,7 @@ function renderDefaultState({
|
|||||||
class: "btn dashboard-sections-grid-item-test-latency",
|
class: "btn dashboard-sections-grid-item-test-latency",
|
||||||
click: () => testLatency()
|
click: () => testLatency()
|
||||||
},
|
},
|
||||||
"Test latency"
|
_("Test latency")
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
E(
|
E(
|
||||||
@@ -1891,6 +1904,7 @@ return baseclass.extend({
|
|||||||
onMount,
|
onMount,
|
||||||
parseValueList,
|
parseValueList,
|
||||||
renderDashboard,
|
renderDashboard,
|
||||||
|
splitProxyString,
|
||||||
triggerLatencyGroupTest,
|
triggerLatencyGroupTest,
|
||||||
triggerLatencyProxyTest,
|
triggerLatencyProxyTest,
|
||||||
triggerProxySelector,
|
triggerProxySelector,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
PODIR="po"
|
PODIR="po"
|
||||||
|
|||||||
@@ -51,9 +51,11 @@ msgid "Config without description"
|
|||||||
msgstr "Конфигурация без описания"
|
msgstr "Конфигурация без описания"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs"
|
"Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup "
|
||||||
|
"configs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для резервных конфигураций"
|
"Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для "
|
||||||
|
"резервных конфигураций"
|
||||||
|
|
||||||
msgid "No active configuration found. One configuration is required."
|
msgid "No active configuration found. One configuration is required."
|
||||||
msgstr "Активная конфигурация не найдена. Требуется хотя бы одна незакомментированная строка."
|
msgstr "Активная конфигурация не найдена. Требуется хотя бы одна незакомментированная строка."
|
||||||
@@ -124,14 +126,18 @@ msgstr "Выберите предустановленные сервисы дл
|
|||||||
msgid "Regional options cannot be used together"
|
msgid "Regional options cannot be used together"
|
||||||
msgstr "Нельзя использовать несколько региональных опций одновременно"
|
msgstr "Нельзя использовать несколько региональных опций одновременно"
|
||||||
|
|
||||||
|
#, javascript-format
|
||||||
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
||||||
msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены."
|
msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены."
|
||||||
|
|
||||||
msgid "Russia inside restrictions"
|
msgid "Russia inside restrictions"
|
||||||
msgstr "Ограничения Russia inside"
|
msgstr "Ограничения Russia inside"
|
||||||
|
|
||||||
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
#, javascript-format
|
||||||
msgstr "Внимание: «Russia inside» может использоваться только с %s. %s уже находится в «Russia inside» и был удалён из выбора."
|
msgid ""
|
||||||
|
"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
||||||
|
msgstr ""
|
||||||
|
"Внимание: «Russia inside» может использоваться только с %s. %s уже находится в «Russia inside» и был удалён из выбора."
|
||||||
|
|
||||||
msgid "User Domain List Type"
|
msgid "User Domain List Type"
|
||||||
msgstr "Тип пользовательского списка доменов"
|
msgstr "Тип пользовательского списка доменов"
|
||||||
@@ -214,8 +220,12 @@ msgstr "Введите подсети в нотации CIDR (например:
|
|||||||
msgid "User Subnets List"
|
msgid "User Subnets List"
|
||||||
msgstr "Список пользовательских подсетей"
|
msgstr "Список пользовательских подсетей"
|
||||||
|
|
||||||
msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //"
|
msgid ""
|
||||||
msgstr "Введите подсети в нотации CIDR или IP-адреса через запятую, пробел или новую строку. Можно добавлять комментарии после //"
|
"Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments "
|
||||||
|
"after //"
|
||||||
|
msgstr ""
|
||||||
|
"Введите подсети в нотации CIDR или IP-адреса через запятую, пробел или новую строку. Можно добавлять комментарии "
|
||||||
|
"после //"
|
||||||
|
|
||||||
msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed."
|
msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed."
|
||||||
msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы."
|
msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы."
|
||||||
@@ -568,11 +578,122 @@ msgstr "Конфигурация: "
|
|||||||
msgid "Diagnostics"
|
msgid "Diagnostics"
|
||||||
msgstr "Диагностика"
|
msgstr "Диагностика"
|
||||||
|
|
||||||
msgid "Podkop"
|
msgid "Additional Settings"
|
||||||
msgstr "Podkop"
|
msgstr "Дополнительные настройки"
|
||||||
|
|
||||||
msgid "Extra configurations"
|
msgid "Yacd enable"
|
||||||
msgstr "Дополнительные конфигурации"
|
msgstr "Включить YACD"
|
||||||
|
|
||||||
msgid "Add Section"
|
msgid "Exclude NTP"
|
||||||
msgstr "Добавить раздел"
|
msgstr "Исключить NTP"
|
||||||
|
|
||||||
|
msgid "Allows you to exclude NTP protocol traffic from the tunnel"
|
||||||
|
msgstr "Позволяет исключить направление трафика NTP-протокола в туннель"
|
||||||
|
|
||||||
|
msgid "QUIC disable"
|
||||||
|
msgstr "Отключить QUIC"
|
||||||
|
|
||||||
|
msgid "For issues with the video stream"
|
||||||
|
msgstr "Для проблем с видеопотоком"
|
||||||
|
|
||||||
|
msgid "List Update Frequency"
|
||||||
|
msgstr "Частота обновления списков"
|
||||||
|
|
||||||
|
msgid "Select how often the lists will be updated"
|
||||||
|
msgstr "Выберите как часто будут обновляться списки"
|
||||||
|
|
||||||
|
msgid "Select DNS protocol to use"
|
||||||
|
msgstr "Выберите протокол DNS"
|
||||||
|
|
||||||
|
msgid "Bootstrap DNS server"
|
||||||
|
msgstr "Bootstrap DNS-сервер"
|
||||||
|
|
||||||
|
msgid "The DNS server used to look up the IP address of an upstream DNS server"
|
||||||
|
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
|
||||||
|
|
||||||
|
msgid "DNS Rewrite TTL"
|
||||||
|
msgstr "Перезапись TTL для DNS"
|
||||||
|
|
||||||
|
msgid "Time in seconds for DNS record caching (default: 60)"
|
||||||
|
msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)"
|
||||||
|
|
||||||
|
msgid "TTL value cannot be empty"
|
||||||
|
msgstr "Значение TTL не может быть пустым"
|
||||||
|
|
||||||
|
msgid "TTL must be a positive number"
|
||||||
|
msgstr "TTL должно быть положительным числом"
|
||||||
|
|
||||||
|
msgid "Config File Path"
|
||||||
|
msgstr "Путь к файлу конфигурации"
|
||||||
|
|
||||||
|
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
|
||||||
|
msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете"
|
||||||
|
|
||||||
|
msgid "Cache File Path"
|
||||||
|
msgstr "Путь к файлу кэша"
|
||||||
|
|
||||||
|
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
|
||||||
|
msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете"
|
||||||
|
|
||||||
|
msgid "Cache file path cannot be empty"
|
||||||
|
msgstr "Путь к файлу кэша не может быть пустым"
|
||||||
|
|
||||||
|
msgid "Path must be absolute (start with /)"
|
||||||
|
msgstr "Путь должен быть абсолютным (начинаться с /)"
|
||||||
|
|
||||||
|
msgid "Path must end with cache.db"
|
||||||
|
msgstr "Путь должен заканчиваться на cache.db"
|
||||||
|
|
||||||
|
msgid "Path must contain at least one directory (like /tmp/cache.db)"
|
||||||
|
msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)"
|
||||||
|
|
||||||
|
msgid "Source Network Interface"
|
||||||
|
msgstr "Сетевой интерфейс источника"
|
||||||
|
|
||||||
|
msgid "Select the network interface from which the traffic will originate"
|
||||||
|
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
|
||||||
|
|
||||||
|
msgid "Interface monitoring"
|
||||||
|
msgstr "Мониторинг интерфейсов"
|
||||||
|
|
||||||
|
msgid "Interface monitoring for bad WAN"
|
||||||
|
msgstr "Мониторинг интерфейсов для плохого WAN"
|
||||||
|
|
||||||
|
msgid "Interface for monitoring"
|
||||||
|
msgstr "Интерфейс для мониторинга"
|
||||||
|
|
||||||
|
msgid "Select the WAN interfaces to be monitored"
|
||||||
|
msgstr "Выберите WAN интерфейсы для мониторинга"
|
||||||
|
|
||||||
|
msgid "Interface Monitoring Delay"
|
||||||
|
msgstr "Задержка при мониторинге интерфейсов"
|
||||||
|
|
||||||
|
msgid "Delay in milliseconds before reloading podkop after interface UP"
|
||||||
|
msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса"
|
||||||
|
|
||||||
|
msgid "Delay value cannot be empty"
|
||||||
|
msgstr "Значение задержки не может быть пустым"
|
||||||
|
|
||||||
|
msgid "Dont touch my DHCP!"
|
||||||
|
msgstr "Не трогать мой DHCP!"
|
||||||
|
|
||||||
|
msgid "Podkop will not change the DHCP config"
|
||||||
|
msgstr "Podkop не будет изменять конфигурацию DHCP"
|
||||||
|
|
||||||
|
msgid "Proxy download of lists"
|
||||||
|
msgstr "Загрузка списков через прокси"
|
||||||
|
|
||||||
|
msgid "Downloading all lists via main Proxy/VPN"
|
||||||
|
msgstr "Загрузка всех списков через основной прокси/VPN"
|
||||||
|
|
||||||
|
msgid "IP for exclusion"
|
||||||
|
msgstr "IP для исключения"
|
||||||
|
|
||||||
|
msgid "Specify local IP addresses that will never use the configured route"
|
||||||
|
msgstr "Укажите локальные IP-адреса, которые никогда не будут использовать настроенный маршрут"
|
||||||
|
|
||||||
|
msgid "Mixed enable"
|
||||||
|
msgstr "Включить смешанный режим"
|
||||||
|
|
||||||
|
msgid "Browser port: 2080"
|
||||||
|
msgstr "Порт браузера: 2080"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
SRC_DIR="htdocs/luci-static/resources/view/podkop"
|
SRC_DIR="htdocs/luci-static/resources/view/podkop"
|
||||||
OUT_POT="po/templates/podkop.pot"
|
OUT_POT="po/templates/podkop.pot"
|
||||||
@@ -11,6 +12,7 @@ if [ ${#FILES[@]} -eq 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
mapfile -t FILES < <(printf '%s\n' "${FILES[@]}" | sort)
|
||||||
mkdir -p "$(dirname "$OUT_POT")"
|
mkdir -p "$(dirname "$OUT_POT")"
|
||||||
|
|
||||||
echo "Generating POT template from JS files in $SRC_DIR"
|
echo "Generating POT template from JS files in $SRC_DIR"
|
||||||
|
|||||||
Reference in New Issue
Block a user