chore(i18n): add missing i18n keys
This commit is contained in:
@@ -5,6 +5,8 @@ import path from "path";
|
||||
import process from "process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
import ts from "typescript";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
@@ -46,10 +48,44 @@ const SUPPORTED_EXTENSIONS = new Set([
|
||||
".mjs",
|
||||
".cjs",
|
||||
".vue",
|
||||
".rs",
|
||||
".json",
|
||||
]);
|
||||
|
||||
const TS_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
||||
|
||||
const KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z0-9_-]+)+$/;
|
||||
const TEMPLATE_PREFIX_PATTERN =
|
||||
/^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z0-9_-]+)*\.$/;
|
||||
|
||||
const IGNORED_KEY_PREFIXES = new Set([
|
||||
"text",
|
||||
"primary",
|
||||
"secondary",
|
||||
"error",
|
||||
"warning",
|
||||
"success",
|
||||
"info",
|
||||
"background",
|
||||
"grey",
|
||||
"option",
|
||||
"action",
|
||||
"example",
|
||||
"chrome",
|
||||
"localhost",
|
||||
"www",
|
||||
"pac",
|
||||
"V2",
|
||||
"v2",
|
||||
"v1",
|
||||
]);
|
||||
|
||||
const NOTICE_METHOD_NAMES = new Set(["success", "error", "info", "warning"]);
|
||||
const NOTICE_SERVICE_IDENTIFIERS = new Set([
|
||||
"@/services/noticeService",
|
||||
"./noticeService",
|
||||
"../services/noticeService",
|
||||
]);
|
||||
|
||||
const WHITELIST_KEYS = new Set([
|
||||
"theme.light",
|
||||
"theme.dark",
|
||||
@@ -58,6 +94,8 @@ const WHITELIST_KEYS = new Set([
|
||||
]);
|
||||
|
||||
const MAX_PREVIEW_ENTRIES = 40;
|
||||
const dynamicKeyCache = new Map();
|
||||
const fileUsageCache = new Map();
|
||||
|
||||
function printUsage() {
|
||||
console.log(`Usage: pnpm node scripts/cleanup-unused-i18n.mjs [options]
|
||||
@@ -177,22 +215,37 @@ function getAllFiles(start, predicate) {
|
||||
return files;
|
||||
}
|
||||
|
||||
function loadSourceContents(sourceDirs) {
|
||||
const sourceFiles = sourceDirs.flatMap((dir) =>
|
||||
getAllFiles(
|
||||
dir,
|
||||
(filePath) =>
|
||||
SUPPORTED_EXTENSIONS.has(path.extname(filePath)) &&
|
||||
!EXCLUDE_USAGE_DIRS.some((excluded) =>
|
||||
filePath.startsWith(`${excluded}${path.sep}`),
|
||||
) &&
|
||||
!EXCLUDE_USAGE_DIRS.includes(filePath),
|
||||
),
|
||||
);
|
||||
function collectSourceFiles(sourceDirs) {
|
||||
const seen = new Set();
|
||||
const files = [];
|
||||
|
||||
return sourceFiles
|
||||
.map((filePath) => fs.readFileSync(filePath, "utf8"))
|
||||
.join("\n");
|
||||
for (const dir of sourceDirs) {
|
||||
const resolved = getAllFiles(dir, (filePath) => {
|
||||
if (seen.has(filePath)) return false;
|
||||
if (!SUPPORTED_EXTENSIONS.has(path.extname(filePath))) return false;
|
||||
if (
|
||||
EXCLUDE_USAGE_DIRS.some((excluded) =>
|
||||
filePath.startsWith(`${excluded}${path.sep}`),
|
||||
) ||
|
||||
EXCLUDE_USAGE_DIRS.includes(filePath)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
for (const filePath of resolved) {
|
||||
seen.add(filePath);
|
||||
files.push({
|
||||
path: filePath,
|
||||
extension: path.extname(filePath).toLowerCase(),
|
||||
content: fs.readFileSync(filePath, "utf8"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
files.sort((a, b) => a.path.localeCompare(b.path));
|
||||
return files;
|
||||
}
|
||||
|
||||
function flattenLocale(obj, parent = "") {
|
||||
@@ -239,6 +292,411 @@ function diffLocaleKeys(baselineEntries, localeEntries) {
|
||||
return { missing, extra };
|
||||
}
|
||||
|
||||
function determineScriptKind(extension) {
|
||||
switch (extension) {
|
||||
case ".ts":
|
||||
return ts.ScriptKind.TS;
|
||||
case ".tsx":
|
||||
return ts.ScriptKind.TSX;
|
||||
case ".jsx":
|
||||
return ts.ScriptKind.JSX;
|
||||
case ".js":
|
||||
case ".mjs":
|
||||
case ".cjs":
|
||||
return ts.ScriptKind.JS;
|
||||
default:
|
||||
return ts.ScriptKind.TS;
|
||||
}
|
||||
}
|
||||
|
||||
function getNamespaceFromKey(key) {
|
||||
if (!key || typeof key !== "string") return null;
|
||||
const [namespace] = key.split(".");
|
||||
return namespace ?? null;
|
||||
}
|
||||
|
||||
function addTemplatePrefixCandidate(
|
||||
prefix,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
) {
|
||||
if (!prefix || typeof prefix !== "string") return;
|
||||
const normalized = prefix.trim();
|
||||
if (!normalized) return;
|
||||
let candidate = normalized;
|
||||
if (!candidate.endsWith(".")) {
|
||||
const lastDotIndex = candidate.lastIndexOf(".");
|
||||
if (lastDotIndex === -1) {
|
||||
return;
|
||||
}
|
||||
candidate = candidate.slice(0, lastDotIndex + 1);
|
||||
}
|
||||
if (!TEMPLATE_PREFIX_PATTERN.test(candidate)) return;
|
||||
const namespace = getNamespaceFromKey(candidate);
|
||||
if (!namespace || IGNORED_KEY_PREFIXES.has(namespace)) return;
|
||||
if (!baselineNamespaces.has(namespace)) return;
|
||||
dynamicPrefixes.add(candidate);
|
||||
}
|
||||
|
||||
function addKeyIfValid(key, usedKeys, baselineNamespaces, options = {}) {
|
||||
if (!key || typeof key !== "string") return false;
|
||||
if (!KEY_PATTERN.test(key)) return false;
|
||||
const namespace = getNamespaceFromKey(key);
|
||||
if (!namespace || IGNORED_KEY_PREFIXES.has(namespace)) return false;
|
||||
if (!options.forceNamespace && !baselineNamespaces.has(namespace)) {
|
||||
return false;
|
||||
}
|
||||
usedKeys.add(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
function collectImportSpecifiers(sourceFile) {
|
||||
const specifiers = new Map();
|
||||
|
||||
sourceFile.forEachChild((node) => {
|
||||
if (!ts.isImportDeclaration(node)) return;
|
||||
const moduleText =
|
||||
ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text;
|
||||
if (!moduleText || !node.importClause) return;
|
||||
|
||||
if (node.importClause.name) {
|
||||
specifiers.set(node.importClause.name.text, moduleText);
|
||||
}
|
||||
|
||||
const { namedBindings } = node.importClause;
|
||||
if (!namedBindings) return;
|
||||
|
||||
if (ts.isNamespaceImport(namedBindings)) {
|
||||
specifiers.set(namedBindings.name.text, `${moduleText}.*`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts.isNamedImports(namedBindings)) {
|
||||
for (const element of namedBindings.elements) {
|
||||
specifiers.set(element.name.text, moduleText);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return specifiers;
|
||||
}
|
||||
|
||||
function getCallExpressionChain(expression) {
|
||||
const chain = [];
|
||||
let current = expression;
|
||||
while (current) {
|
||||
if (ts.isIdentifier(current)) {
|
||||
chain.unshift(current.text);
|
||||
break;
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(current)) {
|
||||
chain.unshift(current.name.text);
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
if (ts.isElementAccessExpression(current)) {
|
||||
const argument = current.argumentExpression;
|
||||
if (ts.isStringLiteralLike(argument)) {
|
||||
chain.unshift(argument.text);
|
||||
current = current.expression;
|
||||
continue;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
function classifyCallExpression(expression, importSpecifiers) {
|
||||
if (!expression) return null;
|
||||
const chain = getCallExpressionChain(expression);
|
||||
if (chain.length === 0) return null;
|
||||
|
||||
const last = chain[chain.length - 1];
|
||||
const root = chain[0];
|
||||
|
||||
if (last === "t") {
|
||||
return { type: "translation", forceNamespace: true };
|
||||
}
|
||||
|
||||
if (
|
||||
NOTICE_METHOD_NAMES.has(last) &&
|
||||
root === "showNotice" &&
|
||||
(!importSpecifiers ||
|
||||
NOTICE_SERVICE_IDENTIFIERS.has(importSpecifiers.get("showNotice") ?? ""))
|
||||
) {
|
||||
return { type: "notice", forceNamespace: true };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveBindingValue(name, scopeStack) {
|
||||
if (!name) return null;
|
||||
for (let index = scopeStack.length - 1; index >= 0; index -= 1) {
|
||||
const scope = scopeStack[index];
|
||||
if (scope && scope.has(name)) {
|
||||
return scope.get(name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveKeyFromExpression(
|
||||
node,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
) {
|
||||
if (!node) return null;
|
||||
|
||||
if (
|
||||
ts.isStringLiteralLike(node) ||
|
||||
ts.isNoSubstitutionTemplateLiteral(node)
|
||||
) {
|
||||
return node.text;
|
||||
}
|
||||
|
||||
if (ts.isTemplateExpression(node)) {
|
||||
addTemplatePrefixCandidate(
|
||||
node.head?.text ?? "",
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
);
|
||||
for (const span of node.templateSpans) {
|
||||
const literalText = span.literal?.text ?? "";
|
||||
const combined = (node.head?.text ?? "") + literalText;
|
||||
addTemplatePrefixCandidate(combined, dynamicPrefixes, baselineNamespaces);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ts.isBinaryExpression(node)) {
|
||||
if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
||||
const left = resolveKeyFromExpression(
|
||||
node.left,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
const right = resolveKeyFromExpression(
|
||||
node.right,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
if (left && right) {
|
||||
return `${left}${right}`;
|
||||
}
|
||||
if (left) {
|
||||
addTemplatePrefixCandidate(left, dynamicPrefixes, baselineNamespaces);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ts.isParenthesizedExpression(node)) {
|
||||
return resolveKeyFromExpression(
|
||||
node.expression,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
}
|
||||
|
||||
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
||||
return resolveKeyFromExpression(
|
||||
node.expression,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
}
|
||||
|
||||
if (ts.isIdentifier(node)) {
|
||||
return resolveBindingValue(node.text, scopeStack);
|
||||
}
|
||||
|
||||
if (ts.isCallExpression(node)) {
|
||||
const classification = classifyCallExpression(
|
||||
node.expression,
|
||||
importSpecifiers,
|
||||
);
|
||||
if (!classification) return null;
|
||||
const firstArg = node.arguments[0];
|
||||
if (!firstArg) return null;
|
||||
return resolveKeyFromExpression(
|
||||
firstArg,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectUsedKeysFromTsFile(
|
||||
file,
|
||||
baselineNamespaces,
|
||||
usedKeys,
|
||||
dynamicPrefixes,
|
||||
) {
|
||||
let sourceFile;
|
||||
|
||||
try {
|
||||
sourceFile = ts.createSourceFile(
|
||||
file.path,
|
||||
file.content,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
determineScriptKind(file.extension),
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`Warning: failed to parse ${file.path}: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const importSpecifiers = collectImportSpecifiers(sourceFile);
|
||||
const scopeStack = [new Map()];
|
||||
|
||||
const visit = (node) => {
|
||||
let scopePushed = false;
|
||||
if (
|
||||
ts.isBlock(node) ||
|
||||
ts.isModuleBlock(node) ||
|
||||
node.kind === ts.SyntaxKind.CaseBlock ||
|
||||
ts.isCatchClause(node)
|
||||
) {
|
||||
scopeStack.push(new Map());
|
||||
scopePushed = true;
|
||||
}
|
||||
|
||||
if (ts.isTemplateExpression(node)) {
|
||||
resolveKeyFromExpression(
|
||||
node,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
}
|
||||
|
||||
if (ts.isVariableDeclaration(node) && node.initializer) {
|
||||
const key = resolveKeyFromExpression(
|
||||
node.initializer,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
if (key && KEY_PATTERN.test(key)) {
|
||||
if (ts.isIdentifier(node.name)) {
|
||||
scopeStack[scopeStack.length - 1].set(node.name.text, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ts.isCallExpression(node)) {
|
||||
const classification = classifyCallExpression(
|
||||
node.expression,
|
||||
importSpecifiers,
|
||||
);
|
||||
if (classification) {
|
||||
const [firstArg] = node.arguments;
|
||||
if (firstArg) {
|
||||
const key = resolveKeyFromExpression(
|
||||
firstArg,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
if (
|
||||
!addKeyIfValid(key, usedKeys, baselineNamespaces, classification)
|
||||
) {
|
||||
addTemplatePrefixCandidate(
|
||||
key ?? "",
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ts.isJsxAttribute(node) && node.name?.text === "i18nKey") {
|
||||
const initializer = node.initializer;
|
||||
if (initializer && ts.isStringLiteralLike(initializer)) {
|
||||
addKeyIfValid(initializer.text, usedKeys, baselineNamespaces, {
|
||||
forceNamespace: false,
|
||||
});
|
||||
} else if (
|
||||
initializer &&
|
||||
ts.isJsxExpression(initializer) &&
|
||||
initializer.expression
|
||||
) {
|
||||
const key = resolveKeyFromExpression(
|
||||
initializer.expression,
|
||||
scopeStack,
|
||||
dynamicPrefixes,
|
||||
baselineNamespaces,
|
||||
importSpecifiers,
|
||||
);
|
||||
addKeyIfValid(key, usedKeys, baselineNamespaces, {
|
||||
forceNamespace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
node.forEachChild(visit);
|
||||
|
||||
if (scopePushed) {
|
||||
scopeStack.pop();
|
||||
}
|
||||
};
|
||||
|
||||
visit(sourceFile);
|
||||
}
|
||||
|
||||
function collectUsedKeysFromTextFile(file, baselineNamespaces, usedKeys) {
|
||||
const regex = /['"`]([A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z0-9_-]+)+)['"`]/g;
|
||||
let match;
|
||||
while ((match = regex.exec(file.content))) {
|
||||
addKeyIfValid(match[1], usedKeys, baselineNamespaces, {
|
||||
forceNamespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function collectUsedI18nKeys(sourceFiles, baselineNamespaces) {
|
||||
const usedKeys = new Set();
|
||||
const dynamicPrefixes = new Set();
|
||||
|
||||
for (const file of sourceFiles) {
|
||||
if (TS_EXTENSIONS.has(file.extension)) {
|
||||
collectUsedKeysFromTsFile(
|
||||
file,
|
||||
baselineNamespaces,
|
||||
usedKeys,
|
||||
dynamicPrefixes,
|
||||
);
|
||||
} else {
|
||||
collectUsedKeysFromTextFile(file, baselineNamespaces, usedKeys);
|
||||
}
|
||||
}
|
||||
|
||||
return { usedKeys, dynamicPrefixes };
|
||||
}
|
||||
|
||||
function alignToBaseline(baselineNode, localeNode, options) {
|
||||
const shouldCopyLocale =
|
||||
localeNode && typeof localeNode === "object" && !Array.isArray(localeNode);
|
||||
@@ -355,12 +813,54 @@ function escapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function isKeyUsed(content, key) {
|
||||
if (WHITELIST_KEYS.has(key)) return true;
|
||||
if (!key) return false;
|
||||
function findKeyInSources(key, sourceFiles) {
|
||||
if (fileUsageCache.has(key)) {
|
||||
return fileUsageCache.get(key);
|
||||
}
|
||||
|
||||
const pattern = new RegExp(`(['"\`])${escapeRegExp(key)}\\1`);
|
||||
return pattern.test(content);
|
||||
let found = false;
|
||||
|
||||
for (const file of sourceFiles) {
|
||||
if (pattern.test(file.content)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fileUsageCache.set(key, found);
|
||||
return found;
|
||||
}
|
||||
|
||||
function isKeyUsed(key, usage, sourceFiles) {
|
||||
if (WHITELIST_KEYS.has(key)) return true;
|
||||
if (!key) return false;
|
||||
if (usage.usedKeys.has(key)) return true;
|
||||
|
||||
if (dynamicKeyCache.has(key)) {
|
||||
return dynamicKeyCache.get(key);
|
||||
}
|
||||
|
||||
const prefixes = getCandidatePrefixes(key);
|
||||
let used = prefixes.some((prefix) => usage.dynamicPrefixes.has(prefix));
|
||||
|
||||
if (!used) {
|
||||
used = findKeyInSources(key, sourceFiles);
|
||||
}
|
||||
|
||||
dynamicKeyCache.set(key, used);
|
||||
return used;
|
||||
}
|
||||
|
||||
function getCandidatePrefixes(key) {
|
||||
if (!key.includes(".")) return [];
|
||||
const parts = key.split(".");
|
||||
const prefixes = [];
|
||||
for (let index = 0; index < parts.length - 1; index += 1) {
|
||||
const prefix = `${parts.slice(0, index + 1).join(".")}.`;
|
||||
prefixes.push(prefix);
|
||||
}
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
function writeReport(reportPath, data) {
|
||||
@@ -403,7 +903,9 @@ function processLocale(
|
||||
locale,
|
||||
baselineData,
|
||||
baselineEntries,
|
||||
allSourceContent,
|
||||
usage,
|
||||
sourceFiles,
|
||||
missingFromSource,
|
||||
options,
|
||||
) {
|
||||
const raw = fs.readFileSync(locale.path, "utf8");
|
||||
@@ -415,15 +917,21 @@ function processLocale(
|
||||
|
||||
const unused = [];
|
||||
for (const key of flattened.keys()) {
|
||||
if (!isKeyUsed(allSourceContent, key)) {
|
||||
if (!isKeyUsed(key, usage, sourceFiles)) {
|
||||
unused.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceMissing =
|
||||
locale.name === options.baseline
|
||||
? missingFromSource.filter((key) => !flattened.has(key))
|
||||
: [];
|
||||
|
||||
if (
|
||||
unused.length === 0 &&
|
||||
missing.length === 0 &&
|
||||
extra.length === 0 &&
|
||||
sourceMissing.length === 0 &&
|
||||
!options.align
|
||||
) {
|
||||
console.log(`[${locale.name}] No issues detected 🎉`);
|
||||
@@ -432,6 +940,10 @@ function processLocale(
|
||||
console.log(
|
||||
` unused: ${unused.length}, missing vs baseline: ${missing.length}, extra: ${extra.length}`,
|
||||
);
|
||||
if (sourceMissing.length > 0) {
|
||||
console.log(` missing in source: ${sourceMissing.length}`);
|
||||
logPreviewEntries("missing-source", sourceMissing);
|
||||
}
|
||||
logPreviewEntries("unused", unused);
|
||||
logPreviewEntries("missing", missing);
|
||||
logPreviewEntries("extra", extra);
|
||||
@@ -479,6 +991,7 @@ function processLocale(
|
||||
removed,
|
||||
missingKeys: missing,
|
||||
extraKeys: extra,
|
||||
missingSourceKeys: sourceMissing,
|
||||
aligned: aligned && options.apply,
|
||||
};
|
||||
}
|
||||
@@ -505,7 +1018,7 @@ function main() {
|
||||
console.log(` - ${dir}`);
|
||||
}
|
||||
|
||||
const allSourceContent = loadSourceContents(sourceDirs);
|
||||
const sourceFiles = collectSourceFiles(sourceDirs);
|
||||
const locales = loadLocales();
|
||||
|
||||
if (locales.length === 0) {
|
||||
@@ -526,6 +1039,13 @@ function main() {
|
||||
|
||||
const baselineData = JSON.parse(fs.readFileSync(baselineLocale.path, "utf8"));
|
||||
const baselineEntries = flattenLocale(baselineData);
|
||||
const baselineNamespaces = new Set(Object.keys(baselineData));
|
||||
const usage = collectUsedI18nKeys(sourceFiles, baselineNamespaces);
|
||||
const baselineKeys = new Set(baselineEntries.keys());
|
||||
const missingFromSource = Array.from(usage.usedKeys).filter(
|
||||
(key) => !baselineKeys.has(key),
|
||||
);
|
||||
missingFromSource.sort();
|
||||
|
||||
locales.sort((a, b) => {
|
||||
if (a.name === baselineLocale.name) return -1;
|
||||
@@ -540,7 +1060,9 @@ function main() {
|
||||
locale,
|
||||
baselineData,
|
||||
baselineEntries,
|
||||
allSourceContent,
|
||||
usage,
|
||||
sourceFiles,
|
||||
missingFromSource,
|
||||
options,
|
||||
),
|
||||
);
|
||||
@@ -557,15 +1079,19 @@ function main() {
|
||||
(count, result) => count + result.extraKeys.length,
|
||||
0,
|
||||
);
|
||||
const totalSourceMissing = results.reduce(
|
||||
(count, result) => count + result.missingSourceKeys.length,
|
||||
0,
|
||||
);
|
||||
|
||||
console.log("\nSummary:");
|
||||
for (const result of results) {
|
||||
console.log(
|
||||
` • ${result.locale}: unused=${result.unusedKeys.length}, missing=${result.missingKeys.length}, extra=${result.extraKeys.length}, total=${result.totalKeys}, expected=${result.expectedKeys}`,
|
||||
` • ${result.locale}: unused=${result.unusedKeys.length}, missing=${result.missingKeys.length}, extra=${result.extraKeys.length}, missingSource=${result.missingSourceKeys.length}, total=${result.totalKeys}, expected=${result.expectedKeys}`,
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`\nTotals → unused: ${totalUnused}, missing: ${totalMissing}, extra: ${totalExtra}`,
|
||||
`\nTotals → unused: ${totalUnused}, missing: ${totalMissing}, extra: ${totalExtra}, missingSource: ${totalSourceMissing}`,
|
||||
);
|
||||
if (options.apply) {
|
||||
console.log(
|
||||
|
||||
@@ -58,11 +58,11 @@ export const ProviderButton = () => {
|
||||
await refreshRules();
|
||||
await refreshRuleProviders();
|
||||
|
||||
showNotice.success("notices.providers.updateSuccess", {
|
||||
showNotice.success("providers.notices.updateSuccess", {
|
||||
name,
|
||||
});
|
||||
} catch (err) {
|
||||
showNotice.error("notices.providers.updateFailed", {
|
||||
showNotice.error("providers.notices.updateFailed", {
|
||||
name,
|
||||
message: String(err),
|
||||
});
|
||||
@@ -78,7 +78,7 @@ export const ProviderButton = () => {
|
||||
// 获取所有provider的名称
|
||||
const allProviders = Object.keys(ruleProviders || {});
|
||||
if (allProviders.length === 0) {
|
||||
showNotice.info("notices.providers.none");
|
||||
showNotice.info("providers.notices.none");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,9 +108,9 @@ export const ProviderButton = () => {
|
||||
await refreshRules();
|
||||
await refreshRuleProviders();
|
||||
|
||||
showNotice.success("notices.providers.allUpdated");
|
||||
showNotice.success("providers.notices.allUpdated");
|
||||
} catch (err) {
|
||||
showNotice.error("notices.providers.genericError", {
|
||||
showNotice.error("providers.notices.genericError", {
|
||||
message: String(err),
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "تعديل القواعد",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "شرط القاعدة مطلوب",
|
||||
"invalidRule": "قاعدة غير صالحة"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "مطابقة اسم المجال الكامل",
|
||||
"DOMAIN-SUFFIX": "مطابقة لاحقة المجال",
|
||||
"DOMAIN-KEYWORD": "مطابقة كلمة مفتاحية في المجال",
|
||||
"DOMAIN-REGEX": "مطابقة المجال باستخدام التعبيرات العادية",
|
||||
"GEOSITE": "مطابقة المجالات ضمن Geosite",
|
||||
"GEOIP": "مطابقة رمز البلد لعنوان IP",
|
||||
"SRC-GEOIP": "مطابقة رمز البلد لعنوان IP المصدر",
|
||||
"IP-ASN": "مطابقة ASN لعنوان IP",
|
||||
"SRC-IP-ASN": "مطابقة ASN لعنوان IP المصدر",
|
||||
"IP-CIDR": "مطابقة نطاق عنوان IP",
|
||||
"IP-CIDR6": "مطابقة نطاق عناوين IPv6",
|
||||
"SRC-IP-CIDR": "مطابقة نطاق عنوان IP المصدر",
|
||||
"IP-SUFFIX": "مطابقة لاحقة عنوان IP",
|
||||
"SRC-IP-SUFFIX": "مطابقة لاحقة عنوان IP المصدر",
|
||||
"SRC-PORT": "مطابقة نطاق المنفذ المصدر",
|
||||
"DST-PORT": "مطابقة نطاق المنفذ الوجهة",
|
||||
"IN-PORT": "مطابقة المنفذ الوارد",
|
||||
"DSCP": "علامة DSCP (لـ tproxy على UDP فقط)",
|
||||
"PROCESS-NAME": "مطابقة اسم العملية (اسم حزمة Android)",
|
||||
"PROCESS-PATH": "مطابقة المسار الكامل للعملية",
|
||||
"PROCESS-NAME-REGEX": "مطابقة اسم العملية باستخدام التعبيرات العادية (اسم حزمة Android)",
|
||||
"PROCESS-PATH-REGEX": "مطابقة المسار الكامل للعملية باستخدام التعبيرات العادية",
|
||||
"NETWORK": "مطابقة بروتوكول النقل (TCP/UDP)",
|
||||
"UID": "مطابقة معرف المستخدم في Linux",
|
||||
"IN-TYPE": "مطابقة نوع الإدخال",
|
||||
"IN-USER": "مطابقة اسم المستخدم للإدخال",
|
||||
"IN-NAME": "مطابقة اسم الإدخال",
|
||||
"SUB-RULE": "قاعدة فرعية",
|
||||
"RULE-SET": "مطابقة مجموعة القواعد",
|
||||
"AND": "منطقي AND",
|
||||
"OR": "منطقي OR",
|
||||
"NOT": "منطقي NOT",
|
||||
"MATCH": "مطابقة جميع الطلبات"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "مجموعات الوكلاء",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 بروكسي السلسلة",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "التبديل إلى وكيل آخر عند حدوث خطأ",
|
||||
"load-balance": "توزيع التحميل بين الوكلاء",
|
||||
"relay": "التمرير عبر سلسلة الوكلاء المحددة"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "البيانات تخرج مباشرة",
|
||||
"REJECT": "رفض الطلبات",
|
||||
"REJECT-DROP": "تجاهل الطلبات",
|
||||
"PASS": "تخطي هذه القاعدة عند المطابقة"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Regeln bearbeiten",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Regelbedingung fehlt",
|
||||
"invalidRule": "Ungültige Regel"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Vollständigen Domainnamen übereinstimmen",
|
||||
"DOMAIN-SUFFIX": "Domain-Suffix übereinstimmen",
|
||||
"DOMAIN-KEYWORD": "Domain-Schlüsselwort übereinstimmen",
|
||||
"DOMAIN-REGEX": "Domain-Regulärer Ausdruck übereinstimmen",
|
||||
"GEOSITE": "Domainnamen in Geosite übereinstimmen",
|
||||
"GEOIP": "IP-Ländercode übereinstimmen",
|
||||
"SRC-GEOIP": "Quell-IP-Ländercode übereinstimmen",
|
||||
"IP-ASN": "IP-ASN übereinstimmen",
|
||||
"SRC-IP-ASN": "Quell-IP-ASN übereinstimmen",
|
||||
"IP-CIDR": "IP-Adressbereich übereinstimmen",
|
||||
"IP-CIDR6": "IP-Adressbereich übereinstimmen",
|
||||
"SRC-IP-CIDR": "Quell-IP-Adressbereich übereinstimmen",
|
||||
"IP-SUFFIX": "IP-Suffix-Bereich übereinstimmen",
|
||||
"SRC-IP-SUFFIX": "Quell-IP-Suffix-Bereich übereinstimmen",
|
||||
"SRC-PORT": "Quellportbereich der Anfrage übereinstimmen",
|
||||
"DST-PORT": "Zielportbereich der Anfrage übereinstimmen",
|
||||
"IN-PORT": "Eingangsport übereinstimmen",
|
||||
"DSCP": "DSCP-Markierung (nur für TPROXY UDP-Eingang)",
|
||||
"PROCESS-NAME": "Prozessnamen übereinstimmen (Android-Paketname)",
|
||||
"PROCESS-PATH": "Vollständigen Prozesspfad übereinstimmen",
|
||||
"PROCESS-NAME-REGEX": "Regulärer Ausdruck für vollständigen Prozessnamen übereinstimmen (Android-Paketname)",
|
||||
"PROCESS-PATH-REGEX": "Regulärer Ausdruck für vollständigen Prozesspfad übereinstimmen",
|
||||
"NETWORK": "Übertragungsprotokoll übereinstimmen (TCP/UDP)",
|
||||
"UID": "Linux-USER-ID übereinstimmen",
|
||||
"IN-TYPE": "Eingangstyp übereinstimmen",
|
||||
"IN-USER": "Eingangsbenutzername übereinstimmen",
|
||||
"IN-NAME": "Eingangsname übereinstimmen",
|
||||
"SUB-RULE": "Unterregel",
|
||||
"RULE-SET": "Regelsatz übereinstimmen",
|
||||
"AND": "Logisches UND",
|
||||
"OR": "Logisches ODER",
|
||||
"NOT": "Logisches NICHT",
|
||||
"MATCH": "Alle Anfragen übereinstimmen"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Proxy-Gruppen",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Ketten-Proxy",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Bei Nichtverfügbarkeit zu einem anderen Proxy wechseln",
|
||||
"load-balance": "Proxy basierend auf Lastverteilung zuweisen",
|
||||
"relay": "Basierend auf definiertem Proxy-Kette weiterleiten"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Direktverbindung",
|
||||
"REJECT": "Anfrage ablehnen",
|
||||
"REJECT-DROP": "Anfrage verwerfen",
|
||||
"PASS": "Diese Regel überspringen"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Edit Rules",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Rule Condition Required",
|
||||
"invalidRule": "Invalid Rule"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Matches the full domain name",
|
||||
"DOMAIN-SUFFIX": "Matches the domain suffix",
|
||||
"DOMAIN-KEYWORD": "Matches the domain keyword",
|
||||
"DOMAIN-REGEX": "Matches the domain using regular expressions",
|
||||
"GEOSITE": "Matches domains within the Geosite",
|
||||
"GEOIP": "Matches the country code of the IP address",
|
||||
"SRC-GEOIP": "Matches the country code of the source IP address",
|
||||
"IP-ASN": "Matches the IP address's ASN",
|
||||
"SRC-IP-ASN": "Matches the source IP address's ASN",
|
||||
"IP-CIDR": "Matches the IP address range",
|
||||
"IP-CIDR6": "Matches the IPv6 address range",
|
||||
"SRC-IP-CIDR": "Matches the source IP address range",
|
||||
"IP-SUFFIX": "Matches the IP address suffix range",
|
||||
"SRC-IP-SUFFIX": "Matches the source IP address suffix range",
|
||||
"SRC-PORT": "Matches the source port range",
|
||||
"DST-PORT": "Matches the destination port range",
|
||||
"IN-PORT": "Matches the inbound port",
|
||||
"DSCP": "DSCP marking (only for tproxy UDP inbound)",
|
||||
"PROCESS-NAME": "Matches the process name (Android package name)",
|
||||
"PROCESS-PATH": "Matches the full process path",
|
||||
"PROCESS-NAME-REGEX": "Matches the full process name using regular expressions (Android package name)",
|
||||
"PROCESS-PATH-REGEX": "Matches the full process path using regular expressions",
|
||||
"NETWORK": "Matches the transport protocol (tcp/udp)",
|
||||
"UID": "Matches the Linux USER ID",
|
||||
"IN-TYPE": "Matches the inbound type",
|
||||
"IN-USER": "Matches the inbound username",
|
||||
"IN-NAME": "Matches the inbound name",
|
||||
"SUB-RULE": "Sub-rule",
|
||||
"RULE-SET": "Matches the rule set",
|
||||
"AND": "Logical AND",
|
||||
"OR": "Logical OR",
|
||||
"NOT": "Logical NOT",
|
||||
"MATCH": "Matches all requests"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Proxy Groups",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Chain Proxy",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Switch to another proxy on error",
|
||||
"load-balance": "Distribute proxy based on load balancing",
|
||||
"relay": "Pass through the defined proxy chain"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Data goes directly outbound",
|
||||
"REJECT": "Intercepts requests",
|
||||
"REJECT-DROP": "Discards requests",
|
||||
"PASS": "Skips this rule when matched"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Editar reglas",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Falta la condición de la regla",
|
||||
"invalidRule": "Regla no válida"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Coincidir con el nombre de dominio completo",
|
||||
"DOMAIN-SUFFIX": "Coincidir con el sufijo del nombre de dominio",
|
||||
"DOMAIN-KEYWORD": "Coincidir con la palabra clave del nombre de dominio",
|
||||
"DOMAIN-REGEX": "Coincidir con la expresión regular del nombre de dominio",
|
||||
"GEOSITE": "Coincidir con los nombres de dominio en Geosite",
|
||||
"GEOIP": "Coincidir con el código de país del IP",
|
||||
"SRC-GEOIP": "Coincidir con el código de país del IP de origen",
|
||||
"IP-ASN": "Coincidir con el ASN del IP",
|
||||
"SRC-IP-ASN": "Coincidir con el ASN del IP de origen",
|
||||
"IP-CIDR": "Coincidir con el rango de direcciones IP",
|
||||
"IP-CIDR6": "Coincidir con el rango de direcciones IP",
|
||||
"SRC-IP-CIDR": "Coincidir con el rango de direcciones IP de origen",
|
||||
"IP-SUFFIX": "Coincidir con el rango de sufijos de IP",
|
||||
"SRC-IP-SUFFIX": "Coincidir con el rango de sufijos de IP de origen",
|
||||
"SRC-PORT": "Coincidir con el rango de puertos de origen de la solicitud",
|
||||
"DST-PORT": "Coincidir con el rango de puertos de destino de la solicitud",
|
||||
"IN-PORT": "Coincidir con el puerto de entrada",
|
||||
"DSCP": "Etiqueta DSCP (solo para entradas UDP TPROXY)",
|
||||
"PROCESS-NAME": "Coincidir con el nombre del proceso (nombre del paquete de Android)",
|
||||
"PROCESS-PATH": "Coincidir con la ruta completa del proceso",
|
||||
"PROCESS-NAME-REGEX": "Coincidir con el nombre completo del proceso mediante expresiones regulares (nombre del paquete de Android)",
|
||||
"PROCESS-PATH-REGEX": "Coincidir con la ruta completa del proceso mediante expresiones regulares",
|
||||
"NETWORK": "Coincidir con el protocolo de transporte (TCP/UDP)",
|
||||
"UID": "Coincidir con el ID de usuario de Linux",
|
||||
"IN-TYPE": "Coincidir con el tipo de entrada",
|
||||
"IN-USER": "Coincidir con el nombre de usuario de entrada",
|
||||
"IN-NAME": "Coincidir con el nombre de entrada",
|
||||
"SUB-RULE": "Subregla",
|
||||
"RULE-SET": "Coincidir con el conjunto de reglas",
|
||||
"AND": "Y lógico",
|
||||
"OR": "O lógico",
|
||||
"NOT": "No lógico",
|
||||
"MATCH": "Coincidir con todas las solicitudes"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Grupos de proxies",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Proxy en cadena",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Cambiar a otro proxy cuando no esté disponible",
|
||||
"load-balance": "Asignar proxy según el equilibrio de carga",
|
||||
"relay": "Transferir según la cadena de proxy definida"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Conexión directa",
|
||||
"REJECT": "Rechazar solicitud",
|
||||
"REJECT-DROP": "Descartar solicitud",
|
||||
"PASS": "Saltar esta regla"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "ویرایش قوانین",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "شرط قانون الزامی است",
|
||||
"invalidRule": "قانون نامعتبر"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "مطابقت با نام کامل دامنه",
|
||||
"DOMAIN-SUFFIX": "مطابقت با پسوند دامنه",
|
||||
"DOMAIN-KEYWORD": "مطابقت با کلمه کلیدی دامنه",
|
||||
"DOMAIN-REGEX": "مطابقت با دامنه با استفاده از عبارات منظم",
|
||||
"GEOSITE": "مطابقت با دامنههای درون Geosite",
|
||||
"GEOIP": "مطابقت با کد کشور IP",
|
||||
"SRC-GEOIP": "مطابقت با کد کشور IP مبدا",
|
||||
"IP-ASN": "مطابقت با ASN آدرس IP",
|
||||
"SRC-IP-ASN": "مطابقت با ASN آدرس IP مبدا",
|
||||
"IP-CIDR": "مطابقت با محدوده آدرس IP",
|
||||
"IP-CIDR6": "مطابقت با محدوده آدرس IPv6",
|
||||
"SRC-IP-CIDR": "مطابقت با محدوده آدرس IP مبدا",
|
||||
"IP-SUFFIX": "مطابقت با محدوده پسوند آدرس IP",
|
||||
"SRC-IP-SUFFIX": "مطابقت با محدوده پسوند آدرس IP مبدا",
|
||||
"SRC-PORT": "مطابقت با محدوده پورت مبدا",
|
||||
"DST-PORT": "مطابقت با محدوده پورت مقصد",
|
||||
"IN-PORT": "مطابقت با پورت ورودی",
|
||||
"DSCP": "علامتگذاری DSCP (فقط برای tproxy UDP ورودی)",
|
||||
"PROCESS-NAME": "مطابقت با نام فرآیند (نام بسته Android)",
|
||||
"PROCESS-PATH": "مطابقت با مسیر کامل فرآیند",
|
||||
"PROCESS-NAME-REGEX": "مطابقت با نام فرآیند با استفاده از عبارات منظم (نام بسته Android)",
|
||||
"PROCESS-PATH-REGEX": "مطابقت با مسیر کامل فرآیند با استفاده از عبارات منظم",
|
||||
"NETWORK": "مطابقت با پروتکل انتقال (tcp/udp)",
|
||||
"UID": "مطابقت با شناسه کاربری Linux",
|
||||
"IN-TYPE": "مطابقت با نوع ورودی",
|
||||
"IN-USER": "مطابقت با نام کاربری ورودی",
|
||||
"IN-NAME": "مطابقت با نام ورودی",
|
||||
"SUB-RULE": "قانون فرعی",
|
||||
"RULE-SET": "مطابقت با مجموعه قوانین",
|
||||
"AND": "منطق AND",
|
||||
"OR": "منطق OR",
|
||||
"NOT": "منطق NOT",
|
||||
"MATCH": "مطابقت با تمام درخواستها"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "گروههای پراکسی",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 پراکسی زنجیرهای",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "تعویض به پروکسی دیگر در صورت بروز خطا",
|
||||
"load-balance": "توزیع پراکسی بر اساس توازن بار",
|
||||
"relay": "عبور از زنجیره پروکسی تعریف شده"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "دادهها به صورت مستقیم خروجی میشوند",
|
||||
"REJECT": "درخواستها را متوقف میکند",
|
||||
"REJECT-DROP": "درخواستها را نادیده میگیرد",
|
||||
"PASS": "این قانون را در صورت تطابق نادیده میگیرد"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Ubah Aturan",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Kondisi Aturan Diperlukan",
|
||||
"invalidRule": "Aturan Tidak Valid"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Cocok dengan nama domain lengkap",
|
||||
"DOMAIN-SUFFIX": "Cocok dengan sufiks domain",
|
||||
"DOMAIN-KEYWORD": "Cocok dengan kata kunci domain",
|
||||
"DOMAIN-REGEX": "Cocok dengan domain menggunakan ekspresi reguler",
|
||||
"GEOSITE": "Cocok dengan domain dalam Geosite",
|
||||
"GEOIP": "Cocok dengan kode negara alamat IP",
|
||||
"SRC-GEOIP": "Cocok dengan kode negara alamat IP sumber",
|
||||
"IP-ASN": "Cocok dengan ASN alamat IP",
|
||||
"SRC-IP-ASN": "Cocok dengan ASN alamat IP sumber",
|
||||
"IP-CIDR": "Cocok dengan rentang alamat IP",
|
||||
"IP-CIDR6": "Cocok dengan rentang alamat IPv6",
|
||||
"SRC-IP-CIDR": "Cocok dengan rentang alamat IP sumber",
|
||||
"IP-SUFFIX": "Cocok dengan rentang sufiks alamat IP",
|
||||
"SRC-IP-SUFFIX": "Cocok dengan rentang sufiks alamat IP sumber",
|
||||
"SRC-PORT": "Cocok dengan rentang port sumber",
|
||||
"DST-PORT": "Cocok dengan rentang port tujuan",
|
||||
"IN-PORT": "Cocok dengan port masuk",
|
||||
"DSCP": "Penandaan DSCP (hanya untuk tproxy UDP masuk)",
|
||||
"PROCESS-NAME": "Cocok dengan nama proses (nama paket Android)",
|
||||
"PROCESS-PATH": "Cocok dengan jalur proses lengkap",
|
||||
"PROCESS-NAME-REGEX": "Cocok dengan nama proses lengkap menggunakan ekspresi reguler (nama paket Android)",
|
||||
"PROCESS-PATH-REGEX": "Cocok dengan jalur proses lengkap menggunakan ekspresi reguler",
|
||||
"NETWORK": "Cocok dengan protokol transportasi (tcp/udp)",
|
||||
"UID": "Cocok dengan ID PENGGUNA Linux",
|
||||
"IN-TYPE": "Cocok dengan jenis masuk",
|
||||
"IN-USER": "Cocok dengan nama pengguna masuk",
|
||||
"IN-NAME": "Cocok dengan nama masuk",
|
||||
"SUB-RULE": "Sub-aturan",
|
||||
"RULE-SET": "Cocok dengan set aturan",
|
||||
"AND": "Logika DAN",
|
||||
"OR": "Logika ATAU",
|
||||
"NOT": "Logika TIDAK",
|
||||
"MATCH": "Cocok dengan semua permintaan"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Grup Proksi",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Proxy Rantai",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Beralih ke proksi lain saat terjadi kesalahan",
|
||||
"load-balance": "Distribusikan proksi berdasarkan penyeimbangan beban",
|
||||
"relay": "Lewatkan melalui rantai proksi yang ditentukan"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Data langsung keluar",
|
||||
"REJECT": "Mencegat permintaan",
|
||||
"REJECT-DROP": "Membuang permintaan",
|
||||
"PASS": "Lewati aturan ini saat cocok"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "ルールを編集",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "ルール条件が必要です",
|
||||
"invalidRule": "無効なルール"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "完全なドメイン名を一致させる",
|
||||
"DOMAIN-SUFFIX": "ドメインサフィックスを一致させる",
|
||||
"DOMAIN-KEYWORD": "ドメインキーワードを一致させる",
|
||||
"DOMAIN-REGEX": "ドメイン正規表現を一致させる",
|
||||
"GEOSITE": "Geosite内のドメインを一致させる",
|
||||
"GEOIP": "IPの所属国コードを一致させる",
|
||||
"SRC-GEOIP": "送信元IPの所属国コードを一致させる",
|
||||
"IP-ASN": "IPの所属ASNを一致させる",
|
||||
"SRC-IP-ASN": "送信元IPの所属ASNを一致させる",
|
||||
"IP-CIDR": "IPアドレス範囲を一致させる",
|
||||
"IP-CIDR6": "IPアドレス範囲を一致させる",
|
||||
"SRC-IP-CIDR": "送信元IPアドレス範囲を一致させる",
|
||||
"IP-SUFFIX": "IPサフィックス範囲を一致させる",
|
||||
"SRC-IP-SUFFIX": "送信元IPサフィックス範囲を一致させる",
|
||||
"SRC-PORT": "送信元ポート範囲を一致させる",
|
||||
"DST-PORT": "宛先ポート範囲を一致させる",
|
||||
"IN-PORT": "入力ポートを一致させる",
|
||||
"DSCP": "DSCPマーク(TPROXY UDP入力のみ)",
|
||||
"PROCESS-NAME": "プロセス名を一致させる(Androidパッケージ名)",
|
||||
"PROCESS-PATH": "完全なプロセスパスを一致させる",
|
||||
"PROCESS-NAME-REGEX": "完全なプロセス名を正規表現で一致させる(Androidパッケージ名)",
|
||||
"PROCESS-PATH-REGEX": "完全なプロセスパスを正規表現で一致させる",
|
||||
"NETWORK": "トランスポートプロトコルを一致させる (TCP/UDP)",
|
||||
"UID": "LinuxユーザーIDを一致させる",
|
||||
"IN-TYPE": "入力タイプを一致させる",
|
||||
"IN-USER": "入力ユーザー名を一致させる",
|
||||
"IN-NAME": "入力名を一致させる",
|
||||
"SUB-RULE": "サブルール",
|
||||
"RULE-SET": "ルールセットを一致させる",
|
||||
"AND": "論理積",
|
||||
"OR": "論理和",
|
||||
"NOT": "論理否定",
|
||||
"MATCH": "すべてのリクエストを一致させる"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "プロキシグループ",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 チェーンプロキシ",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "利用不可の場合は別のプロキシに切り替える",
|
||||
"load-balance": "負荷分散によりプロキシを割り当てる",
|
||||
"relay": "定義されたプロキシチェーンに沿って転送する"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "直接接続",
|
||||
"REJECT": "リクエストを拒否",
|
||||
"REJECT-DROP": "リクエストを破棄",
|
||||
"PASS": "このルールをスキップ"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "규칙 편집",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "규칙 조건 필요",
|
||||
"invalidRule": "잘못된 규칙"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "전체 도메인 이름과 일치",
|
||||
"DOMAIN-SUFFIX": "도메인 접미사와 일치",
|
||||
"DOMAIN-KEYWORD": "도메인 키워드와 일치",
|
||||
"DOMAIN-REGEX": "정규 표현식을 사용한 도메인 일치",
|
||||
"GEOSITE": "Geosite 내의 도메인과 일치",
|
||||
"GEOIP": "IP 주소의 국가 코드와 일치",
|
||||
"SRC-GEOIP": "소스 IP 주소의 국가 코드와 일치",
|
||||
"IP-ASN": "IP 주소의 ASN과 일치",
|
||||
"SRC-IP-ASN": "소스 IP 주소의 ASN과 일치",
|
||||
"IP-CIDR": "IP 주소 범위와 일치",
|
||||
"IP-CIDR6": "IPv6 주소 범위와 일치",
|
||||
"SRC-IP-CIDR": "소스 IP 주소 범위와 일치",
|
||||
"IP-SUFFIX": "IP 주소 접미사 범위와 일치",
|
||||
"SRC-IP-SUFFIX": "소스 IP 주소 접미사 범위와 일치",
|
||||
"SRC-PORT": "소스 포트 범위와 일치",
|
||||
"DST-PORT": "대상 포트 범위와 일치",
|
||||
"IN-PORT": "인바운드 포트와 일치",
|
||||
"DSCP": "DSCP 마킹(tproxy UDP 인바운드만 해당)",
|
||||
"PROCESS-NAME": "프로세스 이름과 일치(안드로이드 패키지 이름)",
|
||||
"PROCESS-PATH": "전체 프로세스 경로와 일치",
|
||||
"PROCESS-NAME-REGEX": "정규 표현식을 사용한 전체 프로세스 이름 일치(안드로이드 패키지 이름)",
|
||||
"PROCESS-PATH-REGEX": "정규 표현식을 사용한 전체 프로세스 경로 일치",
|
||||
"NETWORK": "전송 프로토콜과 일치(tcp/udp)",
|
||||
"UID": "Linux 사용자 ID와 일치",
|
||||
"IN-TYPE": "인바운드 유형과 일치",
|
||||
"IN-USER": "인바운드 사용자 이름과 일치",
|
||||
"IN-NAME": "인바운드 이름과 일치",
|
||||
"SUB-RULE": "하위 규칙",
|
||||
"RULE-SET": "규칙 세트와 일치",
|
||||
"AND": "논리 AND",
|
||||
"OR": "논리 OR",
|
||||
"NOT": "논리 NOT",
|
||||
"MATCH": "모든 요청과 일치"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "프록시 그룹",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 체인 프록시",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "오류 발생 시 다른 프록시로 전환",
|
||||
"load-balance": "부하 분산에 따라 프록시 분배",
|
||||
"relay": "정의된 프록시 체인을 통과"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "데이터가 직접 아웃바운드로 이동",
|
||||
"REJECT": "요청 차단",
|
||||
"REJECT-DROP": "요청 폐기",
|
||||
"PASS": "일치할 경우 이 규칙 건너뛰기"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Редактировать правила",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Требуется условие правила",
|
||||
"invalidRule": "Недействительное правило"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Соответствует полному доменному имени",
|
||||
"DOMAIN-SUFFIX": "Соответствует суффиксу домена",
|
||||
"DOMAIN-KEYWORD": "Соответствует ключевому слову домена",
|
||||
"DOMAIN-REGEX": "Соответствует домену с использованием регулярных выражений",
|
||||
"GEOSITE": "Соответствует доменам в Geosite",
|
||||
"GEOIP": "Соответствует коду страны IP-адреса",
|
||||
"SRC-GEOIP": "Соответствует коду страны исходного IP-адреса",
|
||||
"IP-ASN": "Соответствует ASN IP-адреса",
|
||||
"SRC-IP-ASN": "Соответствует ASN исходного IP-адреса",
|
||||
"IP-CIDR": "Соответствует диапазону IP-адресов",
|
||||
"IP-CIDR6": "Соответствует диапазону IPv6-адресов",
|
||||
"SRC-IP-CIDR": "Соответствует диапазону исходных IP-адресов",
|
||||
"IP-SUFFIX": "Соответствует диапазону суффиксов IP-адресов",
|
||||
"SRC-IP-SUFFIX": "Соответствует диапазону суффиксов исходных IP-адресов",
|
||||
"SRC-PORT": "Соответствует диапазону исходных портов",
|
||||
"DST-PORT": "Соответствует диапазону целевых портов",
|
||||
"IN-PORT": "Соответствует входящему порту",
|
||||
"DSCP": "Маркировка DSCP (только для tproxy UDP входящего)",
|
||||
"PROCESS-NAME": "Соответствует имени процесса (имя пакета Android)",
|
||||
"PROCESS-PATH": "Соответствует полному пути процесса",
|
||||
"PROCESS-NAME-REGEX": "Соответствует имени процесса с использованием регулярных выражений (имя пакета Android)",
|
||||
"PROCESS-PATH-REGEX": "Соответствует полному пути процесса с использованием регулярных выражений",
|
||||
"NETWORK": "Соответствует транспортному протоколу (tcp/udp)",
|
||||
"UID": "Соответствует USER ID в Linux",
|
||||
"IN-TYPE": "Соответствует типу входящего соединения",
|
||||
"IN-USER": "Соответствует имени пользователя входящего соединения",
|
||||
"IN-NAME": "Соответствует имени входящего соединения",
|
||||
"SUB-RULE": "Подправило",
|
||||
"RULE-SET": "Соответствует набору правил",
|
||||
"AND": "Логическое И",
|
||||
"OR": "Логическое ИЛИ",
|
||||
"NOT": "Логическое НЕ",
|
||||
"MATCH": "Соответствует всем запросам"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Группы прокси",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Цепной прокси",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Переключение на другой прокси при ошибке",
|
||||
"load-balance": "Распределение прокси на основе балансировки нагрузки",
|
||||
"relay": "Передача через определенную цепочку прокси"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Данные направляются напрямую наружу",
|
||||
"REJECT": "Перехватывает запросы",
|
||||
"REJECT-DROP": "Отклоняет запросы",
|
||||
"PASS": "Пропускает это правило при совпадении"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Kuralları Düzenle",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Kural Koşulu Gerekli",
|
||||
"invalidRule": "Geçersiz Kural"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Tam alan adıyla eşleşir",
|
||||
"DOMAIN-SUFFIX": "Alan adı sonekiyle eşleşir",
|
||||
"DOMAIN-KEYWORD": "Alan adı anahtar kelimesiyle eşleşir",
|
||||
"DOMAIN-REGEX": "Alan adını düzenli ifadeler kullanarak eşleştirir",
|
||||
"GEOSITE": "Geosite içindeki alan adlarıyla eşleşir",
|
||||
"GEOIP": "IP adresinin ülke koduyla eşleşir",
|
||||
"SRC-GEOIP": "Kaynak IP adresinin ülke koduyla eşleşir",
|
||||
"IP-ASN": "IP adresinin ASN'siyle eşleşir",
|
||||
"SRC-IP-ASN": "Kaynak IP adresinin ASN'siyle eşleşir",
|
||||
"IP-CIDR": "IP adresi aralığıyla eşleşir",
|
||||
"IP-CIDR6": "IPv6 adresi aralığıyla eşleşir",
|
||||
"SRC-IP-CIDR": "Kaynak IP adresi aralığıyla eşleşir",
|
||||
"IP-SUFFIX": "IP adresi sonek aralığıyla eşleşir",
|
||||
"SRC-IP-SUFFIX": "Kaynak IP adresi sonek aralığıyla eşleşir",
|
||||
"SRC-PORT": "Kaynak port aralığıyla eşleşir",
|
||||
"DST-PORT": "Hedef port aralığıyla eşleşir",
|
||||
"IN-PORT": "Gelen port ile eşleşir",
|
||||
"DSCP": "DSCP işaretlemesi (sadece tvekil UDP girişi için)",
|
||||
"PROCESS-NAME": "İşlem adıyla eşleşir (Android paket adı)",
|
||||
"PROCESS-PATH": "Tam işlem yoluyla eşleşir",
|
||||
"PROCESS-NAME-REGEX": "Tam işlem adını düzenli ifadeler kullanarak eşleştirir (Android paket adı)",
|
||||
"PROCESS-PATH-REGEX": "Tam işlem yolunu düzenli ifadeler kullanarak eşleştirir",
|
||||
"NETWORK": "Taşıma protokolüyle eşleşir (tcp/udp)",
|
||||
"UID": "Linux KULLANICI ID'siyle eşleşir",
|
||||
"IN-TYPE": "Gelen bağlantı tipiyle eşleşir",
|
||||
"IN-USER": "Gelen bağlantı kullanıcı adıyla eşleşir",
|
||||
"IN-NAME": "Gelen bağlantı adıyla eşleşir",
|
||||
"SUB-RULE": "Alt kural",
|
||||
"RULE-SET": "Kural setiyle eşleşir",
|
||||
"AND": "Mantıksal VE",
|
||||
"OR": "Mantıksal VEYA",
|
||||
"NOT": "Mantıksal DEĞİL",
|
||||
"MATCH": "Tüm isteklerle eşleşir"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Vekil Grupları",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Zincir Proxy",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Hata durumunda başka bir vekil'e geçin",
|
||||
"load-balance": "Yük dengelemeye göre vekil dağıtın",
|
||||
"relay": "Tanımlanan vekil zincirinden geçirin"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Veri doğrudan dışarı gider",
|
||||
"REJECT": "İstekleri engeller",
|
||||
"REJECT-DROP": "İstekleri atar",
|
||||
"PASS": "Eşleştiğinde bu kuralı atlar"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "Кагыйдәләрне үзгәртү",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "Кагыйдә шарты кирәк",
|
||||
"invalidRule": "Яраксыз кагыйдә"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "Домен исеменең тулы туры килүе",
|
||||
"DOMAIN-SUFFIX": "Домен суффиксына туры килү",
|
||||
"DOMAIN-KEYWORD": "Доменда төп сүзгә туры килү",
|
||||
"DOMAIN-REGEX": "Доменны регекс аша туры китерү",
|
||||
"GEOSITE": "Geosite исемлегендәге доменга туры килү",
|
||||
"GEOIP": "IP-адресның ил коды буенча туры килү",
|
||||
"SRC-GEOIP": "Чыганак IP-адресның ил коды буенча туры килү",
|
||||
"IP-ASN": "IP-адрес ASN'ы буенча туры килү",
|
||||
"SRC-IP-ASN": "Чыганак IP-адрес ASN'ы буенча туры килү",
|
||||
"IP-CIDR": "IP-адреслар диапазонына туры килү",
|
||||
"IP-CIDR6": "IPv6 адреслар диапазонына туры килү",
|
||||
"SRC-IP-CIDR": "Чыганак IP-адреслар диапазонына туры килү",
|
||||
"IP-SUFFIX": "IP-адрес суффиксына туры килү",
|
||||
"SRC-IP-SUFFIX": "Чыганак IP-адрес суффиксына туры килү",
|
||||
"SRC-PORT": "Чыганак портлар диапазонына туры килү",
|
||||
"DST-PORT": "Максат портлар диапазонына туры килү",
|
||||
"IN-PORT": "Керүче портка туры килү",
|
||||
"DSCP": "DSCP тамгалавы (tproxy UDP өчен)",
|
||||
"PROCESS-NAME": "Процесс исеменә туры килү (Android пакет исеме)",
|
||||
"PROCESS-PATH": "Процесс юлына туры килү",
|
||||
"PROCESS-NAME-REGEX": "Процесс исемен регекс белән туры китерү (Android пакет исеме)",
|
||||
"PROCESS-PATH-REGEX": "Процесс юлын регекс белән туры китерү",
|
||||
"NETWORK": "Транспорт протоколына (tcp/udp) туры килү",
|
||||
"UID": "Linux USER ID'га туры килү",
|
||||
"IN-TYPE": "Керүче тоташу төренә туры килү",
|
||||
"IN-USER": "Керүче тоташу кулланучысына туры килү",
|
||||
"IN-NAME": "Керүче тоташу исеменә туры килү",
|
||||
"SUB-RULE": "Кушымча кагыйдә",
|
||||
"RULE-SET": "Кагыйдәләр тупланмасына туры килү",
|
||||
"AND": "Логик ҺӘМ",
|
||||
"OR": "Логик ЯКИ",
|
||||
"NOT": "Логик ТҮГЕЛ",
|
||||
"MATCH": "Барлык сорауларга туры килә"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "Прокси төркемнәре",
|
||||
"chainMode": "Proxy Chain Mode"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "Rule",
|
||||
"global": "Global",
|
||||
"direct": "Direct"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 Чылбыр прокси",
|
||||
"connect": "Connect",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "Хата булган очракта башка проксига күчү",
|
||||
"load-balance": "Трафикны баланслау нигезендә прокси тарату",
|
||||
"relay": "Билгеле прокси чылбыры аша тапшыру"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "Туры чыгу",
|
||||
"REJECT": "Сорауларны тоткарлау",
|
||||
"REJECT-DROP": "Сорауларны кире кагу",
|
||||
"PASS": "Туры килсә дә, бу кагыйдәне урап узу"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "锁定菜单排序"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "浅色",
|
||||
"dark": "深色",
|
||||
"system": "系统"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "编辑规则",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "规则条件缺失",
|
||||
"invalidRule": "无效规则"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "匹配完整域名",
|
||||
"DOMAIN-SUFFIX": "匹配域名后缀",
|
||||
"DOMAIN-KEYWORD": "匹配域名关键字",
|
||||
"DOMAIN-REGEX": "匹配域名正则表达式",
|
||||
"GEOSITE": "匹配 Geosite 内的域名",
|
||||
"GEOIP": "匹配 IP 所属国家代码",
|
||||
"SRC-GEOIP": "匹配来源 IP 所属国家代码",
|
||||
"IP-ASN": "匹配 IP 所属 ASN",
|
||||
"SRC-IP-ASN": "匹配来源 IP 所属 ASN",
|
||||
"IP-CIDR": "匹配 IP 地址范围",
|
||||
"IP-CIDR6": "匹配 IP 地址范围",
|
||||
"SRC-IP-CIDR": "匹配来源 IP 地址范围",
|
||||
"IP-SUFFIX": "匹配 IP 后缀范围",
|
||||
"SRC-IP-SUFFIX": "匹配来源 IP 后缀范围",
|
||||
"SRC-PORT": "匹配请求来源端口范围",
|
||||
"DST-PORT": "匹配请求目标端口范围",
|
||||
"IN-PORT": "匹配入站端口",
|
||||
"DSCP": "DSCP标记(仅限 TPROXY UDP 入站)",
|
||||
"PROCESS-NAME": "匹配进程名称(Android 包名)",
|
||||
"PROCESS-PATH": "匹配完整进程路径",
|
||||
"PROCESS-NAME-REGEX": "正则匹配完整进程名称(Android 包名)",
|
||||
"PROCESS-PATH-REGEX": "正则匹配完整进程路径",
|
||||
"NETWORK": "匹配传输协议 (TCP/UDP)",
|
||||
"UID": "匹配 Linux USER ID",
|
||||
"IN-TYPE": "匹配入站类型",
|
||||
"IN-USER": "匹配入站用户名",
|
||||
"IN-NAME": "匹配入站名称",
|
||||
"SUB-RULE": "子规则",
|
||||
"RULE-SET": "匹配规则集",
|
||||
"AND": "逻辑和",
|
||||
"OR": "逻辑或",
|
||||
"NOT": "逻辑非",
|
||||
"MATCH": "匹配所有请求"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -123,7 +163,7 @@
|
||||
"name": "名称",
|
||||
"description": "描述",
|
||||
"subscriptionUrl": "订阅链接",
|
||||
"httpTimeout": "HTTP Request Timeout",
|
||||
"httpTimeout": "HTTP 请求超时",
|
||||
"updateInterval": "更新间隔",
|
||||
"useSystemProxy": "使用系统代理更新",
|
||||
"useClashProxy": "使用内核代理更新",
|
||||
@@ -204,8 +244,8 @@
|
||||
},
|
||||
"item": {
|
||||
"tooltips": {
|
||||
"showLast": "Click to show last update time",
|
||||
"showNext": "Click to show next update"
|
||||
"showLast": "点击查看上次更新时间",
|
||||
"showNext": "点击查看下一次更新"
|
||||
},
|
||||
"status": {
|
||||
"lastUpdateFailed": "上次更新失败",
|
||||
@@ -226,8 +266,8 @@
|
||||
},
|
||||
"more": {
|
||||
"global": {
|
||||
"merge": "Global Merge",
|
||||
"script": "Global Script"
|
||||
"merge": "全局扩展覆写配置",
|
||||
"script": "全局扩展脚本"
|
||||
},
|
||||
"chips": {
|
||||
"merge": "Merge",
|
||||
@@ -258,7 +298,7 @@
|
||||
"fields": {
|
||||
"coreVersion": "内核版本",
|
||||
"systemProxyAddress": "系统代理地址",
|
||||
"mixedPort": "Mixed Port",
|
||||
"mixedPort": "混合代理端口",
|
||||
"uptime": "运行时间",
|
||||
"rulesCount": "规则数量"
|
||||
}
|
||||
@@ -292,10 +332,10 @@
|
||||
"ipInfo": {
|
||||
"title": "IP 信息",
|
||||
"errors": {
|
||||
"load": "获取IP信息失败"
|
||||
"load": "获取 IP 信息失败"
|
||||
},
|
||||
"labels": {
|
||||
"retry": "Retry",
|
||||
"retry": "重试",
|
||||
"ip": "IP",
|
||||
"asn": "自治域",
|
||||
"isp": "服务商",
|
||||
@@ -320,7 +360,7 @@
|
||||
"download": "下载"
|
||||
},
|
||||
"patterns": {
|
||||
"minutes": "{{time}} Minutes"
|
||||
"minutes": "{{time}} 分钟"
|
||||
}
|
||||
},
|
||||
"currentProxy": {
|
||||
@@ -384,14 +424,14 @@
|
||||
"communication": "内核通信错误"
|
||||
},
|
||||
"labels": {
|
||||
"rule": "规则模式",
|
||||
"global": "全局模式",
|
||||
"direct": "直连模式"
|
||||
"rule": "规则",
|
||||
"global": "全局",
|
||||
"direct": "直连"
|
||||
},
|
||||
"descriptions": {
|
||||
"rule": "按照规则自动选择代理。",
|
||||
"global": "将所有网络请求转发到选定的代理。",
|
||||
"direct": "绕过代理,直接连接互联网。"
|
||||
"rule": "基于预设规则智能判断流量走向,提供灵活的代理策略",
|
||||
"global": "所有流量均通过代理服务器,适用于需要全局科学上网的场景",
|
||||
"direct": "所有流量不经过代理节点,但经过Clash内核转发连接目标服务器,适用于需要通过内核进行分流的特定场景"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -415,7 +455,7 @@
|
||||
"closeConnection": "关闭连接"
|
||||
},
|
||||
"order": {
|
||||
"default": "Default",
|
||||
"default": "默认",
|
||||
"uploadSpeed": "上传速度",
|
||||
"downloadSpeed": "下载速度"
|
||||
}
|
||||
@@ -611,7 +651,7 @@
|
||||
"backupTime": "备份时间",
|
||||
"actions": "操作",
|
||||
"noBackups": "暂无备份",
|
||||
"rowsPerPage": "Rows per page"
|
||||
"rowsPerPage": "每页行数"
|
||||
}
|
||||
},
|
||||
"verge": {
|
||||
@@ -664,7 +704,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"latestVersion": "当前已是最新版本",
|
||||
"versionCopied": "Verge版本已复制到剪贴板"
|
||||
"versionCopied": "Verge 版本已复制到剪贴板"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
@@ -682,10 +722,10 @@
|
||||
"cssInjection": "CSS 注入"
|
||||
},
|
||||
"actions": {
|
||||
"editCss": "Edit CSS"
|
||||
"editCss": "编辑 CSS"
|
||||
},
|
||||
"dialogs": {
|
||||
"editCssTitle": "Edit CSS"
|
||||
"editCssTitle": "编辑 CSS"
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -910,7 +950,7 @@
|
||||
"configError": "DNS 配置错误:"
|
||||
},
|
||||
"errors": {
|
||||
"invalid": "Invalid configuration"
|
||||
"invalid": "配置无效"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
@@ -964,8 +1004,8 @@
|
||||
"updateSuccess": "{{name}} 更新成功",
|
||||
"updateFailed": "{{name}} 更新失败: {{message}}",
|
||||
"genericError": "更新失败: {{message}}",
|
||||
"none": "没有可更新的提供者",
|
||||
"allUpdated": "全部提供者更新成功"
|
||||
"none": "没有可更新的 provider",
|
||||
"allUpdated": "所有 provider 均已更新"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "代理组",
|
||||
"chainMode": "链式代理模式"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "规则",
|
||||
"global": "全局",
|
||||
"direct": "直连"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 链式代理",
|
||||
"connect": "连接",
|
||||
@@ -1105,11 +1150,11 @@
|
||||
"notifications": {
|
||||
"importRetry": "订阅导入失败,尝试使用 Clash 代理导入",
|
||||
"importFail": "使用 Clash 代理导入订阅也失败",
|
||||
"importNeedsRefresh": "Profile imported but may need manual refresh",
|
||||
"importSuccess": "Profile imported successfully, please restart if not visible",
|
||||
"importNeedsRefresh": "订阅已导入,但可能需要手动刷新",
|
||||
"importSuccess": "订阅已成功导入,如未显示请重启应用",
|
||||
"profileSwitched": "订阅已切换",
|
||||
"profileReactivated": "订阅已激活",
|
||||
"switchInterrupted": "配置切换被新选择中断",
|
||||
"switchInterrupted": "订阅切换被新选择中断",
|
||||
"batchDeleted": "选中的订阅已成功删除"
|
||||
},
|
||||
"notices": {
|
||||
@@ -1180,10 +1225,16 @@
|
||||
"proxy": {
|
||||
"strategies": {
|
||||
"select": "手动选择代理",
|
||||
"url-test": "根据URL测试延迟选择代理",
|
||||
"url-test": "根据 URL 测试延迟选择代理",
|
||||
"fallback": "不可用时切换到另一个代理",
|
||||
"load-balance": "根据负载均衡分配代理",
|
||||
"relay": "根据定义的代理链传递"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "直连",
|
||||
"REJECT": "拦截请求",
|
||||
"REJECT-DROP": "抛弃请求",
|
||||
"PASS": "跳过此规则"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
@@ -1232,11 +1283,11 @@
|
||||
"fileError": "脚本文件错误,变更已撤销"
|
||||
},
|
||||
"yaml": {
|
||||
"syntaxError": "YAML语法错误,变更已撤销",
|
||||
"readError": "YAML读取错误,变更已撤销",
|
||||
"mappingError": "YAML映射错误,变更已撤销",
|
||||
"keyError": "YAML键错误,变更已撤销",
|
||||
"generalError": "YAML错误,变更已撤销"
|
||||
"syntaxError": "YAML 语法错误,变更已撤销",
|
||||
"readError": "YAML 读取错误,变更已撤销",
|
||||
"mappingError": "YAML 映射错误,变更已撤销",
|
||||
"keyError": "YAML 键错误,变更已撤销",
|
||||
"generalError": "YAML 错误,变更已撤销"
|
||||
},
|
||||
"merge": {
|
||||
"syntaxError": "覆写文件语法错误,变更已撤销",
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"lock": "鎖定選單排序"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"light": "淺色",
|
||||
"dark": "深色",
|
||||
"system": "系統"
|
||||
},
|
||||
"ruleEditor": {
|
||||
"title": "編輯規則",
|
||||
"form": {
|
||||
@@ -106,6 +111,41 @@
|
||||
"conditionRequired": "規則條件為必填",
|
||||
"invalidRule": "無效規則"
|
||||
}
|
||||
},
|
||||
"ruleTypes": {
|
||||
"DOMAIN": "配對完整網域",
|
||||
"DOMAIN-SUFFIX": "配對網域後綴",
|
||||
"DOMAIN-KEYWORD": "配對網域關鍵字",
|
||||
"DOMAIN-REGEX": "配對網域正規表示式",
|
||||
"GEOSITE": "配對 Geosite 內的網域",
|
||||
"GEOIP": "配對 IP 所屬國家代碼",
|
||||
"SRC-GEOIP": "配對來源 IP 所屬國家代碼",
|
||||
"IP-ASN": "配對 IP 所屬 ASN",
|
||||
"SRC-IP-ASN": "配對來源 IP 所屬 ASN",
|
||||
"IP-CIDR": "配對 IP 位址範圍",
|
||||
"IP-CIDR6": "配對 IP 位址範圍",
|
||||
"SRC-IP-CIDR": "配對來源 IP 位址範圍",
|
||||
"IP-SUFFIX": "配對 IP 後綴範圍",
|
||||
"SRC-IP-SUFFIX": "配對來源 IP 後綴範圍",
|
||||
"SRC-PORT": "配對請求來源連接埠範圍",
|
||||
"DST-PORT": "配對請求目標連接埠範圍",
|
||||
"IN-PORT": "配對傳入連接埠",
|
||||
"DSCP": "DSCP標記(僅限 TPROXY UDP 傳入)",
|
||||
"PROCESS-NAME": "配對程序名稱(Android 應用程式套件名稱)",
|
||||
"PROCESS-PATH": "配對完整程序路徑",
|
||||
"PROCESS-NAME-REGEX": "正規表示式配對完整程序名稱(Android 應用程式套件名稱)",
|
||||
"PROCESS-PATH-REGEX": "正規表示式配對完整程序路徑",
|
||||
"NETWORK": "配對傳輸協定 (TCP/UDP)",
|
||||
"UID": "配對 Linux 使用者 ID",
|
||||
"IN-TYPE": "配對傳入類型",
|
||||
"IN-USER": "配對傳入使用者名稱",
|
||||
"IN-NAME": "配對傳入名稱",
|
||||
"SUB-RULE": "子規則",
|
||||
"RULE-SET": "配對規則集",
|
||||
"AND": "邏輯 AND",
|
||||
"OR": "邏輯 OR",
|
||||
"NOT": "邏輯 NOT",
|
||||
"MATCH": "配對所有請求"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
@@ -1009,6 +1049,11 @@
|
||||
"default": "代理組",
|
||||
"chainMode": "鏈式代理模式"
|
||||
},
|
||||
"modes": {
|
||||
"rule": "規則",
|
||||
"global": "全局",
|
||||
"direct": "直連"
|
||||
},
|
||||
"actions": {
|
||||
"toggleChain": "🔗 鏈式代理",
|
||||
"connect": "連線",
|
||||
@@ -1184,6 +1229,12 @@
|
||||
"fallback": "切換至另一個備用代理",
|
||||
"load-balance": "根據負載平衡分配代理",
|
||||
"relay": "根據定義的代理鏈傳送"
|
||||
},
|
||||
"policies": {
|
||||
"DIRECT": "直連",
|
||||
"REJECT": "拒絕請求",
|
||||
"REJECT-DROP": "丟棄請求",
|
||||
"PASS": "跳過此規則"
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
|
||||
Reference in New Issue
Block a user