[Из песочницы] Локализация WPF приложений на лету

Существует множество способом локализовать WPF-приложение, но сложно найти метод, позволяющий менять надписи элементов в автоматическом режиме без необходимости закрытия и повторного открытия формы или полного перезапуска приложения. В этой публикации я расскажу о способе локализации WPF приложения, который позволяет менять культуру приложения без перезапуска приложения и форм. Данное решение требует использования ResourceDictionary (XAML) для перевода интерфейса (UI); для локализации сообщений из кода можно использовать файлы ресурсов (RESX), которые удобно использовать в коде и для редактирования которых есть плагин с удобным редактором (ResX Resource Manager).Проект написан на Visaul Basic .NET, а также на C#. Надеюсь это облегчит читаемость кода тем, кто не привык к Visaul Basic .NET или к C#.Для начала создаём новый проект WPF Application:

image

Не забываем указать нейтральную культуру для всего проекта Открываем свойства проекта. Идём во вкладку Application. Открываем Assembly Information. Выбираем нейтральную культуруimage Жмём OK. Далее добавляем в проект папку Resources для файлов локализации.В папке Resources создаём файл Resource Dictionary (WPF), называем его lang.xaml и добавляем к уже созданному елементу ResourceDictionary аттрибут, который позволит описывать значения с указанием типа:

xmlns: v=«clr-namespace: System; assembly=mscorlib» Теперь добавим файл в ресурсы приложения: Открываем файл Application.xaml (App.xaml для C#); В Application.Resources добавляем элемент ResourceDictionary; В элемент ResourceDictionary добавляем элемент ResourceDictionary.MergedDictionaries (тут будем хранить все наши ResourceDictionary); В элемент ResourceDictionary.MergedDictionaries добавляем элемент ResourceDictionary с аттрибутом Source, который ссылается на файл lang.xaml. Пример результата Теперь нам нужно добавить локализированные данные для UI внутрь элемента ResourceDictionary в файле lang.xaml: WPF Localization example В данном случае мы поместили текстовое значение (String), доступное по ключу m_Title.Пример данных для приложения WPF Localization example Hello world! Language 20.15 Для других культур приложения дублируем в папке Resources файл lang.xaml и переименовываем в lang.ru-RU.xaml, где ru-RU является названием культуры (Culture name). После дублирования можно переводить значения. Желательно это делать после того, когда добавим все значения в файл ресурсов lang.xaml.Переведённые файл ресурсов на русскую культуру (ru-RU) Пример WPF локализации Привет мир! Язык 10.5 Теперь в xaml коде окна добавим элементы, а текст для них будем браться используя динамические ресурсы: 6ed463da85014daf806e85985f0d22df.png

Как видно из картинки выше, Visual Studio видит ранее нами созданые ресурсы.

Примечание по поводу элемента Slider: свойство Value является типа Double, поэтому можно использовать только ресурс такого же типа.

Первый запуск Мы вынесли в ресурсы название окна, название меню для смены культуры приложения, текст у Label и значение у Slider элемента.1cc38c8a705f4917a74d16eb37cf1da8.png Теперь приступим к написанию кода.Для начала в классе Application (App для C#) укажем какие культуры поддерживает наше приложение:

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 m_Languages = new List();

public static List Languages { get { return m_Languages; } }

public App () { m_Languages.Clear (); m_Languages.Add (new CultureInfo («en-US»)); //Нейтральная культура для этого проекта m_Languages.Add (new CultureInfo («ru-RU»)); } } На уровне приложения реализуем функционал позволяющий переключать культуру из любова окна без дублирующего кода.Добавляем статическое свойство Language в класс Application (App для C#), которое будет возвращать текущую культуру, а меняя культуру заменит словарь ресурсов предыдущей культуры на новую и вызовет эвент позволяющий всем окнам выполнить дополнительные действия при смене культуры.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 ()); } } Ну что ж, осталось научить наше окно переключать культуру программы. При создании нового окна добавим в меню смены культуры все поддерживаемые приложением культуры, а также добавим обработчик эвента юApplication.LanguageChanged, который ранее создали. Также добавим обработчик нажатия по пунту смены культуры ChangeLanguageClick, который будет менять у приложения культуру и функцию LanguageChanged для обработки события Application.LanguageChanged: 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 { ///

/// Interaction logic for MainWindow.xaml /// 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; } }

} } } Приложение готово. Но для полного счастья настроим приложение так, что бы оно запоминало нами выбранyю культуру при запуске приложения.Добавляем в проект настройку DefaultLanguage, указываем тип System.Globalization.CultureInfo (находится в библиотеке mscorlib) и указываем значение по умолчанию нейтральную культуру проекта:

1a7ee77217374c828cab92d92c5944ee.png

Так же в класс Application добавляем 2 дополнительных функции:

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 (); } В App.xaml к элементу Application добавляем обработчик LoadCompleted эвента: LoadCompleted=«Application_LoadCompleted» В конструктор класса App добавляем обработчик App.LanguageChanged эвента: App.LanguageChanged += App_LanguageChanged; Теперь приложение будет запускаться с культурой, которая была выбрана при закрытии приложения.Весь проект выложен на GitHub.

© Habrahabr.ru