{"id":55,"date":"2012-01-21T11:24:00","date_gmt":"2012-01-21T11:24:00","guid":{"rendered":"http:\/\/tpodolak.com.hostingasp.pl\/blog\/2012\/01\/21\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/"},"modified":"2016-01-31T00:22:53","modified_gmt":"2016-01-31T00:22:53","slug":"lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2012\/01\/21\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/","title":{"rendered":"Lokalizowanie aplikacji WPF oraz Silverlight 5 przy u\u017cyciu MarkupExtension"},"content":{"rendered":"<p>W poprzednim wpisie przedstawi\u0142em w jaki spos\u00f3b mo\u017cna lokalizowa\u0107 aplikacj\u0119 napisan\u0105 w Silverlight 4 oraz Windows Phone, wykorzystuj\u0105c do tego ten sam mechanizm. Tym razem zademonstruje w jaki spos\u00f3b mo\u017cna nieco upro\u015bci\u0107 sk\u0142adnie t\u0142umaczenia wykorzystuj\u0105c do tego MarkupExtension.<br \/>\nJe\u017celi kiedykolwiek pisa\u0142e\u015b co\u015b w Silverlighcie, Windows Phonie lub WPF-ie istnieje du\u017ca szansa, \u017ce u\u017cywa\u0142e\u015b ju\u017c MarkupExtension. Do najpopularniejszych MarkupExtensions nale\u017c\u0105 takie s\u0142owa kluczowe (u\u017cywane w XAML-u) jak:<\/p>\n<ul>\n<li>Binding<\/li>\n<li>StaticResource<\/li>\n<li>DynamicResource<\/li>\n<li>TemplateBinding<\/li>\n<\/ul>\n<p>Na potrzeby mechanizmu lokalizowania aplikacji WPF (ewentualnie Silverlight 5) stworzymy customowe MarkupExtenssion, kt\u00f3re b\u0119dzie odpowiedzialne za t\u0142umaczenie element\u00f3w UI naszej aplikacji.<br \/>\nZacznijmy od przygotowania plik\u00f3w zasob\u00f3w. Podobnie jak w poprzednim wpisie utw\u00f3rzmy trzy pliki:<\/p>\n<ul>\n<li>Localization.resx<\/li>\n<li>Localization.pl-PL.resx<\/li>\n<li>Localization.en-US.resx<\/li>\n<\/ul>\n<p>W plikach tych b\u0119dziemy przechowywa\u0107 nasze t\u0142umaczenia. Nast\u0119pnie stw\u00f3rzmy klas\u0119 Translator, kt\u00f3ra b\u0119dzie dziedziczy\u0142a po klasie MarkupExtension.Do klasy tej dodajmy w\u0142a\u015bciwo\u015b\u0107<\/p>\n<pre lang=\"csharp\">\r\npublic string Key{ get; set; }\r\n<\/pre>\n<p>,kt\u00f3ra b\u0119dzie przechowywa\u0107 klucz dzi\u0119ki kt\u00f3remu z pliku zasob\u00f3w wyci\u0105gniemy tekst w odpowiednim j\u0119zyku. W kolejnym kroku musimy przeci\u0105\u017cy\u0107 funkcj\u0119<\/p>\n<pre lang=\"csharp\">\r\npublic abstract object ProvideValue(IServiceProvider serviceProvider)\r\n<\/pre>\n<p>tak aby dostarczy\u0142a nam ona przet\u0142umaczony tekst.W moim pierwszym podej\u015bciu funkcja ta wygl\u0105da\u0142a w nast\u0119puj\u0105cy spos\u00f3b<\/p>\n<pre lang=\"csharp\">\r\npublic override object ProvideValue(IServiceProvider serviceProvider)\r\n{\r\n    Binding binding = new Binding(Key) { Source = new Localization(),Mode = BindingMode.OneWay};\r\n    return binding.ProvideValue(serviceProvider);\r\n}\r\n<\/pre>\n<p>Wadami takiego rozwi\u0105zania by\u0142o:<\/p>\n<ul>\n<li>du\u017ca liczba tworzonych obiekt\u00f3w &#8211; przy ka\u017cdym t\u0142umaczeniu tworzy\u0142em nowy obiekt Localization(), kt\u00f3ry prawdopodobnie mo\u017ce by\u0107 do\u015b\u0107 ci\u0119\u017ckim obiektem (zw\u0142aszcza gdy b\u0119dzie przechowywa\u0142 du\u017co t\u0142umaczonego tekstu)<\/li>\n<li>brak mo\u017cliwo\u015bci dynamicznej zmiany j\u0119zyka<\/li>\n<\/ul>\n<p>Ostatecznie zatem zrezygnowa\u0142em z przedstawionej wy\u017cej opcji i zdecydowa\u0142em si\u0119 na metod\u0119 odrobin\u0119 bardziej zaawansowan\u0105. Po pierwsze utworzy\u0142em klas\u0119 TranslationManager<\/p>\n<pre lang=\"csharp\">\r\npublic class TranslationManager\r\n{\r\n    public event Action<CultureInfo> LanguageChanged = val => { };\r\n    private static TranslationManager _instance;\r\n    private static readonly object LockInstance = new object();\r\n    public static TranslationManager Instance\r\n    {\r\n        get\r\n        {\r\n            lock (LockInstance)\r\n            {\r\n                return _instance = _instance ?? new TranslationManager();\r\n            }\r\n        }\r\n    }\r\n    \r\n    private CultureInfo _currentCulture;\r\n    public CultureInfo CurrentCulture\r\n    {\r\n        get { return _currentCulture ?? (CurrentCulture = Thread.CurrentThread.CurrentUICulture); }\r\n        set\r\n        {\r\n            _currentCulture = value;\r\n            OnLanguageChanged(CurrentCulture);\r\n        }\r\n    }\r\n    \r\n    protected void OnLanguageChanged(CultureInfo culture)\r\n    {\r\n        LanguageChanged(culture);\r\n    }\r\n    \r\n    private TranslationManager()\r\n    {\r\n    }\r\n    \r\n    public string Translate(string key)\r\n    {\r\n        return Localization.ResourceManager.GetString(key, CurrentCulture);\r\n    }\r\n}\r\n<\/pre>\n<p>kt\u00f3ra b\u0119dzie zarz\u0105dza\u0142a t\u0142umaczeniami. Najwa\u017cniejsz\u0105 metod\u0105 tej klasy jest oczywi\u015bcie funkcja<\/p>\n<pre lang=\"csharp\">\r\npublic string Translate(string key)\r\n<\/pre>\n<p>kt\u00f3ra to zwraca tekst w odpowiednim j\u0119zyku &#8211; w zale\u017cno\u015bci od kultury, kt\u00f3ra zostanie ustawiona w TranslationManagerze. Ponadto TranslationManager posiada jedno zdarzenie<\/p>\n<pre lang=\"csharp\">\r\npublic event Action<CultureInfo> LanguageChanged\r\n<\/pre>\n<p>kt\u00f3re ma za zadanie poinformowa\u0107 UI o potrzebie od\u015bwie\u017cenia zbindowanych element\u00f3w. W jaki spos\u00f3b si\u0119 to odbywa ? Wszystko zawdzi\u0119czamy interfejsowi INotifyPropertyChanged oraz bindingom. Funkcja ProvideValue zosta\u0142a zmodyfikowana w nast\u0119puj\u0105cy spos\u00f3b<\/p>\n<pre lang=\"csharp\">\r\npublic override object ProvideValue(IServiceProvider serviceProvider)\r\n{\r\n    Binding binding = new Binding(\"Value\") { Source = new TranslationItem(Key), Mode = BindingMode.OneWay };\r\n    return binding.ProvideValue(serviceProvider);\r\n}\r\n<\/pre>\n<p>W funkcji tej tworze binding, kt\u00f3ry binduje si\u0119 do w\u0142a\u015bciwo\u015bci Value obiektu TranslationItem. TranslationItem jest to prosty obiekt kt\u00f3ry udost\u0119pnia w\u0142a\u015bciwo\u015b\u0107 Value zwracaj\u0105c\u0105 przet\u0142umaczony tekst. Dodatkowo obiekt ten podpina si\u0119 do zdarzenia LanguageChanged z klasy TranslationManager. W przypadku gdy kto\u015b zmieni j\u0119zyk aplikacji, odpalone zostanie zdarzenie NotifyPropertyChanged, kt\u00f3re poinformuje widok o potrzebie od\u015bwie\u017cenia odpowiednich element\u00f3w. Klasa TranslationItem wygl\u0105da zatem w nast\u0119puj\u0105cy spos\u00f3b<\/p>\n<pre lang=\"csharp\">\r\npublic sealed class TranslationItem : INotifyPropertyChanged, IDisposable\r\n{\r\n    public event PropertyChangedEventHandler PropertyChanged = delegate { };\r\n    private readonly string _key;\r\n    public TranslationItem(string key)\r\n    {\r\n        _key = key;\r\n        TranslationManager.Instance.LanguageChanged += Instance_LanguageChanged;\r\n    }\r\n\r\n    ~TranslationItem()\r\n    {\r\n        Dispose();\r\n    }\r\n\r\n    void Instance_LanguageChanged(System.Globalization.CultureInfo obj)\r\n    {\r\n        PropertyChanged(this, new PropertyChangedEventArgs(\"Value\"));\r\n    }\r\n\r\n    public string Value\r\n    {\r\n        get { return TranslationManager.Instance.Translate(_key); }\r\n    }\r\n\r\n    public  void Dispose()\r\n    {\r\n        TranslationManager.Instance.LanguageChanged -= Instance_LanguageChanged;\r\n    }\r\n}\r\n<\/pre>\n<p>Maj\u0105c gotowy mechanizm t\u0142umacz\u0105cy mo\u017cemy wykorzysta\u0107 go w nast\u0119puj\u0105cy spos\u00f3b w XAML-u<\/p>\n<pre lang=\"xml\">\r\n<Label Content=\"{WPFMarkupExtension:Translator Key=Title}\"\/>\r\n<\/pre>\n<p>Zauwa\u017cmy, \u017ce tekst do labelki jest przypisywany z wykorzystywaniem naszego customoweog MarkupExtension &#8211; WPFMarkupExtension:Translator (WPFMarkupExtension &#8211; jest to alias na namespace, w kt\u00f3rym znajduje si\u0119 nasza klasa Translator). W sk\u0142adni przekazujemy do w\u0142a\u015bciwo\u015bci Key klucz do tekstu (znajduj\u0105cego si\u0119 w resourcach), kt\u00f3ry chcemy t\u0142umaczy\u0107. W celu zmiany j\u0119zyka wystarczy, \u017ce ustawimy interesuj\u0105c\u0105 nas kultur\u0119 w klasie TranslationManager<\/p>\n<pre lang=\"csharp\">\r\nTranslationManager.Instance.CurrentCulture = new CultureInfo(\"en-US\")\r\n<\/pre>\n<p>Przyk\u0142adowy widok wykorzystuj\u0105cy napisany translator, oraz pokazuj\u0105cy dynamiczn\u0105 zmian\u0119 j\u0119zyka mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b (dorzucamy nast\u0119puj\u0105ce linijki do MainWindow.xaml)<\/p>\n<pre lang=\"xml\">\r\n<StackPanel>\r\n    <Label Content=\"{WPFMarkupExtension:Translator Key=Key}\"\/>\r\n    <Button Command=\"{Binding PolishCommand}\">Polski<\/Button>\r\n    <Button Command=\"{Binding EnglishCommand}\">English<\/Button>\r\n<\/StackPanel>\r\n<\/pre>\n<p>Nast\u0119pnie tworzymy ViewModel do naszego okna<\/p>\n<pre lang=\"csharp\">\r\npublic class MainPageViewModel\r\n{\r\n        public DelegateCommand PolishCommand { get; set; }\r\n        public DelegateCommand EnglishCommand { get; set; }\r\n        \r\n        public MainPageViewModel()\r\n        {\r\n            PolishCommand = new DelegateCommand(()=> TranslationManager.Instance.CurrentCulture = new CultureInfo(\"pl-PL\"));\r\n            EnglishCommand = new DelegateCommand(()=>TranslationManager.Instance.CurrentCulture = new CultureInfo(\"en-US\"));\r\n        }\r\n}\r\n<\/pre>\n<p>Ostatecznie przypisujemy obiekt klasy MainPageViewModel do DataContextu MainWindow.xaml<\/p>\n<pre lang=\"csharp\">\r\npublic MainWindow()\r\n{\r\n    InitializeComponent();\r\n    DataContext = new MainPageViewModel();\r\n}\r\n<\/pre>\n<p><a href=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2012\/01\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/result.jpg\" rel=\"attachment wp-att-448\"><img decoding=\"async\" src=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2012\/01\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/result.jpg\" alt=\"MarkupExtension\" width=\"800\" class=\"aligncenter size-full wp-image-448\" srcset=\"https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2012\/01\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/result.jpg 1064w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2012\/01\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/result-150x50.jpg 150w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2012\/01\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/result-300x100.jpg 300w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2012\/01\/lokalizowanie-aplikacji-wpf-oraz-silverlight-5-przy-uzyciu-markupextension\/result-1024x343.jpg 1024w\" sizes=\"(max-width: 1064px) 100vw, 1064px\" \/><\/a><br \/>\nKod do projektu mo\u017cna znale\u017a\u0107 pod tym linkiem<br \/>\nhttp:\/\/www.4shared.com\/rar\/SwDXay7V\/WPFMarkupExtension.html<\/p>\n","protected":false},"excerpt":{"rendered":"<p>W poprzednim wpisie przedstawi\u0142em w jaki spos\u00f3b mo\u017cna lokalizowa\u0107 aplikacj\u0119 napisan\u0105 w Silverlight 4 oraz Windows Phone, wykorzystuj\u0105c do tego ten sam mechanizm. Tym razem zademonstruje w jaki spos\u00f3b mo\u017cna nieco upro\u015bci\u0107 sk\u0142adnie t\u0142umaczenia wykorzystuj\u0105c do tego MarkupExtension. Je\u017celi kiedykolwiek pisa\u0142e\u015b co\u015b w Silverlighcie, Windows Phonie lub WPF-ie istnieje du\u017ca szansa, \u017ce u\u017cywa\u0142e\u015b ju\u017c MarkupExtension. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[51,120,110,45],"tags":[197,250,246,191],"class_list":["post-55","post","type-post","status-publish","format-standard","hentry","category-localization","category-markupextension","category-silverlight5","category-wpf","tag-localization","tag-markupextension","tag-silverlight5","tag-wpf"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/55","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=55"}],"version-history":[{"count":6,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/55\/revisions"}],"predecessor-version":[{"id":560,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/55\/revisions\/560"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=55"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=55"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=55"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}