using System.Globalization; using System.Text.RegularExpressions; using XFEExtension.NetCore.XUnit.Runtime; namespace XFEExtension.NetCore.XUnit.Reporting; internal sealed partial class ConsoleLocalizer { public ConsoleLocalizer(ConsoleLanguage requestedLanguage) { RequestedLanguage = requestedLanguage; Language = requestedLanguage == ConsoleLanguage.Auto ? DetectLanguage() : requestedLanguage; } public ConsoleLanguage RequestedLanguage { get; } public ConsoleLanguage Language { get; } public bool IsChinese => Language == ConsoleLanguage.Chinese; public string Select(string english, string chinese) => IsChinese ? chinese : english; public string LanguageName => RequestedLanguage == ConsoleLanguage.Auto ? Select("English (auto)", "简体中文(自动)") : Select("English", "简体中文"); public string Outcome(TestOutcome outcome) => outcome switch { TestOutcome.Passed => Select("PASS", "通过"), TestOutcome.Failed => Select("FAIL", "失败"), TestOutcome.Skipped => Select("SKIP", "跳过"), TestOutcome.TimedOut => Select("TIMEOUT", "超时"), TestOutcome.Crashed => Select("CRASH", "崩溃"), _ => outcome.ToString().ToUpperInvariant() }; public string Message(string message) { if (!IsChinese) return message; if (message == "Explicit test was not selected.") return "未选择显式测试。"; if (message == "Only one ITestActivator extension can be registered per test assembly.") return "每个测试程序集只能注册一个 ITestActivator 扩展。"; if (message == "The benchmark baseline file is invalid.") return "基准基线文件无效。"; if (message == "Benchmark regression gating was refused because the environments differ.") return "由于运行环境不同,已拒绝执行基准回归门禁。"; var timeout = Regex.Match(message, @"^Test exceeded the (?\d+) ms timeout(? and its worker process was terminated)?\.$", RegexOptions.CultureInvariant); if (timeout.Success) return timeout.Groups["terminated"].Success ? $"测试超过 {timeout.Groups["milliseconds"].Value} ms 超时限制,工作进程已终止。" : $"测试超过 {timeout.Groups["milliseconds"].Value} ms 超时限制。"; var worker = Regex.Match(message, @"^Worker exited with code (?-?\d+) without producing a result\.$", RegexOptions.CultureInvariant); if (worker.Success) return $"工作进程以代码 {worker.Groups["code"].Value} 退出,且未生成结果。"; const string missingBaseline = "Benchmark baseline file was not found: "; if (message.StartsWith(missingBaseline, StringComparison.Ordinal)) return "未找到基准基线文件:" + message[missingBaseline.Length..]; const string missingSettings = "Run settings file was not found: "; if (message.StartsWith(missingSettings, StringComparison.Ordinal)) return "未找到运行设置文件:" + message[missingSettings.Length..]; const string gatePrefix = "Cannot gate "; const string gateSuffix = ": baseline environment fingerprint differs."; if (message.StartsWith(gatePrefix, StringComparison.Ordinal) && message.EndsWith(gateSuffix, StringComparison.Ordinal)) return $"无法对 {message[gatePrefix.Length..^gateSuffix.Length]} 执行门禁:基线环境指纹不同。"; return message; } public string Warning(string warning) { if (!IsChinese) return warning; if (warning.StartsWith("DebuggerAttached", StringComparison.Ordinal)) return "已附加调试器:调试器会使基准测量结果不可靠。"; if (warning.StartsWith("NonOptimizedAssembly", StringComparison.Ordinal)) return "程序集未优化:请使用 Release 配置运行基准。"; if (warning.StartsWith("LowResolutionClock", StringComparison.Ordinal)) return "低分辨率计时器:Stopwatch 未使用高分辨率性能计数器。"; if (warning.StartsWith("HighNoise", StringComparison.Ordinal)) return "噪声过高:标准差超过均值的 10%。"; if (warning.StartsWith("MeasurementTrend", StringComparison.Ordinal)) return "测量趋势:样本呈现显著的时间相关趋势。"; if (warning.StartsWith("DistributionSkew", StringComparison.Ordinal)) return "分布偏斜:均值与中位数相差超过 5%。"; if (warning.StartsWith("BelowTimerResolution", StringComparison.Ordinal)) return "低于计时分辨率:扣除基础设施开销后的工作量已低于可靠测量范围。"; if (warning.StartsWith("NotConverged", StringComparison.Ordinal)) { var percentages = PercentageRegex().Matches(warning).Select(static match => match.Value).ToArray(); return percentages.Length >= 2 ? $"未收敛:相对误差 {percentages[0]} 超出目标 {percentages[1]}。" : "未收敛:样本未在允许的迭代次数内达到误差目标。"; } return warning; } public static bool TryParseLanguage(string? value, out ConsoleLanguage language) { switch (value?.Trim().ToLowerInvariant()) { case "auto": case "automatic": language = ConsoleLanguage.Auto; return true; case "en": case "en-us": case "en-gb": case "english": language = ConsoleLanguage.English; return true; case "zh": case "zh-cn": case "zh-hans": case "chinese": case "简体中文": case "中文": language = ConsoleLanguage.Chinese; return true; default: language = ConsoleLanguage.Auto; return false; } } private static ConsoleLanguage DetectLanguage() { var uiCulture = CultureInfo.CurrentUICulture.Name; if (uiCulture.StartsWith("zh", StringComparison.OrdinalIgnoreCase)) return ConsoleLanguage.Chinese; if (!string.IsNullOrWhiteSpace(uiCulture)) return ConsoleLanguage.English; return CultureInfo.CurrentCulture.Name.StartsWith("zh", StringComparison.OrdinalIgnoreCase) ? ConsoleLanguage.Chinese : ConsoleLanguage.English; } [GeneratedRegex(@"\d+(?:[\.,]\d+)?\s*%", RegexOptions.CultureInvariant)] private static partial Regex PercentageRegex(); }