Decompiled source of GTFOAutoTranslator v0.3.0

plugins/GTFOAutoTranslator/GTFOAutoTranslator.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("GTFOAutoTranslator")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("GTFOAutoTranslator")]
[assembly: AssemblyTitle("GTFOAutoTranslator")]
[assembly: AssemblyVersion("1.0.0.0")]
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;
		}
	}
}
namespace GTFOAutoTranslator
{
	internal sealed class LibreTranslateClient
	{
		private sealed class TranslationRequest
		{
			[JsonPropertyName("q")]
			public string Q { get; set; } = "";

			[JsonPropertyName("source")]
			public string Source { get; set; } = "auto";

			[JsonPropertyName("target")]
			public string Target { get; set; } = "en";

			[JsonPropertyName("format")]
			public string Format { get; set; } = "text";

			[JsonPropertyName("api_key")]
			public string? ApiKey { get; set; }
		}

		private sealed class TranslationResponse
		{
			[JsonPropertyName("translatedText")]
			public string? TranslatedText { get; set; }
		}

		private static readonly HttpClient Http = new HttpClient
		{
			Timeout = TimeSpan.FromSeconds(12.0)
		};

		private readonly string _endpoint;

		private readonly string _apiKey;

		private readonly string _target;

		public LibreTranslateClient(string endpoint, string apiKey, string target)
		{
			_endpoint = endpoint.Trim();
			_apiKey = apiKey.Trim();
			_target = (string.IsNullOrWhiteSpace(target) ? "en" : target.Trim());
		}

		public async Task<string> TranslateAsync(string text)
		{
			TranslationRequest value = new TranslationRequest
			{
				Q = text,
				Source = "auto",
				Target = _target,
				Format = "text",
				ApiKey = (string.IsNullOrWhiteSpace(_apiKey) ? null : _apiKey)
			};
			using HttpResponseMessage response = await Http.PostAsJsonAsync(_endpoint, value);
			response.EnsureSuccessStatusCode();
			return (await response.Content.ReadFromJsonAsync<TranslationResponse>((JsonSerializerOptions?)null, default(CancellationToken)))?.TranslatedText ?? text;
		}
	}
	[BepInPlugin("kai.gtfo.autotranslator", "GTFO Auto Translator", "0.2.0")]
	public sealed class Plugin : BasePlugin
	{
		public const string PluginGuid = "kai.gtfo.autotranslator";

		public const string PluginName = "GTFO Auto Translator";

		public const string PluginVersion = "0.2.0";

		internal static ManualLogSource Logger;

		internal static TranslatorConfig Settings;

		public override void Load()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			Logger = ((BasePlugin)this).Log;
			Settings = new TranslatorConfig(((BasePlugin)this).Config);
			((BasePlugin)this).AddComponent<TranslationBehaviour>();
			ManualLogSource log = ((BasePlugin)this).Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(9, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("GTFO Auto Translator");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("0.2.0");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded.");
			}
			log.LogInfo(val);
		}
	}
	internal sealed class TranslatorConfig
	{
		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<string> ToggleKey { get; }

		public ConfigEntry<bool> ShowStatusIndicator { get; }

		public ConfigEntry<float> StatusIndicatorSeconds { get; }

		public ConfigEntry<string> Endpoint { get; }

		public ConfigEntry<string> ApiKey { get; }

		public ConfigEntry<string> TargetLanguage { get; }

		public ConfigEntry<float> ScanInterval { get; }

		public ConfigEntry<int> MinimumLength { get; }

		public ConfigEntry<bool> TranslateAsciiOnly { get; }

		public ConfigEntry<bool> PreserveRichText { get; }

		public ConfigEntry<int> MaxRequestsPerMinute { get; }

		public ConfigEntry<bool> LogTranslations { get; }

		public ConfigEntry<bool> PersistentCache { get; }

		public ConfigEntry<float> CacheSaveInterval { get; }

		public ConfigEntry<bool> OnlyTranslateMatchingHierarchy { get; }

		public ConfigEntry<bool> RequireHierarchyMatchForAscii { get; }

		public ConfigEntry<string> HierarchyIncludePatterns { get; }

		public ConfigEntry<string> HierarchyExcludePatterns { get; }

		public TranslatorConfig(ConfigFile config)
		{
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable or disable automatic translation.");
			ToggleKey = config.Bind<string>("General", "ToggleKey", "F8", "Unity KeyCode used to toggle translation while in game. Example: F8, F9, Insert.");
			ShowStatusIndicator = config.Bind<bool>("General", "ShowStatusIndicator", true, "Show a small on-screen translator status indicator.");
			StatusIndicatorSeconds = config.Bind<float>("General", "StatusIndicatorSeconds", 3f, "How long toggle/translation status messages stay fully visible.");
			TargetLanguage = config.Bind<string>("General", "TargetLanguage", "en", "LibreTranslate target language code. Use 'en' for English.");
			ScanInterval = config.Bind<float>("General", "ScanIntervalSeconds", 0.4f, "How often active TextMeshPro UI is checked.");
			MinimumLength = config.Bind<int>("General", "MinimumTextLength", 2, "Ignore strings shorter than this.");
			TranslateAsciiOnly = config.Bind<bool>("Detection", "TranslateAsciiOnlyText", false, "Also submit ASCII-only text. Useful for French/Spanish/German, but use hierarchy filters to avoid normal English UI.");
			PreserveRichText = config.Bind<bool>("Detection", "PreserveRichTextTags", true, "Temporarily protects common TextMeshPro rich-text tags while translating.");
			OnlyTranslateMatchingHierarchy = config.Bind<bool>("Filtering", "OnlyTranslateMatchingHierarchy", false, "When true, all text must match at least one HierarchyIncludePatterns entry before it can be translated.");
			RequireHierarchyMatchForAscii = config.Bind<bool>("Filtering", "RequireHierarchyMatchForAsciiOnly", true, "When ASCII-only translation is enabled, require a hierarchy include match. Recommended to prevent normal GTFO English UI from being submitted.");
			HierarchyIncludePatterns = config.Bind<string>("Filtering", "HierarchyIncludePatterns", "", "Semicolon-separated case-insensitive fragments matched against the TMP object's Unity hierarchy. Example: MyMod;CustomMenu;ChineseUI");
			HierarchyExcludePatterns = config.Bind<string>("Filtering", "HierarchyExcludePatterns", "", "Semicolon-separated hierarchy fragments that should never be translated.");
			PersistentCache = config.Bind<bool>("Cache", "PersistentCache", true, "Save successful translations to disk and reuse them on future launches.");
			CacheSaveInterval = config.Bind<float>("Cache", "CacheSaveIntervalSeconds", 5f, "How often new cached translations are flushed to disk.");
			Endpoint = config.Bind<string>("LibreTranslate", "Endpoint", "http://localhost:5000/translate", "LibreTranslate /translate endpoint. Local self-hosting is recommended.");
			ApiKey = config.Bind<string>("LibreTranslate", "ApiKey", "", "API key if your LibreTranslate server requires one.");
			MaxRequestsPerMinute = config.Bind<int>("LibreTranslate", "MaxRequestsPerMinute", 60, "Simple request-rate limit to avoid hammering the translation server.");
			LogTranslations = config.Bind<bool>("Debug", "LogTranslations", false, "Log original and translated strings in BepInEx console/log.");
		}
	}
	internal readonly record struct ProtectedText(string Text, Dictionary<string, string> Tokens);
	internal static class RichTextProtector
	{
		private static readonly Regex TagRegex = new Regex("<[^>]+>", RegexOptions.Compiled);

		public static ProtectedText Protect(string input)
		{
			Dictionary<string, string> tokens = new Dictionary<string, string>();
			int index = 0;
			return new ProtectedText(TagRegex.Replace(input, delegate(Match match)
			{
				string text = $"__GTFO_TAG_{index++}__";
				tokens[text] = match.Value;
				return text;
			}), tokens);
		}

		public static string Restore(string input, Dictionary<string, string> tokens)
		{
			foreach (KeyValuePair<string, string> token in tokens)
			{
				input = input.Replace(token.Key, token.Value, StringComparison.Ordinal);
			}
			return input;
		}

		public static string StripTags(string input)
		{
			return TagRegex.Replace(input, string.Empty);
		}
	}
	public sealed class TranslationBehaviour : MonoBehaviour
	{
		private sealed record TranslationResult(TMP_Text Target, string Original, string Translated, bool Success);

		private static readonly ConcurrentQueue<TranslationResult> Completed = new ConcurrentQueue<TranslationResult>();

		private static readonly HashSet<string> Pending = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> MemoryCache = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly Dictionary<int, string> LastApplied = new Dictionary<int, string>();

		private static readonly object Sync = new object();

		private readonly Queue<DateTime> _requestTimes = new Queue<DateTime>();

		private float _nextScan;

		private float _nextCacheSave;

		private float _statusUntil;

		private string _statusMessage = "Translator ready";

		private LibreTranslateClient? _client;

		private TranslationCacheStore? _diskCache;

		private KeyCode _toggleKey = (KeyCode)289;

		public TranslationBehaviour(IntPtr ptr)
			: base(ptr)
		{
		}//IL_001c: Unknown result type (might be due to invalid IL or missing references)


		private void Start()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			_client = new LibreTranslateClient(Plugin.Settings.Endpoint.Value, Plugin.Settings.ApiKey.Value, Plugin.Settings.TargetLanguage.Value);
			if (!Enum.TryParse<KeyCode>(Plugin.Settings.ToggleKey.Value, ignoreCase: true, out _toggleKey))
			{
				_toggleKey = (KeyCode)289;
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unknown ToggleKey '");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Plugin.Settings.ToggleKey.Value);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'. Falling back to F8.");
				}
				logger.LogWarning(val);
			}
			if (Plugin.Settings.PersistentCache.Value)
			{
				_diskCache = new TranslationCacheStore(Plugin.Settings.TargetLanguage.Value);
				_diskCache.Load();
				lock (Sync)
				{
					foreach (KeyValuePair<string, string> entry in _diskCache.Entries)
					{
						MemoryCache[entry.Key] = entry.Value;
					}
				}
				SetStatus($"Loaded {_diskCache.Count} cached translations");
			}
			else
			{
				SetStatus("Translator ready");
			}
		}

		private void Update()
		{
			ApplyCompleted();
			HandleToggleKey();
			SaveCacheWhenDue();
			if (Plugin.Settings.Enabled.Value && !(Time.unscaledTime < _nextScan))
			{
				_nextScan = Time.unscaledTime + Math.Max(0.1f, Plugin.Settings.ScanInterval.Value);
				ScanVisibleText();
			}
		}

		private void OnDestroy()
		{
			_diskCache?.SaveIfDirty();
		}

		private void OnApplicationQuit()
		{
			_diskCache?.SaveIfDirty();
		}

		private void OnGUI()
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings.ShowStatusIndicator.Value)
			{
				bool value = Plugin.Settings.Enabled.Value;
				int num = 0;
				int value2 = 0;
				lock (Sync)
				{
					num = Pending.Count;
					value2 = MemoryCache.Count;
				}
				if (!(!(Time.unscaledTime <= _statusUntil) && value) || num != 0)
				{
					string text = (value ? $"GTFO Translator: ON | Pending {num} | Cache {value2}\n{_statusMessage}" : $"GTFO Translator: OFF | Press {_toggleKey} to enable");
					GUI.Box(new Rect(16f, 16f, 420f, 52f), text);
				}
			}
		}

		private void HandleToggleKey()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			bool flag = default(bool);
			try
			{
				if (Input.GetKeyDown(_toggleKey))
				{
					Plugin.Settings.Enabled.Value = !Plugin.Settings.Enabled.Value;
					SetStatus(Plugin.Settings.Enabled.Value ? "Translation enabled" : "Translation disabled");
					ManualLogSource logger = Plugin.Logger;
					BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(21, 1, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Runtime translation ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Plugin.Settings.Enabled.Value ? "enabled" : "disabled");
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".");
					}
					logger.LogInfo(val);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource logger2 = Plugin.Logger;
				BepInExDebugLogInterpolatedStringHandler val2 = new BepInExDebugLogInterpolatedStringHandler(25, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Toggle key check failed: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message);
				}
				logger2.LogDebug(val2);
			}
		}

		private void SaveCacheWhenDue()
		{
			if (_diskCache != null && !(Time.unscaledTime < _nextCacheSave))
			{
				_nextCacheSave = Time.unscaledTime + Math.Max(1f, Plugin.Settings.CacheSaveInterval.Value);
				_diskCache.SaveIfDirty();
			}
		}

		private void ApplyCompleted()
		{
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Expected O, but got Unknown
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Expected O, but got Unknown
			TranslationResult result;
			while (Completed.TryDequeue(out result))
			{
				lock (Sync)
				{
					Pending.Remove(result.Original);
					if (result.Success && !string.IsNullOrWhiteSpace(result.Translated))
					{
						MemoryCache[result.Original] = result.Translated;
						_diskCache?.Put(result.Original, result.Translated);
					}
				}
				if (!result.Success || (Object)(object)result.Target == (Object)null)
				{
					continue;
				}
				bool flag;
				try
				{
					if (!(result.Target.text == result.Original))
					{
						continue;
					}
					result.Target.text = result.Translated;
					LastApplied[((Object)result.Target).GetInstanceID()] = result.Translated;
					SetStatus("Translated UI text");
					if (Plugin.Settings.LogTranslations.Value)
					{
						ManualLogSource logger = Plugin.Logger;
						BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(20, 2, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Translated: '");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(result.Original);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' -> '");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(result.Translated);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
						}
						logger.LogInfo(val);
					}
				}
				catch (Exception ex)
				{
					ManualLogSource logger2 = Plugin.Logger;
					BepInExDebugLogInterpolatedStringHandler val2 = new BepInExDebugLogInterpolatedStringHandler(29, 1, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Could not apply translation: ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message);
					}
					logger2.LogDebug(val2);
				}
			}
		}

		private void ScanVisibleText()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			TMP_Text[] array;
			try
			{
				array = Il2CppArrayBase<TMP_Text>.op_Implicit(Object.FindObjectsOfType<TMP_Text>());
			}
			catch (Exception ex)
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(16, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("UI scan failed: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				logger.LogWarning(val);
				return;
			}
			TMP_Text[] array2 = array;
			foreach (TMP_Text val2 in array2)
			{
				if ((Object)(object)val2 == (Object)null || !((Behaviour)val2).isActiveAndEnabled)
				{
					continue;
				}
				string text = val2.text;
				if (!ShouldTranslate(val2, text))
				{
					continue;
				}
				int instanceID = ((Object)val2).GetInstanceID();
				if (LastApplied.TryGetValue(instanceID, out string value) && value == text)
				{
					continue;
				}
				string value2;
				lock (Sync)
				{
					MemoryCache.TryGetValue(text, out value2);
				}
				if (!string.IsNullOrEmpty(value2))
				{
					val2.text = value2;
					LastApplied[instanceID] = value2;
					continue;
				}
				lock (Sync)
				{
					if (Pending.Contains(text) || !CanMakeRequest())
					{
						continue;
					}
					Pending.Add(text);
					MarkRequest();
					goto IL_0148;
				}
				IL_0148:
				TranslateAsync(val2, text);
			}
		}

		private async Task TranslateAsync(TMP_Text target, string original)
		{
			try
			{
				if (_client == null)
				{
					Completed.Enqueue(new TranslationResult(target, original, original, Success: false));
					return;
				}
				ProtectedText protectedText = (Plugin.Settings.PreserveRichText.Value ? RichTextProtector.Protect(original) : new ProtectedText(original, new Dictionary<string, string>()));
				string text = RichTextProtector.Restore(await _client.TranslateAsync(protectedText.Text), protectedText.Tokens);
				bool flag = !string.IsNullOrWhiteSpace(text) && !string.Equals(text, original, StringComparison.Ordinal);
				if (!flag)
				{
					text = original;
				}
				Completed.Enqueue(new TranslationResult(target, original, text, flag));
			}
			catch (Exception ex)
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag2 = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(20, 1, ref flag2);
				if (flag2)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Translation failed: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				logger.LogWarning(val);
				Completed.Enqueue(new TranslationResult(target, original, original, Success: false));
			}
		}

		private static bool ShouldTranslate(TMP_Text target, string? text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = RichTextProtector.StripTags(text).Trim();
			if (text2.Length < Plugin.Settings.MinimumLength.Value)
			{
				return false;
			}
			if (Regex.IsMatch(text2, "^[\\d\\s\\p{P}\\p{S}_]+$"))
			{
				return false;
			}
			string value = BuildHierarchy(target);
			bool flag = MatchesAny(value, Plugin.Settings.HierarchyIncludePatterns.Value);
			if (MatchesAny(value, Plugin.Settings.HierarchyExcludePatterns.Value))
			{
				return false;
			}
			if (Plugin.Settings.OnlyTranslateMatchingHierarchy.Value && !flag)
			{
				return false;
			}
			if (text2.Any((char c) => c > '\u007f' && char.IsLetter(c)))
			{
				return true;
			}
			if (!Plugin.Settings.TranslateAsciiOnly.Value)
			{
				return false;
			}
			if (Plugin.Settings.RequireHierarchyMatchForAscii.Value && !flag)
			{
				return false;
			}
			return true;
		}

		private static string BuildHierarchy(TMP_Text target)
		{
			try
			{
				List<string> list = new List<string>(8);
				Transform val = target.transform;
				int num = 0;
				while ((Object)(object)val != (Object)null && num++ < 16)
				{
					list.Add(((Object)val).name ?? string.Empty);
					val = val.parent;
				}
				list.Reverse();
				return string.Join("/", list);
			}
			catch
			{
				return ((Object)target).name ?? string.Empty;
			}
		}

		private static bool MatchesAny(string value, string patternList)
		{
			if (string.IsNullOrWhiteSpace(patternList))
			{
				return false;
			}
			string[] array = patternList.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
			foreach (string value2 in array)
			{
				if (value.Contains(value2, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private bool CanMakeRequest()
		{
			DateTime dateTime = DateTime.UtcNow.AddMinutes(-1.0);
			while (_requestTimes.Count > 0 && _requestTimes.Peek() < dateTime)
			{
				_requestTimes.Dequeue();
			}
			return _requestTimes.Count < Math.Max(1, Plugin.Settings.MaxRequestsPerMinute.Value);
		}

		private void MarkRequest()
		{
			_requestTimes.Enqueue(DateTime.UtcNow);
		}

		private void SetStatus(string message)
		{
			_statusMessage = message;
			_statusUntil = Time.unscaledTime + Math.Max(0.5f, Plugin.Settings.StatusIndicatorSeconds.Value);
		}
	}
	internal sealed class TranslationCacheStore
	{
		private readonly string _path;

		private readonly Dictionary<string, string> _entries = new Dictionary<string, string>(StringComparer.Ordinal);

		private bool _dirty;

		public int Count => _entries.Count;

		public IReadOnlyDictionary<string, string> Entries => _entries;

		public TranslationCacheStore(string targetLanguage)
		{
			string text = string.Concat((targetLanguage ?? "en").Where((char c) => char.IsLetterOrDigit(c) || c == '-' || c == '_'));
			if (string.IsNullOrWhiteSpace(text))
			{
				text = "en";
			}
			_path = Path.Combine(Paths.ConfigPath, "GTFOAutoTranslator.cache." + text + ".json");
		}

		public void Load()
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			try
			{
				if (!File.Exists(_path))
				{
					return;
				}
				Dictionary<string, string> dictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(_path));
				if (dictionary == null)
				{
					return;
				}
				_entries.Clear();
				foreach (KeyValuePair<string, string> item in dictionary)
				{
					if (!string.IsNullOrWhiteSpace(item.Key) && !string.IsNullOrWhiteSpace(item.Value))
					{
						_entries[item.Key] = item.Value;
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(34, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not load translation cache: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				logger.LogWarning(val);
			}
		}

		public bool TryGet(string original, out string translated)
		{
			return _entries.TryGetValue(original, out translated);
		}

		public void Put(string original, string translated)
		{
			if (!string.IsNullOrWhiteSpace(original) && !string.IsNullOrWhiteSpace(translated) && (!_entries.TryGetValue(original, out string value) || !(value == translated)))
			{
				_entries[original] = translated;
				_dirty = true;
			}
		}

		public void SaveIfDirty()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			if (!_dirty)
			{
				return;
			}
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(_path));
				string text = _path + ".tmp";
				string contents = JsonSerializer.Serialize(_entries, new JsonSerializerOptions
				{
					WriteIndented = true
				});
				File.WriteAllText(text, contents);
				File.Move(text, _path, overwrite: true);
				_dirty = false;
			}
			catch (Exception ex)
			{
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(34, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not save translation cache: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				logger.LogWarning(val);
			}
		}
	}
}