Decompiled source of REPOJapaneseTranslation v1.0.7

REPOJapaneseTranslation.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using REPOJapaneseTranslation.Localization;
using REPOJapaneseTranslation.Patches;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("saitogo")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Japanese localization mod for R.E.P.O.")]
[assembly: AssemblyFileVersion("1.0.7.0")]
[assembly: AssemblyInformationalVersion("1.0.7+1d7de09580ffc65836598e4cb836666db8a19682")]
[assembly: AssemblyProduct("REPO Japanese Translation")]
[assembly: AssemblyTitle("REPOJapaneseTranslation")]
[assembly: AssemblyVersion("1.0.7.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 REPOJapaneseTranslation
{
	[BepInPlugin("REPOJapaneseTranslation", "REPO Japanese Translation", "1.0.7")]
	public class Plugin : BaseUnityPlugin
	{
		private readonly Harmony _harmony = new Harmony("REPOJapaneseTranslation");

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Logger { get; private set; }

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

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

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

		private void Awake()
		{
			Instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			EnableTranslation = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableTranslation", true, "テキストを日本語に翻訳するかどうか / Whether to translate text to Japanese");
			EnableJapaneseFont = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableJapaneseFont", true, "日本語フォントをフォールバックとして追加するかどうか / Whether to add a Japanese font as TMP fallback");
			LogUntranslated = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogUntranslated", false, "未翻訳のテキストをログに出力するかどうか (開発者向け) / Log untranslated strings (for developers)");
			if (LogUntranslated.Value)
			{
				Logger.LogInfo((object)"デバッグモード有効: 未翻訳テキストのログ出力を有効化しました。");
			}
			TranslationManager.Initialize();
			FontManager.Initialize(((BaseUnityPlugin)this).Info.Location);
			_harmony.PatchAll(typeof(TMPTextTranslationPatch).Assembly);
			Logger.LogInfo((object)"REPO Japanese Translation v1.0.7 が読み込まれました!");
			Logger.LogInfo((object)$"翻訳エントリ数: {TranslationManager.TranslationCount}");
		}
	}
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "REPOJapaneseTranslation";

		public const string PLUGIN_NAME = "REPO Japanese Translation";

		public const string PLUGIN_VERSION = "1.0.7";
	}
}
namespace REPOJapaneseTranslation.Patches
{
	internal static class MenuPageTranslator
	{
		internal static void TranslatePage(MenuPage? menuPage)
		{
			if (!((Object)(object)menuPage == (Object)null))
			{
				menuPage.menuHeaderName = TranslationManager.Translate(menuPage.menuHeaderName, logUntranslated: false);
				TMP_Text[] componentsInChildren = ((Component)menuPage).GetComponentsInChildren<TMP_Text>(true);
				FontManager.ApplyFallbackToTextComponents(componentsInChildren);
				TMP_Text[] array = componentsInChildren;
				foreach (TMP_Text text in array)
				{
					TranslateTextComponent(text);
				}
			}
		}

		private static void TranslateTextComponent(TMP_Text text)
		{
			if (!((Object)(object)text == (Object)null) && !string.IsNullOrEmpty(text.text))
			{
				string text2 = TranslationManager.Translate(text.text, logUntranslated: false);
				if (text2 != text.text)
				{
					text.text = text2;
				}
			}
		}
	}
	[HarmonyPatch(typeof(MenuButton), "Awake")]
	internal static class MenuButtonTranslationPatch
	{
		[HarmonyPrefix]
		private static void Prefix(MenuButton __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !string.IsNullOrEmpty(__instance.buttonTextString))
			{
				__instance.buttonTextString = TranslationManager.Translate(__instance.buttonTextString);
			}
		}
	}
	[HarmonyPatch(typeof(MenuManager), "PageOpen", new Type[]
	{
		typeof(MenuPageIndex),
		typeof(bool)
	})]
	internal static class MenuPageOpenTranslationPatch
	{
		[HarmonyPostfix]
		private static void Postfix(MenuPage __result)
		{
			MenuPageTranslator.TranslatePage(__result);
		}
	}
	[HarmonyPatch(typeof(MenuPage), "Start")]
	internal static class MenuPageStartTranslationPatch
	{
		[HarmonyPostfix]
		private static void Postfix(MenuPage __instance)
		{
			MenuPageTranslator.TranslatePage(__instance);
		}
	}
	[HarmonyPatch(typeof(MenuManager), "PagePopUp")]
	internal static class MenuPopUpTranslationPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string headerText, ref string bodyText, ref string buttonText)
		{
			headerText = TranslationManager.Translate(headerText);
			bodyText = TranslationManager.Translate(bodyText);
			buttonText = TranslationManager.Translate(buttonText);
		}
	}
	[HarmonyPatch(typeof(MenuManager), "PagePopUpTwoOptions")]
	internal static class MenuTwoOptionPopUpTranslationPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string popUpHeader, ref string popUpText, ref string option1Text, ref string option2Text)
		{
			popUpHeader = TranslationManager.Translate(popUpHeader);
			popUpText = TranslationManager.Translate(popUpText);
			option1Text = TranslationManager.Translate(option1Text);
			option2Text = TranslationManager.Translate(option2Text);
		}
	}
	[HarmonyPatch(typeof(MenuPageSaves), "SaveFileSelected")]
	internal static class MenuPageSavesSaveFileSelectedPatch
	{
		[HarmonyPostfix]
		private static void Postfix(MenuPageSaves __instance)
		{
			if ((Object)(object)__instance.saveFileInfoRow2 == (Object)null)
			{
				return;
			}
			string text = ((TMP_Text)__instance.saveFileInfoRow2).text;
			if (text.Contains("Total Haul:"))
			{
				string text2 = TranslationManager.Translate("Total Haul:", logUntranslated: false);
				if (!(text2 == "Total Haul:"))
				{
					((TMP_Text)__instance.saveFileInfoRow2).text = text.Replace("Total Haul:", text2);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class TMPFontPatch
	{
		[HarmonyPatch(typeof(RunManager), "Awake")]
		[HarmonyPostfix]
		private static void ApplyFontAfterSceneLoad()
		{
			FontManager.ApplyFallbackToAllTextComponents();
		}
	}
	[HarmonyPatch]
	internal static class TMPTextTranslationPatch
	{
		[CompilerGenerated]
		private sealed class <TargetMethods>d__0 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private MethodBase <>2__current;

			private int <>l__initialThreadId;

			private List<MethodInfo>.Enumerator <>7__wrap1;

			MethodBase IEnumerator<MethodBase>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TargetMethods>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 2)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<>7__wrap1 = default(List<MethodInfo>.Enumerator);
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						MethodInfo methodInfo = AccessTools.PropertySetter(typeof(TMP_Text), "text");
						if (methodInfo != null)
						{
							<>2__current = methodInfo;
							<>1__state = 1;
							return true;
						}
						goto IL_0061;
					}
					case 1:
						<>1__state = -1;
						goto IL_0061;
					case 2:
						{
							<>1__state = -3;
							break;
						}
						IL_0061:
						<>7__wrap1 = AccessTools.GetDeclaredMethods(typeof(TMP_Text)).GetEnumerator();
						<>1__state = -3;
						break;
					}
					while (<>7__wrap1.MoveNext())
					{
						MethodInfo current = <>7__wrap1.Current;
						if (!(current.Name != "SetText"))
						{
							ParameterInfo[] parameters = current.GetParameters();
							if (parameters.Length != 0 && !(parameters[0].ParameterType != typeof(string)))
							{
								<>2__current = current;
								<>1__state = 2;
								return true;
							}
						}
					}
					<>m__Finally1();
					<>7__wrap1 = default(List<MethodInfo>.Enumerator);
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				((IDisposable)<>7__wrap1).Dispose();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <TargetMethods>d__0(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<MethodBase>)this).GetEnumerator();
			}
		}

		[IteratorStateMachine(typeof(<TargetMethods>d__0))]
		[HarmonyTargetMethods]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TargetMethods>d__0(-2);
		}

		[HarmonyPrefix]
		private static void TranslateText(ref string __0)
		{
			__0 = TranslationManager.Translate(__0);
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "UpdateTaxmanNickname")]
	internal static class TruckScreenTextUpdateTaxmanNicknamePatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string newName)
		{
			newName = TranslationManager.Translate(newName, logUntranslated: false);
		}
	}
	[HarmonyPatch(typeof(TuckScreenLocked), "LockChatToggle")]
	internal static class TuckScreenLockedLockChatTogglePatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref string _lockedText)
		{
			_lockedText = TranslationManager.Translate(_lockedText, logUntranslated: false);
		}
	}
}
namespace REPOJapaneseTranslation.Localization
{
	internal static class FontManager
	{
		private static class FontEngineLoadFontFacePatch
		{
			private static bool Prefix(Font font, int pointSize, ref FontEngineError __result)
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0023: Expected I4, but got Unknown
				if ((Object)(object)font == (Object)null || !SystemFontMap.TryGetValue(font, out byte[] value))
				{
					return true;
				}
				__result = (FontEngineError)(int)FontEngine.LoadFontFace(value, pointSize);
				return false;
			}
		}

		private const string TtfFileName = "NotoSansJP-Regular-subset.ttf";

		private static readonly Dictionary<Font, byte[]> SystemFontMap = new Dictionary<Font, byte[]>();

		private static TMP_FontAsset? s_japaneseFontAsset;

		private static readonly HashSet<TMP_FontAsset> s_patchedFonts = new HashSet<TMP_FontAsset>();

		private static bool s_initialized;

		private static bool s_fontEnginePatched;

		internal static void Initialize(string pluginLocation)
		{
			if (s_initialized)
			{
				return;
			}
			s_initialized = true;
			if (!Plugin.EnableJapaneseFont.Value)
			{
				return;
			}
			try
			{
				string path = Path.GetDirectoryName(pluginLocation) ?? string.Empty;
				string text = Path.Combine(path, "NotoSansJP-Regular-subset.ttf");
				if (!File.Exists(text))
				{
					Plugin.Logger.LogWarning((object)("日本語フォントファイルが見つかりません: " + text));
					Plugin.Logger.LogWarning((object)"plugins/REPOJapaneseTranslation/NotoSansJP-Regular-subset.ttf を配置してください。");
					return;
				}
				byte[] array = File.ReadAllBytes(text);
				Plugin.Logger.LogInfo((object)string.Format("フォントファイルを読み込みました: {0} ({1:N0} bytes)", "NotoSansJP-Regular-subset.ttf", array.Length));
				PatchFontEngine();
				s_japaneseFontAsset = CreateFontAssetFromBytes(array);
				if ((Object)(object)s_japaneseFontAsset == (Object)null)
				{
					Plugin.Logger.LogWarning((object)"日本語フォントの作成に失敗しました。日本語文字が正しく表示されない可能性があります。");
					return;
				}
				Plugin.Logger.LogInfo((object)("日本語フォントを作成しました: " + ((Object)s_japaneseFontAsset).name));
				TryAddToTMPGlobalFallback(s_japaneseFontAsset);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("フォントの初期化中にエラーが発生しました: " + ex.Message));
			}
		}

		internal static void ApplyFallbackToAllTextComponents()
		{
			if (!((Object)(object)s_japaneseFontAsset == (Object)null))
			{
				TMP_Text[] texts = Object.FindObjectsOfType<TMP_Text>(true);
				int num = ApplyFallbackToTextComponents(texts);
				if (num > 0)
				{
					Plugin.Logger.LogDebug((object)$"{num} 個のフォントアセットにフォールバックを適用しました。");
				}
			}
		}

		internal static int ApplyFallbackToTextComponents(IEnumerable<TMP_Text> texts)
		{
			if ((Object)(object)s_japaneseFontAsset == (Object)null)
			{
				return 0;
			}
			int num = 0;
			foreach (TMP_Text text in texts)
			{
				if (!((Object)(object)text.font == (Object)null) && !s_patchedFonts.Contains(text.font) && !text.font.fallbackFontAssetTable.Contains(s_japaneseFontAsset))
				{
					text.font.fallbackFontAssetTable.Add(s_japaneseFontAsset);
					s_patchedFonts.Add(text.font);
					num++;
				}
			}
			return num;
		}

		private static void PatchFontEngine()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Expected O, but got Unknown
			if (s_fontEnginePatched)
			{
				return;
			}
			try
			{
				Harmony val = new Harmony("REPOJapaneseTranslation.FontEngineHook");
				MethodInfo method = typeof(FontEngine).GetMethod("LoadFontFace", BindingFlags.Static | BindingFlags.Public, null, new Type[2]
				{
					typeof(Font),
					typeof(int)
				}, null);
				MethodInfo method2 = typeof(FontEngineLoadFontFacePatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic);
				if (method != null && method2 != null)
				{
					val.Patch((MethodBase)method, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					s_fontEnginePatched = true;
					Plugin.Logger.LogDebug((object)"FontEngine.LoadFontFace(Font, int) をパッチしました。");
				}
				else
				{
					Plugin.Logger.LogWarning((object)"FontEngine.LoadFontFace(Font, int) のメソッドが見つかりませんでした。");
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogDebug((object)("FontEngine パッチ適用をスキップしました: " + ex.Message));
			}
		}

		private static TMP_FontAsset? CreateFontAssetFromBytes(byte[] fontBytes)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			FontEngine.InitializeFontEngine();
			FontEngineError val = FontEngine.LoadFontFace(fontBytes);
			if ((int)val != 0)
			{
				Plugin.Logger.LogWarning((object)$"FontEngine.LoadFontFace(byte[]) 失敗: {val}");
				return null;
			}
			Font val2 = new Font("NotoSansJP-subset");
			SystemFontMap[val2] = fontBytes;
			TMP_FontAsset val3;
			try
			{
				val3 = TMP_FontAsset.CreateFontAsset(val2);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogDebug((object)("TMP_FontAsset.CreateFontAsset 例外: " + ex.Message));
				val3 = null;
			}
			if ((Object)(object)val3 == (Object)null)
			{
				CleanupFailedDummyFont(val2);
				Plugin.Logger.LogWarning((object)"TMP_FontAsset を作成できませんでした。");
				return null;
			}
			((Object)val3).name = "JapaneseFallback_NotoSansJP";
			val3.atlasPopulationMode = (AtlasPopulationMode)1;
			return val3;
		}

		private static void CleanupFailedDummyFont(Font dummyFont)
		{
			SystemFontMap.Remove(dummyFont);
			Object.Destroy((Object)(object)dummyFont);
		}

		private static void TryAddToTMPGlobalFallback(TMP_FontAsset fontAsset)
		{
			try
			{
				if ((Object)(object)TMP_Settings.instance == (Object)null)
				{
					Plugin.Logger.LogDebug((object)"TMP_Settings のインスタンスがまだ生成されていません。");
				}
				else if (typeof(TMP_Settings).GetField("m_FallbackFontAssets", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(TMP_Settings.instance) is List<TMP_FontAsset> list)
				{
					if (!list.Contains(fontAsset))
					{
						list.Add(fontAsset);
						Plugin.Logger.LogInfo((object)"TMP グローバルフォールバックリストに日本語フォントを追加しました。");
					}
				}
				else
				{
					Plugin.Logger.LogDebug((object)"TMP_Settings.m_FallbackFontAssets が見つかりませんでした。個別適用で対応します。");
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogDebug((object)("TMP グローバルフォールバック設定をスキップしました: " + ex.Message));
			}
		}
	}
	internal static class TranslationManager
	{
		private sealed class TranslationTemplate
		{
			private readonly Regex _pattern;

			private readonly string _translatedTemplate;

			private readonly string[] _placeholderTokens;

			internal int SourceLength { get; }

			private TranslationTemplate(Regex pattern, string translatedTemplate, string[] placeholderTokens, int sourceLength)
			{
				_pattern = pattern;
				_translatedTemplate = translatedTemplate;
				_placeholderTokens = placeholderTokens;
				SourceLength = sourceLength;
			}

			internal static TranslationTemplate Create(string sourceTemplate, string translatedTemplate)
			{
				MatchCollection matchCollection = s_placeholderTokenRegex.Matches(sourceTemplate);
				StringBuilder stringBuilder = new StringBuilder();
				string[] array = new string[matchCollection.Count];
				stringBuilder.Append('^');
				int num = 0;
				int num2;
				for (int i = 0; i < matchCollection.Count; i++)
				{
					Match match = matchCollection[i];
					num2 = num;
					stringBuilder.Append(Regex.Escape(sourceTemplate.Substring(num2, match.Index - num2)));
					stringBuilder.Append($"(?<p{i}>.+?)");
					array[i] = match.Value;
					num = match.Index + match.Length;
				}
				num2 = num;
				stringBuilder.Append(Regex.Escape(sourceTemplate.Substring(num2, sourceTemplate.Length - num2)));
				stringBuilder.Append('$');
				Regex pattern = new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
				return new TranslationTemplate(pattern, translatedTemplate, array, sourceTemplate.Length);
			}

			internal bool TryTranslate(string text, out string result)
			{
				Match match = _pattern.Match(text);
				if (!match.Success)
				{
					result = text;
					return false;
				}
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
				for (int i = 0; i < _placeholderTokens.Length; i++)
				{
					string key = _placeholderTokens[i];
					if (!dictionary.ContainsKey(key))
					{
						dictionary[key] = match.Groups[$"p{i}"].Value;
					}
				}
				result = _translatedTemplate;
				foreach (KeyValuePair<string, string> item in dictionary)
				{
					item.Deconstruct(out var key2, out var value);
					string oldValue = key2;
					string newValue = value;
					result = result.Replace(oldValue, newValue, StringComparison.Ordinal);
				}
				return true;
			}
		}

		private static readonly Regex s_placeholderTokenRegex = new Regex("\\{[^{}]+\\}|\\[[^\\[\\]]+\\]", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		private static readonly Regex s_actionSuffixRegex = new Regex("^(?<body>.+?)(?<suffix>\\s*(?:<[^>]+>)*\\[[^\\[\\]]+\\](?:</[^>]+>)*\\s*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		private static readonly Regex s_richTextSuffixRegex = new Regex("^(?<body>.+?)(?<suffix>\\s*(?:<[^>]+>)+\\s*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		private static readonly Regex s_labeledPrefixRegex = new Regex("^(?<leadingTags>(?:<[^>]+>)*)(?<label>[^<>]+?)(?<separator>\\s*>\\s*)(?<trailingTags>(?:</[^>]+>)*)(?<body>.+)$", RegexOptions.Compiled | RegexOptions.CultureInvariant);

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

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

		private static readonly List<TranslationTemplate> s_templates = new List<TranslationTemplate>();

		private static bool s_initialized;

		internal static int TranslationCount => s_translations.Count;

		internal static void Initialize()
		{
			if (s_initialized)
			{
				return;
			}
			s_initialized = true;
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using Stream stream = executingAssembly.GetManifestResourceStream("REPOJapaneseTranslation.translations.ja.json");
			if (stream == null)
			{
				Plugin.Logger.LogError((object)"埋め込みリソース 'REPOJapaneseTranslation.translations.ja.json' が見つかりません。");
				return;
			}
			using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
			LoadJson(streamReader.ReadToEnd());
			Plugin.Logger.LogInfo((object)$"翻訳辞書を読み込みました: {s_translations.Count} 件");
		}

		private static void LoadJson(string json)
		{
			//IL_00e9: Expected O, but got Unknown
			try
			{
				Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
				if (dictionary == null)
				{
					return;
				}
				foreach (var (text3, text4) in dictionary)
				{
					if (!string.IsNullOrEmpty(text3) && text3[0] != '_' && !text3.StartsWith("//", StringComparison.Ordinal))
					{
						s_translations[text3] = text4;
						string key = NormalizeKey(text3).ToUpperInvariant();
						s_translationsUpper.TryAdd(key, text4);
						if (s_placeholderTokenRegex.IsMatch(text3))
						{
							s_templates.Add(TranslationTemplate.Create(NormalizeKey(text3), text4));
						}
					}
				}
				s_templates.Sort((TranslationTemplate left, TranslationTemplate right) => right.SourceLength.CompareTo(left.SourceLength));
			}
			catch (JsonException val)
			{
				JsonException val2 = val;
				Plugin.Logger.LogError((object)("翻訳JSONのパースに失敗しました: " + ((Exception)(object)val2).Message));
			}
		}

		internal static string Translate(string text, bool logUntranslated = true)
		{
			if (!Plugin.EnableTranslation.Value || string.IsNullOrEmpty(text))
			{
				return text;
			}
			SplitOuterWhitespace(text, out string leadingWhitespace, out string coreText, out string trailingWhitespace);
			if (coreText.Length == 0)
			{
				return text;
			}
			if (TryTranslateCore(coreText, out string result))
			{
				return leadingWhitespace + result + trailingWhitespace;
			}
			if (logUntranslated && Plugin.LogUntranslated.Value)
			{
				Plugin.Logger.LogDebug((object)("[未翻訳] \"" + text.Replace("\n", "\\n") + "\""));
			}
			return text;
		}

		private static bool TryTranslateCore(string text, out string result)
		{
			if (TryTranslateInlineText(text, out result))
			{
				return true;
			}
			if (TryTranslateMultilineBlock(text, out result))
			{
				return true;
			}
			result = text;
			return false;
		}

		private static bool TryTranslateInlineText(string text, out string result)
		{
			if (TryTranslateLookup(text, out result))
			{
				return true;
			}
			if (TryTranslateTemplate(text, out result))
			{
				return true;
			}
			if (TryTranslateLabeledPrefix(text, out result))
			{
				return true;
			}
			if (TryTranslateRichTextSuffix(text, out result))
			{
				return true;
			}
			if (TryTranslateActionSuffix(text, out result))
			{
				return true;
			}
			if (TryTranslatePhraseSequence(text, out result))
			{
				return true;
			}
			result = text;
			return false;
		}

		private static bool TryTranslateLookup(string text, out string result)
		{
			if (s_translations.TryGetValue(text, out result))
			{
				return true;
			}
			string text2 = NormalizeKey(text);
			if (text2 != text && s_translations.TryGetValue(text2, out result))
			{
				return true;
			}
			string key = text2.ToUpperInvariant();
			if (s_translationsUpper.TryGetValue(key, out result))
			{
				return true;
			}
			result = text;
			return false;
		}

		private static bool TryTranslateTemplate(string text, out string result)
		{
			string text2 = NormalizeKey(text);
			foreach (TranslationTemplate s_template in s_templates)
			{
				if (s_template.TryTranslate(text2, out result))
				{
					return true;
				}
			}
			result = text;
			return false;
		}

		private static bool TryTranslateLabeledPrefix(string text, out string result)
		{
			Match match = s_labeledPrefixRegex.Match(text);
			if (!match.Success)
			{
				result = text;
				return false;
			}
			string value = match.Groups["leadingTags"].Value;
			string value2 = match.Groups["label"].Value;
			string value3 = match.Groups["separator"].Value;
			string value4 = match.Groups["trailingTags"].Value;
			string value5 = match.Groups["body"].Value;
			if (!TryTranslateCore(value5, out string result2))
			{
				result = text;
				return false;
			}
			string result3;
			string text2 = (TryTranslateLookup(value2, out result3) ? result3 : value2);
			result = value + text2 + value3 + value4 + result2;
			return true;
		}

		private static bool TryTranslateActionSuffix(string text, out string result)
		{
			Match match = s_actionSuffixRegex.Match(text);
			if (!match.Success)
			{
				result = text;
				return false;
			}
			string value = match.Groups["body"].Value;
			string value2 = match.Groups["suffix"].Value;
			if (!TryTranslateLookup(value, out string result2) && !TryTranslateTemplate(value, out result2) && !TryTranslatePhraseSequence(value, out result2))
			{
				result = text;
				return false;
			}
			result = result2 + value2;
			return true;
		}

		private static bool TryTranslateRichTextSuffix(string text, out string result)
		{
			Match match = s_richTextSuffixRegex.Match(text);
			if (!match.Success)
			{
				result = text;
				return false;
			}
			string value = match.Groups["body"].Value;
			string value2 = match.Groups["suffix"].Value;
			if (!TryTranslateLookup(value, out string result2) && !TryTranslateTemplate(value, out result2) && !TryTranslatePhraseSequence(value, out result2))
			{
				result = text;
				return false;
			}
			result = result2 + value2;
			return true;
		}

		private static bool TryTranslateMultilineBlock(string text, out string result)
		{
			if (!text.Contains('\n'))
			{
				result = text;
				return false;
			}
			string[] array = text.Split('\n');
			bool flag = false;
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i];
				if (string.IsNullOrWhiteSpace(text2))
				{
					continue;
				}
				SplitOuterWhitespace(text2, out string leadingWhitespace, out string coreText, out string trailingWhitespace);
				if (coreText.Length != 0 && TryTranslateInlineText(coreText, out string result2))
				{
					string text3 = leadingWhitespace + result2 + trailingWhitespace;
					if (!(text3 == text2))
					{
						array[i] = text3;
						flag = true;
					}
				}
			}
			result = (flag ? string.Join("\n", array) : text);
			return flag;
		}

		private static bool TryTranslatePhraseSequence(string text, out string result)
		{
			result = text;
			if (!CanUsePhraseSegmentation(text))
			{
				return false;
			}
			SplitNumericPrefix(text, out string prefix, out string body);
			if (body.Length == 0)
			{
				return false;
			}
			string[] array = body.Split(' ', StringSplitOptions.RemoveEmptyEntries);
			if (array.Length < 2)
			{
				return false;
			}
			List<string> list = new List<string>();
			int matchedTokenCount;
			for (int i = 0; i < array.Length; i += matchedTokenCount)
			{
				if (!TryMatchLongestPhrase(array, i, out matchedTokenCount, out string translatedFragment))
				{
					return false;
				}
				list.Add(translatedFragment);
			}
			if (list.Count < 2)
			{
				return false;
			}
			result = prefix + string.Join("\n", list);
			return true;
		}

		private static bool TryMatchLongestPhrase(IReadOnlyList<string> tokens, int startIndex, out int matchedTokenCount, out string translatedFragment)
		{
			int val = tokens.Count - startIndex;
			int num = Math.Min(5, val);
			for (int num2 = num; num2 >= 1; num2--)
			{
				string text = string.Join(" ", tokens, startIndex, num2);
				if (TryTranslateLookup(text, out translatedFragment))
				{
					matchedTokenCount = num2;
					return true;
				}
			}
			matchedTokenCount = 0;
			translatedFragment = string.Empty;
			return false;
		}

		private static bool CanUsePhraseSegmentation(string text)
		{
			bool result = false;
			foreach (char c in text)
			{
				if (char.IsLetter(c))
				{
					result = true;
				}
				else if (!char.IsDigit(c) && !char.IsWhiteSpace(c) && c != '\'' && c != '-' && c != '(' && c != ')' && c != '&')
				{
					return false;
				}
			}
			return result;
		}

		private static void SplitNumericPrefix(string text, out string prefix, out string body)
		{
			int i;
			for (i = 0; i < text.Length && char.IsDigit(text[i]); i++)
			{
			}
			if (i == 0 || i >= text.Length || !char.IsWhiteSpace(text[i]))
			{
				prefix = string.Empty;
				body = text;
				return;
			}
			for (; i < text.Length && char.IsWhiteSpace(text[i]); i++)
			{
			}
			prefix = text.Substring(0, i);
			int num = i;
			body = text.Substring(num, text.Length - num);
		}

		private static void SplitOuterWhitespace(string text, out string leadingWhitespace, out string coreText, out string trailingWhitespace)
		{
			int i;
			for (i = 0; i < text.Length && char.IsWhiteSpace(text[i]); i++)
			{
			}
			int num = text.Length - 1;
			while (num >= i && char.IsWhiteSpace(text[num]))
			{
				num--;
			}
			leadingWhitespace = text.Substring(0, i);
			string text2;
			if (i > num)
			{
				text2 = string.Empty;
			}
			else
			{
				int num2 = i;
				text2 = text.Substring(num2, num + 1 - num2);
			}
			coreText = text2;
			string text3;
			if (num + 1 >= text.Length)
			{
				text3 = string.Empty;
			}
			else
			{
				int num2 = num + 1;
				text3 = text.Substring(num2, text.Length - num2);
			}
			trailingWhitespace = text3;
		}

		private static string NormalizeKey(string text)
		{
			return text.Replace("\r\n", "\n").Trim();
		}
	}
}