Всем добрый день. Я хочу представить на суд общественности (ещё один) простой способ сделать локализацию приложений. Стандартный механизм с ресурсными сборками меня не устраивает по следующим причинам:
- Получая значение локализованной строки в коде, очень хочется полагаться на всю мощь ООП и подсказки компилятора. Очень неприятно собрать проект в вечером в пятницу, а утром в субботу получить звонок от впахивающих overtime QA на тему того, что кто-то невнимательный написал GetResource(«asdf») вместо GetResource(«assf»), и теперь что-то падает или отображается неверно, а проект в понедельник уже сдавать в печать ...
- (В продолжение предыдущего пункта...) Писать string foo = language.Ui.PromtDialog.AdditionalQuestion просто приятнее, чем string foo = Resources.GetResource(«Ui_PromtDialog_AdditionalQuestion»). Да, в том числе и за счёт подсказок компилятора.
- Иногда локализовать нужно не строки, а целые объекты. Например, существительное (строка + род М/Ж/С/Мн) и прилагательное (строка М + строка Ж + строка С + строка Мн). Пихать в ресурсы сериализованную строку, а потом доставать и десериализовать каждый раз? Мсье знает толк в извращениях...
- Ресурсный файл — это плоский список строк, а хотелось бы, чтобы данные всё-таки имели более сложную иерархическую структуру, по которой не нужно ползать с помощью Ctrl+F.
- Создание нового языка должно быть настолько простым, насколько это возможно. Локализовать приложение должен быть способен человек, умеющий обращаться с компьютером и владеющий нужными языками. И ему для этого не нужны ни Visual Studio, ни возня с созданием ресурсных сборок.
Ещё одно обязательное требование — возможность простой привязки к локализации элементов UI. Желательно — одновременно и WPF, и WinForms.
Решение лежит на поверхности и по простоте способно соперничать с топором и лопатой. Следите за руками:
- Создаём класс с названием, например, Language, который и будет содержать в себе все локализованные ресурсы.
- Заполняем его свойствами типа «строка» и свойствами-объектами со строковыми свойствами («категориями»), и свойствами-объектами свойств-объектов, и… Глубину вложения выбрать по вкусу.
- Делаем класс Language (и все вложенные в него) сериализуемыми с помощью способа, который вызовет у почти рядового пользователя минимальное отторжение при попытке отредактировать файл с сериализованным языком. Мне больше всего импонирует XML, поэтому я выбираю, соответственно, атрибуты XmlType, XmlRoot, XmlElement, XmlAttribute. Фанаты JSON могут использовать JSON. Если под рукой есть удобный враппер для работы с ini-файлами — можно использовать и его. Всё в ваших руках.
- Вытягиваем язык на форму с помощью компонента BindingSource (WinForms), {x:Static} или <ObjectDataProvider> (WPF) и простой привязки данных.
- Создаём в папке с нашим приложением папку «Languages», «Localizations» (или как-нибудь в этом роде) и делаем в ней один или несколько файлов, в которых будут находиться сериализованные выбранным способом языки.
- При необходимости локализации более сложных вещей (картинок, например) язык будет хранить относительный путь к файлу ресурса. Сам файл в таком случае будет находиться в подпапке папки «Languages/Localizations».
- При загрузке приложения с помощью стандартного десериализатора подгружаются языки. Текущий выбранный язык определяется из сохранённых настроек, выбирается в выпадающем списке диалога на старте приложения (например, если приложение запускается впервые и в конфиге ничего нет), или выбирается автоматически из имеющихся на основании CultureInfo.CurrentCulture. Выбранный язык можно сохранить в любом объекте, доступ к которому (неважно каким образом — хоть через singleton, хоть через dependency injection,… — вписать предпочтительный вариант) может быть получен из тех мест, где требуются локализованные ресурсы.
Любому, кто захочет сделать новую локаль, нужно будет просто скопировать файл с удобным языком в папке «Languages/Localizations» и перевести имеющиеся в нём строки.
Пользуйтесь на здоровье. Код примера доступен для скачивания здесь. Написан он, очевидно, впопыхах, а потому несёт на себе печать непродуманного дизайна: например, языки подгружаются в коде главной формы, а не метода Main. Ну а за способ, которым язык привязан к форме в примере Wpf, можно и пальцы пассатижами обжать. Зато — работает на 100%. Рад буду предложениям по улучшению этого метода.
Автор: courage_andrey