📜 ⬆️ ⬇️

Localization of WPF applications on the fly

There are many ways to localize a WPF application, but it is difficult to find a method that allows you to change the labels of elements in automatic mode without the need to close and reopen the form or completely restart the application. In this publication, I will talk about how to localize the WPF application, which allows you to change the application culture without restarting the application and forms. This solution requires the use of ResourceDictionary (XAML) for translating the interface (UI), and for localizing messages from code you can use resource files (RESX), which are conveniently used in the code and for editing which there is a plugin with a convenient editor ( ResX Resource Manager ).

The project is written in Visaul Basic .NET, as well as in C #. I hope this will facilitate the readability of the code for those who are not used to Visaul Basic .NET or C #.

First, create a new WPF Application project:

image
')
Do not forget to specify a neutral culture for the entire project.
  1. Open the project properties.
  2. Go to the tab Application .
  3. Open Assembly Information .
  4. Choosing a neutral culture
    image
  5. Click OK.

Next, add the resources folder to the project for localization files.

In the Resources folder, create a Resource Dictionary file (WPF) , call it lang.xaml, and add an attribute to the ResourceDictionary element already created that will allow you to describe values ​​with an indication of the type:

xmlns:v="clr-namespace:System;assembly=mscorlib" 

Now add the file to the application resources:
  1. Open the Application.xaml file ( App.xaml for C #);
  2. Add a ResourceDictionary element to Application.Resources ;
  3. In the ResourceDictionary element, add the ResourceDictionary.MergedDictionaries element (here we will store all our ResourceDictionary);
  4. In the ResourceDictionary.MergedDictionaries element, add a ResourceDictionary element with a Source attribute that refers to the lang.xaml file.

Result example
 <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Resources/lang.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> 

Now we need to add localized data for the UI inside the ResourceDictionary element in the lang.xaml file:

 <v:String x:Key="m_Title">WPF Localization example</v:String> 

In this case, we put a text value (String), accessible by the key m_Title .

Sample application data
 <v:String x:Key="m_Title">WPF Localization example</v:String> <v:String x:Key="m_lblHelloWorld">Hello world!</v:String> <v:String x:Key="m_menu_Language">Language</v:String> <v:Double x:Key="m_Number">20.15</v:Double> 

For other application cultures, duplicate the lang.xaml file in the Resources folder and rename it to lang. ru-RU .xaml , where ru-RU is the name of the culture ( Culture name ). After duplication, you can translate the values. It is advisable to do this after adding all the values ​​to the lang.xaml resource file .

Translated resource file into Russian culture (ru-RU)
 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:v="clr-namespace:System;assembly=mscorlib"> <!-- Main window --> <v:String x:Key="m_Title"> WPF </v:String> <v:String x:Key="m_lblHelloWorld"> !</v:String> <v:String x:Key="m_menu_Language"></v:String> <v:Double x:Key="m_Number">10.5</v:Double> </ResourceDictionary> 

Now in the xaml code of the window we add elements, and the text for them will be taken using dynamic resources:



As you can see from the image above, Visual Studio sees the resources we created earlier.

A note about the Slider element: The Value property is a Double , so you can only use a resource of the same type.

First start
We brought to the resources the name of the window, the name of the menu for changing the culture of the application, the text of the Label and the value of the Slider element.

Now let's start writing the code.

To begin with, in the Application class ( App for C #) let us indicate which cultures our application supports:
Visual Basic .NET
 Class Application Private Shared m_Languages As New List(Of CultureInfo) Public Shared ReadOnly Property Languages As List(Of CultureInfo) Get Return m_Languages End Get End Property Public Sub New() m_Languages.Clear() m_Languages.Add(New CultureInfo("en-US")) '     m_Languages.Add(New CultureInfo("ru-RU")) End Sub End Class 
C #
 public partial class App : Application { private static List<CultureInfo> m_Languages = new List<CultureInfo>(); public static List<CultureInfo> Languages { get { return m_Languages; } } public App() { m_Languages.Clear(); m_Languages.Add(new CultureInfo("en-US")); //     m_Languages.Add(new CultureInfo("ru-RU")); } } 

At the application level, we implement the functionality that allows switching the culture from any window without duplicate code.
We add the static Language property to the Application class ( App for C #), which will return the current culture, and changing the culture will replace the dictionary of the resources of the previous culture with a new one and cause an event that allows all windows to perform additional actions when changing culture.

Visual Basic .NET
 '      Public Shared Event LanguageChanged(sender As Object, e As EventArgs) Public Shared Property Language As CultureInfo Get Return System.Threading.Thread.CurrentThread.CurrentUICulture End Get Set(value As CultureInfo) If value Is Nothing Then Throw New ArgumentNullException("value") If value.Equals(System.Threading.Thread.CurrentThread.CurrentUICulture) Then Exit Property '1.   : System.Threading.Thread.CurrentThread.CurrentUICulture = value '2.  ResourceDictionary    Dim dict As New ResourceDictionary() Select Case value.Name Case "ru-RU" dict.Source = New Uri(String.Format("Resources/lang.{0}.xaml", value.Name), UriKind.Relative) Case Else dict.Source = New Uri("Resources/lang.xaml", UriKind.Relative) End Select '3.   ResourceDictionary       ResourceDictionary Dim oldDict As ResourceDictionary = (From d In My.Application.Resources.MergedDictionaries _ Where d.Source IsNot Nothing _ AndAlso d.Source.OriginalString.StartsWith("Resources/lang.") _ Select d).First If oldDict IsNot Nothing Then Dim ind As Integer = My.Application.Resources.MergedDictionaries.IndexOf(oldDict) My.Application.Resources.MergedDictionaries.Remove(oldDict) My.Application.Resources.MergedDictionaries.Insert(ind, dict) Else My.Application.Resources.MergedDictionaries.Add(dict) End If '4.      . RaiseEvent LanguageChanged(Application.Current, New EventArgs) End Set End Property 
C #
 //      public static event EventHandler LanguageChanged; public static CultureInfo Language { get { return System.Threading.Thread.CurrentThread.CurrentUICulture; } set { if(value==null) throw new ArgumentNullException("value"); if(value==System.Threading.Thread.CurrentThread.CurrentUICulture) return; //1.   : System.Threading.Thread.CurrentThread.CurrentUICulture = value; //2.  ResourceDictionary    ResourceDictionary dict = new ResourceDictionary(); switch(value.Name){ case "ru-RU": dict.Source = new Uri(String.Format("Resources/lang.{0}.xaml", value.Name), UriKind.Relative); break; default: dict.Source = new Uri("Resources/lang.xaml", UriKind.Relative); break; } //3.   ResourceDictionary       ResourceDictionary ResourceDictionary oldDict = (from d in Application.Current.Resources.MergedDictionaries where d.Source != null && d.Source.OriginalString.StartsWith("Resources/lang.") select d).First(); if (oldDict != null) { int ind = Application.Current.Resources.MergedDictionaries.IndexOf(oldDict); Application.Current.Resources.MergedDictionaries.Remove(oldDict); Application.Current.Resources.MergedDictionaries.Insert(ind, dict); } else { Application.Current.Resources.MergedDictionaries.Add(dict); } //4.      . LanguageChanged(Application.Current, new EventArgs()); } } 

Well, it remains to teach our window to switch the culture of the program. When creating a new window, we’ll add all the cultures supported by the application to the culture change menu, as well as add an event handler, Application.LanguageChanged , which we’ve previously created. We also add a handler for clicking on the ChangeLanguageClick culture change point , which will change the application culture and the LanguageChanged function for handling the Application.LanguageChanged event:

Visual Basic .NET
 Class MainWindow Public Sub New() InitializeComponent() '       AddHandler Application.LanguageChanged, AddressOf LanguageChanged Dim currLang = Application.Language '   : menuLanguage.Items.Clear() For Each lang In Application.Languages Dim menuLang As New MenuItem() menuLang.Header = lang.DisplayName menuLang.Tag = lang menuLang.IsChecked = lang.Equals(currLang) AddHandler menuLang.Click, AddressOf ChangeLanguageClick menuLanguage.Items.Add(menuLang) Next End Sub Private Sub LanguageChanged(sender As Object, e As EventArgs) Dim currLang = Application.Language '        For Each i As MenuItem In menuLanguage.Items Dim ci As CultureInfo = TryCast(i.Tag, CultureInfo) i.IsChecked = ci IsNot Nothing AndAlso ci.Equals(currLang) Next End Sub Private Sub ChangeLanguageClick(sender As Object, e As RoutedEventArgs) Dim mi As MenuItem = TryCast(sender, MenuItem) If mi IsNot Nothing Then Dim lang As CultureInfo = TryCast(mi.Tag, CultureInfo) If lang IsNot Nothing Then Application.Language = lang End If End If End Sub End Class 
C #
 namespace WPFLocalizationCSharp { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); App.LanguageChanged += LanguageChanged; CultureInfo currLang = App.Language; //   : menuLanguage.Items.Clear(); foreach (var lang in App.Languages) { MenuItem menuLang = new MenuItem(); menuLang.Header = lang.DisplayName; menuLang.Tag = lang; menuLang.IsChecked = lang.Equals(currLang); menuLang.Click += ChangeLanguageClick; menuLanguage.Items.Add(menuLang); } } private void LanguageChanged(Object sender, EventArgs e) { CultureInfo currLang = App.Language; //        foreach (MenuItem i in menuLanguage.Items) { CultureInfo ci = i.Tag as CultureInfo; i.IsChecked = ci != null && ci.Equals(currLang); } } private void ChangeLanguageClick(Object sender, EventArgs e) { MenuItem mi = sender as MenuItem; if (mi != null) { CultureInfo lang = mi.Tag as CultureInfo; if (lang != null) { App.Language = lang; } } } } } 

The application is ready. But for complete happiness, we will configure the application in such a way that it will remember our chosen culture when launching the application.

Add the DefaultLanguage setting to the project, specify the System.Globalization.CultureInfo type (located in the mscorlib library), and specify the default neutral project culture:



We also add 2 additional functions to the Application class:

Visaul Basic .NET
  Private Sub Application_LoadCompleted(sender As Object, e As NavigationEventArgs) Handles Me.LoadCompleted Language = My.Settings.DefaultLanguage End Sub Private Shared Sub OnLanguageChanged(sender As Object, e As EventArgs) Handles MyClass.LanguageChanged My.Settings.DefaultLanguage = Language My.Settings.Save() End Sub 
C #
 private void Application_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) { Language = WPFLocalizationCSharp.Properties.Settings.Default.DefaultLanguage; } private void App_LanguageChanged(Object sender, EventArgs e) { WPFLocalizationCSharp.Properties.Settings.Default.DefaultLanguage = Language; WPFLocalizationCSharp.Properties.Settings.Default.Save(); } 

In App.xaml, we add a LoadCompleted event handler to the Application element:

 LoadCompleted="Application_LoadCompleted" 

Add the App.LanguageChanged event handler to the App class constructor:

 App.LanguageChanged += App_LanguageChanged; 

The application will now start with the culture that was selected when the application was closed.

The whole project is posted on GitHub .

UDP : (2017.02.13)
The code contains a bug with preserving culture and initializing a program with a culture that is not a default culture. The bug has been fixed on github.

Source: https://habr.com/ru/post/256193/


All Articles