清理死代码、死译文与重复代码

- 删掉没人调用的函数、属性与解码字段:网络弹窗的两个标签函数、supportsStyleOverride、probedAddress、countryEn、辅助工具客户端的单风扇恢复自动封装(协议与辅助工具端保留,不动 XPC 接口)
- 译文表删掉 19 条不再使用的条目,补上测速窗口“开始”按钮漏掉的英文
- 网络弹窗与出口窗口各自一份的归属地格式化和 IP 类型徽章合并为 GeoText;两个折线图里重复的参考线绘制提成 ChartGrid
- 官网样式删掉没人用的 .mb-drop__ext
- server/sync/sync 是误提交的 Go 编译产物,从仓库移除并忽略
This commit is contained in:
gentpan committed 2026-09-21 03:38:08 +05:00
1 parent 1022e4e541
commit 645d161cbd
12 files changed
+53 -96

No files matched your search

+2
View File
@@ -7,3 +7,5 @@ xcuserdata/
Packages/OpenStatsKit/Package.resolved
dist/
__pycache__/
# go build 在同步服务目录下生成的本地二进制
server/sync/sync
+1
View File
@@ -10,6 +10,7 @@
#### 修复
- 账号登录后应用直接退出:在登录窗口里选完 GitHub、Google 或 Apple 账号,应用立即闪退,重新打开也没有登录状态。原因是系统在后台队列回调登录结果,而回调闭包带着主线程隔离,被运行时当场中止,令牌还没来得及换取和保存。现在回调改为不带隔离、收到后再回到主线程处理,并补了回归测试。
- 英文界面下测速窗口的“开始”按钮没有翻译,仍显示中文。
## 0.6.1 · 2026-09-18
@@ -60,7 +60,6 @@ GPU 占用 GPU Usage
Globalping 返回了错误(HTTP {}) Globalping returned an error (HTTP {})
Google(8.8.8.8) Google (8.8.8.8)
IP 地址 IP Addresses
IP 类型 IP type
IP 纯净度 IP cleanliness
IPv6 没有经过代理:只走 IPv6 的连接会直接暴露本机地址 {}。 IPv6 isn't going through the proxy: IPv6-only connections expose your address {}.
IPv6 连接也经过代理。 IPv6 connections also go through the proxy.
@@ -104,7 +103,6 @@ npm 缓存 npm Cache
{} 个应用,系统自带的不列出 {} apps; built-in apps are not listed
{} 个网站 {} sites
{} 个进程 {} processes
{} 中位延迟 {} median
{} 分钟 {} min
{} 可用 {} · 共 {} {} {} free · {} total
{} 天 {} d
@@ -117,7 +115,6 @@ npm 缓存 npm Cache
{} 正在运行,卸载前需要先退出。 {} is running. Quit it before uninstalling.
{} 测 at {}
{} 秒 {}s
{} 秒 / {} MB {}s / {} MB
{} 秒·{} MB {}s · {} MB
{} 组 {} groups
{} 缓存 {} Cache
@@ -127,7 +124,6 @@ npm 缓存 npm Cache
{} 项 · 需要管理员权限,只读 {} items · needs admin, read-only
{} 项失败 {} failed
{}%(阈值 {}%) {}% (threshold {}%)
{}/{} 个节点连得上 {}/{} nodes reachable
{}{}只剩 {}%,记得充电。 {}{} is down to {}%. Time to charge.
{}{}({}) {}{} ({})
{}。系统会自动分配任务,不用你操心 {}. macOS assigns work between them automatically.
@@ -205,7 +201,6 @@ npm 缓存 npm Cache
以后再说 Later
以系统权限运行的后台服务,只接受本应用的请求 A background service with system privileges that only accepts requests from this app
仪表盘 Dashboard
企业 Business
企业 IP Business IP
企业网络 Business network
会一并找出它留在资源库里的缓存、偏好设置、容器与登录启动项,全部移到废纸篓,可以放回 Also finds its caches, preferences, containers and launch agents in your Library and moves them all to the Trash, where you can restore them
@@ -217,8 +212,6 @@ npm 缓存 npm Cache
低风险 Low risk
住宅 IP Residential IP
住宅代理 Residential proxy
住宅宽带 Residential
住宅概率 Residential probability
余量 {} Headroom {}
使用 {} 登录 Sign in with {}
使用云端设置 Use cloud settings
@@ -311,14 +304,12 @@ npm 缓存 npm Cache
剪切 Cut
功耗 Power
包名是 {} Bundle ID is {}
包含城市数据 Include City Data
协议版本 Protocol version
单个节点一次测速最多花的时间与流量,先到哪个停哪个 Time and data cap for one node's speed test, whichever comes first
单行 Inline
单行文字 Inline Text
占整机 CPU 的比例:{} 个核心全部跑满为 100% Share of total CPU: all {} cores fully busy is 100%
占用 Usage
占用超过 85% 时数值与图形显示为红色 Show values and graphs in red above 85%
占用超过 85%(电池电量低于 20%)时数值与图形显示为红色 Values and graphics turn red above 85% usage (below 20% battery)
占用高于 Usage above
卷 Volume
@@ -339,14 +330,12 @@ npm 缓存 npm Cache
压缩包里应当只有一个 .app The archive should contain exactly one .app
压缩比 Compression ratio
原因 Reason
原生 / 广播 Native / broadcast
原生 IP Native IP
去批准 Approve
去授权 Grant Access
双行圆点 Dots
双行文字 Stacked Text
双行箭头 Arrows
反向解析 Reverse DNS
反馈问题 Report a Problem
发现新版本 {} Version {} available
发现问题 Problems found
@@ -378,7 +367,6 @@ npm 缓存 npm Cache
可用 {} {} available
可用 {} / 共 {} {1} free of {2}
可用内存只剩 {},系统正在压缩和交换内存。关闭不用的应用可以缓解。 Only {} of memory is available and the system is compressing and swapping. Quitting unused apps will help.
可用空间包含系统可以随时清除的缓存,与访达显示一致 Available space includes purgeable caches, matching Finder
可访问 Reachable
右侧风扇 Right Fan
右耳 Right
@@ -536,6 +524,7 @@ npm 缓存 npm Cache
开启 On
开启 / 关闭“合盖不睡眠”(等同 pmset disablesleep) Turn lid-closed sleep prevention on or off (same as pmset disablesleep)
开启后图标出现在菜单栏;按住 ⌘ 键拖动图标可以调整位置 Adds the icon to the menu bar; hold ⌘ and drag the icon to move it
开始 Start
开始分析 Analyze
开始探测 Run
开机以来 Since boot
@@ -582,7 +571,6 @@ npm 缓存 npm Cache
打包版本、系统与辅助工具状态、主要设置、最近 3 天的运行日志和崩溃报告,反馈问题时附上;会去掉序列号、IP 与硬件地址 Bundles version, system and helper status, main settings, and the last 3 days of logs and crash reports to attach to a bug report. Serial numbers, IP and hardware addresses are removed.
打包生成的 .xcarchive,删除后无法重新符号化旧版本崩溃日志 .xcarchive bundles; without them old crash logs can't be symbolicated
打开 OpenStats Open OpenStats
打开 OpenStats 主窗口 Open the OpenStats Window
打开 SMC 失败 ({}) Failed to open SMC ({})
打开登录项设置 Open Login Items Settings
打开网络详情时向 Cloudflare(1.1.1.1)或 ipify 查询一次公网地址,10 分钟内不重复请求;归属地、ASN、网络类型与纯净度由这台 Mac 直接向 cleanip.io 查询,只发送公网地址 When network details open, asks Cloudflare (1.1.1.1) or ipify for your public address, at most once every 10 minutes; location, ASN, network type and cleanliness are looked up by this Mac directly at cleanip.io, sending only the public address
@@ -632,7 +620,6 @@ npm 缓存 npm Cache
教育网 Education
散热模式 Cooling
数据 Data
数据来源 Source
整体风格 Overall style
整体风格套用到所有项目。想让某个项目不一样,在下面“显示项目”里给它单独选一种,比如 CPU 用圆环、风扇用数字。 The overall style applies to every item. To make one item different, pick a style for it under Items below, say a ring for CPU and plain numbers for the fan.
整机 System
@@ -908,7 +895,6 @@ npm 缓存 npm Cache
社交 Social
移到废纸篓 Move to Trash
移到废纸篓? Move to the Trash?
移动网络 Mobile
移动网络 IP Mobile IP
移动运营商 Mobile carrier
空 empty
@@ -972,7 +958,6 @@ npm 缓存 npm Cache
网络断开 Network Down
网络测速 Network Speed Test
网络环境变了,下面是之前的结果,正在重新检测。 The network changed. These are the previous results; checking again.
网络类型 Network type
网络详情打开时每秒 ping 一次,通了是绿格、不通是红格,显示最近 60 次 Pings once a second while network details are open: green when reachable, red when not, showing the last 60
网络运营方 Network operator
网络连接中断超过 20 秒,恢复后再提示一次 Network has been down for over 20 seconds; notifies again when it's back
@@ -1046,7 +1031,6 @@ npm 缓存 npm Cache
超时 Timed out
超级核 Super
超过 1 天未更新的 .crdownload / .part / .download .crdownload / .part / .download files untouched for over a day
跟随整体 Follow global
跟随整体({}) Follow overall ({})
跟随系统 System
路径不可读 Path not readable
@@ -1113,7 +1097,6 @@ npm 缓存 npm Cache
重新分析 Analyze again
重新安装 Reinstall
重新扫描 Rescan
重新查询公网 IP Look Up Public IP Again
重新检测 Check Again
重新生成 Regenerate
重新读取蓝牙设备电量 Read Bluetooth device batteries again
@@ -1143,7 +1126,6 @@ npm 缓存 npm Cache
风扇模式 Fan mode
风扇设置 Fan Settings
风扇转速 Fan Speed
风格 Style
风险标记 Risk flags
风险评分 Risk score
饼图 Pie
@@ -159,7 +159,6 @@ public enum PublicAddressLookup {
struct Geo: Decodable {
let countryCode: String?
let country: String?
let countryEn: String?
let region: String?
let city: String?
}
@@ -42,6 +42,18 @@ struct BarHistoryChart: View {
}
}
/// 图表里 25% / 50% / 75% 三条淡淡的参考线
enum ChartGrid {
static func draw(in context: GraphicsContext, size: CGSize) {
let usable = size.height - DS.Size.chartLine * 2
for fraction in [0.25, 0.5, 0.75] {
let y = (DS.Size.chartLine + usable * CGFloat(1 - fraction)).rounded()
context.fill(Path(CGRect(x: 0, y: y, width: size.width, height: DS.Size.stroke)),
with: .color(DS.Palette.border))
}
}
}
/// 历史折线(可带实色半透明填充)
struct LineHistoryChart: View {
let values: [Double]
@@ -55,14 +67,7 @@ struct LineHistoryChart: View {
var body: some View {
Canvas { context, size in
if grid {
let usable = size.height - DS.Size.chartLine * 2
for fraction in [0.25, 0.5, 0.75] {
let y = (DS.Size.chartLine + usable * CGFloat(1 - fraction)).rounded()
context.fill(Path(CGRect(x: 0, y: y, width: size.width, height: DS.Size.stroke)),
with: .color(DS.Palette.border))
}
}
if grid { ChartGrid.draw(in: context, size: size) }
let line = LineHistoryChart.path(values: values, capacity: capacity, maxValue: maxValue, in: size)
guard let line else { return }
if filled {
@@ -112,14 +117,7 @@ struct TimedLineChart: View {
var body: some View {
Canvas { context, size in
if grid {
let usable = size.height - DS.Size.chartLine * 2
for fraction in [0.25, 0.5, 0.75] {
let y = (DS.Size.chartLine + usable * CGFloat(1 - fraction)).rounded()
context.fill(Path(CGRect(x: 0, y: y, width: size.width, height: DS.Size.stroke)),
with: .color(DS.Palette.border))
}
}
if grid { ChartGrid.draw(in: context, size: size) }
for segment in Self.segments(points, duration: duration, end: end, in: size) {
guard let first = segment.first, let last = segment.last else { continue }
var line = Path()
@@ -0,0 +1,25 @@
import Foundation
import Localization
/// 归属地与 IP 类型的文字:网络弹窗与出口窗口共用
enum GeoText {
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
static func location(countryCode: String, region: String?, regionEnglish: String?, city: String?, cityEnglish: String?) -> String {
let country = Locale(identifier: L10n.isEnglish ? "en" : "zh-Hans").localizedString(forRegionCode: countryCode) ?? countryCode
let region = L10n.isEnglish ? (regionEnglish ?? region) : region
let city = L10n.isEnglish ? (cityEnglish ?? city) : city
var parts = [country]
for part in [region, city].compactMap({ $0 }) where part != parts.last { parts.append(part) }
return parts.joined(separator: " · ")
}
/// 数据源给的 IP 类型(residential ip、datacenter ip……)对应的徽章;不认识的返回 nil
static func ipTypeBadge(_ raw: String) -> (icon: String, text: String, tone: TagBadge.Tone)? {
let type = raw.lowercased()
if type.contains("residential") { return ("house.fill", tr("住宅 IP"), .success) }
if type.contains("mobile") { return ("iphone.radiowaves.left.and.right", tr("移动网络 IP"), .success) }
if type.contains("business") { return ("building.2.fill", tr("企业 IP"), .success) }
if type.contains("idc") || type.contains("datacenter") || type.contains("hosting") { return ("server.rack", tr("机房 IP"), .warning) }
return nil
}
}
@@ -306,7 +306,7 @@ private struct ExitNode: View {
.lineLimit(1)
.truncationMode(.tail)
}
if let type = geo?.ipType.flatMap(EgressText.ipTypeBadge) {
if let type = geo?.ipType.flatMap(GeoText.ipTypeBadge) {
TagBadge(text: type.text, tone: type.tone, compact: true)
}
}
@@ -744,24 +744,11 @@ private enum EgressText {
}
}
static func ipTypeBadge(_ raw: String) -> (text: String, tone: TagBadge.Tone)? {
let type = raw.lowercased()
if type.contains("residential") { return (tr("住宅 IP"), .success) }
if type.contains("mobile") { return (tr("移动网络 IP"), .success) }
if type.contains("business") { return (tr("企业 IP"), .success) }
if type.contains("idc") || type.contains("datacenter") || type.contains("hosting") { return (tr("机房 IP"), .warning) }
return nil
}
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
static func location(_ geo: EgressGeo?, fallback country: String?) -> String {
guard let code = geo?.countryCode ?? country else { return "" }
let name = Locale(identifier: L10n.isEnglish ? "en" : "zh-Hans").localizedString(forRegionCode: code) ?? code
let region = L10n.isEnglish ? (geo?.regionEnglish ?? geo?.region) : geo?.region
let city = L10n.isEnglish ? (geo?.cityEnglish ?? geo?.city) : geo?.city
var parts = [name]
for part in [region, city].compactMap({ $0 }) where part != parts.last { parts.append(part) }
return parts.joined(separator: " · ")
return GeoText.location(countryCode: code, region: geo?.region, regionEnglish: geo?.regionEnglish,
city: geo?.city, cityEnglish: geo?.cityEnglish)
}
static func checkedAt(_ date: Date, now: Date) -> String {
@@ -244,12 +244,8 @@ private struct AddressSection: View {
/// 国家 · 省 / 州 · 城市;英文界面优先用英文地名,重复的相邻项只留一个
private func location(_ addresses: PublicAddresses, code: String) -> String {
let country = Locale(identifier: L10n.isEnglish ? "en" : "zh-Hans").localizedString(forRegionCode: code) ?? code
let region = L10n.isEnglish ? (addresses.regionEnglish ?? addresses.region) : addresses.region
let city = L10n.isEnglish ? (addresses.cityEnglish ?? addresses.city) : addresses.city
var parts: [String] = [country]
for part in [region, city].compactMap({ $0 }) where part != parts.last { parts.append(part) }
return parts.joined(separator: " · ")
GeoText.location(countryCode: code, region: addresses.region, regionEnglish: addresses.regionEnglish,
city: addresses.city, cityEnglish: addresses.cityEnglish)
}
/// 标题行:IP 地址 ……… [IPv4 | IPv6] ⟳;数据来源的字标只放在下面的纯净度区块里
@@ -292,13 +288,11 @@ private struct AddressSection: View {
badges.append(isNative ? Badge(icon: "checkmark.shield.fill", text: tr("原生 IP"), tone: .success)
: Badge(icon: "antenna.radiowaves.left.and.right", text: tr("广播 IP"), tone: .warning))
}
if let ipType = addresses.ipType?.lowercased() {
switch ipType {
case "residential ip": badges.append(Badge(icon: "house.fill", text: tr("住宅 IP"), tone: .success))
case "datacenter ip", "hosting ip": badges.append(Badge(icon: "server.rack", text: tr("机房 IP"), tone: .warning))
case "mobile ip": badges.append(Badge(icon: "iphone.radiowaves.left.and.right", text: tr("移动网络 IP"), tone: .success))
case "business ip": badges.append(Badge(icon: "building.2.fill", text: tr("企业 IP"), tone: .success))
default: badges.append(Badge(icon: "questionmark.circle", text: addresses.ipType ?? "", tone: .neutral))
if let ipType = addresses.ipType {
if let known = GeoText.ipTypeBadge(ipType) {
badges.append(Badge(icon: known.icon, text: known.text, tone: known.tone))
} else {
badges.append(Badge(icon: "questionmark.circle", text: ipType, tone: .neutral))
}
}
if let asnType = addresses.asnType {
@@ -314,28 +308,6 @@ private struct AddressSection: View {
}
return badges
}
static func networkTypeLabel(_ raw: String) -> String {
switch raw.lowercased() {
case "residential", "isp": tr("住宅宽带")
case "business": tr("企业")
case "hosting", "datacenter", "data center": tr("机房")
case "mobile", "cellular": tr("移动网络")
case "education": tr("教育网")
case "government": tr("政府")
default: raw
}
}
static func ipTypeLabel(_ raw: String) -> String {
switch raw.lowercased() {
case "residential ip": tr("住宅 IP")
case "datacenter ip", "hosting ip": tr("机房 IP")
case "mobile ip": tr("移动网络 IP")
case "business ip": tr("企业 IP")
default: raw
}
}
}
// MARK: - IP 纯净度
@@ -76,9 +76,6 @@ public enum MenuBarItem: String, CaseIterable, Identifiable, Sendable {
}
}
/// 可以单独指定风格的指标(网速有自己的样式)
var supportsStyleOverride: Bool { self != .network }
var symbol: String {
switch self {
case .cpu: "cpu"
@@ -140,10 +140,6 @@ public final class HelperClient {
await call { proxy, reply in proxy.setFanTarget(fan: fan, rpm: rpm, reply: reply) }
}
func setFanAutomatic(fan: Int) async -> String? {
await call { proxy, reply in proxy.setFanAutomatic(fan: fan, reply: reply) }
}
func resetAllFans() async -> String? {
await call { proxy, reply in proxy.resetAllFans(reply: reply) }
}
@@ -42,7 +42,6 @@ public final class NetworkController {
@ObservationIgnored private let settings: AppSettings
@ObservationIgnored private var probeTask: Task<Void, Never>?
@ObservationIgnored private var detailTask: Task<Void, Never>?
@ObservationIgnored private var probedAddress: String?
@ObservationIgnored private var lastPublicLookup: (date: Date, localIPv4: [String])?
@ObservationIgnored private var isPaused = false
@ObservationIgnored private var wantsDetail = false
-1
View File
@@ -155,7 +155,6 @@
}
.mb-drop a:hover { background: var(--color-primary); color: #fff; }
.mb-drop hr { border: 0; border-top: 1px solid var(--drop-border); margin: var(--space-1) var(--space-2); }
.mb-drop__ext { opacity: 0.6; }
.menubar__right { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }
.menubar__clock { font-variant-numeric: tabular-nums; }