Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Autolocalization v1.5.6
Autolocalization.dll
Decompiled 5 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Autolocalization.Core; using Autolocalization.Localization; using Autolocalization.Services; using Autolocalization.Utils; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyCompany("Autolocalization")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.5.6.0")] [assembly: AssemblyInformationalVersion("1.5.6+ac5904faf28cc8daa86fe4696e62de00b16ba609")] [assembly: AssemblyProduct("Autolocalization")] [assembly: AssemblyTitle("Autolocalization")] [assembly: AssemblyVersion("1.5.6.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Autolocalization { [BepInPlugin(".com.HsgtLgt.autolocalization", "智能本地化引擎", "1.5.6")] public class Autolocalization : BaseUnityPlugin { public const string PluginGuid = ".com.HsgtLgt.autolocalization"; public const string PluginName = "智能本地化引擎"; public const string PluginVersion = "1.5.6"; private static readonly Harmony Harmony = new Harmony(".com.HsgtLgt.autolocalization"); public static ManualLogSource? Slog; private static SmartLocalizationManager? _smartLocalizationManager; private static LocalizationFileManager? _fileManager; public static Autolocalization? Instance { get; private set; } public static bool EnableCollection { get; private set; } public static string LogLevel { get; private set; } = "Basic"; private void Awake() { Slog = ((BaseUnityPlugin)this).Logger; Instance = this; LoadConfigurations(); PerformanceMonitor.SetLogSource(Slog); PerformanceMonitor.StartMonitoring(); PerformanceMonitor.StartPeriodicReporting(30, LogLevel); InitializeTranslationServicesSync(); Slog.LogDebug((object)FormatLog("INFO", "Main", "版本1.5.6启动成功")); Harmony.PatchAll(); Task.Delay(5000).ContinueWith(delegate { if (ConfigDescriptionPatch._translatedCount > 0 || ConfigDescriptionPatch._unchangedCount > 0) { ManualLogSource? slog = Slog; if (slog != null) { slog.LogInfo((object)FormatLog("INFO", "Config", $"已处理配置: {ConfigDescriptionPatch._translatedCount}项已翻译, {ConfigDescriptionPatch._unchangedCount}项未变化")); } } }); } private void LoadConfigurations() { EnableCollection = ((BaseUnityPlugin)this).Config.Bind<bool>("Collection", "EnableCollection", true, "收集功能开关。true=启用收集待翻译内容,false=禁用收集(仅应用已有翻译)。翻译完成后可关闭此开关以提升启动速度").Value; LogLevel = ((BaseUnityPlugin)this).Config.Bind<string>("General", "LogLevel", "None", "日志详细程度。可选: None(仅错误)/Basic(基本进度)/Detailed(详细统计)/Debug(调试信息)").Value; if (!(LogLevel != "None")) { return; } ManualLogSource? slog = Slog; if (slog != null) { slog.LogInfo((object)FormatLog("INFO", "Config", "收集功能: " + (EnableCollection ? "启用" : "禁用"))); } if (!EnableCollection) { ManualLogSource? slog2 = Slog; if (slog2 != null) { slog2.LogInfo((object)FormatLog("INFO", "Config", "配置说明:收集功能已禁用,系统将仅应用已有翻译,不会收集新内容")); } ManualLogSource? slog3 = Slog; if (slog3 != null) { slog3.LogInfo((object)FormatLog("INFO", "Config", " 说明:此模式适合翻译已完成的情况,可提升游戏启动速度")); } } } private void InitializeTranslationServicesSync() { try { _fileManager = new LocalizationFileManager(); _smartLocalizationManager = new SmartLocalizationManager(_fileManager); _smartLocalizationManager.SetLogSource(Slog); Slog.LogDebug((object)"智能本地化管理器初始化完成"); Task<bool> task = _smartLocalizationManager.InitializeAsync(); task.Wait(); if (task.Result) { Slog.LogDebug((object)"✅ 智能本地化系统同步初始化成功"); Slog.LogDebug((object)$"当前阶段: {_smartLocalizationManager.CurrentPhase}"); } else { Slog.LogError((object)"❌ 智能本地化系统同步初始化失败"); } Slog.LogDebug((object)"本地化服务同步初始化完成"); } catch (Exception ex) { Slog.LogError((object)("同步初始化本地化服务失败: " + ex.Message)); } } public static SmartLocalizationManager? GetSmartLocalizationManager() { return _smartLocalizationManager; } public static string FormatLog(string level, string module, string message) { return LogFormatter.FormatLog(level, module, message); } public void OnDestroy() { _smartLocalizationManager?.Dispose(); PerformanceMonitor.StopMonitoring(); } } public static class PerformanceMonitor { private static readonly Stopwatch _stopwatch = new Stopwatch(); private static long _totalProcessed = 0L; private static long _totalCacheHits = 0L; private static readonly object _lock = new object(); private static ManualLogSource? _logSource; public static void SetLogSource(ManualLogSource logSource) { _logSource = logSource; } public static void StartMonitoring() { _stopwatch.Start(); } public static void StopMonitoring() { _stopwatch.Stop(); } public static void RecordProcessing(long processed, long cacheHits) { lock (_lock) { _totalProcessed += processed; _totalCacheHits += cacheHits; } } public static PerformanceStats GetCurrentStats() { lock (_lock) { long elapsedMilliseconds = _stopwatch.ElapsedMilliseconds; long totalProcessed = _totalProcessed; long totalCacheHits = _totalCacheHits; double cacheHitRate = ((totalProcessed > 0) ? ((double)totalCacheHits / (double)totalProcessed * 100.0) : 0.0); double processingRate = ((elapsedMilliseconds > 0) ? ((double)totalProcessed / (double)elapsedMilliseconds * 1000.0) : 0.0); PerformanceStats result = default(PerformanceStats); result.TotalProcessed = totalProcessed; result.TotalCacheHits = totalCacheHits; result.CacheHitRate = cacheHitRate; result.ProcessingRate = processingRate; result.ElapsedMilliseconds = elapsedMilliseconds; result.MemoryUsage = GC.GetTotalMemory(forceFullCollection: false); return result; } } private static void LogPerformanceReport() { PerformanceStats currentStats = GetCurrentStats(); ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)"=== 性能监控报告 ==="); } ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogInfo((object)$"总处理数量: {currentStats.TotalProcessed:N0}"); } ManualLogSource? logSource3 = _logSource; if (logSource3 != null) { logSource3.LogInfo((object)$"缓存命中数: {currentStats.TotalCacheHits:N0}"); } ManualLogSource? logSource4 = _logSource; if (logSource4 != null) { logSource4.LogInfo((object)$"缓存命中率: {currentStats.CacheHitRate:F2}%"); } ManualLogSource? logSource5 = _logSource; if (logSource5 != null) { logSource5.LogInfo((object)$"处理速率: {currentStats.ProcessingRate:F0} 条/秒"); } ManualLogSource? logSource6 = _logSource; if (logSource6 != null) { logSource6.LogInfo((object)$"总耗时: {currentStats.ElapsedMilliseconds:N0} 毫秒"); } ManualLogSource? logSource7 = _logSource; if (logSource7 != null) { logSource7.LogInfo((object)$"内存使用: {currentStats.MemoryUsage / 1024 / 1024:F2} MB"); } ManualLogSource? logSource8 = _logSource; if (logSource8 != null) { logSource8.LogInfo((object)"=================="); } } public static void StartPeriodicReporting(int intervalSeconds = 30, string logLevel = "Basic") { if (intervalSeconds > 0 && !(logLevel == "None")) { Timer timer = new Timer(ReportPeriodicStats, logLevel, TimeSpan.FromSeconds(intervalSeconds), TimeSpan.FromSeconds(intervalSeconds)); } } private static void ReportPeriodicStats(object state) { string text = (state as string) ?? "Basic"; PerformanceStats currentStats = GetCurrentStats(); if (currentStats.TotalProcessed <= 0 || !(text != "None")) { return; } if (text == "Debug" || text == "Detailed") { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)$"[拦截统计] 配置拦截: {currentStats.TotalProcessed:N0}次, 拦截速率: {currentStats.ProcessingRate:F0}次/秒, 拦截缓存命中: {currentStats.CacheHitRate:F2}%"); } } else { _ = text == "Basic"; } } } public struct PerformanceStats { public long TotalProcessed { get; set; } public long TotalCacheHits { get; set; } public double CacheHitRate { get; set; } public double ProcessingRate { get; set; } public long ElapsedMilliseconds { get; set; } public long MemoryUsage { get; set; } } } namespace Autolocalization.Utils { public static class LogFormatter { public static string FormatLog(string level, string module, string message) { string text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); string text2 = level switch { "ERROR" => "\ud83d\udd34", "WARN" => "\ud83d\udfe1", "INFO" => "\ud83d\udd35", "DEBUG" => "⚪", _ => "⚪", }; if (string.IsNullOrWhiteSpace(module)) { return $"{text2} [{text}] [{level,-5}] {message}"; } return $"{text2} [{text}] [{level,-5}] [{module,-20}] {message}"; } public static string FormatSection(string title) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(""); stringBuilder.AppendLine("\ud83d\udccb " + new string('═', 60)); stringBuilder.AppendLine(" " + title); stringBuilder.AppendLine(" " + new string('═', 60)); return stringBuilder.ToString(); } public static string FormatConfigTable(string title, params (string key, string value)[] configs) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("⚙\ufe0f " + title); stringBuilder.AppendLine(" " + new string('─', 50)); for (int i = 0; i < configs.Length; i++) { var (arg, arg2) = configs[i]; stringBuilder.AppendLine($" {arg,-25}: {arg2}"); } stringBuilder.AppendLine(" " + new string('─', 50)); return stringBuilder.ToString(); } public static string FormatProgressBar(int current, int total, string description = "") { double num = ((total > 0) ? ((double)current / (double)total) : 0.0); int num2 = (int)(30.0 * num); int count = 30 - num2; string text = "[" + new string('█', num2) + new string('░', count) + "]"; string text2 = $"{num * 100.0:F1}%"; return $"\ud83d\udcca {description} {text} {current}/{total} ({text2})"; } public static string FormatPerformanceStats(string operation, TimeSpan elapsed, int itemsProcessed) { double num = ((itemsProcessed > 0) ? ((double)itemsProcessed / elapsed.TotalSeconds) : 0.0); double num2 = ((itemsProcessed > 0) ? (elapsed.TotalMilliseconds / (double)itemsProcessed) : 0.0); return $"⚡ {operation} - 耗时: {elapsed.TotalSeconds:F1}s, 处理: {itemsProcessed}项, " + $"吞吐量: {num:F1}项/s, 平均: {num2:F1}ms/项"; } public static string FormatErrorDetails(string context, Exception ex) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("❌ " + context); stringBuilder.AppendLine(" 错误类型: " + ex.GetType().Name); stringBuilder.AppendLine(" 错误消息: " + ex.Message); if (!string.IsNullOrEmpty(ex.StackTrace)) { stringBuilder.AppendLine(" 堆栈跟踪:"); string[] array = ex.StackTrace.Split(new char[1] { '\n' }); string[] array2 = array; foreach (string text in array2) { if (!string.IsNullOrWhiteSpace(text)) { stringBuilder.AppendLine(" " + text.Trim()); } } } return stringBuilder.ToString(); } public static string FormatBatchSummary(string operation, int total, int success, int failed, int cached = 0) { double num = ((total > 0) ? ((double)success / (double)total * 100.0) : 0.0); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("\ud83d\udcc8 " + operation + " 摘要"); stringBuilder.AppendLine($" 总计: {total} 项"); stringBuilder.AppendLine($" 成功: {success} 项 ({num:F1}%)"); if (failed > 0) { stringBuilder.AppendLine($" 失败: {failed} 项"); } if (cached > 0) { stringBuilder.AppendLine($" 缓存: {cached} 项"); } return stringBuilder.ToString(); } } } namespace Autolocalization.Services { public static class PathManager { public static string GetPluginDirectory() { string location = typeof(PathManager).Assembly.Location; return Path.GetDirectoryName(location) ?? ""; } public static string GetProjectDirectory() { return Path.Combine(Paths.ConfigPath, "Autolocalization"); } public static string GetTranslationsFilePath() { return Path.Combine(GetProjectDirectory(), "translations.yaml"); } public static string GetCollectedItemsFilePath() { return Path.Combine(GetProjectDirectory(), "collected_items.yaml"); } public static void EnsureDirectoryExists(string directoryPath) { if (!Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); } } } public class SmartLocalizationManager : IDisposable { private readonly LocalizationFileManager _fileManager = fileManager ?? throw new ArgumentNullException("fileManager"); private readonly string _translationFilePath = PathManager.GetTranslationsFilePath(); private readonly string _collectionFilePath = PathManager.GetCollectedItemsFilePath(); private readonly string? _configFilePath; private readonly string? _cacheFilePath; private readonly ConcurrentDictionary<string, string> _translations = new ConcurrentDictionary<string, string>(); private readonly ConcurrentDictionary<string, string> _collectedItems = new ConcurrentDictionary<string, string>(); private readonly ConcurrentDictionary<string, DateTime> _translationTimestamps = new ConcurrentDictionary<string, DateTime>(); private readonly object _stateLock = new object(); private LocalizationPhase _currentPhase; private bool _isInitialized; private bool _isDisposed; private int _totalCollected; private int _totalTranslated; private int _totalApplied; private int _cacheHits; private int _cacheMisses; private long _lastSavedCount; private long _translationQueryCount; private long _translationCacheHits; private long _translationNewCount; private DateTime _lastCollectTime = DateTime.MinValue; private Timer? _collectionEndCheckTimer; private bool _collectionEnded; private int _unsavedCount; private readonly ConcurrentDictionary<string, string> _reverseTranslations = new ConcurrentDictionary<string, string>(); private readonly ConcurrentDictionary<string, string> _persistentGroups = new ConcurrentDictionary<string, string>(); private ManualLogSource? _logSource; public LocalizationPhase CurrentPhase => _currentPhase; public bool IsInitialized => _isInitialized; public LocalizationStatistics Statistics => GetStatistics(); public event Action<LocalizationPhase, LocalizationPhase>? OnPhaseChanged; public event Action<string, string>? OnTranslationAdded; public event Action<string, string, string>? OnTranslationUpdated; public event Action<LocalizationStatistics>? OnStatisticsUpdated; public SmartLocalizationManager(LocalizationFileManager fileManager) { } public void SetLogSource(ManualLogSource logSource) { _logSource = logSource; } public async Task<bool> InitializeAsync() { if (_isInitialized) { return true; } try { string projectDirectory = PathManager.GetProjectDirectory(); PathManager.EnsureDirectoryExists(projectDirectory); if (File.Exists(_translationFilePath)) { try { await LoadTranslationsAsync(); } catch (Exception ex) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogError((object)("❌ 从新位置加载翻译文件失败: " + ex.Message)); } } } Dictionary<string, string> dictionary = null; if (File.Exists(_collectionFilePath)) { try { dictionary = _fileManager.LoadFromYaml(_collectionFilePath); } catch (Exception ex2) { ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogError((object)("加载收集文件失败: " + ex2.Message)); } } } if (dictionary != null && dictionary.Count > 0 && !File.Exists(_translationFilePath)) { ManualLogSource? logSource3 = _logSource; if (logSource3 != null) { logSource3.LogInfo((object)"\ud83d\udcdd 发现收集文件但缺少翻译文件,创建空模板..."); } Dictionary<string, string> translations = new Dictionary<string, string>(); _fileManager.SaveToYaml(translations, _translationFilePath); ManualLogSource? logSource4 = _logSource; if (logSource4 != null) { logSource4.LogInfo((object)("✅ 已创建空的翻译文件: " + _translationFilePath)); } ManualLogSource? logSource5 = _logSource; if (logSource5 != null) { logSource5.LogInfo((object)$"\ud83d\udca1 提示: 请参考 collected_items.yaml ({dictionary.Count} 项) 进行翻译"); } ManualLogSource? logSource6 = _logSource; if (logSource6 != null) { logSource6.LogInfo((object)"\ud83d\udca1 将翻译添加到 translations.yaml 格式: 原文: 翻译"); } } bool flag = false; if (dictionary != null && dictionary.Count > 0) { int num = dictionary.Keys.Count((string key) => !_translations.ContainsKey(key) || string.IsNullOrWhiteSpace(_translations[key])); if (num > 0) { flag = true; LogInfo($"\ud83d\udcdd 发现收集文件中有 {num} 项未翻译内容,保持在收集阶段"); } else { LogInfo($"✅ 收集文件中的所有 {dictionary.Count} 项都已翻译"); } } if (_translations.Count == 0) { if (Autolocalization.EnableCollection) { ChangePhase(LocalizationPhase.Collection); LogInfo("未找到翻译数据,进入收集阶段"); } else { ChangePhase(LocalizationPhase.Application); LogInfo("未找到翻译数据,但收集功能已禁用,进入应用阶段(仅应用已有翻译)"); } } else if (flag) { if (Autolocalization.EnableCollection) { ChangePhase(LocalizationPhase.Collection); LogInfo("有未翻译的收集项,保持在收集阶段"); } else { ChangePhase(LocalizationPhase.Application); LogInfo("有未翻译的收集项,但收集功能已禁用,进入应用阶段(仅应用已有翻译)"); } } else { ChangePhase(LocalizationPhase.Application); LogInfo("✅ 所有翻译数据完整,进入应用阶段"); } await CleanupCollectedItemsAsync(dictionary); await LoadCacheAsync(); _collectionEndCheckTimer = new Timer(CheckCollectionEnd, null, 5000, 5000); _isInitialized = true; return true; } catch (Exception ex3) { ManualLogSource? logSource7 = _logSource; if (logSource7 != null) { logSource7.LogError((object)("❌ 初始化失败: " + ex3.Message)); } return false; } } public string GetTranslation(string originalText, bool autoCollect = true) { if (string.IsNullOrWhiteSpace(originalText)) { return originalText; } Interlocked.Increment(ref _translationQueryCount); if (_translations.TryGetValue(originalText, out string value)) { Interlocked.Increment(ref _cacheHits); Interlocked.Increment(ref _translationCacheHits); return value; } if (_reverseTranslations.ContainsKey(originalText)) { Interlocked.Increment(ref _cacheHits); Interlocked.Increment(ref _translationCacheHits); return originalText; } Interlocked.Increment(ref _cacheMisses); if (autoCollect && Autolocalization.EnableCollection) { CollectText(originalText); Interlocked.Increment(ref _translationNewCount); } return originalText; } public void StartCollectionPhase() { lock (_stateLock) { ChangePhase(LocalizationPhase.Collection); } LogInfo("\ud83d\udccb 开始收集阶段 - 将收集所有需要翻译的内容"); } public async Task EndCollectionPhaseAsync() { lock (_stateLock) { ChangePhase(LocalizationPhase.Application); } LogInfo($"\ud83d\udccb 收集阶段结束 - 共收集 {_totalCollected} 项内容"); await SaveCollectedItemsAsync(); LogInfo("✅ 收集阶段完成"); } public async Task<bool> StartApplicationPhaseAsync() { try { if (File.Exists(_translationFilePath)) { await LoadTranslationsAsync(); ChangePhase(LocalizationPhase.Application); LogInfo($"✅ 应用阶段开始 - 已加载 {_totalTranslated} 条翻译"); return true; } ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogWarning((object)"⚠\ufe0f 未找到翻译文件,将进入收集阶段"); } StartCollectionPhase(); return false; } catch (Exception ex) { ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogError((object)("❌ 加载翻译文件失败: " + ex.Message)); } StartCollectionPhase(); return false; } } public void UpdateTranslation(string key, string newTranslation) { if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(newTranslation)) { string orAdd = _translations.GetOrAdd(key, newTranslation); _translations[key] = newTranslation; _translationTimestamps[key] = DateTime.Now; _reverseTranslations[newTranslation] = key; this.OnTranslationUpdated?.Invoke(key, orAdd, newTranslation); LogInfo("\ud83d\udd04 更新翻译: " + key + " -> " + newTranslation, "Debug"); } } public int CleanupTranslatedItems() { List<string> list = _collectedItems.Keys.Where((string key) => _translations.ContainsKey(key)).ToList(); foreach (string item in list) { _collectedItems.TryRemove(item, out string _); } if (list.Count > 0) { LogInfo($"\ud83e\uddf9 清理了 {list.Count} 个已翻译的收集项", "Detailed"); } return list.Count; } public Task<bool> ReloadTranslationsAsync() { try { LoadTranslationsAsync().Wait(); LogInfo("\ud83d\udd04 翻译文件重新加载完成"); return Task.FromResult(result: true); } catch (Exception ex) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogError((object)("❌ 重新加载翻译文件失败: " + ex.Message)); } return Task.FromResult(result: false); } } public void ClearCache() { _translations.Clear(); _translationTimestamps.Clear(); _cacheHits = 0; _cacheMisses = 0; LogInfo("\ud83e\uddf9 翻译缓存已清理", "Debug"); } private void UpdatePersistentGroups(Dictionary<string, Dictionary<string, string>> grouped) { if (grouped == null) { return; } foreach (KeyValuePair<string, Dictionary<string, string>> item in grouped) { string key = item.Key; Dictionary<string, string> value = item.Value; if (value == null) { continue; } foreach (string key2 in value.Keys) { _persistentGroups[key2] = key; } } } public Dictionary<string, string> GetCollectedItems() { return _collectedItems.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kvp) => kvp.Key, (KeyValuePair<string, string> kvp) => kvp.Value); } public Dictionary<string, string> GetCurrentTranslations() { return _translations.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kvp) => kvp.Key, (KeyValuePair<string, string> kvp) => kvp.Value); } public void UpdateTranslations(Dictionary<string, string> translations) { LogInfo($"\ud83d\udd04 批量更新翻译: {translations.Count} 条", "Debug"); foreach (KeyValuePair<string, string> translation in translations) { UpdateTranslation(translation.Key, translation.Value); } LogInfo($"✅ 翻译更新完成,当前缓存中共有 {_translations.Count} 条翻译", "Debug"); } private void LogInfo(string message, string requiredLevel = "Basic") { if (_logSource != null) { string text = LogFormatter.FormatLog("INFO", "", message); switch (requiredLevel) { case "Basic": _logSource.LogInfo((object)text); break; case "Detailed": _logSource.LogInfo((object)text); break; case "Debug": _logSource.LogInfo((object)text); break; } } } private void CheckCollectionEnd(object? state) { try { if (_collectionEnded || _collectedItems.Count == 0 || _lastCollectTime == DateTime.MinValue) { return; } double totalSeconds = (DateTime.Now - _lastCollectTime).TotalSeconds; if (totalSeconds >= 10.0 && _unsavedCount > 0) { _collectionEnded = true; LogInfo($"✅ 检测到收集阶段结束({totalSeconds:F1}秒无新增),准备保存..."); Task task = SaveCollectedItemsAsync(); task.Wait(); LogInfo($"\ud83d\udcbe 收集结束自动保存完成: {_collectedItems.Count} 项"); LogInfo($"\ud83d\udcca 收集统计: 总检测 {_totalCollected} 项, 需要翻译 {_collectedItems.Count} 项"); if (!File.Exists(_translationFilePath)) { Task task2 = GenerateTranslationTemplateAsync(); task2.Wait(); } _collectionEndCheckTimer?.Dispose(); _collectionEndCheckTimer = null; LogTranslationCacheStats(); if (_collectedItems.Count > 0) { ChangePhase(LocalizationPhase.Application); } } } catch (Exception ex) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogError((object)("❌ 检查收集结束时出错: " + ex.Message)); } } } private void CollectText(string text) { if (_collectedItems.TryAdd(text, text)) { Interlocked.Increment(ref _totalCollected); Interlocked.Increment(ref _unsavedCount); this.OnTranslationAdded?.Invoke(text, text); _lastCollectTime = DateTime.Now; if (_totalCollected % 100 == 0) { LogInfo($"\ud83d\udcdd 已收集 {_totalCollected} 项待翻译内容", "Detailed"); } } } private void ChangePhase(LocalizationPhase newPhase) { LocalizationPhase currentPhase = _currentPhase; _currentPhase = newPhase; this.OnPhaseChanged?.Invoke(currentPhase, newPhase); } private Task LoadTranslationsAsync() { try { Dictionary<string, string> dictionary = _fileManager.LoadFromYaml(_translationFilePath); _translations.Clear(); _reverseTranslations.Clear(); int num = 0; int num2 = 0; foreach (KeyValuePair<string, string> item in dictionary) { _translations[item.Key] = item.Value; _translationTimestamps[item.Key] = DateTime.Now; if (!string.IsNullOrWhiteSpace(item.Value)) { _reverseTranslations[item.Value] = item.Key; num++; } else { num2++; } } _totalTranslated = dictionary.Count; _totalApplied = num; Dictionary<string, string> dictionary2 = _fileManager.LoadGroupsFromYaml(_translationFilePath); foreach (KeyValuePair<string, string> item2 in dictionary2) { _persistentGroups[item2.Key] = item2.Value; } if (File.Exists(_collectionFilePath)) { Dictionary<string, string> dictionary3 = _fileManager.LoadGroupsFromYaml(_collectionFilePath); foreach (KeyValuePair<string, string> item3 in dictionary3) { if (!_persistentGroups.ContainsKey(item3.Key)) { _persistentGroups[item3.Key] = item3.Value; } } } LogInfo("\ud83d\udce5 已加载翻译文件: " + _translationFilePath); LogInfo($" \ud83d\udcca 总条目: {dictionary.Count}, 有效翻译: {num}, 待翻译: {num2}", "Detailed"); if (num2 > 0) { LogInfo($"\ud83d\udca1 提示: 有 {num2} 项待翻译,您可以编辑翻译文件进行翻译"); } } catch (Exception ex) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogError((object)("加载翻译文件失败: " + ex.Message)); } } return Task.CompletedTask; } public Task SaveCollectedItemsAsync() { if (_collectedItems.Count == 0) { return Task.CompletedTask; } if (_collectedItems.Count == _lastSavedCount) { return Task.CompletedTask; } try { Dictionary<string, string> dictionary = _collectedItems.Where<KeyValuePair<string, string>>((KeyValuePair<string, string> kvp) => !_translations.ContainsKey(kvp.Key) || string.IsNullOrWhiteSpace(_translations[kvp.Key])).ToDictionary((KeyValuePair<string, string> kvp) => kvp.Key, (KeyValuePair<string, string> kvp) => kvp.Value); int num = _collectedItems.Count - dictionary.Count; ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)$"\ud83d\udcbe 保存收集项: {dictionary.Count} 项 (已过滤 {num} 项已翻译内容)"); } Dictionary<string, Dictionary<string, string>> dictionary2 = TranslationGroupBuilder.Create(GuidMapper.Instance).WithTranslations(dictionary).Build(); UpdatePersistentGroups(dictionary2); _fileManager.SaveToYamlNested(dictionary2, _collectionFilePath); _lastSavedCount = dictionary.Count; _unsavedCount = 0; } catch (Exception ex) { ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogError((object)("❌ 保存收集项失败: " + ex.Message)); } } return Task.CompletedTask; } private Task GenerateTranslationTemplateAsync() { try { if (File.Exists(_translationFilePath)) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)"\ud83d\udcdd 翻译文件已存在,跳过模板生成"); } return Task.CompletedTask; } Dictionary<string, string> translations = new Dictionary<string, string>(); _fileManager.SaveToYaml(translations, _translationFilePath); ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogInfo((object)("\ud83d\udcdd 已创建空的翻译文件模板: " + _translationFilePath)); } ManualLogSource? logSource3 = _logSource; if (logSource3 != null) { logSource3.LogInfo((object)"\ud83d\udca1 提示: 请参考 collected_items.yaml 中的内容进行翻译"); } } catch (Exception ex) { ManualLogSource? logSource4 = _logSource; if (logSource4 != null) { logSource4.LogError((object)("❌ 生成翻译模板失败: " + ex.Message)); } } return Task.CompletedTask; } private Task CleanupCollectedItemsAsync(Dictionary<string, string>? collectedItems) { try { if (_translations.Count == 0) { return Task.CompletedTask; } if (collectedItems == null || collectedItems.Count == 0) { return Task.CompletedTask; } int count = collectedItems.Count; Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (KeyValuePair<string, string> collectedItem in collectedItems) { if (!_translations.ContainsKey(collectedItem.Key) || string.IsNullOrWhiteSpace(_translations[collectedItem.Key])) { dictionary[collectedItem.Key] = collectedItem.Value; } } int num = count - dictionary.Count; if (num > 0) { Dictionary<string, Dictionary<string, string>> dictionary2 = TranslationGroupBuilder.Create(GuidMapper.Instance).WithTranslations(dictionary).Build(); UpdatePersistentGroups(dictionary2); _fileManager.SaveToYamlNested(dictionary2, _collectionFilePath); ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogDebug((object)$"\ud83e\uddf9 已清理收集文件: 移除 {num} 项已翻译内容,剩余 {dictionary.Count} 项待翻译"); } } else { ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogDebug((object)"✅ 收集文件无需清理 (所有项目均未翻译)"); } } } catch (Exception ex) { ManualLogSource? logSource3 = _logSource; if (logSource3 != null) { logSource3.LogError((object)("❌ 清理收集文件失败: " + ex.Message)); } } return Task.CompletedTask; } private Task CopyUserGuideAsync(string projectDir) { try { string text = Path.Combine(projectDir, "README_翻译指南.md"); if (File.Exists(text)) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogDebug((object)"\ud83d\udcd6 用户指南文档已存在,跳过复制"); } return Task.CompletedTask; } string contents = "# 智能本地化翻译指南\n\n## 快速开始\n1. 编辑 `translations.yaml` 文件\n2. 格式:`原文: 翻译`\n3. 保存文件并重启服务器\n\n## 文件位置\n- 翻译文件:`translations.yaml`\n- 收集文件:`collected_items.yaml`\n\n## 注意事项\n- 保持冒号左边的原文不变\n- 只修改冒号右边的翻译\n- 修改后需要重启服务器生效\n\n更多详细信息请查看插件日志。\n"; File.WriteAllText(text, contents, Encoding.UTF8); ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogInfo((object)("已创建基本用户指南: " + text)); } } catch (Exception ex) { ManualLogSource? logSource3 = _logSource; if (logSource3 != null) { logSource3.LogError((object)("复制用户指南失败: " + ex.Message)); } } return Task.CompletedTask; } private Task LoadCacheAsync() { if (!string.IsNullOrEmpty(_cacheFilePath) && File.Exists(_cacheFilePath)) { Dictionary<string, string> dictionary = _fileManager.LoadFromYaml(_cacheFilePath); foreach (KeyValuePair<string, string> item in dictionary) { _translations[item.Key] = item.Value; } ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)$"\ud83d\udce5 已加载 {dictionary.Count} 条缓存翻译"); } } return Task.CompletedTask; } private LocalizationStatistics GetStatistics() { return new LocalizationStatistics { TotalCollected = _totalCollected, TotalTranslated = _totalTranslated, TotalApplied = _totalApplied, CacheHits = _cacheHits, CacheMisses = _cacheMisses, CacheHitRate = ((_cacheHits + _cacheMisses > 0) ? ((double)_cacheHits / (double)(_cacheHits + _cacheMisses) * 100.0) : 0.0), CurrentPhase = _currentPhase, TranslationCount = _translations.Count, CollectedCount = _collectedItems.Count }; } public void LogTranslationCacheStats() { if (_translationQueryCount > 0) { double num = (double)_translationCacheHits / (double)_translationQueryCount * 100.0; ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)$"[翻译统计] 查询: {_translationQueryCount}次, 缓存命中: {_translationCacheHits}次({num:F1}%), 新收集: {_translationNewCount}次"); } } } public void Dispose() { if (_isDisposed) { return; } _collectionEndCheckTimer?.Dispose(); if (_unsavedCount > 0) { ManualLogSource? logSource = _logSource; if (logSource != null) { logSource.LogInfo((object)"\ud83d\udcbe 插件卸载前保存未保存的数据..."); } SaveCollectedItemsAsync().Wait(); } _isDisposed = true; ManualLogSource? logSource2 = _logSource; if (logSource2 != null) { logSource2.LogInfo((object)"\ud83d\udd04 智能本地化管理器已释放"); } } } public enum LocalizationPhase { Initialization, Collection, Application } public class LocalizationStatistics { public int TotalCollected { get; set; } public int TotalTranslated { get; set; } public int TotalApplied { get; set; } public int CacheHits { get; set; } public int CacheMisses { get; set; } public double CacheHitRate { get; set; } public LocalizationPhase CurrentPhase { get; set; } public int TranslationCount { get; set; } public int CollectedCount { get; set; } } } namespace Autolocalization.Localization { public class LocalizationFileManager { private readonly ISerializer _serializer; private readonly IDeserializer _deserializer; private readonly object _fileLock = new object(); private static readonly ManualLogSource _logger = Logger.CreateLogSource("Autolocalization"); public LocalizationFileManager() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown _serializer = ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); _deserializer = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); } public Dictionary<string, string> LoadFromYaml(string filePath) { if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath)) { _logger.LogWarning((object)("YAML文件不存在: " + filePath)); return new Dictionary<string, string>(); } try { lock (_fileLock) { string text = File.ReadAllText(filePath, Encoding.UTF8); try { Dictionary<string, Dictionary<string, string>> dictionary = _deserializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(text); if (dictionary != null && dictionary.Count > 0 && IsNestedStructure(dictionary)) { Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary) { if (item.Value == null) { continue; } foreach (KeyValuePair<string, string> item2 in item.Value) { dictionary2[item2.Key] = item2.Value; } } _logger.LogInfo((object)$"从YAML文件加载翻译(嵌套格式): {filePath},共 {dictionary2.Count} 条,来自 {dictionary.Count} 个mod"); return dictionary2; } } catch { } Dictionary<string, string> dictionary3 = _deserializer.Deserialize<Dictionary<string, string>>(text); _logger.LogInfo((object)$"从YAML文件加载翻译(扁平格式): {filePath},共 {dictionary3?.Count ?? 0} 条"); return dictionary3 ?? new Dictionary<string, string>(); } } catch (Exception ex) { _logger.LogError((object)("加载YAML文件失败: " + filePath + ",错误: " + ex.Message)); return new Dictionary<string, string>(); } } private bool IsNestedStructure(Dictionary<string, Dictionary<string, string>> data) { if (data == null || data.Count == 0) { return false; } foreach (string key in data.Keys) { if (((key.Contains(".") && !key.Contains(" ")) || key == "_unknown") && data[key] != null) { return true; } } return false; } public Dictionary<string, string> LoadGroupsFromYaml(string filePath) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath)) { return dictionary; } try { lock (_fileLock) { string text = File.ReadAllText(filePath, Encoding.UTF8); Dictionary<string, Dictionary<string, string>> dictionary2 = _deserializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(text); if (dictionary2 == null || dictionary2.Count == 0 || !IsNestedStructure(dictionary2)) { return dictionary; } foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary2) { string key = item.Key; Dictionary<string, string> value = item.Value; if (value == null) { continue; } foreach (KeyValuePair<string, string> item2 in value) { string key2 = item2.Key; if (!dictionary.ContainsKey(key2)) { dictionary[key2] = key; } } } } } catch (Exception ex) { _logger.LogError((object)("从YAML文件读取分组信息失败: " + filePath + ",错误: " + ex.Message)); } return dictionary; } public void SaveToYamlNested(Dictionary<string, Dictionary<string, string>> nestedTranslations, string filePath) { if (nestedTranslations == null || string.IsNullOrWhiteSpace(filePath)) { return; } try { lock (_fileLock) { EnsureDirectoryExists(filePath); Dictionary<string, Dictionary<string, string>> dictionary = nestedTranslations.OrderBy<KeyValuePair<string, Dictionary<string, string>>, string>((KeyValuePair<string, Dictionary<string, string>> kvp) => kvp.Key).ToDictionary((KeyValuePair<string, Dictionary<string, string>> kvp) => kvp.Key, (KeyValuePair<string, Dictionary<string, string>> kvp) => kvp.Value); string content = _serializer.Serialize((object)dictionary); int num = 0; int count = dictionary.Count; foreach (Dictionary<string, string> value in dictionary.Values) { if (value != null) { num += value.Count; } } string nestedFileHeader = GetNestedFileHeader(filePath, num, count); WriteFileWithHeader(filePath, nestedFileHeader, content); _logger.LogInfo((object)$"保存嵌套YAML文件: {filePath},共 {count} 个分组,{num} 条翻译"); } } catch (Exception ex) { _logger.LogError((object)("保存嵌套YAML文件失败: " + filePath + ",错误: " + ex.Message)); } } public void SaveToYaml(Dictionary<string, string> translations, string filePath) { if (translations == null || string.IsNullOrWhiteSpace(filePath)) { return; } try { lock (_fileLock) { EnsureDirectoryExists(filePath); string content = _serializer.Serialize((object)translations); string flatFileHeader = GetFlatFileHeader(filePath, translations); WriteFileWithHeader(filePath, flatFileHeader, content); _logger.LogInfo((object)$"保存翻译到YAML文件: {filePath},共 {translations.Count} 条"); } } catch (Exception ex) { _logger.LogError((object)("保存YAML文件失败: " + filePath + ",错误: " + ex.Message)); } } private void EnsureDirectoryExists(string filePath) { string directoryName = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } } private void WriteFileWithHeader(string filePath, string header, string content) { File.WriteAllText(filePath, header + content, Encoding.UTF8); } private string GetNestedFileHeader(string filePath, int totalTranslations, int modCount) { string fileName = Path.GetFileName(filePath); if (fileName.Equals("translations.yaml", StringComparison.OrdinalIgnoreCase)) { return $"# ============================================\n# 智能本地化翻译文件(按mod分组)\n# ============================================\n# \n# 使用说明:\n# 1. 此文件按mod的GUID分组,每个GUID下的翻译属于同一个mod\n# 2. 请保持冒号左边的原文不变,只修改冒号右边的翻译\n# 3. 如果您不想翻译某个项目,可以删除该行或保持翻译部分为空\n# 4. 修改后请保存文件并重启服务器以使翻译生效\n# \n# 格式示例:\n# com.example.mod1:\n# \"Enable Shadows\": \"启用阴影\"\n# \"Master Volume\": \"主音量\"\n# \n# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n# 条目数量: {totalTranslations} (来自 {modCount} 个mod)\n# \n# ============================================\n\n"; } if (fileName.Equals("collected_items.yaml", StringComparison.OrdinalIgnoreCase)) { return $"# ============================================\n# 收集文件 (待翻译项目) - 按mod分组\n# ============================================\n# \n# 说明:\n# - 此文件包含所有待翻译的英文原文,按mod的GUID分组\n# - 已翻译的项目会自动从此文件中移除\n# - 请参考此文件内容编辑 translations.yaml\n# \n# 格式示例:\n# com.example.mod1:\n# \"Enable Shadows\": \"Enable Shadows\"\n# \"Master Volume\": \"Master Volume\"\n# \n# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n# 待翻译数量: {totalTranslations} (来自 {modCount} 个mod)\n# \n# ============================================\n\n"; } return ""; } private string GetFlatFileHeader(string filePath, Dictionary<string, string> translations) { string fileName = Path.GetFileName(filePath); if (fileName.Equals("translations.yaml", StringComparison.OrdinalIgnoreCase)) { int num = translations.Count<KeyValuePair<string, string>>((KeyValuePair<string, string> kvp) => !string.IsNullOrWhiteSpace(kvp.Value)); int num2 = translations.Count - num; if (translations.Count == 0) { return $"# ============================================\n# 智能本地化翻译文件\n# ============================================\n# \n# 使用说明:\n# 1. 这是您的翻译文件,请手动添加翻译条目\n# 2. 格式为 \"原文: 翻译\"\n# 3. 请参考 collected_items.yaml 查看需要翻译的内容\n# 4. 只添加您已经翻译好的条目,无需添加空值\n# \n# 示例:\n# Enable Shadows: 启用阴影\n# Master Volume: 主音量\n# Graphics Settings: 图形设置\n# \n# 提示:\n# - 从 collected_items.yaml 复制需要翻译的原文\n# - 在冒号后面添加中文翻译\n# - 保存文件后重启服务器生效\n# \n# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n# \n# ============================================\n\n# 在下方添加您的翻译(删除此注释行)\n"; } return $"# ============================================\n# 智能本地化翻译文件\n# ============================================\n# \n# 使用说明:\n# 1. 这是一个简单的翻译文件,格式为 \"原文: 翻译\"\n# 2. 请保持冒号左边的原文不变,只修改冒号右边的翻译\n# 3. 如果您不想翻译某个项目,可以删除该行或保持翻译部分为空\n# 4. 修改后请保存文件并重启服务器以使翻译生效\n# \n# 格式示例:\n# Enable Shadows: 启用阴影\n# Master Volume: 主音量\n# Test Section: 测试区域\n# \n# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n# 条目数量: {translations.Count} (已翻译: {num}, 待翻译: {num2})\n# \n# ============================================\n\n"; } if (fileName.Equals("collected_items.yaml", StringComparison.OrdinalIgnoreCase)) { return $"# ============================================\n# 收集文件 (待翻译项目)\n# ============================================\n# \n# 说明:\n# - 此文件包含所有待翻译的英文原文\n# - 已翻译的项目会自动从此文件中移除\n# - 请参考此文件内容编辑 translations.yaml\n# \n# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n# 待翻译数量: {translations.Count}\n# \n# ============================================\n\n"; } return $"# 翻译文件\n# 生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n# 条目数量: {translations.Count}\n\n"; } } } namespace Autolocalization.Core { public class GuidGroupingStrategy : ITranslationGroupingStrategy { private const string UnknownGuid = "_unknown"; public Dictionary<string, Dictionary<string, string>> Group(Dictionary<string, string> translations, IGuidMapper mapper) { Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(); foreach (KeyValuePair<string, string> translation in translations) { string guid = mapper.GetGuid(translation.Key); dictionary.GetOrAdd(guid, () => new Dictionary<string, string>())[translation.Key] = translation.Value; } return dictionary; } } public sealed class GuidMapper : IGuidMapper { private static readonly Lazy<GuidMapper> _instance = new Lazy<GuidMapper>(() => new GuidMapper()); private readonly ConcurrentDictionary<string, string> _mappings = new ConcurrentDictionary<string, string>(); private readonly IGuidExtractionStrategy _extractionStrategy; private const string UnknownGuid = "_unknown"; public static GuidMapper Instance => _instance.Value; private GuidMapper() : this(new ReflectionGuidExtractionStrategy()) { } internal GuidMapper(IGuidExtractionStrategy strategy) { _extractionStrategy = strategy ?? throw new ArgumentNullException("strategy"); } public string GetGuid(string text) { if (!_mappings.TryGetValue(text, out string value)) { return "_unknown"; } return value; } public void RegisterMapping(string text, string guid) { _mappings.TryAdd(text, guid ?? "_unknown"); } public void RegisterFromConfigFile(ConfigFile? configFile, ConfigDefinition? definition, ConfigDescription? description) { string guid = _extractionStrategy.ExtractGuid(configFile); if (definition != (ConfigDefinition)null) { RegisterIfNotEmpty(definition.Section, guid); RegisterIfNotEmpty(definition.Key, guid); RegisterIfNotEmpty(definition.Section + "." + definition.Key, guid); } RegisterIfNotEmpty((description != null) ? description.Description : null, guid); } private void RegisterIfNotEmpty(string? text, string guid) { if (!string.IsNullOrEmpty(text)) { RegisterMapping(text, guid); } } public IReadOnlyDictionary<string, string> GetAllMappings() { return _mappings; } } public static class Helpers { private const int ChineseStart = 19968; private const int ChineseEnd = 40959; public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TValue> valueFactory) where TKey : notnull { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } if (key == null) { throw new ArgumentNullException("key"); } if (valueFactory == null) { throw new ArgumentNullException("valueFactory"); } if (!dictionary.TryGetValue(key, out TValue value)) { return dictionary[key] = valueFactory(); } return value; } public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue)) where TKey : notnull { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } if (key == null) { throw new ArgumentNullException("key"); } if (!dictionary.TryGetValue(key, out TValue value)) { return defaultValue; } return value; } public static string FormatLog(string level, string module, string message) { string text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); string text2 = level switch { "ERROR" => "\ud83d\udd34", "WARN" => "\ud83d\udfe1", "INFO" => "\ud83d\udd35", "DEBUG" => "⚪", _ => "⚪", }; return $"{text2} [{text}] [{level,-5}] [{module,-20}] {message}"; } public static bool IsPredominantlyChinese(string text, double threshold = 0.3) { if (string.IsNullOrWhiteSpace(text)) { return false; } return GetChineseRatio(text) >= threshold; } public static double GetChineseRatio(string text) { if (string.IsNullOrWhiteSpace(text)) { return 0.0; } int num = 0; int num2 = 0; foreach (char c in text) { if (!char.IsWhiteSpace(c) && !char.IsPunctuation(c) && !char.IsSymbol(c)) { num++; if (IsChineseCharacter(c)) { num2++; } } } if (num <= 0) { return 0.0; } return (double)num2 / (double)num; } private static bool IsChineseCharacter(char c) { if (c < '一' || c > '\u9fff') { if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.OtherLetter && c >= '一') { return c <= '\u9fff'; } return false; } return true; } } public interface IGuidExtractionStrategy { string ExtractGuid(ConfigFile? configFile); } public interface IGuidMapper { string GetGuid(string text); void RegisterMapping(string text, string guid); IReadOnlyDictionary<string, string> GetAllMappings(); } public interface ITranslationGroupingStrategy { Dictionary<string, Dictionary<string, string>> Group(Dictionary<string, string> translations, IGuidMapper mapper); } internal static class PatchHelper { internal static string ProcessTranslation(string original, bool autoCollect = true) { return Autolocalization.GetSmartLocalizationManager()?.GetTranslation(original, autoCollect) ?? original; } } [HarmonyPatch(typeof(ConfigDescription))] public static class ConfigDescriptionPatch { private static readonly ConcurrentDictionary<string, string> _cache = new ConcurrentDictionary<string, string>(); private static long _processedCount = 0L; private static long _cacheHits = 0L; public static long _translatedCount = 0L; public static long _unchangedCount = 0L; [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void Prefix(ref string description) { if (string.IsNullOrWhiteSpace(description)) { return; } if (_cache.TryGetValue(description, out string value)) { Interlocked.Increment(ref _cacheHits); description = value; PerformanceMonitor.RecordProcessing(0L, 1L); return; } string text = PatchHelper.ProcessTranslation(description); if (text != description) { Interlocked.Increment(ref _translatedCount); } else { Interlocked.Increment(ref _unchangedCount); } if (_cache.Count < 50000) { _cache.TryAdd(description, text); } description = text; Interlocked.Increment(ref _processedCount); PerformanceMonitor.RecordProcessing(1L, 0L); } } [HarmonyPatch(typeof(ConfigDefinition))] public static class ConfigDefinitionPatch { private static readonly ConcurrentDictionary<string, string> _keyCache = new ConcurrentDictionary<string, string>(); private static readonly ConcurrentDictionary<string, string> _sectionCache = new ConcurrentDictionary<string, string>(); private static readonly ConcurrentDictionary<string, string> _usedTranslatedKeys = new ConcurrentDictionary<string, string>(); private static readonly char[] _invalidChars = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' }; private static int _invalidSectionCount = 0; private static int _invalidKeyCount = 0; private static int _keyConflictCount = 0; [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void Prefix(ref string section, ref string key) { string text = section; string text2 = key; string text3 = text + "." + text2; if (!string.IsNullOrWhiteSpace(section)) { section = _sectionCache.GetOrAdd(section, (string s) => ProcessAndValidate(s, isSection: true)); } if (!string.IsNullOrWhiteSpace(key)) { key = _keyCache.GetOrAdd(key, (string k) => ProcessAndValidate(k, isSection: false)); } string text4 = section + "." + key; if (string.IsNullOrWhiteSpace(text4) || !(text4 != text3) || _usedTranslatedKeys.TryAdd(text4, text3)) { return; } string text5 = _usedTranslatedKeys[text4]; if (!(text5 != text3)) { return; } Interlocked.Increment(ref _keyConflictCount); string text6 = GenerateUniqueSuffix(text3); if (key != text2) { key += text6; } else if (section != text) { section += text6; } string key2 = section + "." + key; _usedTranslatedKeys.TryAdd(key2, text3); if (Autolocalization.LogLevel == "Debug" || Autolocalization.LogLevel == "Detailed") { ManualLogSource? slog = Autolocalization.Slog; if (slog != null) { slog.LogWarning((object)("⚠\ufe0f 检测到翻译键冲突,已添加唯一后缀避免重复键错误:'" + text5 + "' 和 '" + text3 + "' 都翻译为 '" + text4 + "',已为后者添加后缀 '" + text6 + "'")); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string ProcessAndValidate(string original, bool isSection) { string text = PatchHelper.ProcessTranslation(original); if (string.IsNullOrEmpty(text) || text == original) { return original; } if (IsValidConfigValue(text)) { if (_sectionCache.Count < 10000 && isSection) { _sectionCache.TryAdd(original, text); } if (_keyCache.Count < 10000 && !isSection) { _keyCache.TryAdd(original, text); } return text; } if (isSection) { Interlocked.Increment(ref _invalidSectionCount); } else { Interlocked.Increment(ref _invalidKeyCount); } if (Autolocalization.LogLevel == "Debug" || Autolocalization.LogLevel == "Detailed") { ManualLogSource? slog = Autolocalization.Slog; if (slog != null) { slog.LogWarning((object)("⚠\ufe0f " + (isSection ? "Section" : "Key") + "翻译无效已移除: '" + original + "' -> '" + text + "'")); } } return original; } private static bool IsValidConfigValue(string value) { if (value != null && value == value.Trim()) { return !value.Any((char c) => _invalidChars.Contains(c)); } return false; } public static (int KeyCacheSize, int SectionCacheSize) GetCacheStats() { return (_keyCache.Count, _sectionCache.Count); } public static (int InvalidSectionCount, int InvalidKeyCount) GetInvalidTranslationStats() { return (_invalidSectionCount, _invalidKeyCount); } public static int GetKeyConflictCount() { return _keyConflictCount; } private static string GenerateUniqueSuffix(string original) { using MD5 mD = MD5.Create(); byte[] array = mD.ComputeHash(Encoding.UTF8.GetBytes(original)); return $"_{array[0]:X2}{array[1]:X2}"; } } [HarmonyPatch(typeof(ConfigEntryBase))] public static class ConfigEntryBasePatch { private static readonly GuidMapper Mapper = GuidMapper.Instance; [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void Prefix(ConfigFile configFile, ConfigDefinition definition, ConfigDescription configDescription) { try { Mapper.RegisterFromConfigFile(configFile, definition, configDescription); } catch (Exception ex) when (Autolocalization.LogLevel == "Debug") { ManualLogSource? slog = Autolocalization.Slog; if (slog != null) { slog.LogWarning((object)("⚠\ufe0f 建立GUID映射时出错: " + ex.Message)); } } } public static ConcurrentDictionary<string, string> GetTextToGuidMap() { ConcurrentDictionary<string, string> concurrentDictionary = new ConcurrentDictionary<string, string>(); foreach (KeyValuePair<string, string> allMapping in Mapper.GetAllMappings()) { concurrentDictionary[allMapping.Key] = allMapping.Value; } return concurrentDictionary; } public static string GetGuidForText(string text) { return Mapper.GetGuid(text); } } public class ReflectionGuidExtractionStrategy : IGuidExtractionStrategy { private static readonly FieldInfo? OwnerMetadataField = typeof(ConfigFile).GetField("_ownerMetadata", BindingFlags.Instance | BindingFlags.NonPublic); private const string UnknownGuid = "_unknown"; public string ExtractGuid(ConfigFile? configFile) { object obj; if (configFile != null) { obj = ExtractGuidInternal(configFile); if (obj == null) { return "_unknown"; } } else { obj = "_unknown"; } return (string)obj; } private string? ExtractGuidInternal(ConfigFile configFile) { try { object? obj = OwnerMetadataField?.GetValue(configFile); BepInPlugin val = (BepInPlugin)((obj is BepInPlugin) ? obj : null); return string.IsNullOrEmpty((val != null) ? val.GUID : null) ? null : val.GUID; } catch { return null; } } } public class TranslationGroupBuilder { private readonly IGuidMapper _mapper; private readonly ITranslationGroupingStrategy _strategy; private Dictionary<string, string>? _translations; public static TranslationGroupBuilder Create(IGuidMapper mapper) { return new TranslationGroupBuilder(mapper, new GuidGroupingStrategy()); } public static TranslationGroupBuilder Create(IGuidMapper mapper, ITranslationGroupingStrategy strategy) { return new TranslationGroupBuilder(mapper, strategy); } private TranslationGroupBuilder(IGuidMapper mapper, ITranslationGroupingStrategy strategy) { _mapper = mapper ?? throw new ArgumentNullException("mapper"); _strategy = strategy ?? throw new ArgumentNullException("strategy"); } public TranslationGroupBuilder WithTranslations(Dictionary<string, string> translations) { _translations = translations ?? throw new ArgumentNullException("translations"); return this; } public Dictionary<string, Dictionary<string, string>> Build() { if (_translations != null) { return _strategy.Group(_translations, _mapper); } throw new InvalidOperationException("Translations must be set before building"); } } }