Wpf: FrameworkElement.Language does not use culture user overrides

Created on 24 Sep 2019  路  5Comments  路  Source: dotnet/wpf

  • .NET Core Version: 3.0.100
  • Windows version: 18985.1.amd64fre.vb_release.190913-1426
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes

Problem description:
Applying FrameworkElement.Language and related Language properties does not take into account current culture settings. There is no way how to supply a specific instance of CultureInfo to the WPF infrastructure.

Minimal repro:

<Window x:Class="TestWpfBugs.StaticProperties.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Language="en-GB">
    <TextBlock DataContext="{x:Static s:DateTime.Now}" Text="{Binding}" />
</Window>

(this assumes English (United Kingdom) to be used for formatting. Use other cultures accordingly.)

Go to _Settings > Time and Language > Region > Change data formats_ and change _Short date_ format to a non-default option, e.g. _yyyy-MM-dd_. Run the WPF app.

Actual behavior: Current culture settings not respected, the date is shown as dd/MM/yyyy.

Expected behavior: The date to be in the selected format.

Ultimately, the XmlLanguage calls new CultureInfo(name, useUserOverride) when converting the string into culture. The actual behavior passes false for the useUserOverride while the expected behavior would mean passing true instead.

As a result, there isn't any way I am aware of to make the WPF infrastructure use the current culture that user prefers other than annotating all data bindings and template bindings with converters with explicit cultures, which is a considerable performance hit if possible at all.

Clearly changing the current behavior would not only be a compatibility break but also prevent people from using non-overridden cultures, but there might be other solutions, such as (in decreasing number of scenarios it would allow):

  1. letting developers to override language with an instance CultureInfo rather than just a name string, possible with an extra property of type CultureInfo that would be used if set (either on XmlLanguage or FE, otherwise fallback to Language.
  2. introducing new property e.g. UseLanguageOverride on XmlLanguage supporting returning user overridden cultures, possibly but not necessarily with an extra property on FE too.
  3. introducing special XML language strings meaning "current culture" and "current UI culture".

Any thoughts?

issue-type-enhancement

All 5 comments

The problem is that WPF expects the XmlLanguage name to be equivalent to the CultureInfo in some places, so the name must be unique and must not cause mismatches between the true CultureInfo and a customized one.

I'm using this as a workaround:
```C#

public static void InitializeCurrentLanguageForWPF()
{
// Create a made-up IETF language tag "more specific" than the culture we are based on.
// This allows all standard logic regarding IETF language tag hierarchy to still make sense and we are
// compatible with the fact that we may have overridden language specific defaults with Windows OS settings.
var culture = CultureInfo.CurrentCulture;
var language = XmlLanguage.GetLanguage(culture.IetfLanguageTag + "-current");
var type = typeof(XmlLanguage);
const BindingFlags kField = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
type.GetField("_equivalentCulture", kField).SetValue(language, culture);
type.GetField("_compatibleCulture", kField).SetValue(language, culture);
if (culture.IsNeutralCulture)
culture = CultureInfo.CreateSpecificCulture(culture.Name);
type.GetField("_specificCulture", kField).SetValue(language, culture);
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(language));
}
```

To make something like this possible without reflection we just need to be able to define a custom XmlLanguage based on a CultureInfo. Since the IETF language tag is not optional the only way to make this working is by adding a custom suffix. Even though this is not an "official" IETF language tag it should be as close as you can get compatibility wise. (You don't want to use an official IETF language tag with a custom CultureInfo because that can cause bugs when roundtripping, using a custom tag at least makes it clear what the cause is when anything goes wrong, and "correctly written" code can still generalize the custom tag properly to actually existing languages.)

There's another workaround. On app startup, you can set FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name)));

The bigger issue is that the default language for FrameworkElement is "en-us" when it should be CultureInfo.CurrentCulture.

The current behavior shows a naive approach to localization as it assumes "en-us" is a suitable global default when it isn't. It also means that anyone who doesn't want their app to use formatting or other localization details for "English (United States)" has to do extra work.
The default for an app should be to use the system settings. If an app wants to hard-code that only a single, specific culture should be used everywhere, this should only need to be set in a single place.

There's another workaround. On app startup, you can set [default Language based on CurrentUICulture]

Considering that the culture is used for formatting in bindings I don't think using CurrentUICulture is correct.

There's another workaround. On app startup, you can set [default Language based on CurrentUICulture]

Considering that the culture is used for formatting in bindings I don't think using CurrentUICulture is correct.

yes, my mistake. (will correct above)

Unfortunately when you switch to CurrentCulture your workaround no longer works. The CurrentCulture is not identifyable by its name as it contains customizations the user made in the system controls panel. Once you roundtrip back to CultureInfo from the name you'll lose those user settings, meaning bindings still don't match what non-WPF components produce when they are asked to format with CurrentCulture (e.g. in ToString calls). See my above workaround to define a custom name and keep user specific format settings.

I think the whole WPF localization infrastructure was a design mistake. It looks like originally it was intended to localize XAML resources so you'd be supposed to use CurrentUICulture (which does rountrip based on its name), but then they made the mistake of using the same culture for formatting, which does not work out at all.

Considering this issue is about the XAML respecting user customizations in the CultureInfo I think my workaround is the only one which reliably works. WPF will pick up the culture from the XmlLanguage object so if you didn't store it there via reflection you have no chance to work around the problem, CurrentCulture is not obtainable by name I believe.

Was this page helpful?
0 / 5 - 0 ratings