From 9bbc96d88e3a31edf1955fcfa0668da5f297bf4a Mon Sep 17 00:00:00 2001 From: divya0795 <12871391+divya0795@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:41:19 +0000 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfix(i18n):=20stop=20leaking=20a=20Reso?= =?UTF-8?q?urceDictionary=20on=20every=20language=20switch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SetLanguage builds the replacement locale dictionary with "new ResourceDictionary()" and never assigns a Source, but the lookup that finds the dictionary to replace requires "d.Source != null". Only the first switch matches, because that one still targets the Source-bearing Locale/lang.en-US.xaml merged by App.xaml. From then on the installed dictionary has no Source, the lookup returns null, and control falls to the else branch, which appends without removing. Switching languages N times therefore leaves N fully parsed locale dictionaries merged into Application.Current.Resources. Displayed text stays correct because the last merged dictionary wins, so the symptom is an unbounded memory leak rather than wrong strings. Track the installed dictionary in a static field and prefer it when looking up the one to replace, falling back to the Source-based predicate for the first call. The index is checked before use so a dictionary that is no longer merged appends instead of throwing. --- .../Core/Services/LocalizationManager.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/WandEnhancer/Core/Services/LocalizationManager.cs b/WandEnhancer/Core/Services/LocalizationManager.cs index 254f125..bcd28a7 100644 --- a/WandEnhancer/Core/Services/LocalizationManager.cs +++ b/WandEnhancer/Core/Services/LocalizationManager.cs @@ -29,6 +29,11 @@ public static class LocalizationManager private static CultureInfo _currentLanguage; private static ResourceDictionary _englishBaseDictionary; + // The locale dictionary currently merged into the application resources. It is built in + // code and therefore has no Source, so it cannot be found again by the Source-based lookup + // below; tracking it here is what allows the previous one to be removed on every switch. + private static ResourceDictionary _currentLocaleDictionary; + public static CultureInfo CurrentLanguage { get => _currentLanguage; @@ -104,20 +109,27 @@ private static void SetLanguage(CultureInfo culture, bool saveSettings = true) localeDict[entry.Key] = targetDict[entry.Key]; } - // Find and replace the old locale dictionary - var oldDict = Application.Current.Resources.MergedDictionaries + // Find and replace the old locale dictionary. The tracked instance is checked first + // because only the very first replacement targets the Source-bearing dictionary merged + // by App.xaml; every later one targets a code-built dictionary with no Source. + var oldDict = _currentLocaleDictionary ?? Application.Current.Resources.MergedDictionaries .FirstOrDefault(d => d.Source != null && d.Source.OriginalString.StartsWith("Locale/lang.")); - if (oldDict != null) + var index = oldDict != null + ? Application.Current.Resources.MergedDictionaries.IndexOf(oldDict) + : -1; + + if (index >= 0) { - var index = Application.Current.Resources.MergedDictionaries.IndexOf(oldDict); - Application.Current.Resources.MergedDictionaries.Remove(oldDict); + Application.Current.Resources.MergedDictionaries.RemoveAt(index); Application.Current.Resources.MergedDictionaries.Insert(index, localeDict); } else { Application.Current.Resources.MergedDictionaries.Add(localeDict); } + + _currentLocaleDictionary = localeDict; if (saveSettings) {