Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of REPOJapaneseTranslation v1.0.7
REPOJapaneseTranslation.dll
Decompiled 2 weeks agousing 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(); } } }