How to change language at run-time in WPF with loadable Resource Dictionaries and DynamicResource Binding
Update: I created a project for this. It also has a NuGet package.
See https://github.com/rhyous/WPFSharp.Globalizer.
Dynamically changing the language of a WPF application at run-time or on-the-fly is possible and quite simple.
With this solution you do not have Resources.resx files, you do not have to have x:Uid on all your XAML tags. See the expectations this solution is following here: My WPF Localization and Language Expectations. Most of my expectations are met with this solution.
Here is the sample project code: How to load a DictionaryStyle.xaml file at run time?
Example 1 – Dynamic Localization in WPF with Code-behind
Step 1 – Create a new WPF Application project in Visual Studio
- In Visual Studio, go to File | New | Project.
- Select WPF Application.
- Provide a name for the project and make sure the path is correct.
- Click OK.
Step 2 – Configure App.xaml.cs to support dynamic ResourceDictionary loading
The App.xaml.cs is empty by default. We are going to add a few variables, a constructor, and add a few simple functions. This is a fairly small amount of code.
- Open App.xaml.cs.
- Add a static member variable for App called Instance, so it can be accesses from anywhere.
- Add a static member variable for App called Directory, so it can be accesses from anywhere.
- Add a LanguageChangedEvent.
- Add a private GetLocXAMLFilePath(string inFiveCharLang) method.
- Add a public SwitchLanguage(string inFiveCharLanguage) method.
- Add a private SetLanguageResourceDictionary(string inFile) method.
- Add code to the constructor to initialize these variables and to set the default language.Note: The code is well commented.
using System; using System.Globalization; using System.IO; using System.Threading; using System.Windows; namespace WpfRuntimeLocalizationExample { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { #region Member variables public static App Instance; public static String Directory; public event EventHandler LanguageChangedEvent; #endregion #region Constructor public App() { // Initialize static variables Instance = this; Directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); // Load the Localization Resource Dictionary based on OS language SetLanguageResourceDictionary(GetLocXAMLFilePath(CultureInfo.CurrentCulture.Name)); } #endregion #region Functions /// <summary> /// Dynamically load a Localization ResourceDictionary from a file /// </summary> public void SwitchLanguage(string inFiveCharLang) { if (CultureInfo.CurrentCulture.Name.Equals(inFiveCharLang)) return; var ci = new CultureInfo(inFiveCharLang); Thread.CurrentThread.CurrentCulture = ci; Thread.CurrentThread.CurrentUICulture = ci; SetLanguageResourceDictionary(GetLocXAMLFilePath(inFiveCharLang)); if (null != LanguageChangedEvent) { LanguageChangedEvent(this, new EventArgs()); } } /// <summary> /// Returns the path to the ResourceDictionary file based on the language character string. /// </summary> /// <param name="inFiveCharLang"></param> /// <returns></returns> private string GetLocXAMLFilePath(string inFiveCharLang) { string locXamlFile = "LocalizationDictionary." + inFiveCharLang + ".xaml"; return Path.Combine(Directory, inFiveCharLang, locXamlFile); } /// <summary> /// Sets or replaces the ResourceDictionary by dynamically loading /// a Localization ResourceDictionary from the file path passed in. /// </summary> /// <param name="inFile"></param> private void SetLanguageResourceDictionary(String inFile) { if (File.Exists(inFile)) { // Read in ResourceDictionary File var languageDictionary = new ResourceDictionary(); languageDictionary.Source = new Uri(inFile); // Remove any previous Localization dictionaries loaded int langDictId = -1; for (int i = 0; i < Resources.MergedDictionaries.Count; i++) { var md = Resources.MergedDictionaries[i]; // Make sure your Localization ResourceDictionarys have the ResourceDictionaryName // key and that it is set to a value starting with "Loc-". if (md.Contains("ResourceDictionaryName")) { if (md["ResourceDictionaryName"].ToString().StartsWith("Loc-")) { langDictId = i; break; } } } if (langDictId == -1) { // Add in newly loaded Resource Dictionary Resources.MergedDictionaries.Add(languageDictionary); } else { // Replace the current langage dictionary with the new one Resources.MergedDictionaries[langDictId] = languageDictionary; } } } #endregion } }
Step 3 – Create a basic WPF User Interface
We need a little sample project to demonstrate the localization, so lets quickly make one.
- Create a basic interface.
Note 1: You can make one yourself, or you can use the basic interface I used. Just copy and paste from below.
Note 2: I have already added a menu for selecting the Language for you.
<Window x:Class="WpfRuntimeLocalizationExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF Run-time Localization Example" MinHeight="350" MinWidth="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="30" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="25" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="15" /> <ColumnDefinition Width="*" MinWidth="100"/> <ColumnDefinition Width="*" MinWidth="200"/> <ColumnDefinition Width="15" /> </Grid.ColumnDefinitions> <DockPanel Grid.ColumnSpan="4" > <Menu DockPanel.Dock="Top" > <MenuItem Header="_File"> <MenuItem Header="E_xit" Click="MenuItem_Exit_Click" /> </MenuItem> <MenuItem Name="menuItemLanguages" Header="_Languages"> <MenuItem Tag="en-US" Header="_English" Click="MenuItem_Style_Click" /> <MenuItem Tag="es-ES" Header="_Spanish" Click="MenuItem_Style_Click" /> <MenuItem Tag="he-IL" Header="_Hebrew" Click="MenuItem_Style_Click" /> </MenuItem> </Menu> </DockPanel> <Label Content="First Name" Name="labelFirstName" Grid.Row="2" FlowDirection="RightToLeft" Grid.Column="1" /> <Label Content="Last Name" Name="labelLastName" Grid.Row="4" FlowDirection="RightToLeft" Grid.Column="1" /> <Label Content="Age" Name="labelAge" Grid.Row="3" FlowDirection="RightToLeft" Grid.Column="1" /> <TextBox Name="textBox1" Grid.Column="2" Grid.Row="2" /> <TextBox Name="textBox2" Grid.Column="2" Grid.Row="3" /> <TextBox Name="textBox3" Grid.Column="2" Grid.Row="4" /> <Button Content="Clear" Grid.Column="2" Grid.Row="5" Height="23" HorizontalAlignment="Right" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" /> </Grid> </Window>
- Also populate the MainWindow.xaml.cs file with the code-behind needed for the menu click events.
using System; using System.Globalization; using System.Windows; using System.Windows.Controls; namespace WpfRuntimeLocalizationExample { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); foreach (MenuItem item in menuItemLanguages.Items) { if (item.Tag.ToString().Equals(CultureInfo.CurrentUICulture.Name)) item.IsChecked = true; } } private void MenuItem_Exit_Click(object sender, RoutedEventArgs e) { Environment.Exit(0); } private void MenuItem_Style_Click(object sender, RoutedEventArgs e) { // Uncheck each item foreach (MenuItem item in menuItemLanguages.Items) { item.IsChecked = false; } MenuItem mi = sender as MenuItem; mi.IsChecked = true; App.Instance.SwitchLanguage(mi.Tag.ToString()); } private void button1_Click(object sender, RoutedEventArgs e) { labelFirstName.Content = labelLastName.Content = labelAge.Content = string.Empty; } } }
Though localization is not yet working, this should compile and run.
Step 4 – Create Resource Dictionaries for Localization
By recommendation of my expert localization team, we are going to create a folder for each language, using the five character string (en-US, es-ES, he-IL), and put a ResourceDictionary in each folder. The resource dictionary will also have the five character language string.
- Create the folder and the file.
- Right-click on the project in Visual Studio and choose Add | New Folder.
- Name the folder en-US
- Right-click on the en-US folder and choose Properties.
- Set the ‘Namespace Provider’ value to false.
- Right-click on the en-US folder and choose Add | Resource Dictionary.
- Provide a file name and make sure that Resource Dictionary (WPF) is selected.
- Note: I named my first resource dictionary LocalizationDictionary.en-US.xaml.
- Click Add.
- Right-click on the LocalizationDictionary.en-US.xaml file and choose Properties.
- Set ‘Build Action’ to ‘Content’.
- Set ‘Copy to Output Directory’ to be ‘Copy if newer’.
- Set ‘Custom Tool’ to blank.
- Name the ResourceDictionary.
- Open the resource LocalizationDictionary.en-US.xaml
- Add a reference to the System namespace from the mscorlib assembly.
- Add a string with the x:Key set to ResourecDictionaryName.
- Set the string value to “Loc-en-US”.
Important! Because our code is specifically looking for Loc-, you need to use that scheme or change the code. - Add to the string a Localization.Comment and set the value to $Content(DoNotLocalize).
- Save the resource dictionary.
<ResourceDictionary 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"> <!-- The name of this ResourceDictionary. Should not be localized. --> <sys:String x:Key="ResourceDictionaryName" Localization.Comments="$Content(DoNotLocalize)">Loc-en-US</sys:String> </ResourceDictionary>
- Repeat all the steps in steps 1 and 2 for each langauge.
Note: We will do the following three languages in this example:- en-US
- es-ES
- he-IL
Step 4 – Localize the strings
- Open the MainWindow.xaml.
- The first string is the Title of the Window.
Note: My window title is “WPF Run-time Localization Example” - Replace the value with a DynamicResource to MainWindow_Title.
Here is a snippet…<Window x:Class="WpfRuntimeLocalizationExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{DynamicResource ResourceKey=MainWindow_Title}" MinHeight="350" MinWidth="525"> <Grid>
- Open the LocalizationDictionary.en-US.xaml.
- Add a string as a resource for the MainWindow’s Title, making sure the x:Key value is matches the ResourceKey in the MainWindow.xaml: MainWindow_Title.
<!-- Localized strings --> <sys:String x:Key="MainWindow_Title">WPF Run-time Localization Example</sys:String>
- Repeat steps for each of the remaining string that needs to be localized.
The following strings need to be localized in my sample UI:- _File
- E_xit
- _Language
- _English
- _Spanish
- _Hebrew
- First Name
- Last Name
- Age
- Clear
Step 5 – Configure the FlowDirection
The main motivation for adding Hebrew to this example is so that we can show how to dynamically switch FlowDirection for languages that flow from right to left.
Note: This step is optional and only necessary if your application will support a language that flows right to left.
- Add two FlowDirection resources to the LocalizationDictionary.en-US.xaml files.
<!-- Localization specific styles --> <FlowDirection x:Key="FlowDirection_Default" Localization.Comments="$Content(DoNotLocalize)">LeftToRight</FlowDirection> <FlowDirection x:Key="FlowDirection_Reverse" Localization.Comments="$Content(DoNotLocalize)">RightToLeft</FlowDirection>
- For Spanish, copy these same lines to the LocalizationDictionary.es-ES.xaml file. Spanish and English have the same FlowDirection.
- For Hebrew, (you have probably already guessed this), add these same lines to the LocalizationDictionary.he-IL.xaml file but reverse the values.
<!-- Localization specific styles --> <FlowDirection x:Key="FlowDirection_Default" Localization.Comments="$Content(DoNotLocalize)">RightToLeft</FlowDirection> <FlowDirection x:Key="FlowDirection_Reverse" Localization.Comments="$Content(DoNotLocalize)">LeftToRight</FlowDirection>
- Open the MainWindow.xaml file.
- Add a FlowDirection tag to the MainWindow.xaml file.
- Set the FlowDirection value to using DynamicResource binding to FlowDirection_Default.Here is the XAML snipppet.
<Window x:Class="WpfRuntimeLocalizationExample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{DynamicResource MainWindow_Title}" MinHeight="350" MinWidth="525" FlowDirection="{DynamicResource FlowDirection_Default}"> <Grid>
Build compile and test. Both the language and FlowDirection should now change when switching to Hebrew.
See the Framework for this here:
https://github.com/rhyous/WPFSharp.Globalizer
How to dyanamically edit or add string resources in resources.resw file
Thank you for this trick, it’s very useful!!
I use on my application and it work perfect. But I have one problem, I’m trying to include a WPF Theme and when I activate it, localization does not work. I have been trying something,and I have noticed, if you include in App.xaml, localization stop working. ¿Someone have a solution for it?
Please submit a bug here: https://github.com/rhyous/WPFSharp.Globalizer
Please provide a github or other online project that reproduced the issue.
Hi,
I create multi lang on datagrid in wpf, but not resolved.
Pls help me.
P/s: Multi Lang on windows in okie (ex, label…)
Thanks.
I have created wpf localization as said above.
It works..fixed my issue..!
Great post thanks for it..!
But i am facing one problem, now i have to localiza the containt of text box too.
I have wpf forms lets say customer master.
Now in customer name text box how can i localize it.
the contains also be loclized and save in database & fetched with it.
In short if i select hindi language the lables are changed no doubt but the containt
of data in text should change.
How can i do it ..?
Plzz help
Please tell me how can i apply localization to labels.
As in my project it has labels oneach form.
when i try this :
DataContext=”{DynamicResource ResourceKey=lblAge}”
also i tried
Content =”{DynamicResource ResourceKey=lblAge}”
couldn’t work..
plz help me
Have you checked out the WPFSharp.Globalizer nuget packager and GitHub project? It has way more info. It has a sample project. The GitHub is newer than the NuGet package. I would pull the source from GitHub if I were you.
http://rhyous.github.io/WPFSharp.Globalizer
This is a great solution – very well explained and easy to implement. I just have one question…
Is there a way to configure a design-time resource dictionary, to the WPF designer will show some text for the labels, buttons, etc. It is hard to get a feeling for the look of the dialog when there isn’t any text displayed.
Thank you again.
I have downloaded and experimented with the solution. I can see that the binding is not quite finished for the MVVM example. I found that it is referencing the wrong names – I changed them to be the same as the non MMVM version (with an underscore separator). However, I found that I had to change the Content to reference the GlobalizedResource rather than the LocalizationBinding approach i.e
This works:-
this does not work:-
The trouble with using the globalizer:GlobalizedResource approach however, is that I do not see DESIGN time fallback values. Is there a simple fix to resolve this?
Kind regards
Alan.
Mmm – some details stripped out there.
Essentially globalizer:LocalizationBinding binds correctly, but not at design time.
Al.
There are design time values.
Did you look at the GitHub link?
https://github.com/rhyous/WPFSharp.Globalizer
My example on the GitHub page has design time values.
https://github.com/rhyous/WPFSharp.Globalizer/blob/master/Examples/WPF.Globalizer.Example/MainWindow.xaml
Thanks Rhyous. I did get the data from Github – it is the same code that I was testing with. The design time binding does NOT work for the MVVMExample. If I use LocalizationBinding for the Name and Age values they appear at design time – but are never replaced and looks not to be binding at all. If I use GlobalizedResource (the values are missing at DesignTime) but appear at runtime.
Likewise the values do NOT appear at design time for the non Mvvm example but do appear at runtime.
Any help is much appreciated.
Kind regards
Alan.
what is DoNotLocalize I got error reading Localization.Comments=”$Content(DoNotLocalize)” and don’t know it refers to what?
The problem is that the XML schema for ‘sys:String’ or ‘FlowDirection’ does not include the ‘Localization.Comments’ attribute. I found that the error was harmless (ignored) but if you are having problems with it, you should be able to remove it. It is used for translation tools, to indicate that the text value should not be translated (leave the value alone). In my case, we have a policy that there cannot be any errors, so I removed them and simply put an XML comment at the beginning stating that the value should not be translated:
Loc-ENU
It looks like my example got swallowed up… Let me try again with some markup.
<!– DO NOT LOCALIZE –%gt;<sys:String x:Key=”ResourceDictionaryName”>Loc-ENU</sys:String>
(if that doesn’t work, you should at least get the idea)
Ha Ha – one more time, without the typo…
<– DO NOT LOCALIZE –> <sys:String x:Key=”ResourceDictionaryName”>Loc-ENU</sys:String>
Thank you for this trick, it’s very useful!!
I use on my application and it work perfect. But I have one problem, I’m trying to include a WPF Theme and when I activate it, localization does not work. I have been trying something,and I have noticed, if you include in App.xaml, localization stop working. ¿Someone have a solution for it?
Hi, I was pretty excited, reading about your solution. So, I downloaded your sample project for testing, but unfortunately it doesn’t work. All I can see if I run the solution are three empty TextBoxes and one Button (which has a height of about 1 mm). :/
Any suggestions?
I don’t have the MVVM example built out yet.
I sent you an email. I am not sure. I assume the localization files weren’t loaded for some reason. I’d love to debug it with you.
Hey there. I was about to take your Globalizer project code for a spin but the project file has oodles of dead source file links (ie, “EnhancedResourceDictionary.cs”, “AvailableLanguages.cs”).
Were the files intentionally removed?
Cheers,
Berryl
It is working now. please try again.
The MVVM example is not actual an MVVM example, yet.
Yeah it does. It looks like some of the files didn’t come along with the check out. I will investigate.
Sorry. What happened was I got hired at a new company to be a WPF engineer and they pulled the carpet out from under me and decided not to use WPF.
I am turning this into a API. Stay tuned…
[…] Tutorial – Binding to Resources.resx for strings in a WPF Application: A technique to prepare for localization October 20, 2010, 12:14 pm by Rhyous Update: Check out my preferred way for doing WPF Localization on my other blog: How to change language at run-time in WPF with loadable Resource Dictionaries and DynamicResource Bi… […]
Thanks for sharing your great solution. My ideas went in similar directions, but I was still struggling with cognitively putting some pieces together. Luckily I found your post, YMMD.
Nevertheless, I am still very interested in reading about your ideas regarding this topic and MVVM – as you indicated at the end of your post. Any plans when you will be publishing it? Looking forward… Have a nice weekend.
http://social.msdn.microsoft.com/Forums/en/wpf/thread/eae7d245-7010-49db-929a-e37791b6829e
[…] How to change language at run-time in WPF with loadable Resource Dictionaries and DynamicResource Bi… Category: Localization, WPF | Comment (RSS) | Trackback […]
Hi, I was pretty excited, reading about your solution. So, I downloaded your sample project for testing, but unfortunately it doesn’t work. All I can see if I run the solution are three empty TextBoxes and one Button (which has a height of about 1 mm). :/
Any suggestions?
[…] « How to load a DictionaryStyle.xaml file at run time? How to change language at run-time in WPF with loadable Resource Dictionaries and DynamicResource Bi… […]