Существует множество способом локализовать WPF-приложение, но сложно найти метод, позволяющий менять надписи элементов в автоматическом режиме без необходимости закрытия и повторного открытия формы или полного перезапуска приложения. В этой публикации я расскажу о способе локализации WPF приложения, который позволяет менять культуру приложения без перезапуска приложения и форм. Данное решение требует использования ResourceDictionary (XAML) для перевода интерфейса(UI); для локализации сообщений из кода можно использовать файлы ресурсов (RESX), которые удобно использовать в коде и для редактирования которых есть плагин с удобным редактором (ResX Resource Manager).
Проект написан на Visaul Basic .NET, а также на C#. Надеюсь это облегчит читаемость кода тем, кто не привык к Visaul Basic .NET или к C#.
Для начала создаём новый проект WPF Application:
- Открываем свойства проекта.
- Идём во вкладку Application.
- Открываем Assembly Information.
- Выбираем нейтральную культуру
- Жмём 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.
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Resources/lang.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
Теперь нам нужно добавить локализированные данные для UI внутрь элемента ResourceDictionary в файле lang.xaml:
<v:String x:Key="m_Title">WPF Localization example</v:String>
В данном случае мы поместили текстовое значение (String), доступное по ключу m_Title.
<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>
Для других культур приложения дублируем в папке Resources файл lang.xaml и переименовываем в lang.ru-RU.xaml, где ru-RU является названием культуры (Culture name). После дублирования можно переводить значения. Желательно это делать после того, когда добавим все значения в файл ресурсов lang.xaml.
<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>
Теперь в xaml коде окна добавим элементы, а текст для них будем браться используя динамические ресурсы:
Как видно из картики выше, Visual Studio видит ранее нами созданые ресурсы.
Примечание по поводу элемента Slider: свойство Value является типа Double, поэтому можно использовать только ресурс такого же типа.
Теперь приступим к написанию кода.
Для начала в классе Application(App для C#) укажем какие культуры поддерживает наше приложение:
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
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")); } }
На уровне приложения реализуем функционал позволяющий переключать культуру из любова окна без дублирующего кода.
Добавляем статическое свойство Language в класс Application(App для C#), которое будет возвращать текущую культуру, а меняя культуру заменит словарь ресурсов предыдущей культуры на новую и вызовет эвент позволяющий всем окнам выполнить дополнительные действия при смене культуры.
'Евент для оповещения всех окон приложения 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
//Евент для оповещения всех окон приложения 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:
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
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; } } } } }
Приложение готово. Но для полного счастья настроим приложение так, что бы оно запоминало нами выбранyую культуру при запуске приложения.
Добавляем в проект настройку DefaultLanguage , указываем тип System.Globalization.CultureInfo (находится в библиотеке mscorlib) и указываем значение по умолчанию нейтральную культуру проекта:
Так же в класс Application добавляем 2 дополнительных функции:
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
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.
ссылка на оригинал статьи http://habrahabr.ru/post/256193/
Добавить комментарий