diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt index 6d38c6bbd..5ca0e3e17 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt @@ -134,7 +134,7 @@ object DataStore : OnPreferenceDataStoreChangeListener { var rulesGeositeUrl by configurationStore.string(Key.RULES_GEOSITE_URL) { "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" } var rulesGeoipUrl by configurationStore.string(Key.RULES_GEOIP_URL) { "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" } - var rulesUpdateInterval by configurationStore.string(Key.RULES_UPDATE_INTERVAL) { "0" } // 默认为0,不自动更新 + var rulesUpdateInterval by configurationStore.string(Key.RULES_UPDATE_INTERVAL) { "0" } // defaults to 0, no automatic update // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() } @@ -320,7 +320,7 @@ object DataStore : OnPreferenceDataStoreChangeListener { set(value) = configurationStore.putString("webdavPassword", value) var webdavPath: String? - get() = configurationStore.getString("webdavPath") ?: "NekoBox" // 设置默认值 + get() = configurationStore.getString("webdavPath") ?: "NekoBox" // set default value set(value) = configurationStore.putString("webdavPath", value) var globalMode by configurationStore.boolean(Key.GLOBAL_MODE) diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt index 6b401130a..7fc70faa0 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt @@ -206,7 +206,7 @@ object ProfileManager { ) val fuckedCountry = mutableListOf("cn:中国") if (Locale.getDefault().country != Locale.CHINA.country) { - // 非中文用户 + // non-Chinese users fuckedCountry += "ir:Iran" fuckedCountry += "ru:Russia" } diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt index b34ae0124..4d9f04baf 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt @@ -272,7 +272,7 @@ fun buildConfig( rules = mutableListOf() rule_set = mutableListOf() - // 添加并发拨号设置 + // add concurrent dial setting concurrent_dial = DataStore.concurrentDial } @@ -534,11 +534,11 @@ fun buildConfig( val mainProxyTag = (if (buildSelector) TAG_PROXY else tagMap[proxy.id]) ?: TAG_PROXY - // 在应用用户规则之前检查全局模式 + // check global mode before applying user rules if (!forTest && DataStore.globalMode) { - // 全局模式下的规则处理 + // rule handling in global mode - // 绕过内部网络(如果启用) + // bypass internal networks (if enabled) if (DataStore.bypassLan) { route.rules.add(Rule_DefaultOptions().apply { ip_cidr = listOf( @@ -568,7 +568,7 @@ fun buildConfig( route.final_ = mainProxyTag } else { - // 应用用户规则 + // apply user rules for (rule in extraRules) { if (rule.packages.isNotEmpty()) { PackageCache.awaitLoadSync() @@ -601,10 +601,10 @@ fun buildConfig( if (rule_set != null) generateRuleSet(rule_set, ruleSets) - // 存储ruleset标签和类型信息 + // store ruleset tag and type info val rulesetTags = mutableListOf>() - // 处理远程ruleset + // handle remote ruleset if (rule.ruleset.isNotBlank()) { val rulesetUrls = rule.ruleset.listByLineOrComma() rulesetUrls.forEach { origUrl -> @@ -665,7 +665,7 @@ fun buildConfig( if (rule_set != null && rulesetTags.isNotEmpty()) { for (tag in rule_set) { - // 只处理ruleset标签,且必须是非IP类型 + // only handle ruleset tags, and they must be non-IP type val tagInfo = rulesetTags.find { it.first == tag } if (tag.startsWith("ruleset-") && tagInfo != null && !tagInfo.second) { userDNSRuleList += DNSRule_DefaultOptions().apply { @@ -749,7 +749,7 @@ fun buildConfig( Toast.LENGTH_LONG ).show() } else { - // block 改用新的写法 + // block now uses the new approach if (ruleObj.outbound == TAG_BLOCK) { ruleObj.outbound = null ruleObj.action = "reject" @@ -761,7 +761,7 @@ fun buildConfig( } } - // 对 rule_set tag 去重 + // deduplicate rule_set tags if (route.rule_set != null) { route.rule_set = route.rule_set.distinctBy { it.tag } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/snell/SnellFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/snell/SnellFmt.kt index 4050f8c1e..015779152 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/snell/SnellFmt.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/snell/SnellFmt.kt @@ -4,7 +4,7 @@ import io.nekohasekai.sagernet.ktx.urlSafe import io.nekohasekai.sagernet.ktx.unUrlSafe import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -// URI 格式: snell://base64(psk)@server:port?version=4&obfs-mode=http&obfs-host=bing.com&reuse=true&network=tcp#name +// URI format: snell://base64(psk)@server:port?version=4&obfs-mode=http&obfs-host=bing.com&reuse=true&network=tcp#name fun parseSnell(url: String): SnellBean { val link = url.replace("snell://", "https://").toHttpUrlOrNull() ?: error("Invalid snell URL") diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java index fa4aaf4e5..8b09edb24 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java @@ -14,63 +14,65 @@ public class TrojanGoBean extends AbstractBean { /** - * Trojan 的密码。 - * 不可省略,不能为空字符串,不建议含有非 ASCII 可打印字符。 - * 必须使用 encodeURIComponent 编码。 + * Trojan password. + * Must not be omitted or empty; non-ASCII printable characters are discouraged. + * Must be encoded with encodeURIComponent. */ public String password; /** - * 自定义 TLS 的 SNI。 - * 省略时默认与 trojan-host 同值。不得为空字符串。 + * Custom TLS SNI. + * Defaults to the same value as trojan-host when omitted. Must not be empty. *

- * 必须使用 encodeURIComponent 编码。 + * Must be encoded with encodeURIComponent. */ public String sni; /** - * 传输类型。 - * 省略时默认为 original,但不可为空字符串。 - * 目前可选值只有 original 和 ws,未来可能会有 h2、h2+ws 等取值。 + * Transport type. + * Defaults to "original" when omitted, but must not be an empty string. + * Currently only "original" and "ws" are valid; future values may include h2, h2+ws, etc. *

- * 当取值为 original 时,使用原始 Trojan 传输方式,无法方便通过 CDN。 - * 当取值为 ws 时,使用 wss 作为传输层。 + * "original" uses the raw Trojan transport, which is not easily served via a CDN. + * "ws" uses wss as the transport layer. */ public String type; /** - * 自定义 HTTP Host 头。 - * 可以省略,省略时值同 trojan-host。 - * 可以为空字符串,但可能带来非预期情形。 + * Custom HTTP Host header. + * May be omitted; defaults to trojan-host when omitted. + * May be an empty string, but that can lead to unexpected behavior. *

- * 警告:若你的端口非标准端口(不是 80 / 443),RFC 标准规定 Host 应在主机名后附上端口号,例如 example.com:44333。至于是否遵守,请自行斟酌。 + * Warning: if your port is non-standard (not 80 / 443), the RFC requires the Host to + * include the port after the hostname, e.g. example.com:44333. Whether to follow this + * is up to you. *

- * 必须使用 encodeURIComponent 编码。 + * Must be encoded with encodeURIComponent. */ public String host; /** - * 当传输类型 type 取 ws、h2、h2+ws 时,此项有效。 - * 不可省略,不可为空。 - * 必须以 / 开头。 - * 可以使用 URL 中的 & # ? 等字符,但应当是合法的 URL 路径。 + * Effective when the transport type is ws, h2, or h2+ws. + * Must not be omitted or empty. + * Must start with "/". + * May use URL characters such as & # ?, but must be a valid URL path. *

- * 必须使用 encodeURIComponent 编码。 + * Must be encoded with encodeURIComponent. */ public String path; /** - * 用于保证 Trojan 流量密码学安全的加密层。 - * 可省略,默认为 none,即不使用加密。 - * 不可以为空字符串。 + * Encryption layer used to cryptographically secure Trojan traffic. + * May be omitted; defaults to "none" (no encryption). + * Must not be an empty string. *

- * 必须使用 encodeURIComponent 编码。 + * Must be encoded with encodeURIComponent. *

- * 使用 Shadowsocks 算法进行流量加密时,其格式为: + * When using the Shadowsocks algorithm for traffic encryption, the format is: *

* ss;method:password *

- * 其中 ss 是固定内容,method 是加密方法,必须为下列之一: + * where "ss" is fixed and "method" is the encryption method, which must be one of: *

* aes-128-gcm * aes-256-gcm @@ -79,8 +81,8 @@ public class TrojanGoBean extends AbstractBean { public String encryption; /** - * 额外的插件选项。本字段保留。 - * 可省略,但不可以为空字符串。 + * Extra plugin options. This field is reserved. + * May be omitted, but must not be an empty string. */ // not used in NB4A public String plugin; diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java index 875e16366..3a7d17db7 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java @@ -143,7 +143,7 @@ public void initializeDefaultValues() { if (JavaUtil.isNullOrBlank(mKcpSeed)) mKcpSeed = ""; if (JavaUtil.isNullOrBlank(headerType)) headerType = "none"; - // kcpMtu、kcpTti 和 kcpCwndMultiplier 保持 null,不设置默认值 + // kcpMtu, kcpTti and kcpCwndMultiplier stay null, no default values set } @Override @@ -265,7 +265,7 @@ public void deserialize(ByteBufferInput input) { case "grpc": { path = input.readString(); if (version < 4) { - // 解决老版本数据的读取问题 + // fix the reading issue with old version data input.readString(); input.readString(); } @@ -327,13 +327,13 @@ public void deserialize(ByteBufferInput input) { } } } else if (version == 0) { - // 从老版本升级上来但是 version == 0, 可能有 enableECH 也可能没有,需要做判断 - int position = input.getByteBuffer().position(); // 当前位置 + // upgraded from an old version but version == 0; may or may not have enableECH, so a check is needed + int position = input.getByteBuffer().position(); // current position boolean tmpEnableECH = input.readBoolean(); int tmpPacketEncoding = input.readInt(); - input.setPosition(position); // 读后归位 + input.setPosition(position); // reset position after reading if (tmpPacketEncoding != 1 && tmpPacketEncoding != 2) { enableECH = tmpEnableECH; @@ -342,7 +342,7 @@ public void deserialize(ByteBufferInput input) { input.readBoolean(); echConfig = input.readString(); } - } // 否则后一位就是 packetEncoding + } // otherwise the next field is packetEncoding } packetEncoding = input.readInt(); diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt index eee137df2..d2e920b91 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt @@ -316,7 +316,7 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) { } } -// 不确定是谁的格式 +// not sure whose format this is private fun tryResolveVmess4Kitsunebi(server: String): VMessBean { // vmess://YXV0bzo1YWY1ZDBlYy02ZWEwLTNjNDMtOTNkYi1jYTMwMDg1MDNiZGJAMTgzLjIzMi41Ni4xNjE6MTIwMg // ?remarks=*%F0%9F%87%AF%F0%9F%87%B5JP%20-355%20TG@moon365free&obfsParam=%7B%22Host%22:%22183.232.56.161%22%7D&path=/v2ray&obfs=websocket&alterId=0 @@ -772,7 +772,7 @@ fun buildSingBoxOutboundTLS(bean: StandardV2RayBean): OutboundTLSOptions? { insecure = bean.allowInsecure || DataStore.globalAllowInsecure if (bean.sni.isNotBlank()) server_name = bean.sni if (bean.alpn.isNotBlank()) { - // 当传输协议为WebSocket时,过滤掉h2和h3 + // when the transport protocol is WebSocket, filter out h2 and h3 val alpnList = bean.alpn.listByLineOrComma() if (bean.type == "ws") { val filtered = alpnList.filter { it == "http/1.1" } diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt index 80d68dfba..5d41fe148 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt @@ -84,7 +84,7 @@ object RawUpdater : GroupUpdater() { subscription.subscriptionUserinfo = Util.getStringBox(response.getHeader("Subscription-Userinfo")) - // 修改默认名字 + // modify the default name if (proxyGroup.name?.startsWith("Subscription #") == true) { var remoteName = Util.getStringBox(response.getHeader("content-disposition")) if (remoteName.isNotBlank()) { @@ -182,7 +182,7 @@ object RawUpdater : GroupUpdater() { if (toReplace.contains(name)) { val entity = toReplace[name]!! val existsBean = entity.requireBean() - // 更新订阅,保留自定义覆写设置 + // update subscription, preserving custom override settings bean.customOutboundJson = existsBean.customOutboundJson bean.customConfigJson = existsBean.customConfigJson when { diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt index 42c02c349..d48e596c8 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt @@ -88,7 +88,7 @@ fun JSONObject.getBool(name: String): Boolean? { } -// 重名了喵 +// name collision, nya fun JSONObject.getIntNya(name: String): Int? { return try { getInt(name) diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt index 8fe6b35d5..2bc531650 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt @@ -62,7 +62,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { super.onDestroyView() snackbar?.dismiss() snackbar = null - // 如果正在进行恢复操作,取消它 + // if a restore operation is in progress, cancel it if (isRestoreInProgress) { restoreJob?.cancel() restoreJob = null @@ -189,25 +189,25 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { try { isWebDAVBackup = true val backupData = doBackup( - true, // 备份配置和分组 - true, // 备份路由规则 - true // 备份设置 + true, // back up configs and groups + true, // back up route rules + true // back up settings ) isWebDAVBackup = false val client = OkHttpClient() - // 规范化 URL + // normalize URL val baseUrl = DataStore.webdavServer!!.trimEnd('/') val path = DataStore.webdavPath?.trim('/')?.takeIf { it.isNotEmpty() } ?: "Nekobox" - // 使用英文格式的时间戳作为文件名,修改后缀为 .zip + // use an English-formatted timestamp as the file name, with a .zip suffix val timestamp = java.text.SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) val version = BuildConfig.VERSION_NAME val fileName = "nekobox_backup_${version}_$timestamp.zip" - // 确保 baseUrl 是有效的且使用 TLS 的 URL - // (WebDAV 备份包含所有配置密钥,禁止明文 http://) + // ensure baseUrl is a valid URL that uses TLS + // (WebDAV backup contains all config keys, plaintext http:// is forbidden) val baseHttpUrl = WebDAVSecurity.requireSecureUrl(baseUrl) val dirUrl = baseHttpUrl.newBuilder().apply { @@ -223,7 +223,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { Logs.d("WebDAV backup - Directory URL: $dirUrl") Logs.d("WebDAV backup - File URL: $fileUrl") - // 先检查目录是否存在 + // first check whether the directory exists val propfindRequest = Request.Builder() .url(dirUrl) .method("PROPFIND", null) @@ -239,7 +239,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { Logs.d("WebDAV backup - PROPFIND response: ${response.code}") when (response.code) { 404 -> needCreateDir = true - 207 -> needCreateDir = false // 目录存在 + 207 -> needCreateDir = false // directory exists 401 -> throw Exception("Authentication failed") else -> { if (!response.isSuccessful) { @@ -251,7 +251,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { } } - // 如果需要,创建目录 + // create the directory if needed if (needCreateDir) { Logs.d("WebDAV backup - Creating directory") val mkcolRequest = Request.Builder() @@ -272,7 +272,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { } } - // 上传文件时使用正确的 Content-Type + // use the correct Content-Type when uploading the file val putRequest = Request.Builder() .url(fileUrl) .put(backupData.toRequestBody("application/zip".toMediaType())) @@ -297,7 +297,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { MessageStore.showMessage(activity, R.string.webdav_backup_success) } } catch (e: Exception) { - isWebDAVBackup = false // 确保发生异常时也重置标志 + isWebDAVBackup = false // ensure the flag is reset even when an exception occurs Logs.w(e) val errorMessage = try { @@ -332,7 +332,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { val baseUrl = DataStore.webdavServer!!.trimEnd('/') val path = DataStore.webdavPath?.trim('/')?.takeIf { it.isNotEmpty() } ?: "Nekobox" - // WebDAV 备份包含所有配置密钥,禁止明文 http:// + // WebDAV backup contains all config keys, plaintext http:// is forbidden val baseHttpUrl = WebDAVSecurity.requireSecureUrl(baseUrl) val dirUrl = baseHttpUrl.newBuilder().apply { @@ -343,7 +343,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { Logs.d("WebDAV restore - Directory URL: $dirUrl") - // 先列出目录内容找到最新的备份文件 + // first list the directory contents to find the latest backup file val propfindRequest = Request.Builder() .url(dirUrl) .method("PROPFIND", null) @@ -354,7 +354,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { .header("Depth", "1") .build() - // 获取最新的备份文件名 + // get the latest backup file name val latestBackup = client.newCall(propfindRequest).execute().use { response -> if (!response.isSuccessful && response.code != 207) { val errorBody = response.body?.string() @@ -394,7 +394,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { } ?: throw Exception("No backup found") } - // 下载最新的备份文件 + // download the latest backup file val fileUrl = dirUrl.newBuilder() .addPathSegment(latestBackup) .build() @@ -420,9 +420,9 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { Logs.d("WebDAV restore - Successfully downloaded backup file, size: ${content.size}") - // 根据文件类型处理内容 + // process the content based on file type val backupContent = if (latestBackup.endsWith(".zip")) { - // ZIP 文件处理 + // ZIP file handling ZipInputStream(content.inputStream()).use { zis -> zis.nextEntry?.let { entry -> if (entry.name.endsWith(".json")) { @@ -433,14 +433,14 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { } ?: throw Exception("Invalid backup file format") } } else { - // JSON 文件处理 + // JSON file handling content.toString(Charsets.UTF_8) } - // 解析并导入备份数据 + // parse and import the backup data val json = JSONObject(backupContent) onMainDispatcher { - // 如果 Fragment 已经被销毁,取消恢复操作 + // if the Fragment has been destroyed, cancel the restore operation if (!isAdded) { MessageStore.showMessage(activity, R.string.restore_cancelled) return@onMainDispatcher @@ -470,7 +470,7 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { .show() runOnDefaultDispatcher { runCatching { - // 再次检查是否已被取消 + // check again whether it has been cancelled if (!isAdded) { MessageStore.showMessage(activity, R.string.restore_cancelled) return@runOnDefaultDispatcher @@ -566,19 +566,19 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) { method = ZipEntry.DEFLATED } - // 写入数据 + // write data zos.putNextEntry(entry) val bytes = jsonContent.toByteArray(Charsets.UTF_8) zos.write(bytes) zos.closeEntry() - // 确保所有数据都被写入和压缩 + // ensure all data is written and compressed zos.finish() } bos.toByteArray() } } else { - // 本地导出和分享功能使用 JSON 格式 + // local export and share functionality uses JSON format jsonContent.toByteArray() } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index 60284a33b..f0518ebbe 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -662,12 +662,12 @@ class ConfigurationFragment @JvmOverloads constructor( if (DataStore.serviceState.canStop) { runOnDefaultDispatcher { try { - // 等待一段时间确保配置已保存 + // wait a while to ensure the config has been saved delay(500) snackbar(getString(R.string.need_reload)).setAction(R.string.apply) { runOnDefaultDispatcher { try { - // 再次等待确保配置已保存 + // wait again to ensure the config has been saved delay(100) SagerNet.reloadService() } catch (e: Exception) { diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt index c902f93e7..6aba247ee 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt @@ -239,7 +239,7 @@ class GroupSettingsActivity( val group = ProxyGroup() group.init() - // 如果有订阅链接,设置为订阅类型并填充链接 + // if there's a subscription link, set it to subscription type and fill in the link if (!subscriptionLink.isNullOrEmpty()) { DataStore.groupType = GroupType.SUBSCRIPTION DataStore.subscriptionLink = subscriptionLink diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt index 2f276ee9f..a56afc60c 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt @@ -76,7 +76,7 @@ class LogcatFragment : ToolbarFragment(R.layout.layout_logcat), } binding.textview.text = span binding.textview.clearFocus() - // 等 textview 完成最终 layout 再滚动到底部 + // wait for the textview to finish its final layout before scrolling to the bottom binding.textview.doOnLayout { binding.scroolview.scrollTo(0, binding.textview.height) } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt index 999a89ba8..e817ac5bc 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt @@ -115,7 +115,7 @@ class MainActivity : ThemedActivity(), val checkPermission = ContextCompat.checkSelfPermission(this@MainActivity, POST_NOTIFICATIONS) if (checkPermission != PackageManager.PERMISSION_GRANTED) { - //动态申请 + //request dynamically ActivityCompat.requestPermissions( this@MainActivity, arrayOf(POST_NOTIFICATIONS), 0 ) diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt index 9aebee5ff..cf4e7aa6c 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt @@ -286,12 +286,12 @@ class RouteFragment : ToolbarFragment(R.layout.layout_route), Toolbar.OnMenuItem profileType.text = rule.mkSummary() routeOutbound.text = rule.displayOutbound() - // 根据路由类型设置文字颜色 + // set text color based on route type val colorRes = when (rule.outbound) { - -2L -> R.color.color_route_block // 屏蔽:红色 - -1L -> R.color.color_route_direct // 直连:绿色 - 0L -> R.color.color_route_proxy // 代理:蓝色 - else -> R.color.color_route_config // 配置:紫色 + -2L -> R.color.color_route_block // block: red + -1L -> R.color.color_route_direct // direct: green + 0L -> R.color.color_route_proxy // proxy: blue + else -> R.color.color_route_config // config: purple } routeOutbound.setTextColor(ContextCompat.getColor(itemView.context, colorRes)) diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt index 99f6f6468..31fc89b7d 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt @@ -48,7 +48,7 @@ class ScannerActivity : ThemedActivity(), setHomeAsUpIndicator(R.drawable.ic_navigation_close) } - // 二维码库 + // QR code library initCameraScan() startCamera() binding.ivFlashlight.setOnClickListener { toggleTorchState() } @@ -105,9 +105,9 @@ class ScannerActivity : ThemedActivity(), var importedN = AtomicInteger(0) /** - * 接收扫码结果回调 - * @param result 扫码结果 - * @return 返回true表示拦截,将不自动执行后续逻辑,为false表示不拦截,默认不拦截 + * Callback for receiving scan results + * @param result scan result + * @return returning true means intercept (subsequent logic won't run automatically); false means don't intercept; defaults to not intercepting */ override fun onScanResultCallback(result: Result?): Boolean { return onScanResultCallback(result, false) @@ -153,7 +153,7 @@ class ScannerActivity : ThemedActivity(), } /** - * 初始化CameraScan + * Initialize CameraScan */ fun initCameraScan() { cameraScan = DefaultCameraScan(this, binding.previewView) @@ -163,7 +163,7 @@ class ScannerActivity : ThemedActivity(), } /** - * 启动相机预览 + * Start camera preview */ fun startCamera() { if (PermissionUtils.checkPermission(this, Manifest.permission.CAMERA)) { @@ -177,14 +177,14 @@ class ScannerActivity : ThemedActivity(), } /** - * 释放相机 + * Release the camera */ private fun releaseCamera() { cameraScan.release() } /** - * 切换闪光灯状态(开启/关闭) + * Toggle flashlight state (on/off) */ protected fun toggleTorchState() { val isTorch = cameraScan.isTorchEnabled @@ -204,7 +204,7 @@ class ScannerActivity : ThemedActivity(), } /** - * 请求Camera权限回调结果 + * Callback result for requesting Camera permission * @param permissions * @param grantResults */ diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt index aee7f3564..fa0f33a21 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt @@ -221,7 +221,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { enableTLSFragment.onPreferenceChangeListener = reloadListener - // 恢复默认设置功能 + // reset to default settings feature val resetSettings = findPreference("resetSettings")!! resetSettings.setOnPreferenceClickListener { MaterialAlertDialogBuilder(requireContext()).apply { @@ -236,7 +236,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { true } - // 清理缓存功能 + // clear cache feature val clearCache = findPreference(Key.CLEAR_CACHE)!! clearCache.setOnPreferenceClickListener { MaterialAlertDialogBuilder(requireContext()).apply { diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/WebDAVSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/WebDAVSettingsActivity.kt index a10c13b51..cb4d70d2f 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/WebDAVSettingsActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/WebDAVSettingsActivity.kt @@ -52,7 +52,7 @@ class WebDAVSettingsActivity : ThemedActivity() { class WebDAVSettingsFragment : PreferenceFragmentCompat(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private var lastClickTime = 0L - private val DEBOUNCE_TIME = 1000L // 1秒内不允许重复点击 + private val DEBOUNCE_TIME = 1000L // no repeated clicks allowed within 1 second private var isFragmentAlive = true private fun isClickAllowed(): Boolean { @@ -95,7 +95,7 @@ class WebDAVSettingsActivity : ThemedActivity() { editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD editText.setSelection(editText.text.length) } - // 使用与其他密码字段一致的隐藏摘要样式 + // use the hidden summary style consistent with other password fields summaryProvider = GroupSettingsActivity.PasswordSummaryProvider } @@ -134,7 +134,7 @@ class WebDAVSettingsActivity : ThemedActivity() { .writeTimeout(10, TimeUnit.SECONDS) .build() - // 首先测试连接和认证 + // first test connection and authentication val authRequest = Request.Builder() .url(secureUrl) .method("PROPFIND", null) @@ -161,7 +161,7 @@ class WebDAVSettingsActivity : ThemedActivity() { throw Exception(getString(R.string.webdav_connect_failed, response.code)) } - // 如果认证成功,再测试目录操作 + // if authentication succeeds, then test directory operations val path = (DataStore.webdavPath ?: "").trim('/') if (path.isNotBlank()) { val dirUrl = secureUrl.newBuilder().apply { @@ -183,7 +183,7 @@ class WebDAVSettingsActivity : ThemedActivity() { .build() val dirResponse = client.newCall(dirRequest).execute() - if (!dirResponse.isSuccessful && dirResponse.code != 405) { // 405 表示目录已存在 + if (!dirResponse.isSuccessful && dirResponse.code != 405) { // 405 means the directory already exists throw Exception(getString(R.string.webdav_create_dir_failed)) } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt index c2e70558e..e1f0a6cd5 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt @@ -15,7 +15,7 @@ import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.databinding.LayoutWebviewBinding import moe.matsuri.nb4a.utils.WebViewUtil -// Fragment必须有一个无参public的构造函数,否则在数据恢复的时候,会报crash +// Fragment must have a no-argument public constructor, otherwise it will crash during data restoration class WebviewFragment : ToolbarFragment(R.layout.layout_webview), Toolbar.OnMenuItemClickListener { diff --git a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java index 963aae8ea..b45d1073a 100644 --- a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java +++ b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java @@ -33,7 +33,7 @@ public class SingBoxOptions { public static class SingBoxOption { - public transient Map _hack_config_map; // 仍然用普通json方式合并,所以Object内不要使用 _hack + public transient Map _hack_config_map; // still merged using the normal json approach, so don't use _hack inside Object public transient String _hack_custom_config; @@ -65,16 +65,16 @@ public Map getBasicMap() { } } - // 自定义序列化器 + // custom serializer public static class SingBoxOptionSerializer implements JsonSerializer { @Override public JsonElement serialize(SingBoxOption src, Type typeOfSrc, JsonSerializationContext context) { - // 拿到原始的 delegate(默认序列化器) + // get the original delegate (default serializer) TypeAdapter delegate = gsonSingbox.getDelegateAdapter( new TypeAdapterFactory() { @Override public TypeAdapter create(Gson gson, TypeToken type) { - return null; // 返回 null,表示只作为“跳过当前自定义”的 marker + return null; // return null, serving only as a marker for "skip current custom" } }, TypeToken.get(src.getClass()) @@ -4719,7 +4719,7 @@ public static class V2RayTransportOptions_KCPOptions extends V2RayTransportOptio } - // sing-box Options 生成器已经坏了,以下是从 husi 抄的 + // the sing-box Options generator is already broken; the following is copied from husi public static class Outbound_AnyTLSOptions extends Outbound { diff --git a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt index 54d7c95fe..79e725fac 100644 --- a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt +++ b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt @@ -170,11 +170,11 @@ fun SingBoxOptions.Rule_DefaultOptions.checkEmpty(): Boolean { fun processRulesetUrl(origUrl: String): Pair { return when { origUrl.startsWith("rsip:") -> { - // IP类型ruleset + // IP-type ruleset Pair(origUrl.substring(5), true) } origUrl.startsWith("rssite:") -> { - // 域名类型ruleset + // domain-type ruleset Pair(origUrl.substring(7), false) } else -> { @@ -187,7 +187,7 @@ fun generateRemoteRuleSet(url: String, ruleSets: MutableList, updateInt val hashCode = kotlin.math.abs(url.hashCode()) val tag = "ruleset-$hashCode" - // 添加到规则集列表 + // add to the rule set list ruleSets.add(RuleSet().apply { type = "remote" this.tag = tag diff --git a/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt b/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt index 9b125aeea..1bb631adf 100644 --- a/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt +++ b/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt @@ -125,7 +125,7 @@ object LocalResolverImpl : LocalDNSTransport { } } else { runOnIoDispatcher { - // 老版本系统,继续用阻塞的 InetAddress + // older system version, continue using the blocking InetAddress try { val u = SagerNet.underlyingNetwork val answer = try { diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java b/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java index 1e3614736..12335b95b 100644 --- a/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java +++ b/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java @@ -80,7 +80,7 @@ public static void handleWebviewDir(Context context) { String huaweiWebViewDir = "/app_hws_webview"; String lockFile = "/webview_data.lock"; String processName = Application.getProcessName(); - if (!BuildConfig.APPLICATION_ID.equals(processName)) {//判断不等于默认进程名称 + if (!BuildConfig.APPLICATION_ID.equals(processName)) {//check if not equal to the default process name suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName; WebView.setDataDirectorySuffix(suffix); suffix = "_" + suffix; @@ -89,11 +89,11 @@ public static void handleWebviewDir(Context context) { pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); } } else { - //主进程 + //main process suffix = "_" + processName; - pathSet.add(dataPath + webViewDir + lockFile);//默认未添加进程名后缀 - pathSet.add(dataPath + webViewDir + suffix + lockFile);//系统自动添加了进程名后缀 - if (checkIsHuaweiRom()) {//部分华为手机更改了webview目录名 + pathSet.add(dataPath + webViewDir + lockFile);// path variant without the process-name suffix + pathSet.add(dataPath + webViewDir + suffix + lockFile);// path variant with the process-name suffix (device-dependent) + if (checkIsHuaweiRom()) {//some Huawei phones changed the webview directory name pathSet.add(dataPath + huaweiWebViewDir + lockFile); pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); } diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt b/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt index c5ae09abb..1a211195a 100644 --- a/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt +++ b/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt @@ -15,12 +15,12 @@ import java.util.zip.Inflater object Util { /** - * 取两个文本之间的文本值 + * Get the text value between two pieces of text * - * @param text 源文本 比如:欲取全文本为 12345 - * @param left 文本前面 - * @param right 后面文本 - * @return 返回 String + * @param text source text, e.g. the full text to extract from is 12345 + * @param left text before + * @param right text after + * @return returns String */ fun getSubString(text: String, left: String?, right: String?): String { var zLen: Int @@ -63,13 +63,13 @@ object Util { fun b64Decode(b: String): ByteArray { var ret: ByteArray? = null - // padding 自动处理,不用理 - // URLSafe 需要替换这两个,不要用 URL_SAFE 否则处理非 Safe 的时候会乱码 + // padding is handled automatically, no need to worry about it + // URLSafe needs to replace these two; don't use URL_SAFE, otherwise non-Safe input will be garbled val str = b.replace("-", "+").replace("_", "/") val flags = listOf( - Base64.DEFAULT, // 多行 - Base64.NO_WRAP, // 单行 + Base64.DEFAULT, // multi-line + Base64.NO_WRAP, // single-line ) for (flag in flags) { diff --git a/libcore/assets_android.go b/libcore/assets_android.go index c3c98310d..b69057e36 100644 --- a/libcore/assets_android.go +++ b/libcore/assets_android.go @@ -28,10 +28,10 @@ func extractAssets() { extract(yacdDstFolder) } -// 这里解压的是 apk 里面的 +// this extracts the ones inside the apk func extractAssetName(name string, useOfficialAssets bool) error { - // 支持非官方源的,就是 replaceable,放 Android 目录 - // 不支持非官方源的,就放 file 目录 + // those that support unofficial sources are replaceable, placed in the Android directory + // those that don't support unofficial sources go in the file directory replaceable := true var version string @@ -83,7 +83,7 @@ func extractAssetName(name string, useOfficialAssets bool) error { // assetFileMissing doExtract = true } else if useOfficialAssets || !replaceable { - // 官方源升级 + // official source upgrade b, err := os.ReadFile(dir + version) if err != nil { // versionFileMissing @@ -104,7 +104,7 @@ func extractAssetName(name string, useOfficialAssets bool) error { } } } else { - //非官方源不升级 + //unofficial sources are not upgraded } if !doExtract { diff --git a/libcore/dns_box.go b/libcore/dns_box.go index 3f1cd5fbb..32664b27a 100644 --- a/libcore/dns_box.go +++ b/libcore/dns_box.go @@ -56,7 +56,7 @@ func (p *platformLocalDNSTransport) Close() error { func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { if p.raw && rawQueryFunc != nil { - // Raw - Android 10 及以上才有 + // Raw - only available on Android 10 and above messageBytes, err := message.Pack() if err != nil { @@ -73,7 +73,7 @@ func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS. } return responseMessage, nil } else { - // Lookup - Android 10 以下 + // Lookup - below Android 10 question := message.Question[0] var network string diff --git a/libcore/http.go b/libcore/http.go index ef8288952..c737e2701 100644 --- a/libcore/http.go +++ b/libcore/http.go @@ -288,7 +288,7 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) { defer func() { if successCount.Load() == 0 { if failedCount.Add(1) >= uint32(len(funcs)) { - // 全部失败了 + // all failed cancel() } } @@ -302,7 +302,7 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) { t = "h3" } - // 执行HTTP请求 + // execute the HTTP request rsp, err := f() if rsp == nil || err != nil { mu.Lock() @@ -314,7 +314,7 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) { return } - // 处理 HTTP 状态码 + // handle the HTTP status code if rsp.StatusCode != http.StatusOK { hr := &httpResponse{Response: rsp} err = fmt.Errorf("%s: %s", t, hr.errorString()) @@ -326,7 +326,7 @@ func (r *httpRequest) doH3Direct() (HTTPResponse, error) { select { case successCh <- rsp: - // 第一个成功的请求,不要关闭 body + // first successful request, don't close the body successCount.Add(1) default: rsp.Body.Close() diff --git a/libcore/platform_box.go b/libcore/platform_box.go index 316cbef58..a674604f4 100644 --- a/libcore/platform_box.go +++ b/libcore/platform_box.go @@ -158,7 +158,7 @@ func (w *boxPlatformInterfaceWrapper) Write(p []byte) (n int, err error) { return len(p), nil } -// 日志 +// logging type boxPlatformLogWriterWrapper struct { }