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.

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.

  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.