Globalization in WPF using ResourceDictionary





5.00/5 (14 votes)
Multilingual application using ResourceDictionary in WPF.
Introduction
This article introduces how to run a WPF application in difference languages. We will go through how to create a multilingual application. Sometimes question arises as to how to create a single version of an application that can be used in multiple languages (different culture). ResourceDictionary comes in place, and plays a role in running WPF applications in different languages.
Background
ResourceDictionary
is based on XML and takes advantage of the globalization support defined in the XML specification. We can create multiple
resource files for each language and add at the root level (App.xaml) to implement in a whole application.
Using the code
Creating resources
- Right click on the WPF project and select Add New Item, and select UserControl from list of items in the opened dialog.
- Convert
UserControl
toResourceDictionary
. - Give a proper name to the
ResourceDictionary
page based on the language, for example: EnglishLanguage.xaml, and write a string resource like this: - Using a string resource in the application:
- In this application I have created a demo for English and French languages, you can create more resource files based on your requirements.
ExportAttribute
ImportAttribute
-
ImportManyAttribute
- Open the EnglishLanguage.xaml.cs file and write the following to export this class (resource):
- The same way attach metadata in the
FrenchLanguage
class. - Now, create a class that has a property to import all exported classes, like this:
- Create a Singleton class and add a property for the
ImportModule
class. - In the App.xaml.cs file write the following code in the
OnStartup
event to import all classes using the Catalog. - Create a class for the language that has
Code
andName
properties for binding. - Create a property in the ViewModel that has a list of languages.
- Add a ComoBox in the Usercontrol for changing the language and bind the languages from the ViewModel class, like this:
- Write code to apply the selected language resource in the application on change of the selected language from the combobox.
A question arises in our mind that why do we need to add a
UserControl
and convert it into a ResourceDictionary
instead of adding a
ResourceDictionary
directly.
The answer is we are going to use MEF (Import/Export) classes in the next step.
<ResourceDictionary x:Class="WPF_Globalization.Resources.EnglishLanguage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<s:String x:Key="keyEmployeeName">Employee Name:</s:String>
<s:String x:Key="keyAddress">Address:</s:String>
<s:String x:Key="keyCountry">Country:</s:String>
<s:String x:Key="keyState">State:</s:String>
<s:String x:Key="keyCity">City:</s:String>
<s:String x:Key="keyPhone">Phone Number:</s:String>
<s:String x:Key="keyDesignation">Designation:</s:String>
</ResourceDictionary></ResourceDictionary>
x:key
for strings in above code is a unique name, it used to identify a string resource.
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="{DynamicResource keyEmployeeName}" />
To use global file resources you have to set DynamicResource
for
the local file resource you need to set with StaticResource
.
The same way, we add a ReourceDictionary
file for other languages, for example, FrenchLanguage.xaml, like this:
<ResourceDictionary x:Class="WPF_Globalization.Resources.FrenchLanguage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<s:String x:Key="keyEmployeeName">Nom de l'employé:</s:String>
<s:String x:Key="keyAddress">adresse:</s:String>
<s:String x:Key="keyCountry">pays:</s:String>
<s:String x:Key="keyState">état:</s:String>
<s:String x:Key="keyCity">ville:</s:String>
<s:String x:Key="keyPhone">Numéro de téléphone:</s:String>
<s:String x:Key="keyDesignation">désignation:</s:String>
</ResourceDictionary>
Using MEF
Microsoft .NET Framework has a System.ComponentModel.Composition
namespace which provides classes that constitute the core of MEF (Manage Extensibility Framework). For more details, visit MSDN.
What is MEF?
The Managed Extensibility Framework (MEF) is a composition layer for .NET that improves the flexibility, maintainability, and testability of large applications.
It allows application developers to discover and use extensions with no configuration required. By using MEF, developers can easily encapsulate code and avoid fragile hard dependencies.
Features of MEF
MEF components (classes, methods, properties) specify both its dependencies (Imports) and capabilities (Exports) that are discovered by the runtime. When an object is created, the MEF composition engine satisfies its imports with what is available from other objects. It provides exports, an object that satisfies imports.
There are a list of attributes available in MEF, apart from them I have used the following attributes in this application:
ImportAttribute
specifies that a property, field, or parameter should be populated with matching exports. It will import a list of operations.
ExportAttribute
specifies that a type, property, field, or method provides a particular export. Any export declared with a matching contract
will fulfill this import.
[ExportMetadata("Culture", "en-US")]
[Export(typeof(ResourceDictionary))]
public partial class EnglishLanguage : ResourceDictionary
{
public EnglishLanguage()
{
InitializeComponent();
}
}
ExportMetadata
specifies the metadata for a type (or we can say it will attach metadata) in a key-value pair that will implement
the operation.
public class ImportModule
{
[ImportMany(typeof(ResourceDictionary))]
public IEnumerable<Lazy<ResourceDictionary, IDictionary<string,
object>>> ResourceDictionaryList { get; set; }
}
The above code snippet imports all classes from different assemblies that have
the matching type ResourceDictionary
.
Composition Container: it is the core of MEF. It is used to discover parts (objects) by using
a composable part catalog. A catalog can be any given type from hosting (like
DirectoryCatalog
, AssemblyCatalog
, AggregateCatalog
, etc.).
public class BaseModel
{
private static BaseModel _instance;
public static BaseModel Instance
{
get
{
if (_instance == null)
_instance = new BaseModel();
return _instance;
}
}
private ImportModule _importCatalog;
public ImportModule ImportCatalog
{
get
{
_importCatalog = _importCatalog ?? new ImportModule();
return _importCatalog;
}
}
}
string path = AppDomain.CurrentDomain.BaseDirectory;
DirectoryCatalog catalog = new DirectoryCatalog(path);
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(BaseModel.Instance.ImportCatalog);
public class Languages
{
public string Code { get; set; }
public string Name { get; set; }
}
private List<Languages> _languageList;
public List<Languages> LanguageList
{
get { return _languageList; }
set
{
_languageList = value;
RaisePropertyChanged("LanguageList");
}
}
LanguageList = new List<Languages>();
LanguageList.Add(new Languages() { Code = "en-US", Name = "English" });
LanguageList.Add(new Languages() { Code = "fr-FR", Name = "French" });
<ComboBox x:Name="LanguageComboBox"
Width="150"
Margin="5"
HorizontalAlignment="Left"
DisplayMemberPath="Name"
ItemsSource="{Binding LanguageList}"
SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"
SelectionChanged="LanguageComboBox_SelectionChanged" />
private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var currentResourceDictionary = (from d in BaseModel.Instance.ImportCatalog.ResourceDictionaryList
where d.Metadata.ContainsKey("Culture")
&& d.Metadata["Culture"].ToString().Equals(vm.SelectedLanguage.Code)
select d).FirstOrDefault();
if (currentResourceDictionary != null)
{
Application.Current.Resources.MergedDictionaries.Add(currentResourceDictionary.Value);
CultureInfo cultureInfo = new CultureInfo(vm.SelectedLanguage.Code);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
Application.Current.MainWindow.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);
}
}
On change of the selected language, first find out the ResourceDictionary
of
the selected language culture from ResourceDictionaryList
. Then add
the Selected Resource into Application Resources and set CurrentCulture
as
the selected language culture. Last, set the application main window language using
the System.Windows.Markup.XmlLanguage.GetLanguage
method.
That's it.
Points of interest
This way developers can create a ResourceDictionary
for each language and set the resources for the whole application using MEF.