use std::collections::HashMap;
use once_cell::sync::Lazy;
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
#[clap(rename_all = "lower")]
pub enum Lang {
Zh,
En,
}
static mut LANG: Lang = Lang::Zh;
static mut INITIALIZED: bool = false;
pub fn init(lang: Option<Lang>) {
let detected = lang.unwrap_or_else(detect);
unsafe {
LANG = detected;
INITIALIZED = true;
}
}
pub fn current() -> Lang {
if unsafe { !INITIALIZED } {
init(None);
}
unsafe { LANG }
}
pub fn detect() -> Lang {
if let Ok(v) = std::env::var("NETUTILS_LANG") {
match v.to_lowercase().as_str() {
"en" | "english" => return Lang::En,
"zh" | "chinese" | "cn" => return Lang::Zh,
_ => {}
}
}
if let Ok(v) = std::env::var("LANG") {
if v.starts_with("zh") {
return Lang::Zh;
}
if !v.is_empty() {
return Lang::En;
}
}
#[cfg(target_os = "windows")]
{
if is_chinese_windows() {
return Lang::Zh;
}
return Lang::En;
}
#[cfg(not(target_os = "windows"))]
{
Lang::En
}
}
#[cfg(target_os = "windows")]
fn is_chinese_windows() -> bool {
use std::process::Command;
if let Ok(output) = Command::new("powershell")
.args([
"-Command",
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; (Get-Culture).Name",
])
.output()
{
let text = String::from_utf8_lossy(&output.stdout);
return text.starts_with("zh");
}
false
}
fn build_dict() -> HashMap<&'static str, Vec<&'static str>> {
let entries: Vec<(&str, &str, &str)> = vec![
("common.success", "成功", "success"),
("common.fail", "失败", "fail"),
("common.error", "错误", "error"),
("common.unknown", "未知", "unknown"),
("common.not_set", "未设置", "not set"),
("common.yes", "是", "yes"),
("common.no", "否", "no"),
("common.none", "--", "--"),
("banner.title", "本地网络检测报告", "Local Network Report"),
("iface.title", "📡 网络接口列表", "📡 Network Interfaces"),
("iface.name", "名称", "Name"),
("iface.mac", "MAC 地址", "MAC Address"),
("iface.ipv4", "IPv4", "IPv4"),
("iface.status", "状态", "Status"),
("iface.type", "类型", "Type"),
("iface.metric", "跃点", "Metric"),
("iface.egress", "出口", "Egress"),
("iface.egress_yes", "✓ 出口", "✓ egress"),
("iface.egress_backup", "~ 备用", "~ backup"),
("iface.summary", "共 {0} 个接口,其中 {1} 个虚拟网卡", "{0} interfaces, {1} virtual"),
("iface.type_loopback", "回环", "Loopback"),
("iface.type_ethernet", "以太网", "Ethernet"),
("iface.type_wireless", "无线", "Wireless"),
("iface.type_mihomo", "Mihomo/TUN", "Mihomo/TUN"),
("iface.type_clash", "Clash/TUN", "Clash/TUN"),
("iface.type_wireguard", "WireGuard", "WireGuard"),
("iface.type_openvpn", "OpenVPN", "OpenVPN"),
("iface.type_virtualbox", "VirtualBox", "VirtualBox"),
("iface.type_vmware", "VMware", "VMware"),
("iface.type_hyperv", "Hyper-V", "Hyper-V"),
("iface.type_docker", "Docker", "Docker"),
("iface.type_tuntap", "TUN/TAP", "TUN/TAP"),
("iface.type_other", "其他", "Other"),
("egress.title", "🚪 流量出口", "🚪 Egress"),
("egress.iface", "接口", "Interface"),
("egress.ip", "IP", "IP"),
("egress.type", "类型", "Type"),
("egress.metric", "跃点", "Metric"),
("egress.metric_hint", "接口跃点,越小优先级越高", "interface metric, lower = higher priority"),
("egress.logic_title", "选路逻辑", "Routing Logic"),
("egress.logic_1", "系统为出站流量选择出口时,比较每个候选路由的 有效跃点:", "System selects egress by comparing effective metric of each candidate route:"),
("egress.logic_2", "有效跃点 = 路由跃点(RouteMetric) + 接口跃点(InterfaceMetric)", "Effective Metric = RouteMetric + InterfaceMetric"),
("egress.logic_3", "有效跃点越低,接口越优先。", "Lower effective metric = higher priority."),
("egress.logic_selected", "{0} 的有效跃点 = 路由跃点({1}) + 接口跃点({2}) = {3},选中", "{0} effective metric = route({1}) + interface({2}) = {3}, selected"),
("egress.unreachable", "无法检测(可能无网络连接)", "Unable to detect (no network connection?)"),
("route.title", "🗺️ 路由表 (默认路由优先)", "🗺️ Routing Table (default first)"),
("route.dest", "目标", "Destination"),
("route.gateway", "网关", "Gateway"),
("route.interface", "接口", "Interface"),
("route.metric", "跃点", "Metric"),
("proxy.title", "🔒 代理设置", "🔒 Proxy Settings"),
("proxy.type", "类型", "Type"),
("proxy.value", "值", "Value"),
("proxy.http", "HTTP 代理", "HTTP Proxy"),
("proxy.https", "HTTPS 代理", "HTTPS Proxy"),
("proxy.all", "全局代理", "All Proxy"),
("proxy.no", "排除列表", "No Proxy"),
("proxy.env", "环境变量", "Env Variables"),
("proxy.system", "系统代理", "System Proxy"),
("proxy.disabled", "未启用", "disabled"),
("ping.title", "🏓 Ping {0}", "🏓 Ping {0}"),
("ping.resolve_fail", "❌ 无法解析主机: {0}", "❌ Failed to resolve host: {0}"),
("ping.target", "目标: {0} ({1})", "Target: {0} ({1})"),
("ping.icmp_fallback", "⚠ ICMP 不可用,回退到 TCP ping (端口 80)", "⚠ ICMP unavailable, falling back to TCP ping (port 80)"),
("ping.client_fail", "ICMP client 创建失败: {0}", "ICMP client creation failed: {0}"),
("ping.reply", "seq={0} 来自 {1} 时间={2}ms", "seq={0} from {1} time={2}ms"),
("ping.timeout", "TCP: 超时", "TCP: timeout"),
("ping.fail", "seq={0} 失败: {1}", "seq={0} failed: {1}"),
("ping.stats", "📊 统计", "📊 Statistics"),
("ping.sent", "发送", "Sent"),
("ping.recv", "接收", "Received"),
("ping.lost", "丢失", "Lost"),
("ping.loss_rate", "丢包率", "Loss Rate"),
("ping.min", "最小延迟", "Min"),
("ping.max", "最大延迟", "Max"),
("ping.avg", "平均延迟", "Avg"),
("dns.title", "🔍 DNS 查询: {0} ({1})", "🔍 DNS Query: {0} ({1})"),
("dns.no_record", "未找到 {0} 记录", "No {0} records found"),
("dns.fail", "❌ 查询失败: {0}", "❌ Query failed: {0}"),
("dns.elapsed", "查询耗时: {0}ms", "Elapsed: {0}ms"),
("dns.idx", "序号", "#"),
("dns.value", "记录值", "Value"),
("dns.ttl", "TTL", "TTL"),
("trace.title", "🛤️ Traceroute to {0}", "🛤️ Traceroute to {0}"),
("trace.resolve_fail", "❌ 无法解析主机: {0}", "❌ Failed to resolve host: {0}"),
("trace.target", "目标: {0} ({1})", "Target: {0} ({1})"),
("trace.max_hops", "最大跳数: {0}", "Max hops: {0}"),
("trace.not_reached", "⚠ 未在 {0} 跳内到达目标", "⚠ Did not reach target within {0} hops"),
("trace.hop", "跳数", "Hop"),
("trace.ip", "IP 地址", "IP Address"),
("trace.probe", "延迟 {0}", "Probe {0}"),
("scan.title", "🔎 端口扫描: {0}", "🔎 Port Scan: {0}"),
("scan.resolve_fail", "❌ 无法解析主机: {0}", "❌ Failed to resolve host: {0}"),
("scan.target", "目标: {0} ({1})", "Target: {0} ({1})"),
("scan.info", "扫描 {0} 个端口,并发 {1}", "Scanning {0} ports, concurrency {1}"),
("scan.no_open", "未发现开放端口", "No open ports found"),
("scan.done", "扫描完成: {0}/{1} 开放", "Done: {0}/{1} open"),
("scan.port", "端口", "Port"),
("scan.state", "状态", "State"),
("scan.service", "服务", "Service"),
("check.title", "🔌 连通性测试: {0}", "🔌 Connectivity: {0}"),
("check.format_err", "❌ 格式错误,请使用 host:port", "❌ Invalid format, use host:port"),
("check.port_err", "❌ 端口号无效: {0}", "❌ Invalid port: {0}"),
("check.tcp", "类型: TCP", "Type: TCP"),
("check.http", "类型: HTTP", "Type: HTTP"),
("check.tcp_ok", "[{0}/{1}] ✓ 连接成功 {2:.2}ms", "[{0}/{1}] ✓ connected {2:.2}ms"),
("check.tcp_fail", "[{0}/{1}] ✗ 连接失败 {2}", "[{0}/{1}] ✗ failed {2}"),
("check.tcp_timeout", "[{0}/{1}] ✗ 连接超时 ({2}s)", "[{0}/{1}] ✗ timeout ({2}s)"),
("check.http_ok", "[{0}/{1}] {2} {3} {4:.2}ms", "[{0}/{1}] {2} {3} {4:.2}ms"),
("check.http_fail", "[{0}/{1}] ✗ {2} {3:.2}ms", "[{0}/{1}] ✗ {2} {3:.2}ms"),
("check.conn_fail", "连接失败", "connection failed"),
("check.req_timeout", "请求超时", "request timeout"),
("check.count", "测试次数", "Tests"),
("check.ok", "成功", "OK"),
("check.fail_count", "失败/错误", "Failed"),
("check.ok_2xx", "成功 (2xx)", "OK (2xx)"),
("diag.title", "🔍 网络诊断报告", "🔍 Network Diagnostics"),
("diag.elapsed", "诊断耗时: {0:.1}s", "Time: {0:.1}s"),
("diag.net_ok", "网络连接正常 (出口: {0} {1})", "Network connected (egress: {0} {1})"),
("diag.net_fail", "无网络连接", "No network connection"),
("diag.dns_ok", "DNS 解析正常 ({0} → {1}, {2}ms)", "DNS OK ({0} → {1}, {2}ms)"),
("diag.dns_fail", "DNS 解析失败 ({0})", "DNS failed ({0})"),
("diag.gw_ok", "默认网关可达 ({0}, {1}ms)", "Gateway reachable ({0}, {1}ms)"),
("diag.gw_fail", "默认网关不可达", "Gateway unreachable"),
("diag.proxy_on", "系统代理已启用 ({0})", "System proxy enabled ({0})"),
("diag.proxy_off", "系统代理未启用", "System proxy disabled"),
("diag.http_ok", "HTTPS 连通正常 ({0} → {1}, {2}ms)", "HTTPS OK ({0} → {1}, {2}ms)"),
("diag.http_fail", "HTTPS 连通失败 ({0})", "HTTPS failed ({0})"),
("diag.ipv6_ok", "IPv6 可用", "IPv6 available"),
("diag.ipv6_fail", "IPv6 不可用", "IPv6 unavailable"),
];
let mut map = HashMap::new();
for (key, zh, en) in entries {
map.insert(key, vec![zh, en]);
}
map
}
static DICT: Lazy<HashMap<&'static str, Vec<&'static str>>> = Lazy::new(build_dict);
pub fn t(key: &str) -> String {
let lang = current();
if let Some(vals) = DICT.get(key) {
match lang {
Lang::Zh => vals[0].to_string(),
Lang::En => vals[1].to_string(),
}
} else {
key.to_string()
}
}
pub fn t1(key: &str, a: &str) -> String {
t(key).replace("{0}", a)
}
pub fn t2(key: &str, a: &str, b: &str) -> String {
t(key).replace("{0}", a).replace("{1}", b)
}
#[allow(dead_code)]
pub fn t3(key: &str, a: &str, b: &str, c: &str) -> String {
t(key).replace("{0}", a).replace("{1}", b).replace("{2}", c)
}