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 GermanTranslation v1.0.0
GermanTranslation.dll
Decompiled 11 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; 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 Newtonsoft.Json.Linq; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 GermanTranslation { internal static class CodeStrings { private static readonly Dictionary<string, Dictionary<string, string>> map = new Dictionary<string, Dictionary<string, string>>(); private static readonly Dictionary<MethodBase, Dictionary<string, string>> byMethod = new Dictionary<MethodBase, Dictionary<string, string>>(); public static int Patched; public static void Load(string path) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 if (!File.Exists(path)) { Plugin.Log.LogWarning((object)("Missing " + path)); return; } foreach (KeyValuePair<string, JToken> item in JObject.Parse(File.ReadAllText(path, Encoding.UTF8))) { if (item.Key.StartsWith("_")) { continue; } JToken value = item.Value; JObject val = (JObject)(object)((value is JObject) ? value : null); if (val == null) { continue; } if (!map.TryGetValue(item.Key, out var value2)) { value2 = (map[item.Key] = new Dictionary<string, string>()); } foreach (KeyValuePair<string, JToken> item2 in val) { if ((int)item2.Value.Type == 8) { value2[item2.Key] = (string)item2.Value; } } } } private static Type FindType(string typeName) { int num = typeName.IndexOf("d__*", StringComparison.Ordinal); if (num < 0) { return AccessTools.TypeByName(typeName); } int num2 = typeName.IndexOf('/'); Type type = AccessTools.TypeByName(typeName.Substring(0, num2).Replace('/', '+')); string prefix = typeName.Substring(num2 + 1, num + 3 - num2 - 1); return type?.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((Type t) => t.Name.StartsWith(prefix, StringComparison.Ordinal)); } public static void Patch(Harmony harmony) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(CodeStrings), "Transpiler", (Type[])null, (Type[])null)); foreach (KeyValuePair<string, Dictionary<string, string>> item in map) { int num = item.Key.LastIndexOf("::", StringComparison.Ordinal); if (num < 0) { continue; } string text = item.Key.Substring(0, num); string methodName = item.Key.Substring(num + 2); Type type = FindType((text.IndexOf("d__*", StringComparison.Ordinal) >= 0) ? text : text.Replace('/', '+')); if (type == null) { Plugin.Log.LogWarning((object)("Type not found: " + item.Key)); continue; } IEnumerable<MethodBase> enumerable = ((methodName == ".ctor") ? AccessTools.GetDeclaredConstructors(type, (bool?)null) : ((!(methodName == ".cctor")) ? (from m in AccessTools.GetDeclaredMethods(type) where m.Name == methodName && !m.IsAbstract select m).Cast<MethodBase>() : AccessTools.GetDeclaredConstructors(type, (bool?)true))); foreach (MethodBase item2 in enumerable) { byMethod[item2] = item.Value; try { harmony.Patch(item2, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); Patched++; } catch (Exception ex) { Plugin.Log.LogError((object)("Code patch " + item.Key + ": " + ex.Message)); } } } } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> code, MethodBase original) { byMethod.TryGetValue(original, out var strings); foreach (CodeInstruction item in code) { if (strings != null && item.opcode == OpCodes.Ldstr && item.operand is string key && strings.TryGetValue(key, out var value)) { item.operand = value; } yield return item; } } } internal static class Commands { private static readonly List<KeyValuePair<string[], string>> phrases = new List<KeyValuePair<string[], string>>(); private static List<string> englishWords; private static bool loggedValidation; public static int Count => phrases.Count; public static void Load(string path) { if (!File.Exists(path)) { Plugin.Log.LogWarning((object)("Missing " + path)); return; } Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path, Encoding.UTF8)); if (dictionary == null) { return; } foreach (KeyValuePair<string, string> item in dictionary) { if (!item.Key.StartsWith("_")) { Add(item.Key, item.Value); } } Sort(); } internal static void Add(string german, string english) { string[] array = Normalize(german).Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { phrases.Add(new KeyValuePair<string[], string>(array, Normalize(english))); } } internal static void Sort() { phrases.Sort((KeyValuePair<string[], string> a, KeyValuePair<string[], string> b) => b.Key.Length.CompareTo(a.Key.Length)); } private static string Normalize(string s) { StringBuilder stringBuilder = new StringBuilder(); foreach (char c in s) { if (!char.IsPunctuation(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString().ToLowerInvariant(); } public static string Translate(string input) { if (phrases.Count == 0 || string.IsNullOrEmpty(input)) { return input; } string[] array = input.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); List<string> list = new List<string>(); int num = 0; while (num < array.Length) { bool flag = false; foreach (KeyValuePair<string[], string> phrase in phrases) { string[] key = phrase.Key; if (num + key.Length <= array.Length) { bool flag2 = true; for (int i = 0; i < key.Length && flag2; i++) { flag2 = string.Equals(array[num + i], key[i], StringComparison.OrdinalIgnoreCase); } if (flag2) { list.Add(phrase.Value); num += key.Length; flag = true; break; } } } if (!flag) { list.Add(ByPrefix(array[num]) ?? array[num]); num++; } } string text = string.Join(" ", list); if (text != input) { Plugin.Log.LogInfo((object)("terminal: '" + input + "' -> '" + text + "'")); } return text; } private static string ByPrefix(string word) { if (word.Length < 3 || StartsEnglishWord(word)) { return null; } string text = null; foreach (KeyValuePair<string[], string> phrase in phrases) { if (phrase.Key.Length == 1 && phrase.Key[0].StartsWith(word, StringComparison.OrdinalIgnoreCase)) { if (text != null && text != phrase.Value) { return null; } text = phrase.Value; } } return text; } private static bool StartsEnglishWord(string word) { if (englishWords == null) { TerminalKeyword[] array = Object.FindObjectOfType<Terminal>()?.terminalNodes?.allKeywords; if (array == null) { return false; } englishWords = (from k in array where (Object)(object)k != (Object)null && k.word != null select k.word.ToLowerInvariant()).ToList(); } return englishWords.Any((string w) => w.StartsWith(word, StringComparison.OrdinalIgnoreCase)); } public static void AllowAnyInput() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) Terminal[] array = Resources.FindObjectsOfTypeAll<Terminal>(); for (int i = 0; i < array.Length; i++) { TMP_InputField screenText = array[i].screenText; if (!((Object)(object)screenText == (Object)null) && (int)screenText.characterValidation != 0) { if (!loggedValidation) { Plugin.Log.LogInfo((object)$"terminal: input filter {screenText.characterValidation} removed"); loggedValidation = true; } screenText.characterValidation = (CharacterValidation)0; } } } } internal static class Fonts { private const string Needed = "äöüÄÖÜ߀"; private static readonly HashSet<int> done = new HashSet<int>(); private static readonly Dictionary<string, TMP_FontAsset> fallbacks = new Dictionary<string, TMP_FontAsset>(); public static void Apply() { Font val = ((IEnumerable<Font>)Resources.FindObjectsOfTypeAll<Font>()).FirstOrDefault((Func<Font, bool>)((Font f) => ((Object)f).name == "3270-Regular")); List<string> list = new List<string>(); TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); foreach (TMP_FontAsset asset in array) { if (fallbacks.ContainsValue(asset) || !done.Add(((Object)asset).GetInstanceID())) { continue; } string text = new string("äöüÄÖÜ߀".Where((char c) => !asset.HasCharacter(c, false, false)).ToArray()); if (text.Length == 0) { continue; } if ((Object)(object)val == (Object)null) { list.Add(((Object)asset).name + " lacks '" + text + "' (no source font)"); continue; } TMP_FontAsset val2 = FallbackFor(val, asset); if (!((Object)(object)val2 == (Object)null)) { if (asset.fallbackFontAssetTable == null) { asset.fallbackFontAssetTable = new List<TMP_FontAsset>(); } if (!asset.fallbackFontAssetTable.Contains(val2)) { asset.fallbackFontAssetTable.Insert(0, val2); } list.Add(((Object)asset).name + " lacks '" + text + "' -> " + ((Object)val2).name); } } if (list.Count > 0) { Plugin.Log.LogInfo((object)("fonts: " + string.Join("; ", list))); } } private static TMP_FontAsset FallbackFor(Font source, TMP_FontAsset like) { //IL_0002: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Invalid comparison between Unknown and I4 //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) FaceInfo faceInfo = like.faceInfo; int num = Mathf.Max(8, Mathf.RoundToInt((float)((FaceInfo)(ref faceInfo)).pointSize)); int num2 = ((like.atlasPadding > 0) ? like.atlasPadding : 9); GlyphRenderMode atlasRenderMode = like.atlasRenderMode; object[] obj = new object[5] { num, num2, atlasRenderMode, ((Object)(object)((TMP_Asset)like).material != (Object)null) ? ((Object)((TMP_Asset)like).material.shader).name : "-", null }; Material material = ((TMP_Asset)like).material; obj[4] = ((material != null) ? new int?(((Object)material).GetInstanceID()) : ((int?)null)); string key = string.Format("{0}/{1}/{2}/{3}/{4}", obj); if (fallbacks.TryGetValue(key, out var value) && (Object)(object)value != (Object)null) { return value; } TMP_FontAsset val = TMP_FontAsset.CreateFontAsset(source, num, num2, (GlyphRenderMode)4165, 1024, 1024, (AtlasPopulationMode)1, true); if ((Object)(object)val == (Object)null) { return null; } ((Object)val).name = "GermanTranslation " + ((Object)like).name; if ((int)atlasRenderMode != 4165) { AccessTools.Field(typeof(TMP_FontAsset), "m_AtlasRenderMode")?.SetValue(val, atlasRenderMode); } if ((Object)(object)((TMP_Asset)like).material != (Object)null) { Material val2 = new Material(((TMP_Asset)like).material) { name = ((Object)val).name + " Material" }; val2.SetTexture(ShaderUtilities.ID_MainTex, (Texture)(object)val.atlasTexture); ((TMP_Asset)val).material = val2; } Object.DontDestroyOnLoad((Object)(object)val); fallbacks[key] = val; return val; } } [HarmonyPatch] internal static class TmpTextHook { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.PropertySetter(typeof(TMP_Text), "text"); Type[][] array = new Type[2][] { new Type[1] { typeof(string) }, new Type[2] { typeof(string), typeof(bool) } }; foreach (Type[] array2 in array) { MethodInfo methodInfo = AccessTools.Method(typeof(TMP_Text), "SetText", array2, (Type[])null); if (methodInfo != null) { yield return methodInfo; } } } private static void Prefix(ref string __0) { __0 = Translator.Translate(__0); } } [HarmonyPatch] internal static class UiTextHook { private static MethodBase TargetMethod() { return AccessTools.PropertySetter(typeof(Text), "text"); } private static void Prefix(ref string __0) { __0 = Translator.Translate(__0); } } [HarmonyPatch(typeof(Terminal), "TextPostProcess")] internal static class TerminalOutputHook { private static void Postfix(ref string __result) { __result = Words.Apply(StoreNames.Apply(__result)); } } [HarmonyPatch(typeof(Terminal), "RemovePunctuation")] internal static class TerminalInputHook { private static void Postfix(ref string __result) { if (Plugin.GermanCommands.Value) { __result = Commands.Translate(__result); } } } [HarmonyPatch(typeof(HUDManager), "ReadDialogue")] internal static class DialogueHook { private static void Prefix(DialogueSegment[] __0) { if (__0 == null) { return; } foreach (DialogueSegment val in __0) { if (val != null) { val.speakerText = Translator.Translate(val.speakerText); val.bodyText = Translator.Translate(val.bodyText); } } } } [HarmonyPatch(typeof(HUDManager), "GetClockTimeFormatted")] internal static class ClockHook { private static void Postfix(ref string __result, float __0, float __1) { int num = (int)(__0 * (60f * __1)) + 360; int num2 = num / 60; __result = ((num2 >= 24) ? "00:00" : $"{num2:00}:{num % 60:00}"); } } [HarmonyPatch(typeof(StartOfRound), "SetMapScreenInfoToCurrentLevel")] internal static class MapScreenHook { private static void Prefix(StartOfRound __instance) { SelectableLevel currentLevel = __instance.currentLevel; if ((Object)(object)currentLevel != (Object)null) { currentLevel.LevelDescription = Translator.Translate(currentLevel.LevelDescription); } } } [HarmonyPatch(typeof(HUDManager), "SetClock")] internal static class HudClockHook { private static void Postfix(HUDManager __instance, float __0, float __1) { if (!((Object)(object)__instance.clockNumber == (Object)null)) { int num = (int)(__0 * (60f * __1)) + 360; int num2 = num / 60; ((TMP_Text)__instance.clockNumber).text = ((num2 >= 24) ? "00:00" : $"{num2:00}:{num % 60:00}"); } } } [BepInPlugin("nergy.germantranslation", "GermanTranslation", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string Guid = "nergy.germantranslation"; public const string Name = "GermanTranslation"; public const string Version = "1.0.0"; internal static ManualLogSource Log; internal static string Dir; internal static ConfigEntry<bool> LogUntranslated; internal static ConfigEntry<bool> GermanCommands; internal static ConfigEntry<bool> UseEuro; private void Awake() { //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Dir = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "translations"); LogUntranslated = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogUntranslated", false, "Writes every English text without a German translation to BepInEx/GermanTranslation_untranslated.txt (for finding gaps)."); UseEuro = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "UseEuro", true, "Show amounts of money with the euro sign (130€) instead of the dollar sign ($130)."); GermanCommands = ((BaseUnityPlugin)this).Config.Bind<bool>("Terminal", "GermanCommands", true, "Allow German terminal commands (e.g. 'hilfe', 'monde'). English commands always keep working."); LoadFile("texts.json", Translator.Load); LoadFile("extra.json", Translator.Load); LoadFile("code.json", CodeStrings.Load); LoadFile("code_extra.json", CodeStrings.Load); LoadFile("commands.json", Commands.Load); LoadFile("items.json", StoreNames.Load); LoadFile("names.json", StoreNames.Load); Harmony val = new Harmony("nergy.germantranslation"); Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly()); foreach (Type type in typesFromAssembly) { if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0) { try { val.CreateClassProcessor(type).Patch(); } catch (Exception ex) { Log.LogError((object)("Patch " + type.Name + " failed: " + (ex.InnerException?.Message ?? ex.Message))); } } } CodeStrings.Patch(val); SweepRunner.Create(); SceneManager.sceneLoaded += delegate { SweepRunner.Schedule(); }; SweepRunner.Schedule(); Log.LogInfo((object)string.Format("{0} {1}: {2} texts, {3} patched methods, {4} command aliases, {5} item names", "GermanTranslation", "1.0.0", Translator.Count, CodeStrings.Patched, Commands.Count, StoreNames.Count)); } private static void LoadFile(string name, Action<string> load) { try { load(Path.Combine(Dir, name)); } catch (Exception ex) { Log.LogError((object)("Could not load " + name + ": " + ex.Message)); } } internal static void RunSweep() { Safe("fonts", Fonts.Apply); Safe("terminal nodes", Sweeper.TerminalNodes); Safe("data", Sweeper.Data); Safe("scene text", Sweeper.SceneText); Safe("terminal input", Commands.AllowAnyInput); } private static void Safe(string what, Action a) { try { a(); } catch (Exception arg) { Log.LogError((object)$"{what}: {arg}"); } } } internal static class StoreNames { private static readonly List<KeyValuePair<Regex, string>> rules = new List<KeyValuePair<Regex, string>>(); public static int Count => rules.Count; public static void Load(string path) { if (!File.Exists(path)) { Plugin.Log.LogWarning((object)("Missing " + path)); return; } Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path, Encoding.UTF8)); if (dictionary == null) { return; } foreach (KeyValuePair<string, string> item in dictionary) { if (!item.Key.StartsWith("_")) { Regex key = new Regex("(?m)^(\\*\\s*)?" + Regex.Escape(item.Key) + "[ \\t]+//[ \\t]+"); rules.Add(new KeyValuePair<Regex, string>(key, "$1" + item.Key + " (" + item.Value + ") // ")); Commands.Add(item.Value, item.Key); Translator.AddEntry(item.Key, item.Value); } } Commands.Sort(); } public static string Apply(string s) { if (string.IsNullOrEmpty(s) || s.IndexOf("//", StringComparison.Ordinal) < 0) { return s; } foreach (KeyValuePair<Regex, string> rule in rules) { s = rule.Key.Replace(s, rule.Value); } return s; } } internal static class Sweeper { private static readonly (Type type, string[] fields)[] DataFields = new(Type, string[])[5] { (typeof(SelectableLevel), new string[1] { "LevelDescription" }), (typeof(InteractTrigger), new string[3] { "hoverTip", "disabledHoverTip", "holdTip" }), (typeof(Item), new string[2] { "toolTips", "itemName" }), (typeof(ScanNodeProperties), new string[2] { "headerText", "subText" }), (typeof(StunGrenadeItem), new string[1] { "throwString" }) }; public static void TerminalNodes() { TerminalNode[] array = Resources.FindObjectsOfTypeAll<TerminalNode>(); foreach (TerminalNode val in array) { string text = Translator.Translate(val.displayText); if ((object)text != val.displayText) { val.displayText = text; } if (val.creatureName != null) { string text2 = Translator.Translate(val.creatureName); if ((object)text2 != val.creatureName) { val.creatureName = text2; } } } } public static void Data() { (Type, string[])[] dataFields = DataFields; for (int i = 0; i < dataFields.Length; i++) { (Type, string[]) tuple = dataFields[i]; Type item = tuple.Item1; string[] item2 = tuple.Item2; List<FieldInfo> list = new List<FieldInfo>(); string[] array = item2; foreach (string text in array) { FieldInfo fieldInfo = AccessTools.Field(item, text); if (fieldInfo != null) { list.Add(fieldInfo); } } Object[] array2 = Resources.FindObjectsOfTypeAll(item); foreach (Object obj in array2) { foreach (FieldInfo item3 in list) { TranslateField(obj, item3); } } } UnlockablesList[] array3 = Resources.FindObjectsOfTypeAll<UnlockablesList>(); foreach (UnlockablesList val in array3) { if (val.unlockables == null) { continue; } foreach (UnlockableItem unlockable in val.unlockables) { string text2 = Translator.Translate(unlockable.unlockableName); if ((object)text2 != unlockable.unlockableName) { unlockable.unlockableName = text2; } } } TimeOfDay[] array4 = Resources.FindObjectsOfTypeAll<TimeOfDay>(); foreach (TimeOfDay obj2 in array4) { string[] array = new string[2] { "shipLeavingSoonDialogue", "shipLeavingEarlyDialogue" }; foreach (string text3 in array) { if (AccessTools.Field(typeof(TimeOfDay), text3)?.GetValue(obj2) is DialogueSegment[] array5) { DialogueSegment[] array6 = array5; foreach (DialogueSegment obj3 in array6) { obj3.bodyText = Translator.Translate(obj3.bodyText); obj3.speakerText = Translator.Translate(obj3.speakerText); } } } } } private static void TranslateField(object obj, FieldInfo field) { if (field.FieldType == typeof(string)) { string obj2 = (string)field.GetValue(obj); string text = Translator.Translate(obj2); if ((object)obj2 != text) { field.SetValue(obj, text); } } else if (field.FieldType == typeof(string[]) && field.GetValue(obj) is string[] array) { for (int i = 0; i < array.Length; i++) { array[i] = Translator.Translate(array[i]); } } } public static void SceneText() { TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>(); foreach (TMP_Text val in array) { string text = Translator.Translate(val.text); if ((object)text != val.text) { val.text = text; } } Text[] array2 = Resources.FindObjectsOfTypeAll<Text>(); foreach (Text val2 in array2) { string text2 = Translator.Translate(val2.text); if ((object)text2 != val2.text) { val2.text = text2; } } } } internal class SweepRunner : MonoBehaviour { private static SweepRunner instance; private readonly List<float> due = new List<float>(); public static void Create() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown GameObject val = new GameObject("GermanTranslation_Sweeper") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); instance = val.AddComponent<SweepRunner>(); } public static void Schedule() { if (!((Object)(object)instance == (Object)null)) { float unscaledTime = Time.unscaledTime; instance.due.Clear(); instance.due.AddRange(new float[4] { unscaledTime, unscaledTime + 1f, unscaledTime + 3f, unscaledTime + 10f }); } } private void Update() { if (due.Count != 0 && !(Time.unscaledTime < due[0])) { due.RemoveAt(0); Plugin.RunSweep(); } } } internal static class Translator { private static readonly Dictionary<string, string> exact = new Dictionary<string, string>(); private static readonly HashSet<string> germanValues = new HashSet<string>(); private static readonly HashSet<string> reported = new HashSet<string>(); private static readonly Regex Dollars = new Regex("\\$(\\d+(?:[.,]\\d+)?)"); public static int Count => exact.Count; public static void Load(string path) { if (!File.Exists(path)) { Plugin.Log.LogWarning((object)("Missing " + path)); return; } Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(path, Encoding.UTF8)); if (dictionary == null) { return; } foreach (KeyValuePair<string, string> item in dictionary) { if (!item.Key.StartsWith("_")) { exact[item.Key] = item.Value; germanValues.Add(item.Value); } } } internal static void AddEntry(string english, string german) { if (!exact.ContainsKey(english)) { exact[english] = german; } germanValues.Add(german); } public static string Translate(string s) { string text = TranslateCore(s); if (text != null && Plugin.UseEuro != null && Plugin.UseEuro.Value && text.IndexOf('$') >= 0) { text = Dollars.Replace(text, "$1€"); } return text; } private static string TranslateCore(string s) { if (string.IsNullOrEmpty(s) || exact.Count == 0) { return s; } int i = 0; int num; for (num = s.Length; i < num && char.IsWhiteSpace(s[i]); i++) { } while (num > i && char.IsWhiteSpace(s[num - 1])) { num--; } if (i == num) { return s; } string text = ((i == 0 && num == s.Length) ? s : s.Substring(i, num - i)); if (exact.TryGetValue(text, out var value)) { if (text.Length != s.Length) { return s.Substring(0, i) + value + s.Substring(num); } return value; } string text2 = Words.Apply(s); if ((object)text2 != s) { return text2; } ReportMissing(text); return s; } private static void ReportMissing(string core) { if (!Plugin.LogUntranslated.Value || core.Length < 3 || germanValues.Contains(core) || core.StartsWith("FPS:")) { return; } bool flag = false; for (int i = 0; i < core.Length; i++) { if (char.IsLetter(core[i])) { flag = true; break; } } if (!flag || !reported.Add(core)) { return; } try { File.AppendAllText(Path.Combine(Paths.BepInExRootPath, "GermanTranslation_untranslated.txt"), JsonConvert.ToString(core) + Environment.NewLine, Encoding.UTF8); } catch { } } } internal static class Words { private static readonly Dictionary<string, string> Weather = new Dictionary<string, string> { ["Rainy"] = "Regen", ["Stormy"] = "Gewitter", ["Foggy"] = "Nebel", ["Flooded"] = "Überflutet", ["Eclipsed"] = "Sonnenfinsternis", ["DustClouds"] = "Staubwolken", ["None"] = "Klar" }; private static readonly Dictionary<string, string> CauseOfDeath = new Dictionary<string, string> { ["Unknown"] = "Unbekannt", ["Bludgeoning"] = "Stumpfe Gewalt", ["Gravity"] = "Schwerkraft", ["Blast"] = "Explosion", ["Strangulation"] = "Strangulation", ["Suffocation"] = "Ersticken", ["Mauling"] = "Zerfleischt", ["Gunshots"] = "Schussverletzungen", ["Crushing"] = "Zerquetscht", ["Drowning"] = "Ertrinken", ["Abandoned"] = "Zurückgelassen", ["Electrocution"] = "Stromschlag", ["Kicking"] = "Tritte", ["Burning"] = "Verbrennen", ["Stabbing"] = "Erstochen", ["Fan"] = "Ventilator", ["Inertia"] = "Trägheit", ["Snipping"] = "Abgeschnitten", ["Scratching"] = "Zerkratzt" }; private static readonly Dictionary<string, string> Days = new Dictionary<string, string> { ["Monday"] = "Montag", ["Tuesday"] = "Dienstag", ["Wednesday"] = "Mittwoch", ["Thursday"] = "Donnerstag", ["Friday"] = "Freitag", ["Saturday"] = "Samstag", ["Sunday"] = "Sonntag" }; private static readonly Regex Greeting = new Regex("(Schönen )(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\\b"); private static readonly Regex InList = new Regex("\\((Rainy|Stormy|Foggy|Flooded|Eclipsed|DustClouds)\\)"); private static readonly Regex OnScreen = new Regex("(Wetter: )(Rainy|Stormy|Foggy|Flooded|Eclipsed|DustClouds|None)\\b"); private static readonly Regex Cause = new Regex("(Todesursache: )(Unknown|Bludgeoning|Gravity|Blast|Strangulation|Suffocation|Mauling|Gunshots|Crushing|Drowning|Abandoned|Electrocution|Kicking|Burning|Stabbing|Fan|Inertia|Snipping|Scratching)\\b"); private static readonly KeyValuePair<string, string>[] MicPrefixes = new KeyValuePair<string, string>[3] { new KeyValuePair<string, string>("MIC VOLUME: ", "MIKROFONLAUTSTÄRKE: "), new KeyValuePair<string, string>("NOISE GATE: ", "RAUSCHSPERRE: "), new KeyValuePair<string, string>("NOISE SUPPRESSION: ", "RAUSCHUNTERDRÜCKUNG: ") }; public static string Apply(string s) { if (string.IsNullOrEmpty(s)) { return s; } if (s.IndexOf('(') >= 0) { s = InList.Replace(s, (Match m) => "(" + Weather[m.Groups[1].Value] + ")"); } if (s.IndexOf("Wetter: ", StringComparison.Ordinal) >= 0) { s = OnScreen.Replace(s, (Match m) => m.Groups[1].Value + Weather[m.Groups[2].Value]); } s = RuntimeTexts(s); if (s.IndexOf("Schönen ", StringComparison.Ordinal) >= 0) { s = Greeting.Replace(s, (Match m) => m.Groups[1].Value + Days[m.Groups[2].Value]); } if (s.EndsWith(" lb", StringComparison.Ordinal)) { s = PoundsToKg(s); } if (s.StartsWith("Player #", StringComparison.Ordinal)) { s = "Spieler #" + s.Substring("Player #".Length); } if (s.StartsWith("MIC ", StringComparison.Ordinal) || s.StartsWith("NOISE ", StringComparison.Ordinal)) { s = MicLabels(s); } if (s.IndexOf("Todesursache: ", StringComparison.Ordinal) >= 0) { s = Cause.Replace(s, (Match m) => m.Groups[1].Value + CauseOfDeath[m.Groups[2].Value]); } return s; } private static string RuntimeTexts(string s) { if (s.StartsWith("VALUE: ", StringComparison.Ordinal)) { s = "WERT: " + s.Substring(7); } else if (s.StartsWith("Value: ", StringComparison.Ordinal)) { s = "Wert: " + s.Substring(7); } if (s.IndexOf("1 Tage", StringComparison.Ordinal) >= 0) { s = Regex.Replace(s, "(?<!\\d)1 Tage\\b", "1 Tag"); } if (s.IndexOf("Filter quality: ", StringComparison.Ordinal) >= 0) { s = s.Replace("HEALTH RISK!\nAir filter overwhelmed by particulates;\n\nFilter quality: ", "GESUNDHEITSRISIKO!\nLuftfilter durch Partikel überlastet;\n\nFilterqualität: "); } if (s.IndexOf("REACHING ", StringComparison.Ordinal) >= 0) { s = s.Replace("HIGH FEVER DETECTED!\nREACHING ", "HOHES FIEBER ERKANNT!\nSTEIGT AUF "); s = Regex.Replace(s, "(\\d+)°F", (Match m) => Math.Round((double)(int.Parse(m.Groups[1].Value) - 32) / 1.8) + "°C"); } return s; } private static string PoundsToKg(string s) { Match match = Regex.Match(s, "^(\\d+) lb$"); if (!match.Success) { return s; } return (int)Math.Round((double)int.Parse(match.Groups[1].Value) * 0.45359237) + " kg"; } private static string MicLabels(string s) { KeyValuePair<string, string>[] micPrefixes = MicPrefixes; for (int i = 0; i < micPrefixes.Length; i++) { KeyValuePair<string, string> keyValuePair = micPrefixes[i]; if (s.StartsWith(keyValuePair.Key, StringComparison.Ordinal)) { return keyValuePair.Value + s.Substring(keyValuePair.Key.Length); } } return s; } } }