Начну пожалуй.
В процессе работы над проектом столкнулся с проблемой локализации, и моя святая вера в то что язык в приложении, которое собираются продавать в разных странах должен быть английским и никаким другим (что удивительно совпадает с мнением чуваков из MS =) ), разбилась. да разбилась о неколебимую твердь и т.д. и т.п.
Короче. Есть проблема и есть несколько способов ее решения. На codeproject'e есть несколько статей посвященных данной проблеме:
- http://www.codeproject.com/KB/WPF/WPFUsingLocbaml.aspx
- http://www.codeproject.com/KB/WPF/Localization_in_WPF.aspx
- http://www.codeproject.com/KB/WPF/WPFLocalize.aspx
- http://www.codeproject.com/KB/WPF/WPF-Mulit-Lingual.aspx
Во всех этих источниках предлагается готовое решение, но каждый из подходов обладает своими достоинствами и недостатками (с моей точки зрения), которые я вкартце перечислю:
- Локализация, используя resx файлы и custom code generator (его применение обусловлено ограничением binding engine wpf - binding создается только лишь для public свойств public класс'ов) (статья 1): из достоинств - поддержка design-time и легкость в использовании (всего лишь создать ObjectDataProvider натравить его на сгенерированный public класс с ресурсами и спокойно Bind'ить строки к DEPENDENCY свойствам объектов в xaml). К недостаткам прежде всего следует отнести необходимость дополнительной инсталляции к VS, и самое главное таким способом можно локализовать только dependency свойства! В других же сценариях данный способ недейственен.
- Локализация используя LocBaml (упрощенный вариант - создание ResourceDictionary со строками и использование их через MergedDictionaries и DynamicResource)(статья 1): урощенный да не очень. Все равно используя LocBaml сборка будет проходить в 2 этапа.
- Локализация используя LocBaml (полный вариант - следуя официальным руководствам от MS локализуется сам XAML, точнее BAML)(статья 1, 2): см. п.2.
- Подход описанный в статье №3 очень похож на 1 метод в статье 1, но отличается от него тем, что для каждого класса ресурсов автор предлагает писать класс обертку вручную, а у меня в 45 проектов и почти в каждом из них есть ресурсы! (на заметку: если использовать ссылку на файл то может быть удалось бы избежать многократного копирования, надо проверить); в design-time будут отображаться default ресурсы. Из достоинств - возможность переключать культуры в runtime.
- Локализация используя xml файлы, XmlDataProvider, XPath запросы в Binding(статья 4): на мой взгляд отличный способ - и поддержка design-time и runtime переключение культур, но единственный недостаток - сложность поддержания множества файлов с локализованными ресурсами.
Итак, проанализировав предложенные подходы я выделил следующие требования, которым должно удовлетворять искомое решение:
- отсутствие ограничения на локализуемые свойства (не только dependency но и обычные clr свойства);
- легкость локализации и поддержания ресурсов;
- отсутсвие необходимости писать классы обертки или использовать custom tools;
- минимальная поддержка designtime - главное чтобы UI открывался в Blend.
Слив все в один котел и немного помешав, со дна всплыло решение - markup extension.
Идея подхода следующая: все ресурсы хранятся в resx файлах, что дает нам замечательные возможности хранить в VSS, редактировать и добавлять ресурсы прямо в VS, т.к. локализация в Net 2.0 действительно была сделана великолепно; в xaml вместо строк вы подставляете этот extension и передаете ему Id строки в файле ресурсов, а extension в момент распарсивания xaml (runtime) подставляет полученную строку или сообщение об ошибке. Выглядит это примерно так:
<TextBlock Text={me:CultureResource resourceId}/>
, где me: - объявление пространства имен, в котором существует класс CultureResourceExtension. Данный подход позволяет локализовать не только dependency свойства объектов, но вообще любое строковое свойство любого объекта, создаваемого в XAML. Из недостатков же - отсутствие поддержки design-time, точнее весьма ограниченная поддержка ( все открывается в Blend, но вместо искомых строк - "ResourceStr", - что, как показала практика, некритично).
Еще из недостатков необходимость явно задавать имя класса ресурсов, в случае если:
- имя сборки отличается от пространства имен по умолчанию;
- файл с ресурсами не лежит в папке /Properties;
- и то и другое вместе.
Для решения этой проблемы в класс введено свойство BaseName, в котором явно прописывается имя класса с ресурсами в виде строки. Для упрощения синтаксиса, я ввел класс BaseBinder с наследуемым по визуальному дереву attached свойством Base. С помощью него можно существенно сократить семантику записи данного класса, задавая это свойство лишь один раз корневому объектау визуального дерева. И вот тут уже будет действовать ограничение на DependencyObject - таким образом упростить запись можно только в визуальном дереве WPF, для обычных же clr объектов придется писать BaseName при каждом появлении CultureResource.
Самым сложным сценарием оказался текст форматированный с помощью модели документов WPF! Но и этот сценарий удалось решить, загружая в ресурсы не строки, а Xaml (сомнительное решение согласен, но работает). Просмотр же этого самого loose-xaml осуществляет custom control XamlViewer о котором я напишу, пожалуй, в следующий раз.
Код класса с attached свойством для упрощения записи:
public static class ResourceBaseBinder
{
public static string GetBase(DependencyObject obj)
{
return (string)obj.GetValue(BaseProperty);
}
public static void SetBase(DependencyObject obj, string value)
{
obj.SetValue(BaseProperty, value);
}
public static readonly DependencyProperty BaseProperty =
DependencyProperty.RegisterAttached("Base", typeof(string), typeof(CultureResourceExtension),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.Inherits));
}
Главный метод класса расширения:
Удачи.
6 комментариев:
прочитал перввых два предложения и последних два.
очень понравился постинг!
так держать
Йо. Сначала хотел хотел написать что ничего не понял, потом подумал что твой пост не надо комментировать человеку который не разбирается в программировании (т.е. мне) :-)) А так чо, хорошее начинание! :-) Привет!
а код markup extension приведен частично - специально?
главное идея?
я надеялся, что 2 закрывающие скобки вы сами поставите =)
а вообще,да. идея - самое ценное.
да, насчет всего кода, это я погорячился. Пары скобок там конечно будет маловато, ибо есть свойство и поля, но назначения их очевидны. так что
приношу свои извинения,
статья весьма полезная.
в развитие идеи, взгляните на этот проект: http://wpflocalizeextension.codeplex.com/
у них есть ссылки на меня =)
Отправить комментарий