Dynamically Changing the Display Language in Silverlight
On a recent Silverlight project we had a requirement to be able to dynamically change the language of the site without refreshing the page. This ruled out the normal way of doing localisation using Resource files and meant we had to find away of binding text strings to a ‘Dynamic Resource’. Given that Silverlight doesn’t support DynamicResource this presented us with a problem!
First off it is important to note that we had no ‘culture’ dependencies. We were required to format dates, times and numbers using the en-gb culture, had this not been the case, this would have been a significantly trickier problem to solve.
LocalizableTextBlock
We solved this problem by developing a custom control called ‘LocalizableTextBlock’
This control inherits from Control and has a single TemplatePart of type TextBlock, this is because the TextBlock class in Silverlight is sealed so we can’t inherit from it.
[TemplatePart(Name = LocalizedTextBlock.TextBlockElement, Type = typeof(TextBlock))]
public class LocalizedTextBlock : Control
{
}
By adding a TextBlock as a TemplatePart, and then TemplateBinding all the common properties from our control to the same properties on the TextBlock, we can effectively get around this. Obviously there are a few properties, such as WordWrap etc, that don’t exist on Control. We therefore need to add these to LocalizedTextBlock by adding new DependencyProperties.
public static readonly DependencyProperty TextWrappingProperty =
DependencyProperty.Register("TextWrapping",
typeof(TextWrapping),
typeof(LocalizedTextBlock),
new PropertyMetadata(TextWrapping.NoWrap));public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
As well as the missing properties from TextBlock doing this allows us to have a custom TextBlock that we can add our own properties to. The important ones in our case being:
BindingTerm Property
This is the string we use as the key in our resource dictionary to find the corresponding text.
TranslatedTerm Property
This is the value pair associated with the key provided by the BindingTerm property
The first property is used when consuming the control:
<controls:LocalizedTextBlock BindingTerm="MY_RESOURCE_STRING" />
The second property is used in the ControlTemplate definition:
<Style TargetType="local:LocalizedTextBlock">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:LocalizedTextBlock">
<TextBlock x:Name="PART_TextBlock"
Text="{TemplateBinding TranslatedTerm}"
TextWrapping="{TemplateBinding TextWrapping}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Notice here we are also TemplateBinding the TextWrapping property we created above. You will also need to template bind all of the other properties on control and create dependency properties for all of the missing properties from TextBlock and bind them up in the same place.
Now that we have a way of specifying the input resource string and a way of displaying the translated term we obviously need to link them together. We also need to account for the fact that Silverlight doesn’t support DynamicResource. We do this by adding a static dictionary to the control and an event to signal when the dictionary changes.
public static event EventHandler CultureChanged;
private static Dictionary<string, string> currentLanguageDictionary;
public static Dictionary<string, string> CurrentLanguageDictionary
{
get
{
return LocalizedTextBlock.currentLanguageDictionary;
}
set
{
LocalizedTextBlock.currentLanguageDictionary = value;
if (CultureChanged != null)
{
LocalizedTextBlock.CultureChanged(null, null);
}
}
}
So now we have a property that we bind to, a property that the TemplatePart TextBlock binds to and a static Dictionary of strings that we can lookup a key from and get a value. What we are going to do now is add a PropertyChangedCallback to the BindingTerm property so that when it changes we can update the TranslatedTerm property.
public static readonly DependencyProperty BindingTermProperty
= DependencyProperty.Register(
"BindingTerm",
typeof(string),
typeof(LocalizedTextBlock),
new PropertyMetadata(string.Empty,
new PropertyChangedCallback(BindingTermChanged)));
public static void BindingTermChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs eventArgs)
{
LocalizedTextBlock localisedTextBlock = sender as LocalizedTextBlock;
string key = eventArgs.NewValue.ToString();
localisedTextBlock.TranslatedTerm = LocalizedTextBlock.CurrentLanguageDictionary[key];
}
We now need a mechanism for changing the TranslatedTerm when we changing language. To do this we hook up to the static CultureChanged event handler in the constructor so that whenever the culture is changed we can refresh the TranslatedTerm property.
public LocalizedTextBlock()
{
this.DefaultStyleKey = typeof(LocalizedTextBlock);
LocalizedTextBlock.CultureChanged += new EventHandler(this.CultureChangedEventHandler);
}
private void CultureChangedEventHandler(object sender, EventArgs e)
{
this.TranslatedTerm = LocalizedTextBlock.CurrentLanguageDictionary[this.BindingTerm];
}
Now, whenever the CurrentLanguageDictionary is updated, we fire the CultureChanged event, handle it and update the TranslatedTerm for each instance of the LocalizableTextBlock.
private void LanguageChanged(object sender, EventArgs e)
{
LocalizedTextBlock.CurrentLanguageDictionary =
this.LanguageDictionaries[this.LanguageComboBox.SelectedIndex];
}
In this example we are switching between a set of dictionaries held in memory. Obviously you may not want to do this and you may need to pull them down from your webserver. In this case make sure that you always ship a pre-canned culture with your application that you can quickly load and then whenever a user changes culture serialize the LanguageDictionary and store it in isolated storage. That way you will reduce the amount of time the user will have to wait when the application loads on subsequent visits.
To see an example of how this is used visit www.eyeonearth.eu and select the ‘change language’ option in the top right.
Written by Simon Middlemiss
Comments
- Anonymous
November 28, 2010
The comment has been removed - Anonymous
December 03, 2010
The code should now be fixed. unfortunately this was an artifact of the platform migration.