From de46915c70caa9c21097d4aa8cc086b33d99a5dc Mon Sep 17 00:00:00 2001 From: ayuan9957 <107920784+ayuan9957@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:49:53 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=80=A7=E8=83=BD=E5=B9=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E7=BF=BB=E8=AF=91=E4=B8=A2=E5=A4=B1=20-=20?= =?UTF-8?q?=E9=A2=84=E6=9E=84=E5=BB=BAO(1)=E6=9F=A5=E6=89=BE=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E3=80=81=E7=BC=93=E5=AD=98=E7=BB=84=E4=BB=B6=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E5=92=8C=E5=AD=97=E4=BD=93=E7=8A=B6=E6=80=81=E3=80=81?= =?UTF-8?q?=E6=94=B9=E7=94=A8FindObjectsOfType=E3=80=81=E8=B0=83=E6=95=B4S?= =?UTF-8?q?etLanguage=E9=A1=BA=E5=BA=8F=E4=B8=BALanguageChanged=E5=85=88?= =?UTF-8?q?=E4=BA=8ERefreshAll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Localization/LocalizationManager.cs | 145 ++++++++++-------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/Assets/Scripts/Localization/LocalizationManager.cs b/Assets/Scripts/Localization/LocalizationManager.cs index 0c4118e..93a649b 100644 --- a/Assets/Scripts/Localization/LocalizationManager.cs +++ b/Assets/Scripts/Localization/LocalizationManager.cs @@ -18,14 +18,23 @@ public class LocalizationManager : MonoBehaviour public string source; public string localized; public object[] formatArgs; + public bool skipDropdown; // cached: this Text is a Dropdown caption/item + public bool skipInputField; // cached: this Text is inside an InputField + public bool fontApplied; // cached: font has been set at least once } private class DropdownState { public List sources = new List(); public List localized = new List(); + public bool fontApplied; } + // Pre-built lookup dictionaries for O(1) translation + private Dictionary reverseTable; + private Dictionary normalizedTable; + private Dictionary normalizedReverseTable; + public const string PreferenceKey = "UI.Language"; private const string FontResourcePath = "Localization/NotoSansCJKsc-Regular"; @@ -542,6 +551,37 @@ public class LocalizationManager : MonoBehaviour instance = this; language = Mathf.Clamp(PlayerPrefs.GetInt(PreferenceKey, 0), 0, 3); font = Resources.Load(FontResourcePath); + BuildLookupTables(); + } + + private void BuildLookupTables() + { + reverseTable = new Dictionary(); + normalizedTable = new Dictionary(); + normalizedReverseTable = new Dictionary(); + + foreach (var pair in table) + { + // Normalized key → values (for fuzzy Translate) + string nk = System.Text.RegularExpressions.Regex.Replace(pair.Key, @"\s+", " ").Trim(); + if (!normalizedTable.ContainsKey(nk)) + normalizedTable[nk] = pair.Value; + + if (pair.Value == null) continue; + for (int i = 0; i < pair.Value.Length; i++) + { + if (pair.Value[i] == null) continue; + + // Exact value → source key (for exact GetSourceText) + if (!reverseTable.ContainsKey(pair.Value[i])) + reverseTable[pair.Value[i]] = pair.Key; + + // Normalized value → source key (for fuzzy GetSourceText) + string nv = System.Text.RegularExpressions.Regex.Replace(pair.Value[i], @"\s+", " ").Trim(); + if (!normalizedReverseTable.ContainsKey(nv)) + normalizedReverseTable[nv] = pair.Key; + } + } } private void Start() @@ -553,13 +593,10 @@ public class LocalizationManager : MonoBehaviour private void LateUpdate() { - // Covers canvases instantiated after the current frame and avoids duplicate bars. - LocalizationLanguageTestBar.EnsureForActiveCanvases(); - - // Periodically refresh localization for dynamically loaded UI (e.g. student panels). - if (++refreshFrameCounter >= 30) + if (++refreshFrameCounter >= 60) { refreshFrameCounter = 0; + LocalizationLanguageTestBar.EnsureForActiveCanvases(); RefreshAll(); } } @@ -570,8 +607,10 @@ public class LocalizationManager : MonoBehaviour instance.language = Mathf.Clamp(value, 0, 3); PlayerPrefs.SetInt(PreferenceKey, instance.language); PlayerPrefs.Save(); - instance.RefreshAll(); + // Fire LanguageChanged FIRST so handlers rebuild content (table rows, operation messages, etc.), + // then RefreshAll catches all texts including the newly created ones. if (LanguageChanged != null) LanguageChanged(); + instance.RefreshAll(); } public static string Get(string source) @@ -700,10 +739,10 @@ public class LocalizationManager : MonoBehaviour public void RefreshAll() { - Text[] texts = Resources.FindObjectsOfTypeAll(); + Text[] texts = FindObjectsOfType(); for (int i = 0; i < texts.Length; i++) Localize(texts[i]); - Dropdown[] dropdowns = Resources.FindObjectsOfTypeAll(); + Dropdown[] dropdowns = FindObjectsOfType(); for (int i = 0; i < dropdowns.Length; i++) Localize(dropdowns[i]); } @@ -716,14 +755,11 @@ public class LocalizationManager : MonoBehaviour if (table.TryGetValue(source, out values) && values != null && language < values.Length && values[language] != null) return values[language]; - // Fuzzy: try normalized lookup + // Fuzzy: O(1) normalized lookup string normalized = System.Text.RegularExpressions.Regex.Replace(source, @"\s+", " ").Trim(); - foreach (KeyValuePair pair in table) - { - string nk = System.Text.RegularExpressions.Regex.Replace(pair.Key, @"\s+", " ").Trim(); - if (nk == normalized && pair.Value != null && language < pair.Value.Length && pair.Value[language] != null) - return pair.Value[language]; - } + string[] normValues; + if (normalizedTable.TryGetValue(normalized, out normValues) && normValues != null && language < normValues.Length && normValues[language] != null) + return normValues[language]; return source; } @@ -732,31 +768,13 @@ public class LocalizationManager : MonoBehaviour { if (string.IsNullOrEmpty(value) || table.ContainsKey(value)) return value; - // Exact match - foreach (KeyValuePair pair in table) - { - if (pair.Value == null || pair.Value.Length <= 1) continue; - for (int i = 1; i < pair.Value.Length; i++) - { - if (object.ReferenceEquals(pair.Value[i], value)) return pair.Key; - if (pair.Value[i] != null && pair.Value[i] == value) return pair.Key; - } - } + // O(1) exact reverse lookup + string key; + if (reverseTable.TryGetValue(value, out key)) return key; - // Fuzzy match: normalize whitespace (newlines, tabs, multiple spaces → single space) + // O(1) fuzzy reverse lookup string normalizedValue = System.Text.RegularExpressions.Regex.Replace(value, @"\s+", " ").Trim(); - foreach (KeyValuePair pair in table) - { - if (pair.Value == null || pair.Value.Length <= 1) continue; - string normalizedKey = System.Text.RegularExpressions.Regex.Replace(pair.Key, @"\s+", " ").Trim(); - if (normalizedKey == normalizedValue) return pair.Key; - for (int i = 1; i < pair.Value.Length; i++) - { - if (pair.Value[i] == null) continue; - string normalizedVal = System.Text.RegularExpressions.Regex.Replace(pair.Value[i], @"\s+", " ").Trim(); - if (normalizedVal == normalizedValue) return pair.Key; - } - } + if (normalizedReverseTable.TryGetValue(normalizedValue, out key)) return key; return value; } @@ -765,34 +783,40 @@ public class LocalizationManager : MonoBehaviour { if (text == null || !text.gameObject.scene.IsValid()) return; - // Dropdown caption and item texts are handled by Localize(Dropdown). - Dropdown dropdown = text.GetComponentInParent(); - if (dropdown != null && (dropdown.captionText == text || dropdown.itemText == text)) - return; + int id = text.GetInstanceID(); + TextState state; + if (!textStates.TryGetValue(id, out state)) + { + // First encounter: do expensive checks once and cache + Dropdown dropdown = text.GetComponentInParent(); + InputField input = text.GetComponentInParent(); + state = new TextState + { + source = text.text, + localized = text.text, + skipDropdown = dropdown != null && (dropdown.captionText == text || dropdown.itemText == text), + skipInputField = input != null + }; + textStates.Add(id, state); + } + else if (text.text != state.localized) + { + // Text was externally changed; update source but keep cached skip flags + state.source = GetSourceText(text.text); + state.formatArgs = null; + } - // InputField children are managed by their owning panels to avoid - // interfering with user input and placeholder rendering. - if (text.GetComponentInParent() != null) return; + if (state.skipDropdown) return; + if (state.skipInputField) return; - if (font != null) + // Apply font only once per Text + if (font != null && !state.fontApplied) { Color savedColor = text.color; text.font = font; text.color = savedColor; text.verticalOverflow = VerticalWrapMode.Overflow; - } - - int id = text.GetInstanceID(); - TextState state; - if (!textStates.TryGetValue(id, out state)) - { - state = new TextState { source = text.text, localized = text.text }; - textStates.Add(id, state); - } - else if (text.text != state.localized) - { - state.source = GetSourceText(text.text); - state.formatArgs = null; + state.fontApplied = true; } state.localized = state.formatArgs == null @@ -1041,7 +1065,7 @@ public class LocalizationManager : MonoBehaviour - if (font != null) + if (font != null && !state.fontApplied) { Text[] texts = dropdown.GetComponentsInChildren(true); for (int i = 0; i < texts.Length; i++) @@ -1051,6 +1075,7 @@ public class LocalizationManager : MonoBehaviour texts[i].color = savedColor; texts[i].verticalOverflow = VerticalWrapMode.Overflow; } + state.fontApplied = true; } dropdown.RefreshShownValue();