'WPF Change whole application culture at runtime?

I have a big WPF application with the requirement of having a menu to switch the language/culture at runtime. It is used for text localization, but also for units (ex: KMH/MPH using the Culture's RegionInfo IsMetric property) and even generating some URLs for example opening the user manual pdf in the proper language.

In .NET 4.7.2, I had this solution that was working perfectly:

var language = XmlLanguage.GetLanguage(culture.Name);
foreach (Window childWindow in Application.Current.Windows)
{
    childWindow.Dispatcher.Thread.CurrentCulture = culture;
    childWindow.Dispatcher.Thread.CurrentUICulture = culture;
    childWindow.Language = language;
}

Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

LocalizeDictionary.Instance.Culture = culture;

LocalizeDictionary is part of the XAMLMarkupExtensions nuget and allows to refresh all localization bindings instantly.

Unfortunately, since moving to NET6 and adding a bit of async code in there, it stopped working properly.

It does change the current thread culture, all new threads cultures and even refresh the current opened windows cultures.

The problem is, it seems like already running threads don't get updated, and the application now try to re-use them more than it did before instead of creating new ones. So if I put breakpoints in, let's say a button command and check the culture, it still shows the old one. Same for creating a new view model for a new window, it seems to be created using the old culture.

I've seen quite a few answers mentioning this line:

FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(language));

Unfortunately, this needs to be set in a static context once for the whole application, it can't be changed at runtime (PropertyMetadata is already registered for type 'FrameworkElement'.). And I don't think this really is the issue, because looping on current windows and changing the Language property seems to do the same. My problem seems to lie in existing background threads, not WPF elements in themselves.

I tried to find a way to find all existing threads, but only managed to get OS threads with Process.GetCurrentProcess().Threads and not Managed Threads, so no way to change their culture via that method.

Any way to list all existing threads and change their culture? Or force WPF to run commands on new threads instead of re-using them?



Solution 1:[1]

For some reason, the code above changing the culture on the threads/default/dispatchers/window xml works, but when ran from a normal event handler or command.

In my situation, it was run in an async command (with the help of ReactiveUI library ReactiveCommand.CreateFromTask()) and doing it in that context would not keep the changes. Not even if I invoked the WPF dispatcher from inside that task. All following event/commands and even bound property getter would get called with the old culture.

So I can't explain the details, but if your culture changes don't apply, check on which context you are doing the operation.

Also, it is useless to loop on every windows. You can just set it on the Application.Current.MainWindow

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dunge