Expand i18n coverage

This commit is contained in:
ZacharyZcR
2026-05-23 15:18:40 +08:00
parent 9ed6cc95b6
commit 73cbe803c4
79 changed files with 2540 additions and 621 deletions
+6 -5
View File
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/shadow1ng/fscan/common/config"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/parsers"
)
@@ -28,12 +29,12 @@ func BuildConfig(fv *FlagVars, info *HostInfo) (*Config, *State, error) {
// 3. 解析凭据
if err := parseCredentials(fv, cfg); err != nil {
return nil, nil, fmt.Errorf("凭据解析失败: %w", err)
return nil, nil, fmt.Errorf("%s: %w", i18n.GetText("config_credentials_parse_failed"), err)
}
// 4. 解析目标(主机、端口、URL)
if err := parseTargets(fv, info, cfg, state); err != nil {
return nil, nil, fmt.Errorf("目标解析失败: %w", err)
return nil, nil, fmt.Errorf("%s: %w", i18n.GetText("config_targets_parse_failed"), err)
}
// 5. 应用日志级别
@@ -101,7 +102,7 @@ func parseUsernames(fv *FlagVars) []string {
if lines, err := parsers.ReadLinesFromFile(fv.UsersFile); err == nil {
usernames = append(usernames, lines...)
} else {
LogError(fmt.Sprintf("读取用户名文件 %s 失败: %v", fv.UsersFile, err))
LogError(i18n.Tr("config_read_users_failed", fv.UsersFile, err))
}
}
@@ -131,7 +132,7 @@ func parsePasswords(fv *FlagVars) []string {
if lines, err := parsers.ReadLinesFromFile(fv.PasswordsFile); err == nil {
passwords = append(passwords, lines...)
} else {
LogError(fmt.Sprintf("读取密码文件 %s 失败: %v", fv.PasswordsFile, err))
LogError(i18n.Tr("config_read_passwords_failed", fv.PasswordsFile, err))
}
}
@@ -251,7 +252,7 @@ func parseURLs(fv *FlagVars) []string {
urls = append(urls, normalizeURL(line))
}
} else {
LogError(fmt.Sprintf("读取URL文件 %s 失败: %v", fv.URLsFile, err))
LogError(i18n.Tr("config_read_urls_failed", fv.URLsFile, err))
}
}
+24 -22
View File
@@ -9,6 +9,8 @@ import (
"runtime"
"runtime/pprof"
"runtime/trace"
"github.com/shadow1ng/fscan/common/i18n"
)
var (
@@ -19,82 +21,82 @@ var (
func Start() {
if err := os.MkdirAll(profilesPath, 0755); err != nil {
fmt.Printf("[DEBUG] 创建 profiles 目录失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_profiles_failed", err))
return
}
var err error
cpuProfile, err = os.Create(profilesPath + "/cpu.prof")
if err != nil {
fmt.Printf("[DEBUG] 创建 CPU profile 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_cpu_profile_failed", err))
} else {
if err := pprof.StartCPUProfile(cpuProfile); err != nil {
fmt.Printf("[DEBUG] 启动 CPU profile 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_start_cpu_profile_failed", err))
cpuProfile.Close()
cpuProfile = nil
} else {
fmt.Printf("[DEBUG] CPU profiling 已启动 -> %s/cpu.prof\n", profilesPath)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_cpu_profile_started", profilesPath))
}
}
traceFile, err = os.Create(profilesPath + "/trace.out")
if err != nil {
fmt.Printf("[DEBUG] 创建 trace 文件失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_trace_failed", err))
} else {
if err := trace.Start(traceFile); err != nil {
fmt.Printf("[DEBUG] 启动 trace 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_start_trace_failed", err))
traceFile.Close()
traceFile = nil
} else {
fmt.Printf("[DEBUG] Execution trace 已启动 -> %s/trace.out\n", profilesPath)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_trace_started", profilesPath))
}
}
fmt.Printf("[DEBUG] 性能分析已启动,程序结束时自动保存到 %s/\n", profilesPath)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_profiling_started", profilesPath))
}
func Stop() {
if cpuProfile != nil {
pprof.StopCPUProfile()
cpuProfile.Close()
fmt.Printf("[DEBUG] CPU profile 已保存\n")
fmt.Printf("[DEBUG] %s\n", i18n.GetText("debug_cpu_profile_saved"))
}
if traceFile != nil {
trace.Stop()
traceFile.Close()
fmt.Printf("[DEBUG] Trace 已保存\n")
fmt.Printf("[DEBUG] %s\n", i18n.GetText("debug_trace_saved"))
}
memProfile, err := os.Create(profilesPath + "/mem.prof")
if err != nil {
fmt.Printf("[DEBUG] 创建内存 profile 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_mem_profile_failed", err))
} else {
runtime.GC()
if err := pprof.WriteHeapProfile(memProfile); err != nil {
fmt.Printf("[DEBUG] 写入内存 profile 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_write_mem_profile_failed", err))
} else {
fmt.Printf("[DEBUG] 内存 profile 已保存 -> %s/mem.prof\n", profilesPath)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_mem_profile_saved", profilesPath))
}
memProfile.Close()
}
goroutineProfile, err := os.Create(profilesPath + "/goroutine.prof")
if err != nil {
fmt.Printf("[DEBUG] 创建 goroutine profile 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_goroutine_profile_failed", err))
} else {
if err := pprof.Lookup("goroutine").WriteTo(goroutineProfile, 0); err != nil {
fmt.Printf("[DEBUG] 写入 goroutine profile 失败: %v\n", err)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_write_goroutine_profile_failed", err))
} else {
fmt.Printf("[DEBUG] Goroutine profile 已保存 -> %s/goroutine.prof\n", profilesPath)
fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_goroutine_profile_saved", profilesPath))
}
goroutineProfile.Close()
}
fmt.Printf("\n[DEBUG] 所有性能分析文件已保存到 %s/\n", profilesPath)
fmt.Printf("[DEBUG] 查看方法:\n")
fmt.Printf(" CPU 火焰图: go tool pprof -http=:8081 %s/cpu.prof\n", profilesPath)
fmt.Printf(" 内存火焰图: go tool pprof -http=:8081 %s/mem.prof\n", profilesPath)
fmt.Printf(" 协程分析: go tool pprof -http=:8081 %s/goroutine.prof\n", profilesPath)
fmt.Printf(" 执行时间线: go tool trace %s/trace.out\n", profilesPath)
fmt.Printf("\n[DEBUG] %s\n", i18n.Tr("debug_profiles_saved", profilesPath))
fmt.Printf("[DEBUG] %s\n", i18n.GetText("debug_view_methods"))
fmt.Printf(" %s: go tool pprof -http=:8081 %s/cpu.prof\n", i18n.GetText("debug_cpu_flamegraph"), profilesPath)
fmt.Printf(" %s: go tool pprof -http=:8081 %s/mem.prof\n", i18n.GetText("debug_mem_flamegraph"), profilesPath)
fmt.Printf(" %s: go tool pprof -http=:8081 %s/goroutine.prof\n", i18n.GetText("debug_goroutine_analysis"), profilesPath)
fmt.Printf(" %s: go tool trace %s/trace.out\n", i18n.GetText("debug_execution_timeline"), profilesPath)
}
+7 -3
View File
@@ -2,7 +2,11 @@
package common
import "flag"
import (
"flag"
"github.com/shadow1ng/fscan/common/i18n"
)
// WebMode 表示是否启动Web管理界面
var WebMode bool
@@ -11,6 +15,6 @@ var WebMode bool
var WebPort int
func init() {
flag.BoolVar(&WebMode, "web", false, "启动Web管理界面 (Start Web UI)")
flag.IntVar(&WebPort, "webport", 10240, "Web服务器端口 (Web server port)")
flag.BoolVar(&WebMode, "web", false, i18n.GetText("flag_web_mode"))
flag.IntVar(&WebPort, "webport", 10240, i18n.GetText("flag_web_port"))
}
+4 -2
View File
@@ -5,6 +5,8 @@ import (
"fmt"
"strings"
"sync"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
@@ -92,9 +94,9 @@ type PacketLimitError struct {
func (e *PacketLimitError) Error() string {
if e.Sentinel == ErrMaxPacketReached {
return fmt.Sprintf("已达到最大发包数量限制: %d", e.Limit)
return i18n.Tr("packet_limit_max_reached", e.Limit)
}
return fmt.Sprintf("发包速率受限: %d包/分钟", e.Limit)
return i18n.Tr("packet_limit_rate_limited", e.Limit)
}
func (e *PacketLimitError) Unwrap() error {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -30,7 +30,7 @@ func Initialize(info *HostInfo) (*InitResult, error) {
// 2. 从 FlagVars 构建 Config 和 State
cfg, state, err := BuildConfig(GetFlagVars(), info)
if err != nil {
return nil, fmt.Errorf("配置构建失败: %w", err)
return nil, fmt.Errorf("%s: %w", i18n.GetText("config_build_failed"), err)
}
// 3. 设置全局实例
@@ -39,7 +39,7 @@ func Initialize(info *HostInfo) (*InitResult, error) {
// 4. 初始化输出系统
if err := InitOutput(); err != nil {
return nil, fmt.Errorf("输出初始化失败: %w", err)
return nil, fmt.Errorf("%s: %w", i18n.GetText("output_init_failed"), err)
}
session := NewScanSession(cfg, state, GetFlagVars())
@@ -67,7 +67,7 @@ func ValidateExclusiveParams(info *HostInfo) error {
if fv.TargetURL != "" {
paramCount++
if activeParam != "" {
activeParam += " 和 -u"
activeParam = i18n.Tr("param_join_and", activeParam, "-u")
} else {
activeParam = "-u"
}
@@ -75,7 +75,7 @@ func ValidateExclusiveParams(info *HostInfo) error {
if fv.LocalPlugin != "" {
paramCount++
if activeParam != "" {
activeParam += " 和 -local"
activeParam = i18n.Tr("param_join_and", activeParam, "-local")
} else {
activeParam = "-local"
}
+5 -5
View File
@@ -16,8 +16,8 @@ import (
"sync"
"time"
"github.com/shadow1ng/fscan/common/proxy"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/common/proxy"
)
// =============================================================================
@@ -109,14 +109,14 @@ func createProxyConfig(timeout time.Duration) *proxy.ProxyConfig {
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
// 检查发包限制 - 在代理连接前进行控制
if canSend, reason := CanSendPacket(); !canSend {
LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, reason))
LogError(i18n.Tr("tcp_connection_restricted", address, reason))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason))
}
// 获取全局拨号器(复用,避免重复创建)
dialer, err := getGlobalDialer(timeout)
if err != nil {
LogError(fmt.Sprintf("获取代理拨号器失败: %v", err))
LogError(i18n.Tr("proxy_dialer_failed", err))
GetGlobalState().IncrementTCPFailedPacketCount()
return nil, err
}
@@ -127,7 +127,7 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.
// 统计TCP包数量 - 无论是否使用代理都要计数
if err != nil {
GetGlobalState().IncrementTCPFailedPacketCount()
LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err))
LogDebug(i18n.Tr("connection_failed", address, err))
return nil, err
}
@@ -166,7 +166,7 @@ func IsSOCKS5Proxy() bool {
func SafeHTTPDo(client *http.Client, req *http.Request) (*http.Response, error) {
// 检查发包限制
if canSend, reason := CanSendPacket(); !canSend {
LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), reason))
LogError(i18n.Tr("http_request_restricted", req.URL.String(), reason))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason))
}
+7 -5
View File
@@ -9,6 +9,8 @@ import (
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// escapeControlChars 转义控制字符
@@ -112,13 +114,13 @@ func (w *TXTWriter) Write(result *ScanResult) error {
func (w *TXTWriter) getSeparator(newType ResultType) string {
switch newType {
case TypeHost:
return "# ===== 存活主机 ====="
return i18n.GetText("output_section_hosts")
case TypePort:
return "# ===== 开放端口 ====="
return i18n.GetText("output_section_ports")
case TypeService:
return "# ===== 服务信息 ====="
return i18n.GetText("output_section_services")
case TypeVuln:
return "# ===== 漏洞信息 ====="
return i18n.GetText("output_section_vulns")
default:
return "# ===================="
}
@@ -376,7 +378,7 @@ func (w *TXTWriter) writeWebServices() {
return
}
_, _ = w.bufWriter.WriteString("# ===== Web服务 =====\n")
_, _ = w.bufWriter.WriteString(i18n.GetText("output_section_web_services") + "\n")
for _, url := range urls {
_, _ = w.bufWriter.WriteString(url + "\n")
}
+8 -6
View File
@@ -221,7 +221,7 @@ func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 {
spinner := pm.getActivityIndicator()
base := fmt.Sprintf("%s %s 等待中...", pm.description, spinner)
base := fmt.Sprintf("%s %s %s", pm.description, spinner, i18n.GetText("progress_waiting"))
if packetInfo != "" {
return base + " " + packetInfo
}
@@ -319,13 +319,15 @@ func (pm *ProgressManager) showCompletionInfo() {
fmt.Print("\n")
completionMsg := i18n.GetText("progress_scan_completed")
doneMsg := i18n.GetText("progress_done")
durationMsg := i18n.GetText("progress_duration")
if pm.noColor {
fmt.Printf("[完成] %s %d/%d (耗时: %s)\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
fmt.Printf("[%s] %s %d/%d (%s: %s)\n",
doneMsg, completionMsg, pm.total, pm.total, durationMsg, formatDuration(elapsed))
} else {
fmt.Printf("%s[完成] %s %d/%d%s %s(耗时: %s)%s\n",
AnsiGreen, completionMsg, pm.total, pm.total, AnsiReset,
AnsiGray, formatDuration(elapsed), AnsiReset)
fmt.Printf("%s[%s] %s %d/%d%s %s(%s: %s)%s\n",
AnsiGreen, doneMsg, completionMsg, pm.total, pm.total, AnsiReset,
AnsiGray, durationMsg, formatDuration(elapsed), AnsiReset)
}
}
+18 -16
View File
@@ -2,6 +2,8 @@ package proxy
import (
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
@@ -151,41 +153,41 @@ const (
// 错误消息常量
// =============================================================================
const (
var (
// ErrMsgUnsupportedProxyType Manager错误消息 - 不支持的代理类型
ErrMsgUnsupportedProxyType = "不支持的代理类型"
ErrMsgUnsupportedProxyType = i18n.GetText("proxy_unsupported_type")
// ErrMsgEmptyConfig 配置不能为空
ErrMsgEmptyConfig = "配置不能为空"
ErrMsgEmptyConfig = i18n.GetText("proxy_empty_config")
// ErrMsgSOCKS5ParseFailed SOCKS5错误消息 - 地址解析失败
ErrMsgSOCKS5ParseFailed = "SOCKS5代理地址解析失败"
ErrMsgSOCKS5ParseFailed = i18n.GetText("proxy_socks5_parse_failed")
// ErrMsgSOCKS5CreateFailed 拨号器创建失败
ErrMsgSOCKS5CreateFailed = "SOCKS5拨号器创建失败"
ErrMsgSOCKS5CreateFailed = i18n.GetText("proxy_socks5_create_failed")
// ErrMsgSOCKS5ConnTimeout 连接超时
ErrMsgSOCKS5ConnTimeout = "SOCKS5连接超时"
ErrMsgSOCKS5ConnTimeout = i18n.GetText("proxy_socks5_conn_timeout")
// ErrMsgSOCKS5ConnFailed 连接失败
ErrMsgSOCKS5ConnFailed = "SOCKS5连接失败"
ErrMsgSOCKS5ConnFailed = i18n.GetText("proxy_socks5_conn_failed")
// ErrMsgDirectConnFailed 直连错误消息 - 直连失败
ErrMsgDirectConnFailed = "直连失败"
ErrMsgDirectConnFailed = i18n.GetText("proxy_direct_conn_failed")
// ErrMsgHTTPConnFailed HTTP代理错误消息 - 连接失败
ErrMsgHTTPConnFailed = "连接HTTP代理服务器失败"
ErrMsgHTTPConnFailed = i18n.GetText("proxy_http_conn_failed")
// ErrMsgHTTPSetWriteTimeout 设置写超时失败
ErrMsgHTTPSetWriteTimeout = "设置写超时失败"
ErrMsgHTTPSetWriteTimeout = i18n.GetText("proxy_http_set_write_timeout")
// ErrMsgHTTPSendConnectFail 发送CONNECT请求失败
ErrMsgHTTPSendConnectFail = "发送CONNECT请求失败"
ErrMsgHTTPSendConnectFail = i18n.GetText("proxy_http_send_connect_failed")
// ErrMsgHTTPSetReadTimeout 设置读超时失败
ErrMsgHTTPSetReadTimeout = "设置读超时失败"
ErrMsgHTTPSetReadTimeout = i18n.GetText("proxy_http_set_read_timeout")
// ErrMsgHTTPReadRespFailed 读取响应失败
ErrMsgHTTPReadRespFailed = "读取HTTP响应失败"
ErrMsgHTTPReadRespFailed = i18n.GetText("proxy_http_read_response_failed")
// ErrMsgHTTPProxyAuthFailed 代理认证失败
ErrMsgHTTPProxyAuthFailed = "HTTP代理连接失败,状态码: %d"
ErrMsgHTTPProxyAuthFailed = i18n.GetText("proxy_http_status_failed")
// ErrMsgTLSTCPConnFailed TLS错误消息 - TCP连接失败
ErrMsgTLSTCPConnFailed = "建立TCP连接失败"
ErrMsgTLSTCPConnFailed = i18n.GetText("proxy_tls_tcp_conn_failed")
// ErrMsgTLSHandshakeFailed TLS握手失败
ErrMsgTLSHandshakeFailed = "TLS握手失败"
ErrMsgTLSHandshakeFailed = i18n.GetText("proxy_tls_handshake_failed")
)
// =============================================================================
+4 -4
View File
@@ -93,14 +93,14 @@ func (s *ScanSession) LogError(errMsg string) {
func (s *ScanSession) DialTCP(ctx context.Context, network, address string, timeout time.Duration) (net.Conn, error) {
// 检查发包限制
if ok, err := CanSendPacketWith(s.Config, s.State); !ok {
s.LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, err.Error()))
s.LogError(i18n.Tr("tcp_connection_restricted", address, err.Error()))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error()))
}
// 获取 dialer
dialer, err := s.getDialer(timeout)
if err != nil {
s.LogError(fmt.Sprintf("获取代理拨号器失败: %v", err))
s.LogError(i18n.Tr("proxy_dialer_failed", err))
s.State.IncrementTCPFailedPacketCount()
return nil, err
}
@@ -108,7 +108,7 @@ func (s *ScanSession) DialTCP(ctx context.Context, network, address string, time
conn, err := dialer.DialContext(ctx, network, address)
if err != nil {
s.State.IncrementTCPFailedPacketCount()
s.LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err))
s.LogDebug(i18n.Tr("connection_failed", address, err))
return nil, err
}
@@ -141,7 +141,7 @@ func (s *ScanSession) DialUDP(ctx context.Context, address string, timeout time.
// HTTPDo executes an HTTP request with the session's packet limits and counters.
func (s *ScanSession) HTTPDo(client *http.Client, req *http.Request) (*http.Response, error) {
if ok, err := CanSendPacketWith(s.Config, s.State); !ok {
s.LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), err.Error()))
s.LogError(i18n.Tr("http_request_restricted", req.URL.String(), err.Error()))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error()))
}