This is the second post in a series of X. Read the first one here about our overall design.
Since we will develop a product that will be sold in more countries in a not to far future it is important to make sure that it can be localized. In Silverlight the key to localization is (as many other things in SL) data binding. This different for developers that are used to Windows Forms, but it is still very easy and it is still resource files that are used so lets start there.
First step is to add a resource file and I usually create a Resources folder in my project in which I put a new resource (resx) file. There is one important step to do in the resource editor that I have never done before and that is to set the Access Modifier to public (Internal is default). Even though it is set to public, the constructor of the class is generated as internal, which makes it hard to instantiate the class in XAML. Instead we choose to expose the resource in our ViewModel base class.
[Display(AutoGenerateField = false)]
public Resources.Resource Resource { get { return resources; } }
The Display attribute is from data annotations and we put it there to make sure that the property never should show up when using auto generation in the controls that supports it. With this in place we can bind to it in XAML (and we have our ViewModel as data context for the view/user control).
<Button Content="{Binding Path=Resource.Save, FallbackValue=Spara}" />
The first part in the path refers to the Resource property in the view model and the second part is the name of the string in the resource file. We also set FallbackValue to have something shown in the designer.
We will also use data annotations extensively. This means that we need to make sure that the annotations will use localized strings for everything. Lets start by looking at some more code.
[Display(Order = 1, ResourceType = typeof(Resources.Resource), Name = "ProcessGroup")]
[Required(ErrorMessageResourceType = typeof(Resources.Resource), ErrorMessageResourceName = "RequiredErrorMessage")]
public int ProcessGroupId { get; set; }
Here we use two data annotation attributes; Display and Required. The Required attributes of course means that the property is required to be set and the Display attribute has information about how this property should be handled when using auto generation in for example the data grid. Both attributes also uses a resource file to get the error message and the caption for the property in auto generation. When WCF RIA Services tooling auto generates this class on the client side the resource is not available there, but it will have to be. To solve this you add a structure that will give the same namespace as the generated code, in our case this means adding a Web folder and under that a Resources child folder. In this folder we then adds an existing item, but we add it as a link so that changes on the client is immediately available in the client.
There is also a DisplayFormat attribute that controls formatting of values, even though this does not seem to get handled by the data grid. For example we use this for dates:
[DisplayFormat(DataFormatString = "{0:d}")]
To work around that you could easily create a value converter, for example:
public class StringFormatValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter == null) return value;
string formatString = parameter.ToString();
if (string.IsNullOrEmpty(formatString)) return value;
return string.Format(System.Globalization.CultureInfo.CurrentCulture, formatString, value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
This converter can then be used in the Binding element in XAML. When we use auto generation in for example the data grid we need to catch the AutoGeneratingColumn and add the formatter to the binding.
private void dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Imported")
{
DataGridTextColumn textColumn = e.Column as DataGridTextColumn;
if (textColumn != null)
{
textColumn.Binding.ConverterParameter = "{0:d}";
textColumn.Binding.Converter = new Converters.StringFormatValueConverter();
}
}
}
Finally we need to control the culture of the web project by adding the globalization information to web.config or the hosting page.
<globalization culture="sv-SE" uiCulture="sv-SE" />
Another possibility is to use the enableClientBasedCulture attribute of the globalization element. It is also possible to set it just for the Silverlight object by setting uiculture and culture param for the object in the Silverlight test aspx page. In all cases it probably would be nice to allow the user to control which culture that should be used, either in a web page or in a settings dialog of the Silverlight application.