1

I am new to WPF but want to build a Windows application with the UI defined in XAML that supports localization from the beginning. It is not only the UI elements that needs to be localized but also a lot of content which is residing in a database.

I have set up a database structure to be able to hold the localized information, but I am not sure how to get it displayed with regards to the locale chosen e.g. "en-US" or "de-DE".

How do I provide the localized texts to the XAML UI elements in a way that is natural to WPF/XAML? I do not want to rely on any third party code such as WPFLocalizationExtension.

I have seen how this is possible with a resource file but that will only support localization for the known UI elements not the content which is dynamically generated.

Is there a specific provider I should implement or am I missing something completely different?

Bjarne
  • 153
  • 12
  • Can't you just assign the "Text" or "Content" on the elements that you generate dinamically? I mean, when you add let's say a button, you can just do a query on your db and set the content property from code behind. – Daniele Sartori Oct 31 '17 at 16:44
  • Yes that can certainly be done. However I want to do this "the right way" with regards to WPF XAML and I am not sure if this is the intended way to handle this in WPF. – Bjarne Nov 01 '17 at 08:25
  • Well you don't have particular choice. It's like i said in the first comment, or you define a viewmodel that implement the INotifyPropertyChanged and define enough property to cover all your possible dynamic control. Each property represent a text or a content that is filled with a query and is binded to a single control. However in my opinion you shouldn't use the viewmodel since we are talking of something that is purely Visual (i.e. your data are not afflicted, it's just a different localization for some text), that's why i suggested to do that via code behind – Daniele Sartori Nov 02 '17 at 07:52
  • I think you are right in your first comment @DanieleSartori . After researching a bit further I have come to the same conclusion that localization tag (i.e. "en-US") should be part of the query for the data. You could think of it as items in a store that have different names in different languages. – Bjarne Nov 02 '17 at 10:44

1 Answers1

0

The way I ended up solving this was as follows. Remember I need to extract the localized texts from a database/remote api.

With this solution I can databind like this and all bound texts will change automatically when I change the language:

<Label Content="{Binding Path=Strings.ErrorLevelColumnHeaderLabel}"/>

Of course the Strings object must be accessible from all datacontexts.

The strings are stored in a database table that looks like this, an ID-column and one column for each of the supported languages:

ID  en        da        de
24  'Level'   'Niveau'  'Stufe'

I created a UIStringsVM class which implements the INotifyPropertyChanged interface. In my example I have implemented this in a base class called Observable, which I am sure many others have as well. See this answer for details.

public class UIStringsVM : Observable
{
    public static UIStringsVM CurrentStringsInstance;

    private bool stringsAreLoading = false;
    private Dictionary<int, string> stringDictionary;
}

The UIStringsVM class have a property for each string I need to have localized. Since the class supports the INotifyPropertyChanged interface through the base class I can then rely on changes to be reflected in the UI when ever the language changes.

Inside the UIStringsVM class the strings for the current language is stored in a Dictionary<int, string>. The reason for this is that I can use the string ID from the database to access the correct string.

Now I can use the ID inside the property Get method to return whatever string is stored for that value. So the properties will look like this:

public string ErrorLevelColumnHeaderLabel
{
    get =>
        this.stringDictionary[24].Replace("\\n", Environment.NewLine);
    private set =>
        this.stringDictionary[24] = value;
}

The properties are never set individually so the setter could be omitted.

The constructor:

public UIStringsVM()
{
    this.stringDictionary = new Dictionary<int, string>();

    // Initialize with default values. The ! at the end makes it easier to identify missing values in the database. 
    this.LoginButtonText = "Login!";
    this.LogoutButtonText = "Logout!";
    this.UserLabelFormatString = "{0} logged in!";
    this.ErrorLevelColumnHeaderLabel = "Level!";

    UIStringsVM.CurrentStringsInstance = this;
}

In order to load the strings I use the following method:

public async Task LoadStringsAsync(string languageCode, CancellationToken ct)
{
    if (languageCode.Length != 2)
        throw new ArgumentException("languageCode must be exactly 2 characters.", nameof(languageCode));

    this.StringsAreLoading = true;

    var client = new UIStringsClient(AppVM.BaseURL);
    try
    {
        var apiStrings = await client.GetByLanguageAsync(languageCode, ct);
        foreach (var r in apiStrings)
        {
            /* Note: this will make it impossible to overwrite a string with an empty string from the database, 
             * thus always keeping the string defined in this class' constructor. However strings will always 
             * have a value as defined in the constructor even if one does not exist in the database.
             * */
            if (string.IsNullOrWhiteSpace(r.Value))
                continue;

            this.stringDictionary[r.Key] = r.Value;
        }

        this.OnPropertyChanged((string)null); // Raise a change event for the entire object, not just a named property
    }
    finally
    {
        this.StringsAreLoading = false;
    }
}

I hope this helps anyone who might happen to come across this late answer. I have been running this solution for 15 months or so, and its been really great to work with.

Bjarne
  • 153
  • 12