Pull to refresh

Локализация WPF приложений

Reading time5 min
Views35K
В статье я расскажу о существующих подходах к локализации WPF приложений и покажу подробно процесс локализации используя файлы ресурсов (resx). Это может быть полезно тем, кто только начинает изучать WPF или уже работает с этой технологией, но не сталкивался с многоязычными приложениями.

MSDN предлагает несколько путей локализации WPF приложений – например, XML-файлом, файлом ресурсов (RESX) или через XAML. В качестве примера там рассмотрен именно последний вариант, расписаны его плюсы – но есть так же и ряд недостатков, как:
1) Разные источники для локализации собственно UI (элементов разметки) и сообщений из кода (MessageBox, любые смены статуса и т.п.).
2) Излишние пляски с правкой проекта, выгрузкой таблицы строк через консоль, загрузка их в сборки-сателлиты.
3) Тщательный контроль Uid у каждого UI элемента.
Встречаются и собственные подходы, например через MergedDictionaries.
Каждый из них в силу тех или иных причин меня не очень устраивал – либо излишние телодвижения (типа ручного редактирование csproj файла, шаманства с локалями девелоперской машины), привлечение различных дополнительных утилит, лишние куски кода (слияние словарей в зависимости от локали) – мелочь, но неэстетично. А вот с resx файлами все сложилось как нельзя лучше – и более того, это оставило привычный (с WinForms) workflow перевода приложения.
Посмотрим подробнее, как же воспользоваться возможностями Visual Studio для локализации WPF приложений. Создадим тестовый проект WpfAppLocalized c одним окном – Login.

Для начала, определим, какие же элементы необходимо локализовать – метки Login, Password, кнопки Login, Cancel, сообщения о неверном логине / пароле, сообщение об успешном входе в систему.
Итак, откроем автоматически созданный файл ресурсов WpfAppLocalized / Properties / Resources.resx (или создадим новый файл ресурсов) – и заполним строковые ресурсы необходиыми значениями.

В коде можно получить доступ к ресурсам несколькими способами – но Студия услужливо создает прокси-классы – и чтобы обратиться к значению можно использовать статические свойства автоматически сгенерированного класса WpfAppLocalization.Properties.Resources – этот момент крайне важен. Пока же, начнем с самого простого – вывод сообщений. Пишем простой обработчик для клика по Login:
if (Login.Text.Equals("admin") && Password.Password.Equals("admin"))
{        
  MessageBox.Show(Properties.Resources.LoginSuccessfullMessage);
}
else
{
  ErrorMessage.Text = Properties.Resources.LoginFailedMessage;
}


* This source code was highlighted with Source Code Highlighter.

Все просто и понятно. Но теперь очередь самого интересного – локализация XAML разметки. Конечно, можно действовать в лоб – назначить каждому элементу имя и в момент инициализаци компонентов присваивать нужным свойствам значения из ресурсов – но это не путь джедая. Как я заметил выше, Студия генерит для ресурсного файла класс с набором статических свойств для доступа к каждой строке-ресурсу… и в голову сразу же приходит «Ну конечно, x:Static!». Markup-Extension x:Static служит для инициализации значений свойств XAML элементов через статичные свойства. Но для начала, нужно объявить свое пространство имен, через которые мы будет в разметке обращатся к статичному классу:
<Window x:Class="WpfAppLocalized.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:res="clr-namespace:WpfAppLocalized.Properties"
Title="Login" Height="150" Width="270" MaxWidth="270" MinWidth="270"  MaxHeight="150" MinHeight="150">


* This source code was highlighted with Source Code Highlighter.

Самая важная для нас строка – «xmlns:res=«clr-namespace:WpfAppLocalized.Properties»». Это собственно привязка XML namespace res к пространству имен в коде. Теперь можно заполнять значения свойств контролов через Static – и вот как это выглядит для метки Login:
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Text="{x:Static res:Resources.LoginLabel}" />

* This source code was highlighted with Source Code Highlighter.

Как вы заметили, свойство Text теперь иницализируется через extension Static – т.е. берется значение статического свойства LoginLabel класса Resources. Однако, при попытке сделать такое в Visual Studio – редактор скажет, что «Type 'res:Resources' does not contain a static member named 'LoginLabel'», а при запуске вывалится исключение «'WpfAppLocalized.Properties.Resources.LoginLabel' StaticExtension value cannot be resolved to an enumeration, static field, or static property». Причина очень проста – по умолчанию, прокси-класс для ресурсов генерится с модификатором internal – т.е. эти ресурсы недоступны загрузчику XAML. Нужно просто изменить модификатор на public – после этого приложение будет работать.


Итак, на данном этапе все элементы интерфейса и все сообщения используют только тексты из ресурсов – теперь собственно пришло время добавить поддержку различных языков. Это делается предельно просто – нужно лишь скопировать исходный файл ресурсов Resources.resx – в новый Resources.{cl-LN}.resx, где cl – культура (страна), а LN – язык. Например, для русского языка в России файл ресурсов будет называться Resources.ru-RU.resx. Для, например, Канады – мы можем сделать два файла Resources.ca-EN.resx, Resources.ca-FR.resx — для английского и франзузского языка. Если необходимости деления на языки нет – то его можно опустить, Resources.ru.resx – будет применен для страны Россия. Итак, открываем новый файл ресурсов – и видим, что сейчас он заполнен так же, как и основной – но теперь можно смело переводить надписи на английском на родной русский – и чтобы избежать лишних файлов в проекте поставить для этого ресурса модификатор доступа No code generation (да, странноватый такой модификатор).

Отлично, перевод закончен. Теперь пришло время спросить — а где нужно кодить логику выбора ресурсов в зависимости от культуры? Ответ – а нигде не нужно. После успешного билда приложения в папке с EXE появится еще одна папка – ru, содержащая файл WpfAppLocalized.resources.dll. Это и есть локализованные ресурсы. В момент запуска приложения .NET Framework в зависимости от культуры и языка операционной системы будет подгружать ту или иную сборку-сателлит и соответственно будут использоваться те или иные ресурсы. Вот так выглядит локализованная версия приложения:


На этом все, спасибо за внимание.

P.S. Пока перечитывал статью, подумал – какие вопросы потенциально могут возникнуть и решил сразу ответить.

В: Как принудительно изменить язык приложения (дать выбор пользователю, например)?
О: Система определяет, ресурсы какой культуры подгружать используя значение System.Threading.Thread.CurrentThread.CurrentUICulture. Соответственно, чтобы изменить язык приложения на русский в конструктор WpfAppLocalization до InitializeComponent нужно добавить:
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(«en-US»);

В: Можно ли использовать одну ресурсную сборку в разных приложениях?
О: Да, можно – только подключение пространства имен несколько поменяется – туда добавится еще и имя сборки — xmlns:res=«clr-namespace:WpfAppLocalized.Properties, assembly=GlobalRes.dll».

В: Что если приложение будет запущено с языком, для которого нет сборки-сателлита?
О: Будут использованы ресурсы основной сборки.

UPD: Выложил проект WpfAppLocalized.
Tags:
Hubs:
+21
Comments22

Articles

Change theme settings