Dynamic Localization In WPF

In my current project, I needed a localization feature where users can switch languages at run time. After googling for several hours, I found an article that was close to my requirements except that it didn't support the design time. Another solution had design time support but it was far more complex. So I decided to develop my own solution by extending one of them that was closer to my requirements.

Please note, this is not purely my idea. I just extended it to fit my requirements and I wanted to share this.

My requirements are.

  • Application UI uses the language of the operating system by default.
  • User can change the language at run time.
  • The application supports design time, so the developer can see the default words while developing.
  • Localized words be separated across multiple files. Each window (page) uses one file for easy development and maintenance.

Let’s begin.

  1. Create a WPF application and name it “DynamicLocalization”. You can change the name to anything you want.



  2. Create a new folder in application root directory and name it “i18N”.



  3. Create a new "Resource Dictionary" file in the folder i18N, and name it “MainWindow.en-US.xaml”.


The name is important as we will search for this file using a certain pattern, as following.

  • MainWindow is the name of the class that we will implement for localization. We will create separate files for each language and window or page.
  • en-US is culture info string code for English US.
  1. Change the properties of the file as follows.


Please note that we use blank for property “Custom Tool”.

  1. In the file MainWindow.en-US.xaml, replace the following line,



  2. with the the following line.

  1. Add the following string resource as identifier, so we can easily manage resources.



  2. Add required string resources as much as you need. In my example, I created three resource strings.


  3. Copy “MainWindow.en-US.xaml” to another file as much as you need, rename it according to culture string code, and change the value. Here, I copied and renamed it to “MainWindow.id-ID.xaml” for Indonesian translation. Do not forget to set its property as above.



  4. Next, we embed a Windows resource in MainWindow.xaml, so that we see the word at design time. Add the following code to “ManWindow.xaml”.


  5. Design a form example to demonstrate the dynamic localization. First, we update the WindowTitle to use the string from resource dictionary we assigned above.


Then, add the following code to MainWindow.xaml between Grid declaration.


And, add the following code to code-behind (MainWindow.xaml.cs).


Now, you can test and run the project. You will see the result as follow. Until now, we have not implemented the dynamic localization.


  1. Next, create a class, name the file as “LocUtil.cs”, and add the following code to it.
    1. using Microsoft.Win32;  
    2. using System;  
    3. using System.Globalization;  
    4. using System.IO;  
    5. using System.Threading;  
    6. using System.Windows;  
    7. namespace DynamicLocalization {  
    8.     public static class LocUtil {  
    9.         /// <summary>  
    10.         /// Get application name from an element  
    11.         /// </summary>  
    12.         /// <param name="element"></param>  
    13.         /// <returns></returns>  
    14.         private static string getAppName(FrameworkElement element) {  
    15.             var elType = element.GetType().ToString();  
    16.             var elNames = elType.Split('.');  
    17.             return elNames[0];  
    18.         }  
    19.         /// <summary>  
    20.         /// Generate a name from an element base on its class name  
    21.         /// </summary>  
    22.         /// <param name="element"></param>  
    23.         /// <returns></returns>  
    24.         private static string getElementName(FrameworkElement element) {  
    25.             var elType = element.GetType().ToString();  
    26.             var elNames = elType.Split('.');  
    27.             var elName = "";  
    28.             if (elNames.Length >= 2) elName = elNames[elNames.Length - 1];  
    29.             return elName;  
    30.         }  
    31.         /// <summary>  
    32.         /// Get current culture info name base on previously saved setting if any,  
    33.         /// otherwise get from OS language  
    34.         /// </summary>  
    35.         /// <param name="element"></param>  
    36.         /// <returns></returns>  
    37.         public static string GetCurrentCultureName(FrameworkElement element) {  
    38.             RegistryKey curLocInfo = Registry.CurrentUser.OpenSubKey("GsmLib" + @ "\" + getAppName(element), false);  
    39.                 var cultureName = CultureInfo.CurrentUICulture.Name;  
    40.                 if (curLocInfo != null) {  
    41.                     cultureName = curLocInfo.GetValue(getElementName(element) + ".localization""en-US").ToString();  
    42.                 }  
    43.                 return cultureName;  
    44.             }  
    45.             /// <summary>  
    46.             /// Set language based on previously save language setting,  
    47.             /// otherwise set to OS lanaguage  
    48.             /// </summary>  
    49.             /// <param name="element"></param>  
    50.             public static void SetDefaultLanguage(FrameworkElement element) {  
    51.                 SetLanguageResourceDictionary(element, GetLocXAMLFilePath(getElementName(element), GetCurrentCultureName(element)));  
    52.             }  
    53.             /// <summary>  
    54.             /// Dynamically load a Localization ResourceDictionary from a file  
    55.             /// </summary>  
    56.             public static void SwitchLanguage(FrameworkElement element, string inFiveCharLang) {  
    57.                 Thread.CurrentThread.CurrentUICulture = new CultureInfo(inFiveCharLang);  
    58.                 SetLanguageResourceDictionary(element, GetLocXAMLFilePath(getElementName(element), inFiveCharLang));  
    59.                 // Save new culture info to registry  
    60.                 RegistryKey UserPrefs = Registry.CurrentUser.OpenSubKey("GsmLib" + @ "\" + getAppName(element), true);  
    61.                     if (UserPrefs == null) {  
    62.                         // Value does not already exist so create it  
    63.                         RegistryKey newKey = Registry.CurrentUser.CreateSubKey("GsmLib");  
    64.                         UserPrefs = newKey.CreateSubKey(getAppName(element));  
    65.                     }  
    66.                     UserPrefs.SetValue(getElementName(element) + ".localization", inFiveCharLang);  
    67.                 }  
    68.                 /// <summary>  
    69.                 /// Returns the path to the ResourceDictionary file based on the language character string.  
    70.                 /// </summary>  
    71.                 /// <param name="inFiveCharLang"></param>  
    72.                 /// <returns></returns>  
    73.                 public static string GetLocXAMLFilePath(string element, string inFiveCharLang) {  
    74.                     string locXamlFile = element + "." + inFiveCharLang + ".xaml";  
    75.                     string directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);  
    76.                     return Path.Combine(directory, "i18N", locXamlFile);  
    77.                 }  
    78.                 /// <summary>  
    79.                 /// Sets or replaces the ResourceDictionary by dynamically loading  
    80.                 /// a Localization ResourceDictionary from the file path passed in.  
    81.                 /// </summary>  
    82.                 /// <param name="inFile"></param>  
    83.                 private static void SetLanguageResourceDictionary(FrameworkElement element, String inFile) {  
    84.                     if (File.Exists(inFile)) {  
    85.                         // Read in ResourceDictionary File  
    86.                         var languageDictionary = new ResourceDictionary();  
    87.                         languageDictionary.Source = new Uri(inFile);  
    88.                         // Remove any previous Localization dictionaries loaded  
    89.                         int langDictId = -1;  
    90.                         for (int i = 0; i < element.Resources.MergedDictionaries.Count; i++) {  
    91.                             var md = element.Resources.MergedDictionaries[i];  
    92.                             // Make sure your Localization ResourceDictionarys have the ResourceDictionaryName  
    93.                             // key and that it is set to a value starting with "Loc-".  
    94.                             if (md.Contains("ResourceDictionaryName")) {  
    95.                                 if (md["ResourceDictionaryName"].ToString().StartsWith("Loc-")) {  
    96.                                     langDictId = i;  
    97.                                     break;  
    98.                                 }  
    99.                             }  
    100.                         }  
    101.                         if (langDictId == -1) {  
    102.                             // Add in newly loaded Resource Dictionary  
    103.                             element.Resources.MergedDictionaries.Add(languageDictionary);  
    104.                         } else {  
    105.                             // Replace the current langage dictionary with the new one  
    106.                             element.Resources.MergedDictionaries[langDictId] = languageDictionary;  
    107.                         }  
    108.                     } else {  
    109.                         MessageBox.Show("'" + inFile + "' not found.");  
    110.                     }  
    111.                 }  
    112.             }  
    113.         }  
  1. Next, update the MainWindow constructor to set the language to previously saved setting (if any); otherwise. set it to OS language.



  2. And add the following code to the MenuItem_Click method.

Now, you can run the application and test dynamic localization using Resource Dictionary. I hope, this article will help someone who is looking for such a solution.

Up Next
    Ebook Download
    View all
    Learn
    View all