2

I've gotten to the point where I need to add localization to my WPF MVVM application (I use Caliburn.Micro + Autofac).

I did some research, and I've found many different ways to accomplish it, but none provide a solution to localize the text of a dialog. As dialogs I use a DialogViewModel that Caption and Message string properties, and I show it in a DialogView using CM's WindowManager. What I have atm is something like

this.windowManager.ShowDialog(new DialogViewModel("Hello!", "Hello everybody!!"))

but also things like

this.windowManager.ShowDialog(new DialogViewModel("Hello!", "Hello " + this.Name + "!!"))

I thought I could use a resource string like "Hello {0}!!" and use it this way

this.windowManager.ShowDialog(new DialogViewModel("Hello!", string.Format(languageResources.HelloName, this.Name)))

Is it good to do reference the localization resources from the ViewModel layer?

Sergio
  • 2,078
  • 3
  • 24
  • 41
  • Argh, I wrote bogus - too late ;) I think your method is workable without a problem. I am doing something similar with Caliburn. You might have to bear in mind, that there are also Titles etc. which might need translation. – Christian Sauer Feb 04 '14 at 18:13
  • ok so it's not considered a bad practice to reference localization resources from vm? – Sergio Feb 04 '14 at 18:16

1 Answers1

3

Resources is the data that uses a View, and my opinion is that is not advisable from the ViewModel refer to resources. On the other hand, if it is a class (may be static) that stores a specific strings, and knows nothing of the View it will be some abstraction that can be in the ViewModel. In any case, you should try to work with the resources on the side View using techniques that I will give, or any other.

Using x:Static Member

In WPF, it is possible to bind static data from a class like this:

<x:Static Member="prefix : typeName . staticMemberName" .../>

Below is an example where the format string is in a class, the format used to display the date and time.

XAML

xmlns:local="clr-namespace:YourNameSpace"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

<Grid>
    <TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat={x:Static Member=local:StringFormats.DateFormat}}" 
               HorizontalAlignment="Right" />

    <TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat={x:Static Member=local:StringFormats.Time}}" />
</Grid>

Code behind

public class StringFormats 
{
    public static string DateFormat = "Date: {0:dddd}";

    public static string Time = "Time: {0:HH:mm}";
}   

In this case, the StringFormats class be regarded as a resource, although actually it is a normal class. For more information, please see x:Static Markup Extension on MSDN.

Using Converter

If you have the resources stored in Application.Current.Resources and need to add some logic, in this case, you can use the converter. This example is taken from here:

XAML

<Button Content="{Binding ResourceKey, Converter={StaticResource resourceConverter}}" />

Code behind

public class StaticResourceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var resourceKey = (string)value;

        // Here you can add logic

        return Application.Current.Resources[resourceKey];
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new Exception("The method or operation is not implemented.");
    }
}   

Note: In the converter, it is better not to use heavy logic, because it can affect the performance. For more complex logic, see below.

Attached Behavior

Attached behavior should be used for complex actions with visual elements when no x:Static Member and converter is not helped. Attached behavior is very powerful and convenient solution that fully satisfies the MVVM pattern, which can also be used in the Blend (with a pre-defined interface). You can define an attached property in which property handler to access elements and to its resources.

Examples of implementation attached behaviors, see below:

Set focus to a usercontrol when it is made visible

Animated (Smooth) scrolling on ScrollViewer

Setting WindowStartupLocation from ResourceDictionary throws XamlParseException

Example with converter

App.xaml

Here I store strings for each culture.

<Application x:Class="MultiLangConverterHelp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             StartupUri="MainWindow.xaml">

    <Application.Resources>
        <sys:String x:Key="HelloStringEN">Hello in english!</sys:String>
        <sys:String x:Key="HelloStringRU">Привет на русском!</sys:String>
    </Application.Resources>
</Application>

MainWindow.xaml

The input is the current culture, which can be obtained within the converter, for simplicity of an example I did so.

<Window x:Class="MultiLangConverterHelp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:local="clr-namespace:MultiLangConverterHelp"
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <local:StaticResourceConverter x:Key="converter" />
        <local:TestViewModel x:Key="viewModel" />
    </Window.Resources>

    <Grid DataContext="{StaticResource viewModel}">
        <TextBlock Text="{Binding Path=CurrentCulture, Converter={StaticResource converter}}" />
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

public class StaticResourceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var currentCulture = (string)value;

        if (currentCulture.Equals("EN-en")) 
        {
            return Application.Current.Resources["HelloStringEN"];
        }
        else if (currentCulture.Equals("RU-ru"))
        {
            return Application.Current.Resources["HelloStringRU"];
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return DependencyProperty.UnsetValue;
    }
}

public class TestViewModel : NotificationObject
{
    private string _currentCulture = "EN-en";

    public string CurrentCulture
    {
        get
        {
            return _currentCulture;
        }

        set
        {
            _currentCulture = value;
            NotifyPropertyChanged("CurrentCulture");
        }
    }
}

Also, I advise you to learn more simple ways, which is already in the WPF technology:

WPF Localization for Dummies

WPF Globalization and Localization Overview

Community
  • 1
  • 1
Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
  • If you use this, you would totally circumvent the resx-file system? – Christian Sauer Feb 05 '14 at 09:03
  • @Christian Sauer: I have the resources, there are stored file paths, styles for controls and all of which are stored in `ResourceDicitionary`, but in the `ViewModel` I'm not referring to the contents of resources. Yes, to some degree it will help you avoid a referenece to resources in `ViewModel`, but will not do away resx-file system. I think, about resources should only know `View`. – Anatoliy Nikolaev Feb 05 '14 at 09:07
  • I like the idea of using converters, I had the same idea when thinking to a solution, but I'm still wondering how can I change the text of a dialog using the solutions you provided, can you please show me a little example? thank you for your time – Sergio Feb 05 '14 at 11:29
  • @Nikolaev: to be clear, let's assume we have to show the dialog "Item has been romved.", how would you localize the text? – Sergio Feb 05 '14 at 13:09
  • @Nikolaev: ok thank you for the example, I got what you meant but doesn't it assume I have to create a view for each different dialog message? – Sergio Feb 05 '14 at 13:54
  • Maybe a solution could be to pass the key of the message from the ViewModel to the converter to tell it which text has to be shown, but at that point it means the ViewModel knows the, so it's better to remove magic strings and reference directly the resx key from the VM. Am I wrong? – Sergio Feb 05 '14 at 13:59
  • @Daedalus: `Maybe a solution could be to pass the key of the message from the ViewModel to the converter to tell it which text has to be shown` - I like this approach, given the dirty work to `Converter` and ViewModel knows nothing of the View. About my example converter, it's just an example. How it will work - you decide. – Anatoliy Nikolaev Feb 05 '14 at 14:14
  • @Nikolaev: `but at that point it means the ViewModel knows the, so it's better to remove magic strings and reference directly the resx key from the VM` am I wrong? – Sergio Feb 05 '14 at 14:19
  • @Daedalus: In the `ViewModel` is not explicitly reference to the resource, and will only string that tells what should be Tittle, rest of the work will make the converter. In this case, an abstract relationship, it can be said that a good relationship. Ie ViewModel do so: `CurrentCulture = "EN-en"`, in View: `{Binding Path=CurrentCulture, Converter={StaticResource converter}}`. – Anatoliy Nikolaev Feb 05 '14 at 14:25
  • @Nikolaev: ok I understand your point, I think I'll use your approach, even though I kinda hate magic strings. If I'll have any improvement I'll update my main post (I hope I'll remember to do it :D) thank you! – Sergio Feb 05 '14 at 14:34