using Il2CppSystem.Runtime.Remoting.Messaging; using Il2CppTMPro; using System.Diagnostics; using System.Text.RegularExpressions; using UnityEngine; namespace PvZ_Fusion_Translator.AssetStore { public static class StringStore { internal static Dictionary stringsDict = []; public static Dictionary translationStringRegex = new(); public static Dictionary translationString = new(); public static Dictionary customLevelStringRegex = new(); public static Dictionary customLevelString = new(); public static Dictionary izLevelTipDictionary = new(); public static Dictionary izTipCollectionString = new(); public static Dictionary fsLevelTipDictionary = new(); public static Dictionary fsTipCollectionString = new(); public static Dictionary> patchesStore = new() { { "Difficulty", new Dictionary { { "English" , "Difficulty:" }, { "French" , "Difficulté:" }, { "Italian" , "Difficoltà:" }, { "German" , "Schwierigkeit:" }, { "Spanish" , "Dificultad:" }, { "Portuguese" , "Dificuldade:" }, { "Filipino" , "Ang Hirap:" }, { "Vietnamese" , "Độ Khó:" }, { "Indonesian" , "Kesulitan:" }, { "Russian" , "Сложность:" }, { "Japanese" , "困難:" }, { "Korean", "난이도:" }, { "Javanese" , "Kesulitan:" }, { "Polish", "Trudność:"}, { "Hungarian", "Nehézség:"}, { "Arabic", "صعوبة:"}, // { "Ukrainian" , "Складність:" }, // { "Slovak" , "Náročnosť:" }, } }, { "Zombies", new Dictionary { { "English" , "Zombies:" }, { "French" , "Les Zombies:" }, { "Italian" , "Zombi:" }, { "German" , "Zombies:" }, { "Spanish" , "Zombis:" }, { "Portuguese" , "Zumbis:" }, { "Filipino" , "Mga Zombie:" }, { "Vietnamese" , "Thây Ma:" }, { "Indonesian" , "Zombi:" }, { "Russian" , "Зомби:" }, { "Japanese" , "ゾンビ:" }, { "Korean", "좀비:" }, { "Javanese" , "Mayit:" }, { "Polish" , "Zombi:" }, { "Hungarian" , "Zombik:" }, { "Arabic" , "الزومبي:" }, // { "Ukrainian" , "Зомбі:" }, // { "Slovak" , "Zombíci:"} } } }; internal static void Init() { FileLoader.LoadStrings(); } internal static void Reload() { translationString.Clear(); translationStringRegex.Clear(); customLevelString.Clear(); customLevelStringRegex.Clear(); izLevelTipDictionary.Clear(); izTipCollectionString.Clear(); fsLevelTipDictionary.Clear(); fsTipCollectionString.Clear(); FileLoader.LoadStrings(); } public static TextMeshPro TranslateText(TextMeshPro originalTMP, bool isLog = false) { /*var frame = new StackTrace().GetFrame(1); var method = frame.GetMethod(); var className = method.DeclaringType.Name; Console.WriteLine($"TextMeshPro: {className}.{method.Name}");*/ #if MULTI_LANGUAGE string currentLanguage = Utils.Language.ToString(); TMP_FontAsset fontAsset = FontStore.LoadTMPFont(currentLanguage); #else TMP_FontAsset fontAsset = FontStore.LoadTMPFont(); #endif string translatedText = TranslateText(originalTMP.text); originalTMP.font = fontAsset; originalTMP.text = translatedText; return originalTMP; } public static TextMeshProUGUI TranslateText(TextMeshProUGUI originalTMP, bool isLog = false) { /*var frame = new StackTrace().GetFrame(1); var method = frame.GetMethod(); var className = method.DeclaringType.Name; Console.WriteLine($"tmp pro ugui: {className}.{method.Name}");*/ #if MULTI_LANGUAGE string currentLanguage = Utils.Language.ToString(); TMP_FontAsset fontAsset = FontStore.LoadTMPFont(currentLanguage); #else TMP_FontAsset fontAsset = FontStore.LoadTMPFont(); #endif string translatedText = TranslateText(originalTMP.text); originalTMP.font = fontAsset; originalTMP.text = translatedText; return originalTMP; } public static string TranslateText(string originalText, bool isLog = false) { /*var frame = new StackTrace().GetFrame(1); var method = frame.GetMethod(); var className = method.DeclaringType.Name; Console.WriteLine($"string: {className}.{method.Name}");*/ string text = DoTranslateText(originalText, false); string checkText; #if DEBUG Regex regex = new("\\p{IsCJKUnifiedIdeographs}+"); Match match = regex.Match(text); if(match.Success) { // Log.LogDebug("Untranslated String: " + match.Value); FileLoader.DumpUntranslatedStrings(text); } #endif checkText = text; return checkText; } public static string TranslateText(string originalText, string pattern, bool isLog = false) { if (TestRegex(originalText, pattern)) { if(translationStringRegex.ContainsKey(pattern)) { // Extract dynamic parts from the original text var regex = new Regex(pattern); var match = regex.Match(originalText); int groupCount = match.Groups.Count; if (isLog) Log.LogDebug("Text found in translationStringRegex {0}: {1}", match, groupCount); // List to hold formatted dynamic parts List dynamicParts = []; // Loop through each group and determine its translation for (int i = 1; i < groupCount; i++) { string groupValue = match.Groups[i].Value; string translatedValue = translationString.ContainsKey(groupValue) ? translationString[groupValue] : groupValue; dynamicParts.Add(translatedValue); } // Format the output string with dynamic parts return string.Format(translationStringRegex[pattern], [.. dynamicParts]); } else if(customLevelStringRegex.ContainsKey(pattern)) { // Extract dynamic parts from the original text var regex = new Regex(pattern); var match = regex.Match(originalText); int groupCount = match.Groups.Count; if (isLog) Log.LogDebug("Text found in customLevelStringRegex {0}: {1}", match, groupCount); // List to hold formatted dynamic parts List dynamicParts = []; // Loop through each group and determine its translation for (int i = 1; i < groupCount; i++) { string groupValue = match.Groups[i].Value; string translatedValue = customLevelString.ContainsKey(groupValue) ? customLevelString[groupValue] : groupValue; dynamicParts.Add(translatedValue); } // Format the output string with dynamic parts return string.Format(customLevelStringRegex[pattern], [.. dynamicParts]); } else { return originalText; } } else { if (isLog) Log.LogDebug($"Text '{originalText}' not translated"); FileLoader.DumpUntranslatedStrings(originalText); return originalText; } } public static string DoTranslateText(string originalText, bool isLog = false) { if (string.IsNullOrEmpty(originalText)) { if (isLog) Log.LogError("Text Null or Empty"); return string.Empty; } if (translationString.ContainsKey(originalText)) { if (isLog) Log.LogDebug($"Text '{originalText} found in translationString"); return translationString[originalText]; } if(izTipCollectionString.ContainsKey(originalText)) { if(isLog) { Log.LogDebug($"Text '{originalText}' found in izTipCollectionString"); } return izTipCollectionString[originalText]; } if(fsTipCollectionString.ContainsKey(originalText)) { if(isLog) { Log.LogDebug($"Text '{originalText}' found in fsTipCollectionString"); } return fsTipCollectionString[originalText]; } if(customLevelString.ContainsKey(originalText)) { if(isLog) { Log.LogDebug($"Text '{originalText}' found in customLevelString"); } return customLevelString[originalText]; } // Regex-based dynamic translation foreach (var entry in translationStringRegex) { if (TestRegex(originalText, entry.Key)) { // Extract dynamic parts from the original text var regex = new Regex(entry.Key); var match = regex.Match(originalText); int groupCount = match.Groups.Count; if (isLog) Log.LogDebug("Text found in translationStringRegex {0}: {1}", match, groupCount); // List to hold formatted dynamic parts List dynamicParts = []; // Loop through each group and determine its translation for (int i = 1; i < groupCount; i++) { string groupValue = match.Groups[i].Value; string translatedValue = translationString.ContainsKey(groupValue) ? translationString[groupValue] : groupValue; dynamicParts.Add(translatedValue); } // Format the output string with dynamic parts return string.Format(entry.Value, [.. dynamicParts]); } } foreach (var entry in customLevelStringRegex) { if (TestRegex(originalText, entry.Key)) { // Extract dynamic parts from the original text var regex = new Regex(entry.Key); var match = regex.Match(originalText); int groupCount = match.Groups.Count; if (isLog) Log.LogDebug("Text found in customLevelRegex {0}: {1}", match, groupCount); // List to hold formatted dynamic parts List dynamicParts = []; // Loop through each group and determine its translation for (int i = 1; i < groupCount; i++) { string groupValue = match.Groups[i].Value; string translatedValue = customLevelString.ContainsKey(groupValue) ? customLevelString[groupValue] : groupValue; dynamicParts.Add(translatedValue); } // Format the output string with dynamic parts return string.Format(entry.Value, [.. dynamicParts]); } } if (isLog) Log.LogDebug($"Text '{originalText}' not translated"); if(Utils.CheckForUntranslatedText(originalText)) { FileLoader.DumpUntranslatedStrings(originalText); } return originalText; } public static string TranslateColorText(string originalText, bool isLog = false) { string text = TranslateColorSegments(originalText); string checkText; #if DEBUG Regex regex = new("\\p{IsCJKUnifiedIdeographs}+"); Match match = regex.Match(text); if (match.Success) { // Log.LogDebug("Untranslated String: " + match.Value); FileLoader.DumpUntranslatedStrings(text); } #endif checkText = text; return checkText; } public static string TranslateColorSegments(string input) { string result = ""; string pattern = @"]+>[\s\S]*?(?:<\/color>|$)|(?:<(?!color=)|[^<])+"; string smallPattern = @"(]+>)(.*?)()"; MatchCollection matches = Regex.Matches(input, pattern); for (int i = 0; i < matches.Count; i++) { Match match = matches[i]; if (Regex.Match(match.Value, smallPattern, RegexOptions.Singleline).Success) { result += TranslateColorSegment(match.Value); } else { result += DoTranslateText(match.Value); } } return result; } public static string TranslateColorSegment(string input) { string result = input; string pattern = @"(]+>)(.*?)()"; Match match = Regex.Match(input, pattern, RegexOptions.Singleline); string translatedText = DoTranslateText(match.Groups[2].Value); if(Utils.CheckForUntranslatedText(translatedText)) { FileLoader.DumpUntranslatedStrings(translatedText); } result = match.Groups[1].Value + translatedText + match.Groups[3].Value; return result; } public static bool TestRegex(string originalText, string pattern, bool singleLine = false) { RegexOptions options = (singleLine) ? RegexOptions.None : RegexOptions.Singleline; return Regex.IsMatch(originalText, pattern, options); } private static TextAlignmentOptions TextAnchorToTMPAlignment(TextAnchor anchor) { return anchor switch { TextAnchor.UpperLeft => TextAlignmentOptions.TopLeft, TextAnchor.UpperCenter => TextAlignmentOptions.Top, TextAnchor.UpperRight => TextAlignmentOptions.TopRight, TextAnchor.MiddleLeft => TextAlignmentOptions.MidlineLeft, TextAnchor.MiddleCenter => TextAlignmentOptions.Center, TextAnchor.MiddleRight => TextAlignmentOptions.MidlineRight, TextAnchor.LowerLeft => TextAlignmentOptions.BottomLeft, TextAnchor.LowerCenter => TextAlignmentOptions.Bottom, TextAnchor.LowerRight => TextAlignmentOptions.BottomRight, _ => TextAlignmentOptions.Center }; } public static TextMeshProUGUI ConvertToTextMeshPro(UnityEngine.UI.Text oldText, TMP_FontAsset fontAsset, bool isLog = false) { if (oldText == null) { Log.LogError("UnityEngine.UI.Text component is null."); return null; } if (fontAsset == null) { Log.LogError("TMP_FontAsset is null. Conversion cannot proceed."); return null; } // Log.LogInfo($"Converting UnityEngine.UI.Text: {oldText.name}"); GameObject textObject = oldText.gameObject; // Preserve text content and settings string originalText = oldText.text; originalText = TranslateText(originalText, isLog); TextAnchor alignment = oldText.alignment; Color color = oldText.color; // Remove old Text component UnityEngine.Object.DestroyImmediate(oldText); // Add TextMeshProUGUI component TextMeshProUGUI newTMP = textObject.AddComponent(); if (newTMP == null) { Log.LogError($"Failed to add TextMeshProUGUI to object: {textObject.name}"); return null; } // Configure TextMeshProUGUI newTMP.text = originalText; newTMP.color = color; newTMP.alignment = TextAnchorToTMPAlignment(alignment); newTMP.font = fontAsset; // Log.LogInfo($"Successfully converted {textObject.name} to TextMeshProUGUI."); return newTMP; } public static void TranslateTextTransform(Transform baseTransform, bool isAutoTextContainer = false, bool isLog = false) { if (!baseTransform) return; #if MULTI_LANGUAGE string currentLanguage = Utils.Language.ToString(); TMP_FontAsset fontAsset = FontStore.LoadTMPFont(currentLanguage); #else TMP_FontAsset fontAsset = FontStore.LoadTMPFont(); #endif if (fontAsset == null) { #if MULTI_LANGUAGE Log.LogError($"Font for language '{currentLanguage}' not found. Translation aborted."); #else Log.LogError("Font not found. Translation aborted."); #endif return; } // Local function for processing transforms void ProcessTextTransform(Transform textTransform) { if (!textTransform) return; // Handle TextMeshPro components TextMeshPro textTMP = textTransform.GetComponent(); if (textTMP) { textTMP.text = TranslateText(textTMP.text, isLog); textTMP.autoSizeTextContainer = isAutoTextContainer; textTMP.font = fontAsset; } TextMeshProUGUI textTMPUGUI = textTransform.GetComponent(); if (textTMPUGUI) { textTMPUGUI.text = TranslateText(textTMPUGUI.text, isLog); textTMPUGUI.enableAutoSizing = isAutoTextContainer; textTMPUGUI.font = fontAsset; } // Handle UnityEngine.UI.Text components UnityEngine.UI.Text textUI = textTransform.GetComponent(); if (textUI) { ConvertToTextMeshPro(textUI, fontAsset, isLog); } } // Process specific text paths ProcessTextTransform(baseTransform.Find("text")); ProcessTextTransform(baseTransform.Find("text1")); // ProcessTextTransform(baseTransform.Find("text2")); Transform shadowTransform = baseTransform.Find("text/shadow"); if (shadowTransform) ProcessTextTransform(shadowTransform); shadowTransform = baseTransform.Find("text1/shadow"); if (shadowTransform) ProcessTextTransform(shadowTransform); #if TESTING Transform childTransform = baseTransform.GetChild(0); if (childTransform) ProcessTextTransform(childTransform); #endif } public static void TranslateTextUI(TextMeshPro textTMP, bool? isAutoTextContainer = null, bool isLog = false) { string currentLanguage = Utils.Language.ToString(); if (textTMP) { textTMP.text = TranslateText(textTMP.text, isLog); if (isAutoTextContainer!=null) textTMP.autoSizeTextContainer = isAutoTextContainer.Value; textTMP.font = FontStore.LoadTMPFont(currentLanguage); } } public static void TranslateTextUI(TextMeshProUGUI textTMP, bool? isAutoTextContainer = null, bool isLog = false) { string currentLanguage = Utils.Language.ToString(); if (textTMP) { string origText = textTMP.text; textTMP.text = TranslateText(origText, isLog) == "" ? origText : TranslateText(origText, isLog); if (isAutoTextContainer != null) textTMP.autoSizeTextContainer = isAutoTextContainer.Value; if (origText != textTMP.text) textTMP.font = FontStore.LoadTMPFont(currentLanguage); } } public static void TranslateTextUI(TMP_InputField TextInput) { string currentLanguage = Utils.Language.ToString(); if (TextInput) { string oriText = TextInput.m_Text; TextInput.m_Text = TranslateText(oriText); TextInput.m_OriginalText = TranslateText(oriText); TextInput.fontAsset = FontStore.LoadTMPFont(currentLanguage); } } public static void LogAll() { Log.LogInfo("Logging all StringStore entries."); Log.LogInfo("Regex Entries: {0}", translationStringRegex.Count); Log.LogInfo("String Entries: {0}", translationString.Count); } } }