Decompiled source of AI Localization v0.1.3

AI Localization.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AI_Localization.Config;
using AI_Localization.Core;
using AI_Localization.Core.AITranslation;
using AI_Localization.Core.DataCollection;
using AI_Localization.Core.DataExporter;
using AI_Localization.HarmonyPatches;
using AI_Localization.Utils;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using YamlDotNet.Serialization;

[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("HsgtLgt")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © Valheim_Localization 2026")]
[assembly: AssemblyDescription("AI Localization")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3+a83599d4d29408d8f8aa93fa69d07d7cc0c48fbf")]
[assembly: AssemblyProduct("AI Localization")]
[assembly: AssemblyTitle("AI Localization")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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 AI_Localization
{
	[BepInPlugin(".com.HsgtLgt.AI_Localization", "AI_Localization", "0.1.3")]
	public class AI_Localization : BaseUnityPlugin
	{
		public const string modGUID = ".com.HsgtLgt.AI_Localization";

		public const string modName = "AI_Localization";

		public const string modVersion = "0.1.3";

		private DataExporter? _exporter;

		private void Awake()
		{
			ModLogger.Initialize(((BaseUnityPlugin)this).Logger);
			ModConfig.Init((BaseUnityPlugin)(object)this);
			ModLogger.SetDebugEnabled(ModConfig.LogTranslations.Value);
			ExemptionManager.Load(ModConfig.Exemptions.Value);
			Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = Yaml.LoadTranslation();
			if (dictionary != null && dictionary.Count > 0)
			{
				TranslationManager.SetCache(dictionary);
				ModLogger.LogInfo($"已加载译文:{dictionary.Count} 个模组");
			}
			else
			{
				ModLogger.LogDebug("启动时未找到译文文件");
			}
			Patches.Instance.Apply();
			TranslationApplicator.ApplyToAlreadyLoadedPlugins();
			if (ModConfig.EnableCollection.Value)
			{
				_exporter = new DataExporter((MonoBehaviour)(object)this);
			}
			CollectionStatus.Initialize((MonoBehaviour)(object)this);
		}

		private void OnDestroy()
		{
			_exporter?.Stop();
			CollectionStatus.Shutdown();
		}
	}
}
namespace AI_Localization.Utils
{
	public static class ExemptionManager
	{
		private struct ExemptionRule
		{
			public readonly string[] Guids;

			public readonly string[] Sections;

			public readonly string[] Keys;

			public ExemptionRule(string[] guids, string[] sections, string[] keys)
			{
				Guids = guids;
				Sections = sections;
				Keys = keys;
			}
		}

		private static readonly List<ExemptionRule> _rules = new List<ExemptionRule>();

		public static int RuleCount => _rules.Count;

		public static void Load(string configValue)
		{
			_rules.Clear();
			if (string.IsNullOrWhiteSpace(configValue))
			{
				return;
			}
			string[] array = configValue.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					string[] array2 = text.Split(new char[1] { '|' });
					if (array2.Length == 3)
					{
						_rules.Add(new ExemptionRule(array2[0].Trim().Split(new char[1] { ';' }), array2[1].Trim().Split(new char[1] { ';' }), array2[2].Trim().Split(new char[1] { ';' })));
					}
				}
			}
		}

		public static bool IsExempt(string guid, string section, string key)
		{
			foreach (ExemptionRule rule in _rules)
			{
				if (Match(rule.Guids, guid) && Match(rule.Sections, section) && Match(rule.Keys, key))
				{
					return true;
				}
			}
			return false;
		}

		private static bool Match(string[] patterns, string value)
		{
			for (int i = 0; i < patterns.Length; i++)
			{
				string text = patterns[i].Trim();
				if (text == "*" || text == value)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class ModLogger
	{
		private static ManualLogSource? _logSource;

		private static bool _debugEnabled;

		public static bool IsDebugEnabled
		{
			get
			{
				try
				{
					if (ModConfig.LogTranslations != null)
					{
						return ModConfig.LogTranslations.Value;
					}
				}
				catch
				{
				}
				return _debugEnabled;
			}
		}

		public static void Initialize(ManualLogSource logSource)
		{
			_logSource = logSource;
		}

		public static void SetDebugEnabled(bool debugEnabled)
		{
			_debugEnabled = debugEnabled;
		}

		public static void LogInfo(object data)
		{
			ManualLogSource? logSource = _logSource;
			if (logSource != null)
			{
				logSource.LogInfo(data);
			}
		}

		public static void LogWarning(object data)
		{
			ManualLogSource? logSource = _logSource;
			if (logSource != null)
			{
				logSource.LogWarning(data);
			}
		}

		public static void LogError(object data)
		{
			ManualLogSource? logSource = _logSource;
			if (logSource != null)
			{
				logSource.LogError(data);
			}
		}

		public static void LogDebug(object data)
		{
			if (IsDebugEnabled)
			{
				ManualLogSource? logSource = _logSource;
				if (logSource != null)
				{
					logSource.LogDebug(data);
				}
			}
		}

		public static void LogDebugAsInfo(object data)
		{
			if (IsDebugEnabled)
			{
				ManualLogSource? logSource = _logSource;
				if (logSource != null)
				{
					logSource.LogInfo(data);
				}
			}
		}
	}
	public class ReflectionHelper
	{
		private static readonly FieldInfo? _ownerMetadataField = typeof(ConfigFile).GetField("_ownerMetadata", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static string GetPluginGuid(ConfigFile configFile)
		{
			object? obj = _ownerMetadataField?.GetValue(configFile);
			object? obj2 = ((obj is BepInPlugin) ? obj : null);
			return ((obj2 != null) ? ((BepInPlugin)obj2).GUID : null) ?? Path.GetFileNameWithoutExtension(configFile.ConfigFilePath);
		}
	}
	internal static class TextHelper
	{
		internal static bool IsMostlyChinese(string? text, double threshold = 0.3)
		{
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			int num = 0;
			int num2 = 0;
			foreach (char c in text)
			{
				bool flag = c >= '一' && c <= '鿿';
				if (flag || char.IsLetter(c))
				{
					num++;
					if (flag)
					{
						num2++;
					}
				}
			}
			if (num > 0)
			{
				return (double)num2 / (double)num >= threshold;
			}
			return false;
		}
	}
	internal class Yaml
	{
		private static readonly string DataDir = Path.Combine(Paths.ConfigPath, "AI_Localization");

		private static string ExportPath => Path.Combine(DataDir, "AI_Locale_Export.yaml");

		private static string TranslationPath
		{
			get
			{
				ModConfig.GetTargetLanguage(out string code, out string _);
				if (code == "zh")
				{
					return Path.Combine(DataDir, "AI_Locale_Translation.yaml");
				}
				return Path.Combine(DataDir, "AI_Locale_Translation." + code + ".yaml");
			}
		}

		private static void EnsureDataDir()
		{
			if (!Directory.Exists(DataDir))
			{
				Directory.CreateDirectory(DataDir);
			}
		}

		public static void ExportYaml(Dictionary<string, Dictionary<string, Dictionary<string, string>>> data)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			EnsureDataDir();
			string exportPath = ExportPath;
			string contents = new SerializerBuilder().Build().Serialize((object)data);
			File.WriteAllText(exportPath, contents);
			ModLogger.LogDebugAsInfo($"[文件] 已写入导出:{data.Count} 个模组 → {exportPath}");
		}

		public static Dictionary<string, Dictionary<string, Dictionary<string, string>>>? LoadExport()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			EnsureDataDir();
			string exportPath = ExportPath;
			if (!File.Exists(exportPath))
			{
				ModLogger.LogDebug("未找到导出文件");
				return null;
			}
			try
			{
				Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = new DeserializerBuilder().Build().Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(File.ReadAllText(exportPath));
				ModLogger.LogDebugAsInfo($"[文件] 已读取导出:{dictionary.Count} 个模组 ← {exportPath}");
				return dictionary;
			}
			catch (Exception ex)
			{
				ModLogger.LogError("读取导出文件失败:" + ex.Message);
				return null;
			}
		}

		public static Dictionary<string, Dictionary<string, Dictionary<string, string>>>? LoadTranslation()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			EnsureDataDir();
			string translationPath = TranslationPath;
			if (!File.Exists(translationPath))
			{
				ModLogger.LogDebug("未找到翻译文件");
				return null;
			}
			try
			{
				Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = new DeserializerBuilder().Build().Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(File.ReadAllText(translationPath));
				ModLogger.LogDebugAsInfo($"[文件] 已读取译文:{dictionary.Count} 个模组 ← {translationPath}");
				return dictionary;
			}
			catch (Exception ex)
			{
				ModLogger.LogError("读取翻译文件失败:" + ex.Message);
				return null;
			}
		}

		public static void SaveTranslation(Dictionary<string, Dictionary<string, Dictionary<string, string>>> data)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				EnsureDataDir();
				string translationPath = TranslationPath;
				string contents = new SerializerBuilder().Build().Serialize((object)data);
				File.WriteAllText(translationPath, contents);
				ModLogger.LogDebugAsInfo("[文件] 已保存译文 → " + translationPath);
			}
			catch (Exception ex)
			{
				ModLogger.LogError("保存翻译文件失败:" + ex.Message);
			}
		}

		public static int DeduplicateExport(Dictionary<string, Dictionary<string, Dictionary<string, string>>> translationData, Dictionary<string, Dictionary<string, Dictionary<string, string>>> exportData)
		{
			if (translationData == null || translationData.Count == 0)
			{
				return 0;
			}
			if (exportData == null || exportData.Count == 0)
			{
				return 0;
			}
			int num = 0;
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, string>>> item in exportData.ToList())
			{
				string key = item.Key;
				Dictionary<string, Dictionary<string, string>> value = item.Value;
				if (!translationData.TryGetValue(key, out Dictionary<string, Dictionary<string, string>> value2))
				{
					continue;
				}
				foreach (KeyValuePair<string, Dictionary<string, string>> item2 in value.ToList())
				{
					string key2 = item2.Key;
					Dictionary<string, string> value3 = item2.Value;
					if (!value2.TryGetValue(key2, out var value4))
					{
						continue;
					}
					foreach (KeyValuePair<string, string> item3 in value3.ToList())
					{
						string key3 = item3.Key;
						if (!ExemptionManager.IsExempt(key, key2, key3) && value4.ContainsKey(key3))
						{
							value3.Remove(key3);
							num++;
						}
					}
					if (value3.Count == 0)
					{
						value.Remove(key2);
					}
				}
				if (value.Count == 0)
				{
					exportData.Remove(key);
				}
			}
			return num;
		}
	}
}
namespace AI_Localization.HarmonyPatches
{
	public class Patches
	{
		private static Patches? _instance;

		private readonly Harmony _harmony;

		public static Patches Instance => _instance ?? (_instance = new Patches());

		private Patches()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			_harmony = new Harmony(".com.HsgtLgt.AI_Localization");
		}

		public void Apply()
		{
			_harmony.PatchAll(typeof(HarmonyPatchTarget));
		}

		public void UnpatchSelf()
		{
			_harmony.UnpatchSelf();
		}
	}
	public class HarmonyPatchTarget
	{
		[ThreadStatic]
		private static Queue<(string section, string key)>? _pendingEntries;

		[ThreadStatic]
		private static string? _pendingDesc;

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static void ConfigDefinition_Prefix(string section, string key)
		{
			if (_pendingEntries == null)
			{
				_pendingEntries = new Queue<(string, string)>();
			}
			_pendingEntries.Enqueue((section, key));
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static void ConfigDescription_Prefix(string description)
		{
			_pendingDesc = description;
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static void ConfigEntryBase_Constructor_Prefix(ConfigFile configFile, ConfigDefinition definition, ConfigDescription configDescription)
		{
			ZConfigPatches.CollectAndApply(configFile, definition, configDescription, _pendingEntries, _pendingDesc, ModConfig.EnableCollection.Value);
			_pendingDesc = null;
			_pendingEntries?.Clear();
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void ConfigEntryBase_Constructor_Postfix(ConfigEntryBase __instance)
		{
			ZConfigPatches.AfterEntryConstructed(__instance);
		}
	}
	public static class ZConfigPatches
	{
		public static void CollectAndApply(ConfigFile configFile, ConfigDefinition definition, ConfigDescription configDescription, Queue<(string section, string key)>? pendingEntries, string? pendingDesc, bool enableCollection)
		{
			string text = null;
			string text2 = null;
			if (pendingEntries != null && pendingEntries.Count > 0)
			{
				(text, text2) = pendingEntries.Dequeue();
			}
			string section = text ?? definition.Section;
			string key = text2 ?? definition.Key;
			string description = pendingDesc ?? ((configDescription != null) ? configDescription.Description : null) ?? "";
			string pluginGuid = ReflectionHelper.GetPluginGuid(configFile);
			if (enableCollection)
			{
				DataCollector.Collect(pluginGuid, section, key, description);
			}
			TranslationApplicator.RememberOrphanSource(configFile, section, key);
			TranslationApplicator.ApplyInPlace(pluginGuid, definition, configDescription);
		}

		public static void AfterEntryConstructed(ConfigEntryBase entry)
		{
			TranslationApplicator.TryMigrateOrphanValue(entry);
		}
	}
}
namespace AI_Localization.Core
{
	internal enum CollectionState
	{
		NotStarted,
		Collecting,
		Completed,
		Stopped
	}
	internal static class CollectionStatus
	{
		private static CollectionState _state;

		private static MonoBehaviour? _coroutineOwner;

		private static bool _translationTriggered;

		internal static CollectionState State => _state;

		internal static bool IsCompleted => _state == CollectionState.Completed;

		internal static bool IsCollecting => _state == CollectionState.Collecting;

		internal static bool CanStartTranslation
		{
			get
			{
				if (_state != CollectionState.Completed)
				{
					return !ModConfig.EnableCollection.Value;
				}
				return true;
			}
		}

		internal static event Action<CollectionState>? OnStateChanged;

		internal static void Initialize(MonoBehaviour owner)
		{
			_coroutineOwner = owner;
			_translationTriggered = false;
			LoadCacheAndMaybeAutoTranslate();
			OnStateChanged += OnStateChangedHandler;
		}

		internal static void Shutdown()
		{
			OnStateChanged -= OnStateChangedHandler;
			_coroutineOwner = null;
		}

		private static void LoadCacheAndMaybeAutoTranslate()
		{
			if (!TranslationManager.HasTranslation)
			{
				Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = Yaml.LoadTranslation();
				if (dictionary != null && dictionary.Count > 0)
				{
					TranslationManager.SetCache(dictionary);
					ModLogger.LogInfo($"已加载译文:{dictionary.Count} 个模组");
				}
			}
			if (!ModConfig.EnableAiTranslation.Value)
			{
				ModLogger.LogInfo("AI 翻译已关闭(仍会使用已有译文)");
			}
			else if (ModConfig.EnableCollection.Value)
			{
				ModLogger.LogDebug("[AI] 已启用收集,等待收集完成后自动翻译");
			}
			else
			{
				TryStartAutoTranslate("未启用收集");
			}
		}

		private static void OnStateChangedHandler(CollectionState state)
		{
			if (state == CollectionState.Completed && ModConfig.EnableCollection.Value)
			{
				TryStartAutoTranslate("收集已完成");
			}
		}

		private static void TryStartAutoTranslate(string reason)
		{
			if (_translationTriggered)
			{
				return;
			}
			if (!ModConfig.EnableAiTranslation.Value)
			{
				ModLogger.LogDebug("[AI] " + reason + ",但 AI 翻译已禁用,跳过");
				return;
			}
			if (ModConfig.EnableCollection.Value && _state != CollectionState.Completed)
			{
				ModLogger.LogDebug("[AI] " + reason + ",收集未完成,暂不开翻");
				return;
			}
			if (string.IsNullOrWhiteSpace(ModConfig.ApiKey.Value))
			{
				ModLogger.LogWarning("请在模组配置「4. AI 翻译 > API密钥」中填写 DeepSeek API 密钥后再翻译");
				return;
			}
			Dictionary<string, Dictionary<string, Dictionary<string, string>>> localeData = DataCollector.LocaleData;
			if (localeData != null && localeData.Count > 0)
			{
				_translationTriggered = true;
				ModLogger.LogInfo("开始自动翻译…");
				ModLogger.LogDebugAsInfo("[AI] 触发原因:" + reason + ",数据来源:内存");
				MonoBehaviour? coroutineOwner = _coroutineOwner;
				if (coroutineOwner != null)
				{
					coroutineOwner.StartCoroutine(AITranslationService.RunTranslationFromMemory());
				}
				return;
			}
			Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = Yaml.LoadExport();
			if (dictionary != null && dictionary.Count > 0)
			{
				_translationTriggered = true;
				ModLogger.LogInfo("开始自动翻译(使用本地导出文件)…");
				ModLogger.LogDebugAsInfo($"[AI] 触发原因:{reason},数据来源:文件,模组数={dictionary.Count}");
				MonoBehaviour? coroutineOwner2 = _coroutineOwner;
				if (coroutineOwner2 != null)
				{
					coroutineOwner2.StartCoroutine(AITranslationService.RunTranslationFromFile());
				}
			}
			else
			{
				ModLogger.LogInfo("没有可翻译的配置,已跳过");
				ModLogger.LogDebugAsInfo("[AI] 触发原因:" + reason + ",内存与导出均空");
			}
		}

		internal static void MarkCollecting()
		{
			if (_state == CollectionState.NotStarted || _state == CollectionState.Stopped)
			{
				_state = CollectionState.Collecting;
				CollectionStatus.OnStateChanged?.Invoke(_state);
				ModLogger.LogDebug($"[状态] 收集开始 → {_state}");
			}
		}

		internal static void MarkCompleted()
		{
			if (_state == CollectionState.Collecting)
			{
				_state = CollectionState.Completed;
				CollectionStatus.OnStateChanged?.Invoke(_state);
				ModLogger.LogDebug($"[状态] 收集完成 → {_state}");
			}
		}

		internal static void MarkStopped()
		{
			if (_state != CollectionState.Stopped)
			{
				_state = CollectionState.Stopped;
				CollectionStatus.OnStateChanged?.Invoke(_state);
				ModLogger.LogDebug($"[状态] 收集停止 → {_state}");
			}
		}

		internal static void Reset()
		{
			_state = CollectionState.NotStarted;
			CollectionStatus.OnStateChanged?.Invoke(_state);
			ModLogger.LogDebug($"[状态] 状态已重置 → {_state}");
		}
	}
}
namespace AI_Localization.Core.DataExporter
{
	public class DataExporter
	{
		private readonly MonoBehaviour _owner;

		private readonly float _checkInterval = 5f;

		private readonly float _quietThreshold = 10f;

		private DateTime _lastCollectTime = DateTime.MinValue;

		private bool _collectionEnded;

		private bool _exportDone;

		private bool _isRunning;

		private bool _shouldStop;

		private Coroutine? _checkCoroutine;

		public DataExporter(MonoBehaviour owner, float checkInterval = 5f, float quietThreshold = 10f)
		{
			_owner = owner ?? throw new ArgumentNullException("owner");
			_checkInterval = checkInterval;
			_quietThreshold = quietThreshold;
			Start();
		}

		public void Start()
		{
			if (!_isRunning)
			{
				_shouldStop = false;
				_lastCollectTime = DateTime.MinValue;
				_collectionEnded = false;
				_exportDone = false;
				CollectionStatus.MarkCollecting();
				DataCollector.OnCollect += OnCollectHandler;
				_checkCoroutine = _owner.StartCoroutine(CheckCollectionEndCoroutine());
				_isRunning = true;
				if (ModConfig.EnableAiTranslation.Value)
				{
					ModLogger.LogInfo("开始收集模组配置,完成后将自动翻译");
				}
				else
				{
					ModLogger.LogInfo("开始收集模组配置");
				}
			}
		}

		public void Stop()
		{
			if (_isRunning)
			{
				DataCollector.OnCollect -= OnCollectHandler;
				_shouldStop = true;
				if (_checkCoroutine != null)
				{
					_owner.StopCoroutine(_checkCoroutine);
					_checkCoroutine = null;
				}
				_isRunning = false;
				ModLogger.LogDebug("收集已停止");
			}
		}

		private void OnCollectHandler()
		{
			_lastCollectTime = DateTime.Now;
			if (_collectionEnded)
			{
				_collectionEnded = false;
				_exportDone = false;
				ModLogger.LogDebug("检测到新的收集活动,重置结束状态");
			}
		}

		private IEnumerator CheckCollectionEndCoroutine()
		{
			while (!_collectionEnded && !_shouldStop)
			{
				yield return (object)new WaitForSeconds(_checkInterval);
				if (_shouldStop)
				{
					yield break;
				}
				if (_lastCollectTime == DateTime.MinValue)
				{
					continue;
				}
				double totalSeconds = (DateTime.Now - _lastCollectTime).TotalSeconds;
				if (!(totalSeconds >= (double)_quietThreshold))
				{
					continue;
				}
				_collectionEnded = true;
				if (!_exportDone)
				{
					_exportDone = true;
					Dictionary<string, Dictionary<string, Dictionary<string, string>>> localeData = DataCollector.LocaleData;
					if (localeData.Count > 0)
					{
						Yaml.ExportYaml(localeData);
						ModLogger.LogInfo($"配置收集完成:{localeData.Count} 个模组");
						ModLogger.LogDebugAsInfo($"[收集] 安静 {totalSeconds:F1}s 后导出");
					}
					else
					{
						ModLogger.LogWarning("配置收集结束,但未收集到任何条目");
					}
					CollectionStatus.MarkCompleted();
				}
				DataCollector.OnCollect -= OnCollectHandler;
				_isRunning = false;
			}
			if (_shouldStop)
			{
				DataCollector.OnCollect -= OnCollectHandler;
				_isRunning = false;
			}
		}
	}
}
namespace AI_Localization.Core.DataCollection
{
	public static class DataCollector
	{
		internal static Dictionary<string, Dictionary<string, Dictionary<string, string>>> LocaleData { get; private set; } = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();

		internal static event Action? OnCollect;

		internal static void Reset()
		{
			LocaleData.Clear();
		}

		internal static void UnsubscribeAll()
		{
			if (DataCollector.OnCollect != null)
			{
				Delegate[] invocationList = DataCollector.OnCollect.GetInvocationList();
				for (int i = 0; i < invocationList.Length; i++)
				{
					OnCollect -= (Action)invocationList[i];
				}
			}
		}

		internal static void Collect(string guid, string section, string key, string description)
		{
			if (!LocaleData.ContainsKey(guid))
			{
				LocaleData[guid] = new Dictionary<string, Dictionary<string, string>>();
			}
			if (!LocaleData[guid].ContainsKey(section))
			{
				LocaleData[guid][section] = new Dictionary<string, string>();
				LocaleData[guid][section][section] = section;
			}
			LocaleData[guid][section][key] = key;
			if (!string.IsNullOrEmpty(description))
			{
				LocaleData[guid][section][description] = description;
			}
			DataCollector.OnCollect?.Invoke();
		}
	}
}
namespace AI_Localization.Core.AITranslation
{
	internal static class AITranslationService
	{
		internal static IEnumerator RunTranslationFromMemory()
		{
			return TranslationPipeline.Run(DataCollector.LocaleData, "内存");
		}

		internal static IEnumerator RunTranslationFromFile()
		{
			return TranslationPipeline.Run(Yaml.LoadExport(), "文件");
		}

		internal static IEnumerator RunTranslationCoroutine()
		{
			return RunTranslationFromMemory();
		}
	}
	internal static class DeepSeekApiClient
	{
		private class ChatRequest
		{
			[JsonProperty("model")]
			public string Model = "deepseek-v4-flash";

			[JsonProperty("messages")]
			public Message[] Messages = new Message[0];

			[JsonProperty("temperature")]
			public float? Temperature = 0f;

			[JsonProperty("max_tokens")]
			public int MaxTokens = 32768;

			[JsonProperty("stream")]
			public bool Stream = true;

			[JsonProperty("thinking")]
			public ThinkingConfig Thinking = new ThinkingConfig();

			[JsonProperty("stream_options")]
			public StreamOptions StreamOptions = new StreamOptions();
		}

		private class ThinkingConfig
		{
			[JsonProperty("type")]
			public string Type = "disabled";
		}

		private class StreamOptions
		{
			[JsonProperty("include_usage")]
			public bool IncludeUsage = true;
		}

		private class Message
		{
			[JsonProperty("role")]
			public string Role = "";

			[JsonProperty("content")]
			public string Content = "";
		}

		private static readonly HttpClient _httpClient = CreateHttpClient();

		private static readonly object _semaphoreLock = new object();

		private static SemaphoreSlim _semaphore = new SemaphoreSlim(12, 12);

		private static int _configuredConcurrency = 12;

		private const int IdleTimeoutSeconds = 120;

		private const int ResponseHeaderTimeoutSeconds = 120;

		private const int TotalTimeoutMinutes = 25;

		private const int DefaultMaxTokens = 32768;

		internal static int ConfiguredConcurrency
		{
			get
			{
				lock (_semaphoreLock)
				{
					return _configuredConcurrency;
				}
			}
		}

		internal static void ConfigureConcurrency(int concurrency)
		{
			if (concurrency < 1)
			{
				concurrency = 1;
			}
			if (concurrency > 10000)
			{
				concurrency = 10000;
			}
			lock (_semaphoreLock)
			{
				if (concurrency == _configuredConcurrency && _semaphore != null)
				{
					return;
				}
				SemaphoreSlim semaphore = _semaphore;
				_semaphore = new SemaphoreSlim(concurrency, concurrency);
				_configuredConcurrency = concurrency;
				try
				{
					semaphore?.Dispose();
				}
				catch
				{
				}
			}
		}

		private static string BuildSystemPrompt()
		{
			ModConfig.GetTargetLanguage(out string code, out string promptName);
			GetDemoLines(code, out string d, out string d2, out string d3, out string d4, out string d5);
			return "你是 Valheim Mod 配置项的专业翻译。\n用户消息是待译列表,每行格式为:ID: 原文\n请为每个原文提供" + promptName + "翻译。\n输出格式要求:\n1. 仅输出纯 YAML 键值对,每行格式为 ID: \"翻译\"\n2. 每个翻译值必须用双引号包裹,以避免特殊字符(如冒号、逗号)干扰\n3. 不要输出任何解释文字、代码块标记或其他内容\n4. 保持技术标识符不变,只翻译自然语言部分\n\n格式示例(仅示意结构,勿照抄译文):\nentry_demo_1: \"" + d + "\"\nentry_demo_2: \"" + d2 + "\"\nentry_demo_3: \"" + d3 + "\"\nentry_demo_4: \"" + d4 + "\"\nentry_demo_5: \"" + d5 + "\"";
		}

		private static void GetDemoLines(string code, out string d1, out string d2, out string d3, out string d4, out string d5)
		{
			switch (code)
			{
			case "en":
				d1 = "Maximum Health";
				d2 = "Enable Debug Mode";
				d3 = "Regeneration per Second";
				d4 = "Damage Multiplier";
				d5 = "Show Floating Damage";
				break;
			case "ja":
				d1 = "最大体力";
				d2 = "デバッグモードを有効化";
				d3 = "毎秒回復";
				d4 = "ダメージ倍率";
				d5 = "フローティングダメージを表示";
				break;
			case "de":
				d1 = "Maximale Lebenspunkte";
				d2 = "Debug-Modus aktivieren";
				d3 = "Regeneration pro Sekunde";
				d4 = "Schadensmultiplikator";
				d5 = "Schadenszahlen anzeigen";
				break;
			case "zh":
				d1 = "最大生命值";
				d2 = "启用调试模式";
				d3 = "每秒恢复";
				d4 = "伤害倍率";
				d5 = "显示浮动伤害";
				break;
			default:
				d1 = "Maximum Health";
				d2 = "Enable Debug Mode";
				d3 = "Regeneration per Second";
				d4 = "Damage Multiplier";
				d5 = "Show Floating Damage";
				break;
			}
		}

		private static HttpClient CreateHttpClient()
		{
			return new HttpClient(new HttpClientHandler
			{
				UseProxy = false
			})
			{
				Timeout = Timeout.InfiniteTimeSpan
			};
		}

		internal static async Task<Dictionary<string, string>?> TranslateBatchAsync(string prompt, int entryCount, int maxRetries = 3)
		{
			await _semaphore.WaitAsync().ConfigureAwait(continueOnCapturedContext: false);
			try
			{
				int maxTokens = ComputeMaxTokens(entryCount);
				ChatRequest chatRequest = new ChatRequest();
				chatRequest.Model = ModConfig.AiModel.Value;
				chatRequest.MaxTokens = maxTokens;
				chatRequest.Messages = new Message[2]
				{
					new Message
					{
						Role = "system",
						Content = BuildSystemPrompt()
					},
					new Message
					{
						Role = "user",
						Content = prompt
					}
				};
				ChatRequest chatRequest2 = chatRequest;
				string json = JsonConvert.SerializeObject((object)chatRequest2);
				for (int retry = 0; retry < maxRetries; retry++)
				{
					try
					{
						Dictionary<string, string> dictionary = await SendStreamOnceAsync(json).ConfigureAwait(continueOnCapturedContext: false);
						if (dictionary != null)
						{
							return dictionary;
						}
						if (retry < maxRetries - 1)
						{
							ModLogger.LogDebugAsInfo($"[AI] 空响应,重试 {retry + 1}/{maxRetries}");
							await Task.Delay((1 << retry) * 1000).ConfigureAwait(continueOnCapturedContext: false);
						}
					}
					catch (Exception ex) when (IsRetryable(ex))
					{
						if (retry < maxRetries - 1)
						{
							int num = (1 << retry) * 1000;
							if (IsRateLimited(ex))
							{
								num = Math.Max(num, 5000 * (retry + 1));
							}
							ModLogger.LogDebugAsInfo($"[AI] 请求可重试失败,重试 {retry + 1}/{maxRetries}:{Describe(ex)}");
							await Task.Delay(num).ConfigureAwait(continueOnCapturedContext: false);
							continue;
						}
						ModLogger.LogWarning("翻译请求失败:" + Describe(ex));
						return null;
					}
					catch (Exception ex2)
					{
						ModLogger.LogWarning("翻译请求不可重试错误:" + Describe(ex2));
						return null;
					}
				}
				return null;
			}
			catch (Exception ex3)
			{
				ModLogger.LogWarning("翻译批处理异常:" + Describe(ex3));
				return null;
			}
			finally
			{
				_semaphore.Release();
			}
		}

		private static async Task<Dictionary<string, string>?> SendStreamOnceAsync(string json)
		{
			using CancellationTokenSource totalCts = new CancellationTokenSource(TimeSpan.FromMinutes(25.0));
			CancellationToken ct = totalCts.Token;
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://api.deepseek.com/chat/completions")
			{
				Content = new StringContent(json, Encoding.UTF8, "application/json")
			};
			request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ModConfig.ApiKey.Value);
			using CancellationTokenSource headerCts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { ct });
			headerCts.CancelAfter(TimeSpan.FromSeconds(120.0));
			using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, headerCts.Token).ConfigureAwait(continueOnCapturedContext: false);
			if (!response.IsSuccessStatusCode)
			{
				string errBody = "";
				try
				{
					errBody = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);
				}
				catch
				{
				}
				int statusCode = (int)response.StatusCode;
				if (statusCode == 429)
				{
					throw new HttpRequestException("请求过于频繁(429):" + Trim(errBody, 200));
				}
				if (statusCode >= 500 || statusCode == 408)
				{
					throw new HttpRequestException($"服务端错误({statusCode}):{Trim(errBody, 200)}");
				}
				ModLogger.LogWarning($"翻译请求失败(状态码 {statusCode})");
				return null;
			}
			StringBuilder contentBuilder = new StringBuilder();
			int cacheHit = 0;
			int cacheMiss = 0;
			bool sawUsage = false;
			using (Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(continueOnCapturedContext: false))
			{
				using StreamReader reader = new StreamReader(stream, Encoding.UTF8);
				while (!ct.IsCancellationRequested)
				{
					string text = await ReadLineWithIdleTimeoutAsync(reader, ct).ConfigureAwait(continueOnCapturedContext: false);
					if (text == null)
					{
						break;
					}
					if (text.Length == 0 || !text.StartsWith("data:", StringComparison.Ordinal))
					{
						continue;
					}
					string text2 = text.Substring(5).Trim();
					if (text2 == "[DONE]")
					{
						break;
					}
					try
					{
						JObject obj2 = JObject.Parse(text2);
						JToken obj3 = obj2["choices"];
						JArray val = (JArray)(object)((obj3 is JArray) ? obj3 : null);
						if (val != null && ((JContainer)val).Count > 0)
						{
							JToken obj4 = val[0];
							JToken obj5 = ((obj4 != null) ? obj4[(object)"delta"] : null);
							JObject val2 = (JObject)(object)((obj5 is JObject) ? obj5 : null);
							string value = ((val2 != null) ? ((JToken)val2).Value<string>((object)"content") : null);
							if (!string.IsNullOrEmpty(value))
							{
								contentBuilder.Append(value);
							}
						}
						if (TryReadCacheUsage(obj2["usage"], out var hit, out var miss))
						{
							sawUsage = true;
							cacheHit = hit;
							cacheMiss = miss;
						}
					}
					catch (Exception)
					{
					}
				}
			}
			if (sawUsage)
			{
				ModLogger.LogDebugAsInfo($"[AI] 前缀缓存 命中={cacheHit} 未命中={cacheMiss}");
			}
			string text3 = contentBuilder.ToString().Trim();
			if (string.IsNullOrWhiteSpace(text3))
			{
				return null;
			}
			return ParseYamlResponse(text3);
		}

		private static async Task<string?> ReadLineWithIdleTimeoutAsync(StreamReader reader, CancellationToken ct)
		{
			Task<string> readTask = reader.ReadLineAsync();
			Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(120.0), ct);
			if (await Task.WhenAny(new Task[2] { readTask, timeoutTask }).ConfigureAwait(continueOnCapturedContext: false) == timeoutTask)
			{
				if (ct.IsCancellationRequested)
				{
					throw new OperationCanceledException(ct);
				}
				throw new TimeoutException($"流式读取空闲超时({120} 秒无新数据)");
			}
			return await readTask.ConfigureAwait(continueOnCapturedContext: false);
		}

		private static int ComputeMaxTokens(int entryCount)
		{
			if (entryCount <= 0)
			{
				return 32768;
			}
			int num = entryCount * 120;
			if (num < 8192)
			{
				num = 8192;
			}
			if (num > 32768)
			{
				num = 32768;
			}
			return num;
		}

		private static bool TryReadCacheUsage(JToken? usageToken, out int hit, out int miss)
		{
			hit = 0;
			miss = 0;
			JObject val = (JObject)(object)((usageToken is JObject) ? usageToken : null);
			if (val == null)
			{
				return false;
			}
			hit = ReadInt(val, "prompt_cache_hit_tokens");
			miss = ReadInt(val, "prompt_cache_miss_tokens");
			if (hit == 0)
			{
				JToken obj = val["prompt_tokens_details"];
				JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
				if (val2 != null)
				{
					hit = ReadInt(val2, "cached_tokens");
				}
			}
			if (miss == 0)
			{
				int num = ReadInt(val, "prompt_tokens");
				if (num > 0 && hit > 0)
				{
					miss = Math.Max(0, num - hit);
				}
			}
			if (hit <= 0 && miss <= 0)
			{
				return val["prompt_tokens"] != null;
			}
			return true;
		}

		private static int ReadInt(JObject obj, string name)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Invalid comparison between Unknown and I4
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			JToken val = obj[name];
			if (val == null || (int)val.Type == 10)
			{
				return 0;
			}
			if ((int)val.Type == 6 || (int)val.Type == 7 || (int)val.Type == 8)
			{
				try
				{
					return Extensions.Value<int>((IEnumerable<JToken>)val);
				}
				catch
				{
					return 0;
				}
			}
			return 0;
		}

		private static bool IsRetryable(Exception ex)
		{
			if (ex is TimeoutException)
			{
				return true;
			}
			if (ex is TaskCanceledException)
			{
				return true;
			}
			if (ex is OperationCanceledException)
			{
				return true;
			}
			if (ex is HttpRequestException)
			{
				return true;
			}
			if (ex is IOException)
			{
				return true;
			}
			if (ex is WebException)
			{
				return true;
			}
			if (ex.InnerException != null && IsRetryable(ex.InnerException))
			{
				return true;
			}
			return false;
		}

		private static bool IsRateLimited(Exception ex)
		{
			string text = Describe(ex);
			if (text.IndexOf("429", StringComparison.Ordinal) < 0 && text.IndexOf("请求过于频繁", StringComparison.Ordinal) < 0)
			{
				return text.IndexOf("Too Many Requests", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static string Describe(Exception ex)
		{
			if (ex is AggregateException { InnerException: not null } ex2)
			{
				return Describe(ex2.InnerException);
			}
			if (ex.InnerException != null && !(ex is HttpRequestException))
			{
				return ex.GetType().Name + ": " + ex.Message + " <- " + ex.InnerException.Message;
			}
			return ex.GetType().Name + ": " + ex.Message;
		}

		private static string Trim(string s, int max)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			s = s.Replace("\r", " ").Replace("\n", " ");
			if (s.Length > max)
			{
				return s.Substring(0, max);
			}
			return s;
		}

		private static Dictionary<string, string>? ParseYamlResponse(string yamlText)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrWhiteSpace(yamlText))
			{
				return null;
			}
			string text = yamlText.Trim();
			if (text.StartsWith("```", StringComparison.Ordinal))
			{
				int num = text.IndexOf('\n');
				if (num > 0)
				{
					text = text.Substring(num + 1);
				}
				int num2 = text.LastIndexOf("```", StringComparison.Ordinal);
				if (num2 >= 0)
				{
					text = text.Substring(0, num2);
				}
				text = text.Trim();
			}
			try
			{
				return new DeserializerBuilder().Build().Deserialize<Dictionary<string, string>>(text);
			}
			catch (Exception ex)
			{
				ModLogger.LogDebugAsInfo("[AI] 解析响应失败:" + ex.Message);
				return null;
			}
		}

		internal static string BuildPrompt(List<TranslationEntry> entries)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (TranslationEntry entry in entries)
			{
				stringBuilder.Append(entry.Id).Append(": ").Append(entry.OriginalText)
					.Append('\n');
			}
			return stringBuilder.ToString();
		}
	}
	internal static class TranslationApplicator
	{
		private static readonly FieldInfo? SectionField = typeof(ConfigDefinition).GetField("<Section>k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo? KeyField = typeof(ConfigDefinition).GetField("<Key>k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo? DescriptionField = typeof(ConfigDescription).GetField("<Description>k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly PropertyInfo? EntriesProp = typeof(ConfigFile).GetProperty("Entries", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly PropertyInfo? OrphanedProp = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic);

		[ThreadStatic]
		private static ConfigFile? _orphanFile;

		[ThreadStatic]
		private static string? _orphanSection;

		[ThreadStatic]
		private static string? _orphanKey;

		internal static void ApplyInPlace(string guid, ConfigDefinition definition, ConfigDescription? configDescription)
		{
			if (TranslationManager.HasTranslation && !string.IsNullOrEmpty(guid) && !(definition == (ConfigDefinition)null))
			{
				string text = definition.Section ?? "";
				string text2 = definition.Key ?? "";
				string text3 = ((configDescription != null) ? configDescription.Description : null) ?? "";
				if (TryResolve(guid, text, text, out string translation) && translation != null && translation != text)
				{
					SetField(SectionField, definition, translation);
				}
				if (TryResolve(guid, text, text2, out string translation2) && translation2 != null && translation2 != text2)
				{
					SetField(KeyField, definition, translation2);
				}
				if (configDescription != null && !string.IsNullOrEmpty(text3) && TryResolve(guid, text, text3, out string translation3) && translation3 != null && translation3 != text3)
				{
					SetField(DescriptionField, configDescription, translation3);
				}
			}
		}

		internal static void RememberOrphanSource(ConfigFile file, string section, string key)
		{
			_orphanFile = file;
			_orphanSection = section;
			_orphanKey = key;
		}

		internal static void TryMigrateOrphanValue(ConfigEntryBase entry)
		{
			if (_orphanFile == null || entry == null || string.IsNullOrEmpty(_orphanSection) || string.IsNullOrEmpty(_orphanKey))
			{
				ClearOrphan();
				return;
			}
			try
			{
				Dictionary<ConfigDefinition, string> orphanedEntries = GetOrphanedEntries(_orphanFile);
				if (orphanedEntries == null)
				{
					return;
				}
				foreach (KeyValuePair<ConfigDefinition, string> item in orphanedEntries.ToList())
				{
					if (item.Key.Section == _orphanSection && item.Key.Key == _orphanKey)
					{
						entry.SetSerializedValue(item.Value);
						orphanedEntries.Remove(item.Key);
						ModLogger.LogDebug("[应用] 已迁移 cfg 原值: " + _orphanSection + "/" + _orphanKey);
						break;
					}
				}
			}
			catch (Exception ex)
			{
				ModLogger.LogDebug("[应用] cfg 孤儿迁移跳过: " + ex.Message);
			}
			finally
			{
				ClearOrphan();
			}
		}

		internal static void ClearOrphan()
		{
			_orphanFile = null;
			_orphanSection = null;
			_orphanKey = null;
		}

		internal static void ApplyToAlreadyLoadedPlugins()
		{
			if (!TranslationManager.HasTranslation)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				BaseUnityPlugin instance = value.Instance;
				if (instance == null || instance.Config == null)
				{
					continue;
				}
				ConfigFile config = instance.Config;
				Dictionary<ConfigDefinition, ConfigEntryBase> entries = GetEntries(config);
				if (entries == null)
				{
					continue;
				}
				string pluginGuid = ReflectionHelper.GetPluginGuid(config);
				List<ConfigEntryBase> list = entries.Values.ToList();
				if (list.Count == 0)
				{
					continue;
				}
				foreach (ConfigEntryBase item in list)
				{
					ConfigDefinition definition = item.Definition;
					if (!(definition == (ConfigDefinition)null))
					{
						config.Remove(definition);
						ApplyInPlace(pluginGuid, definition, item.Description);
						entries[definition] = item;
						num2++;
					}
				}
				num++;
			}
			if (num > 0)
			{
				ModLogger.LogDebugAsInfo($"[应用] 已对 {num} 个先加载模组补应用({num2} 条)");
			}
		}

		private static Dictionary<ConfigDefinition, ConfigEntryBase>? GetEntries(ConfigFile file)
		{
			return EntriesProp?.GetValue(file) as Dictionary<ConfigDefinition, ConfigEntryBase>;
		}

		private static Dictionary<ConfigDefinition, string>? GetOrphanedEntries(ConfigFile file)
		{
			return OrphanedProp?.GetValue(file) as Dictionary<ConfigDefinition, string>;
		}

		private static void SetField(FieldInfo? field, object target, string value)
		{
			if (!(field == null))
			{
				field.SetValue(target, value);
			}
		}

		private static bool TryResolve(string guid, string section, string key, out string? translation)
		{
			translation = null;
			if (string.IsNullOrEmpty(key))
			{
				return false;
			}
			if (ExemptionManager.IsExempt(guid, section, key))
			{
				return false;
			}
			if (!TranslationManager.TryGetTranslation(guid, section, key, out translation) || string.IsNullOrEmpty(translation))
			{
				return false;
			}
			ModLogger.LogDebugAsInfo("[应用] " + guid + " | " + section + " | " + key + " → " + translation);
			return true;
		}
	}
	internal static class TranslationBatchBuilder
	{
		internal static List<(string guid, List<TranslationEntry> entries)> BuildBatches(Dictionary<string, Dictionary<string, Dictionary<string, string>>> data, int maxEntriesPerBatch, int maxCharsPerBatch)
		{
			List<(string, List<TranslationEntry>)> list = new List<(string, List<TranslationEntry>)>();
			int idCounter = 0;
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, string>>> datum in data)
			{
				string key = datum.Key;
				Dictionary<string, Dictionary<string, string>> dictionary = FilterUntranslated(key, datum.Value);
				if (dictionary.Count == 0)
				{
					continue;
				}
				foreach (List<TranslationEntry> item in SplitByLimits(dictionary, key, ref idCounter, maxEntriesPerBatch, maxCharsPerBatch))
				{
					list.Add((key, item));
				}
			}
			return list;
		}

		internal static int GetTotalUntranslatedCount(Dictionary<string, Dictionary<string, Dictionary<string, string>>> data)
		{
			int num = 0;
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, string>>> datum in data)
			{
				Dictionary<string, Dictionary<string, string>> source = FilterUntranslated(datum.Key, datum.Value);
				num += source.Sum((KeyValuePair<string, Dictionary<string, string>> s) => s.Value.Count);
			}
			return num;
		}

		private static Dictionary<string, Dictionary<string, string>> FilterUntranslated(string guid, Dictionary<string, Dictionary<string, string>> sections)
		{
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>();
			foreach (KeyValuePair<string, Dictionary<string, string>> section in sections)
			{
				Dictionary<string, string> dictionary2 = new Dictionary<string, string>();
				foreach (KeyValuePair<string, string> item in section.Value)
				{
					if (!ExemptionManager.IsExempt(guid, section.Key, item.Key) && (!TranslationManager.HasTranslation || !TranslationManager.TryGetTranslation(guid, section.Key, item.Key, out string translation) || translation == null) && (!ModConfig.IsChineseTarget() || !TextHelper.IsMostlyChinese(item.Value)))
					{
						dictionary2[item.Key] = item.Value;
					}
				}
				if (dictionary2.Count > 0)
				{
					dictionary[section.Key] = dictionary2;
				}
			}
			return dictionary;
		}

		private static List<List<TranslationEntry>> SplitByLimits(Dictionary<string, Dictionary<string, string>> untranslated, string guid, ref int idCounter, int maxEntriesPerBatch, int maxCharsPerBatch)
		{
			List<List<TranslationEntry>> list = new List<List<TranslationEntry>>();
			List<TranslationEntry> list2 = new List<TranslationEntry>();
			int num = 0;
			foreach (KeyValuePair<string, Dictionary<string, string>> item in untranslated)
			{
				foreach (KeyValuePair<string, string> item2 in item.Value)
				{
					TranslationEntry translationEntry = new TranslationEntry
					{
						Guid = guid,
						Section = item.Key,
						Key = item2.Key,
						OriginalText = (item2.Value ?? ""),
						Id = "entry_" + idCounter++
					};
					int num2 = EstimateCharCost(translationEntry);
					if (list2.Count > 0 && (list2.Count >= maxEntriesPerBatch || num + num2 > maxCharsPerBatch))
					{
						list.Add(list2);
						list2 = new List<TranslationEntry>();
						num = 0;
					}
					list2.Add(translationEntry);
					num += num2;
				}
			}
			if (list2.Count > 0)
			{
				list.Add(list2);
			}
			return list;
		}

		private static int EstimateCharCost(TranslationEntry entry)
		{
			return (entry.Id?.Length ?? 0) + 2 + (entry.OriginalText?.Length ?? 0) + 1;
		}
	}
	internal struct TranslationEntry
	{
		public string Guid;

		public string Section;

		public string Key;

		public string OriginalText;

		public string Id;
	}
	internal static class TranslationManager
	{
		internal static Dictionary<string, Dictionary<string, Dictionary<string, string>>>? TranslationCache { get; private set; }

		internal static bool HasTranslation
		{
			get
			{
				if (TranslationCache != null)
				{
					return TranslationCache.Count > 0;
				}
				return false;
			}
		}

		internal static void SetCache(Dictionary<string, Dictionary<string, Dictionary<string, string>>>? cache)
		{
			TranslationCache = cache;
		}

		internal static bool TryGetTranslation(string guid, string section, string key, out string? translation)
		{
			translation = null;
			if (TranslationCache == null)
			{
				return false;
			}
			if (TranslationCache.TryGetValue(guid, out Dictionary<string, Dictionary<string, string>> value) && value.TryGetValue(section, out var value2) && value2.TryGetValue(key, out translation))
			{
				return true;
			}
			return false;
		}
	}
	internal static class TranslationPipeline
	{
		private const int MaxCharsPerBatch = 24000;

		internal static bool IsRunning { get; private set; }

		internal static IEnumerator Run(Dictionary<string, Dictionary<string, Dictionary<string, string>>> data, string sourceName)
		{
			if (IsRunning)
			{
				ModLogger.LogDebugAsInfo("[AI] 翻译正在进行中,忽略重复触发");
				yield break;
			}
			if (data == null || data.Count == 0)
			{
				ModLogger.LogInfo("没有可翻译的配置,已跳过");
				ModLogger.LogDebugAsInfo("[AI] 数据为空(来源:" + sourceName + ")");
				yield break;
			}
			if (string.IsNullOrWhiteSpace(ModConfig.ApiKey.Value))
			{
				ModLogger.LogWarning("请在模组配置「4. AI 翻译 > API密钥」中填写 DeepSeek API 密钥后再翻译");
				yield break;
			}
			if (!ModConfig.EnableAiTranslation.Value)
			{
				ModLogger.LogDebugAsInfo("[AI] 启用AI翻译=false,跳过管线");
				yield break;
			}
			IsRunning = true;
			DateTime startTime = DateTime.Now;
			if (!ModConfig.TryGetThroughput(out var concurrency, out var batchSize))
			{
				ModLogger.LogWarning("吞吐参数无效(当前「" + ModConfig.ThroughputParams.Value + "」),已回退默认 12|200");
				concurrency = 12;
				batchSize = 200;
			}
			DeepSeekApiClient.ConfigureConcurrency(concurrency);
			ModConfig.GetTargetLanguage(out string code, out string promptName);
			ModLogger.LogInfo("目标语言:" + promptName + "(" + code + ")");
			ModLogger.LogInfo($"吞吐参数:并发 {concurrency},每批 {batchSize} 条");
			ModLogger.LogDebugAsInfo("[AI] 筛选未译条目(来源:" + sourceName + ")");
			List<(string guid, List<TranslationEntry> entries)> batches = TranslationBatchBuilder.BuildBatches(data, batchSize, 24000);
			int totalUntranslatedCount = TranslationBatchBuilder.GetTotalUntranslatedCount(data);
			if (batches.Count == 0)
			{
				ModLogger.LogInfo("无需翻译:条目均已有译文");
				IsRunning = false;
				yield break;
			}
			ModLogger.LogInfo($"正在翻译:{totalUntranslatedCount} 条,共 {batches.Count} 批");
			ModLogger.LogDebugAsInfo($"[AI] 并发 {concurrency},≤{batchSize} 条/批,≤{24000} 字符,开始请求翻译接口");
			yield return null;
			Task<Dictionary<string, string>?>[] tasks = new Task<Dictionary<string, string>>[batches.Count];
			for (int i = 0; i < batches.Count; i++)
			{
				List<TranslationEntry> item = batches[i].entries;
				string prompt = DeepSeekApiClient.BuildPrompt(item);
				tasks[i] = DeepSeekApiClient.TranslateBatchAsync(prompt, item.Count);
			}
			Task<Dictionary<string, string>[]> allTasks = Task.WhenAll(tasks);
			int lastLoggedDone = -1;
			DateTime lastHeartbeat = DateTime.Now;
			while (!allTasks.IsCompleted)
			{
				int num = 0;
				for (int j = 0; j < tasks.Length; j++)
				{
					if (tasks[j].IsCompleted)
					{
						num++;
					}
				}
				if (num != lastLoggedDone && (num == tasks.Length || num % 5 == 0 || num == 1))
				{
					lastLoggedDone = num;
					lastHeartbeat = DateTime.Now;
					ModLogger.LogInfo($"翻译进度:{num}/{batches.Count} 批");
				}
				else if ((DateTime.Now - lastHeartbeat).TotalSeconds >= 30.0)
				{
					lastHeartbeat = DateTime.Now;
					ModLogger.LogInfo($"翻译进行中:{num}/{batches.Count} 批(等待接口返回)");
				}
				yield return null;
			}
			try
			{
				allTasks.Wait(0);
			}
			catch (AggregateException)
			{
			}
			ModLogger.LogDebugAsInfo("[AI] 请求结束,合并保存");
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = TranslationManager.TranslationCache;
			if (dictionary == null)
			{
				dictionary = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();
				TranslationManager.SetCache(dictionary);
			}
			for (int k = 0; k < batches.Count; k++)
			{
				(string, List<TranslationEntry>) tuple = batches[k];
				Dictionary<string, string> dictionary2 = SafeGetResult(tasks[k]);
				if (dictionary2 == null)
				{
					num3++;
					continue;
				}
				int num5 = 0;
				foreach (TranslationEntry item2 in tuple.Item2)
				{
					if (dictionary2.TryGetValue(item2.Id, out var value))
					{
						if (!dictionary.ContainsKey(item2.Guid))
						{
							dictionary[item2.Guid] = new Dictionary<string, Dictionary<string, string>>();
						}
						if (!dictionary[item2.Guid].ContainsKey(item2.Section))
						{
							dictionary[item2.Guid][item2.Section] = new Dictionary<string, string>();
						}
						dictionary[item2.Guid][item2.Section][item2.Key] = value;
						num5++;
					}
					else
					{
						ModLogger.LogDebugAsInfo("[AI] 响应缺少 ID " + item2.Id + ",跳过");
					}
				}
				if (num5 > 0)
				{
					num2++;
					num4 += num5;
				}
				else
				{
					num3++;
				}
			}
			TranslationManager.SetCache(dictionary);
			if (num4 > 0)
			{
				Yaml.SaveTranslation(dictionary);
				DeduplicateCollectionFile(dictionary);
				double totalSeconds = (DateTime.Now - startTime).TotalSeconds;
				ModLogger.LogInfo($"翻译完成:{num4} 条,耗时 {totalSeconds:F0} 秒");
				if (num3 > 0)
				{
					ModLogger.LogWarning($"有 {num3} 批未写入(软失败/缺响应),成功批次 {num2}/{batches.Count}");
				}
				else
				{
					ModLogger.LogDebugAsInfo($"[AI] 批次成功 {num2}/{batches.Count},失败 {num3}");
				}
				LogSummaryDebug();
			}
			else
			{
				ModLogger.LogWarning("翻译未成功,已保留原有译文文件");
				ModLogger.LogDebugAsInfo($"[AI] 批次成功 0/{batches.Count},失败 {num3}");
			}
			IsRunning = false;
		}

		private static Dictionary<string, string>? SafeGetResult(Task<Dictionary<string, string>?> task)
		{
			if (task == null)
			{
				return null;
			}
			try
			{
				if (task.IsFaulted || task.IsCanceled)
				{
					Exception ex = task.Exception?.GetBaseException();
					if (ex != null)
					{
						ModLogger.LogDebugAsInfo("[AI] 批任务异常:" + ex.GetType().Name + ": " + ex.Message);
					}
					return null;
				}
				return (task.Status == TaskStatus.RanToCompletion) ? task.Result : null;
			}
			catch (Exception ex2)
			{
				ModLogger.LogDebugAsInfo("[AI] 读取批结果失败:" + ex2.Message);
				return null;
			}
		}

		private static void LogSummaryDebug()
		{
			Dictionary<string, Dictionary<string, Dictionary<string, string>>> translationCache = TranslationManager.TranslationCache;
			if (translationCache == null)
			{
				return;
			}
			int count = translationCache.Count;
			int num = 0;
			int num2 = 0;
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, string>>> item in translationCache)
			{
				foreach (KeyValuePair<string, Dictionary<string, string>> item2 in item.Value)
				{
					foreach (KeyValuePair<string, string> item3 in item2.Value)
					{
						if (item3.Key == item2.Key)
						{
							num++;
						}
						else
						{
							num2++;
						}
					}
				}
			}
			ModLogger.LogDebugAsInfo($"[AI] 缓存合计:{count} 模组,{num} 分类,{num2} 条目");
		}

		private static void DeduplicateCollectionFile(Dictionary<string, Dictionary<string, Dictionary<string, string>>> translationData)
		{
			try
			{
				Dictionary<string, Dictionary<string, Dictionary<string, string>>> dictionary = Yaml.LoadExport();
				if (dictionary == null || dictionary.Count == 0)
				{
					ModLogger.LogDebug("[去重] 收集文件为空,跳过");
					return;
				}
				int num = Yaml.DeduplicateExport(translationData, dictionary);
				if (num > 0)
				{
					Yaml.ExportYaml(dictionary);
					ModLogger.LogDebugAsInfo($"[去重] 已从导出文件移除 {num} 条已译条目");
				}
				else
				{
					ModLogger.LogDebug("[去重] 无需移除");
				}
			}
			catch (Exception ex)
			{
				ModLogger.LogDebugAsInfo("[去重] 异常:" + ex.Message);
			}
		}
	}
}
namespace AI_Localization.Config
{
	public static class ModConfig
	{
		internal const string DefaultThroughput = "12|200";

		private const int DefaultConcurrency = 12;

		private const int DefaultBatchSize = 200;

		internal const string DefaultTargetLanguage = "中文(简体)";

		internal static ConfigEntry<bool> EnableCollection { get; private set; }

		internal static ConfigEntry<string> Exemptions { get; private set; }

		internal static ConfigEntry<bool> LogTranslations { get; private set; }

		internal static ConfigEntry<string> ApiKey { get; private set; }

		internal static ConfigEntry<string> AiModel { get; private set; }

		internal static ConfigEntry<bool> EnableAiTranslation { get; private set; }

		internal static ConfigEntry<string> ThroughputParams { get; private set; }

		internal static ConfigEntry<string> TargetLanguage { get; private set; }

		public static void Init(BaseUnityPlugin plugin)
		{
			EnableCollection = plugin.Config.Bind<bool>("1. 收集", "启用收集", true, "是否启用配置项收集功能。关闭后,Harmony 补丁将不会收集任何新配置项。");
			Exemptions = plugin.Config.Bind<string>("2. 翻译豁免", "豁免名单", "", "哪些条目不翻译。格式: GUID|Section|Key,; 表示字段内多选,, 分下条规则,* 是通配\n例: Dreanegade.Hunter_Legacy|Draw Lever|* , *|*|Lock Configuration");
			LogTranslations = plugin.Config.Bind<bool>("3. 调试", "记录翻译日志", false, "是否输出调试详情(逐条界面应用、API 重试、内部阶段、文件读写路径等)。\n默认关闭。日常只需看正常进度与 Warning;排查问题时再打开。");
			ApiKey = plugin.Config.Bind<string>("4. AI 翻译", "API密钥", "", "DeepSeek API 密钥。从 https://platform.deepseek.com/api_keys 获取");
			AiModel = plugin.Config.Bind<string>("4. AI 翻译", "AI模型", "deepseek-v4-flash", "DeepSeek 模型。推荐 deepseek-v4-flash / deepseek-v4-pro(旧名 deepseek-chat、deepseek-reasoner 即将弃用)");
			EnableAiTranslation = plugin.Config.Bind<bool>("4. AI 翻译", "启用AI翻译", true, "是否启用 AI 翻译。开启后:收集完成会自动翻译;若关闭收集则用已有导出文件翻译。关闭则只加载已有译文、不调用 API。");
			ThroughputParams = plugin.Config.Bind<string>("4. AI 翻译", "吞吐参数", "12|200", "格式:并发请求数|每批条数,例 12|200。\n左=同时发几批(越大越快也越容易失败);右=每批多少条。\n官方参考:Flash≈2500,Pro≈500。推荐起步 12|200。");
			TargetLanguage = plugin.Config.Bind<string>("4. AI 翻译", "目标语言", "中文(简体)", "翻译输出语言。常用:中文(简体) / English / 日本語 / Deutsch\n也可写语言代码:zh / en / ja / de。改语言后请重启;各语言译文分文件保存。");
		}

		internal static bool TryGetThroughput(out int concurrency, out int batchSize)
		{
			concurrency = 12;
			batchSize = 200;
			string text = "12|200";
			try
			{
				if (ThroughputParams != null && !string.IsNullOrWhiteSpace(ThroughputParams.Value))
				{
					text = ThroughputParams.Value.Trim();
				}
			}
			catch
			{
			}
			string[] array = text.Split(new char[1] { '|' }, StringSplitOptions.None);
			if (array.Length != 2)
			{
				return false;
			}
			if (!int.TryParse(array[0].Trim(), out var result) || !int.TryParse(array[1].Trim(), out var result2))
			{
				return false;
			}
			if (result < 1 || result2 < 1)
			{
				return false;
			}
			if (result > 10000)
			{
				result = 10000;
			}
			if (result2 > 5000)
			{
				result2 = 5000;
			}
			concurrency = result;
			batchSize = result2;
			return true;
		}

		internal static void GetTargetLanguage(out string code, out string promptName)
		{
			string text = "中文(简体)";
			try
			{
				if (TargetLanguage != null && !string.IsNullOrWhiteSpace(TargetLanguage.Value))
				{
					text = TargetLanguage.Value.Trim();
				}
			}
			catch
			{
			}
			string text2 = text.ToLowerInvariant();
			switch (text2.Replace(" ", "").Replace("(", "(").Replace(")", ")"))
			{
			case "中文(简体)":
			case "简体中文":
			case "中文":
			case "zh":
			case "zh-cn":
			case "zh_cn":
			case "chinese":
			case "chinesesimplified":
				code = "zh";
				promptName = "简体中文";
				break;
			case "english":
			case "en":
			case "en-us":
			case "en_us":
				code = "en";
				promptName = "English";
				break;
			case "日本語":
			case "日语":
			case "japanese":
			case "ja":
			case "jp":
				code = "ja";
				promptName = "日本語";
				break;
			case "deutsch":
			case "german":
			case "de":
			case "de-de":
			case "德语":
				code = "de";
				promptName = "Deutsch";
				break;
			default:
				code = SanitizeLanguageCode(text);
				promptName = text;
				break;
			}
		}

		internal static bool IsChineseTarget()
		{
			GetTargetLanguage(out string code, out string _);
			return code == "zh";
		}

		private static string SanitizeLanguageCode(string raw)
		{
			if (string.IsNullOrWhiteSpace(raw))
			{
				return "custom";
			}
			StringBuilder stringBuilder = new StringBuilder();
			string text = raw.Trim().ToLowerInvariant();
			foreach (char c in text)
			{
				if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
				{
					stringBuilder.Append(c);
				}
				else if (c == '-' || c == '_')
				{
					stringBuilder.Append('_');
				}
			}
			string text2 = stringBuilder.ToString();
			if (text2.Length == 0)
			{
				return "custom";
			}
			if (text2.Length > 16)
			{
				text2 = text2.Substring(0, 16);
			}
			return text2;
		}
	}
}