WPF i aplikacje wielojęzyczne

Chyba każdy zgodzi się z tym, że ogromna część oprogramowania przeznaczona jest do użytku przez osoby z różnych części świata – a co za tym idzie posługującymi się różnymi językami. Oczywiście istnieją również aplikacje przeznaczone na „rynek lokalny”, jednak to raczej nisza. Jak zatem w łatwy i przyjemny sposób sprawić, aby nasza aplikacja obsługiwała wiele języków jednocześnie? Sposobów znajdzie się wiele, jednak na szczęście w przypadku .NET’a ta sprawa jest naprawdę prosta 😉

Pliki zasobów

Właściwie do każdego projektu pisanego w Visual Studio możemy dodać tzw. Resources Files, czyli pliki zasobów. To właśnie w nich możemy przechowywać wszystkie ciągi znaków zawarte w naszej aplikacji – dla każdego języka osobno. Jak to w praktyce wygląda? Załóżmy, że chcemy, aby nasza aplikacja obsługiwała dwa języki: domyślnie angielski, oraz dodatkowo polski. W tym celu należy stworzyć dwa pliki *.resx. Jeden będzie nazywać się Lang.resx – tu przechowywany będzie język domyślny aplikacji, oraz drugi plik o nazwie Lang.pl.resx. W tym pliku z kolei przechowywane będą frazy przetłumaczone na język polski. Nazwa pliku jest tutaj bardzo ważna. Skrót znajdujący się po kropce (w naszym przypadku pl) musi być prawdziwym skrótem danego języka. W przypadku języka polskiego jest to pl, angielskiego en itd… Dzięki niemu środowisko uruchomieniowe będzie wiedziało, że w przypadku uruchomienia aplikacji na „polskim” Windowsie aplikacja będzie korzystać z pliku Lang.pl.resx. Z kolei po uruchomieniu aplikacji na innej wersji językowej Windowsa zostanie załadowany inny odpowiadający tej wersji plik. W przypadku jego braku zostaną załadowane wartości z pliku Lang.resx.

lang

lang2

Jak używać wartości z plików Lang.resx?

Dodawanie wartości do tych plików jest banalnie proste. Sprowadza się do ustawienia nazwy naszego zasobu, jakim jest ciąg znaków, oraz nadanie mu wartości:

lang3

Nazwy zasobów muszą być we wszystkich plikach takie same. Ich wartości to po prostu ten sam tekst, tylko przetłumaczony na inny język 🙂 Należy również pamiętać, aby zmienić wartość Access Modifier na public.

Skoro dodaliśmy już przykładowe wartości do obydwóch plików, możemy przejść do tego jak ich używać w kodzie.

Używanie wartości w kodzie xaml

Jeżeli chodzi o pobieranie wartości bezpośrednio do pliku *.xaml to na początek musimy wskazać, gdzie owe pliki się znajdują:

Następnie zamiast wpisywać w kontrolki „suchy tekst” używamy po prostu:

Takie rozwiązanie ma swoje wady i zalety… Zalety na pewno są takie, że nie musimy ręcznie bindować każdej wartości z poziomu kodu C#. Jeżeli chodzi o wady to umieszczając tutaj zasoby w ten sposób nie możemy ich już przeładować inaczej jak poprzez zamknięcie i ponowne uruchomienie okna. Jednak nie wydaje mi się to jakimś wielkim problemem. W końcu języka aplikacji nie zmienia się codziennie.

Używanie wartości w kodzie C#

Tutaj sprawa też nie jest zbyt skomplikowana. Wartości odczytujemy w taki sposób:

Należy przy tym zaznaczyć, że pomimo tego, że nasz kod wskazuje wyraźnie na plik Lang, to w przypadku kiedy wątek jest ustawiony na działanie w języku polskim, to zostaną załadowane wartości z pliku Lang.pl.resx. Jak w takim razie zmienić język „w locie”? W końcu ktoś może chcieć używać aplikacji w wersji angielskiej, pomimo że ma polski system. To również nie jest zbyt skomplikowane. Wystarczy jedna linijka:

Ten kod ustawia język wątku na inny, w tym przypadku na angielski. Linijkę tą trzeba wykonać zanim, jeszcze dane z pliku Lang.resx zostaną odczytane i ustawione. Jeżeli jednak jest już po fakcie (np. aplikacja działa w języku polskim, a użytkownik zmienia go z poziomu aplikacji na inny) to możemy zawsze zrobić coś takiego:

Aktualnie okno zostanie zamknięte, a nowo utworzone ma już ustawione język na angielski, więc pobierze odpowiednie wartości z innego pliku. Należy też zaznaczyć, że jeżeli posiadamy w naszej aplikacji wiele wątków, które w jakikolwiek sposób korzystają z plików Lang to każdemu z nich również należy ustawić język wykonując powyższą linijkę. Dlaczego? A no dlatego, że każdy nowo utworzony wątek działa w domyślnym języku systemu. Nie dziedziczy go po wątku „matce”, musimy o to zadbać sami.

To byłoby na tyle. Jeżeli gdzieś się pomyliłem to jak zwykle proszę, abyście dali znać w komentarzach 😉

 

384 total views, 1 views today

4 przemyślenia nt. „WPF i aplikacje wielojęzyczne

  1. Cześć Łukasz!

    A robiłeś kiedyś (lub zastanawiałeś się jak to rozwiązać) aplikację, której bardziej ‚dynamiczna część’ (treść) również była wielojęzyczna? Dla przykładu silnik blogowy, którego użytkownik ‚bloger’ może zdefiniować np. 3 języki w których pisze – PL, EN i DE, a następnie pisze swoje posty osobno dla każdej wersji (ma do tego fikuśny UI). Z drugiej strony ekranu, użytkownik ‚czytelnik’ przełączając język (blog ma bardzo klasyczny UI, więc wystarczy kliknąć flagę PL, UK lub DE w rogu ekranu) widzi zmianę nie tylko w menu, stopce itd. ale również widzi treść przetłumaczoną lepiej, niż to robi google translate…

    Pliki *.resx są świetne jeśli chodzi o tłumaczenie stałej treści appki, zdarzyło mi się także zapisywać w nich lokalne (dla danej maszyny) ustawienia użytkownika (więc treść była troszkę bardziej dynamiczna), ale średnio mi się widzi przechowywanie w nich głównej zawartości appki… To naturalny odruch, aby umieścić to w bazie danych, pytanie tylko jaka struktura bazy byłaby najlepsza i jak to ładnie spiąć w aplikacji, aby się nie narobić, ale mieć równie wygodne API, co w przypadku ‚resiksów’.

    Pozdr,

    • Witaj 😉

      Jeżeli chodzi o programowanie webowe (PHP, ASP itp), to do tej pory się w to nie zagłębiałem, więc moja wiedza na ten temat jest czysto teoretyczna i podstawowa. Powiem jednak, jak bym do tego podszedł 😉 Jeżeli chodzi o taki silnik blogowy to starałbym się to rozwiązać w ten sposób, że wszelkie dane wprowadzane przez blogera byłyby zapisywane w różnych bazach danych (każda przechowywałaby dane w innym języku). Ewentualnie, żeby nie robić bałaganu z plikami można też ustawiać dla każdego elementu w innym języku prefixy w stylu: _PL_Post1 = „wartość po polsku”, _EN_Post1 = „po angielsku”, zamykając wszystko w jednej bazie. Zaprogramowanie zapisu takich wartości nie byłoby chyba jakimś wielkim problemem. Natomiast wybór języka wyglądałby tak, że w linku do witryny dodawany byłby parametr „http://witryna.pl/post?lang=PL”. Skrypt szukałby wtedy polskich wersji postów w bazie danych, gdyby ich nie znalazł to wczytywałby wartości domyślne 😉

      • Hej,

        Przykład z silnikiem blogowym był pierwszym co mi do głowy przyszło, więc nie martw się o webówkę (sam unikam webówki jak ognia ;)).

        No offence, ale mam pewne obiekcje do obu twoich propozycji:

        a) Użycie prefixów powodowałoby brzydkie zapytania do bazy (coś jak ‚ […] WHERE POST_ID=@id AND POST_NAME LIKE „_”+@lang+”_%” ‚) i byłoby mało ‚bazodanowe (sic!)’

        b) Może to kwestia uprzedzeń, ale nie jestem zwolennikiem rozpraszania danych po kilku bazach danych jeśli używa ich tylko jedna aplikacja, która w dodatku nie produkuje tysięcy tabel (czytaj: nie ma logicznego/prawnego/biznesowego uzasadnienia ich rozdzielania). Chodzi o to, że jak coś tworzy spójną całość, to niech leży w jednym miejscu. No i dochodzi warunkowe odwoływanie się do różnych baz po stronie appki – źle to dla mnie brzmi, bo albo będzie się to wiązało z jakims patternem nazywania baz i dynamicznym budowaniem Connection Stringów (lub budowaniem ich na podstawie instrukcji zawartej w jakiejś innej, bardziej statycznej, bazie danych/pliku/czymkolwiek), lub koniecznością ingerencji w kod przy okazji dodawania nowego języka (a tworzymy silnik blogowy, nie bloga, więc to brzmi gorzej niż źle).

        [Nie przyszedłem tu prawić morałów, więc jak mówie bzdety, to śmiało mnie linczuj :D]

        Ja bym rozwiązanie widział w postaci tabelki:

        ID_POSTA | ID_JĘZYKA | TREŚĆ | …

        I do tego może widoki/funkcje [tabelaryczne] dające łatwy dostęp do postów w danym języku, etc. Martwi mnie tylko, jak by miał wyglądać DAL, bo powinien być łatwy i przyjemny zarówno w użyciu jak i w rozwijaniu (np. oczekiwania użytkowników naszego silnika rosną i chcą dostać nową kategorię treści (chcą np. opisywać swoje projekty (tak jak Ty)), która z racji specyfiki prezentowanej treści musi trafić do innej tabeli (nie pytaj czemu – tak zmyślam…) i to też ma być wielojęzyczne) – jak z(a)robić i się nie narobić przy DALu. Może szukam dziury w całym i nie ma w tym nic zmyślnego… Jak znajdę chwilę to to sprawdzę (no chyba, że mnie uprzedzisz 😉 ).

        Pozdr!

        • Co do sposobu przechowywania takich danych, kwestia gustu. Faktycznie twoje rozwiązanie wydaje się bardziej eleganckie. Lepiej zrobić to tak, niż dorzucać prefixy:
          ID_POSTA | ID_JĘZYKA | TREŚĆ | …
          Kwestia wczytywania odpowiednich wersji językowych mogłaby zostać rozwiązana w taki sam sposób, jak wspominałem: „http://witryna.pl/post?lang=PL”.
          Jeżeli chodzi o treści inne niż posty… Czemu nie przechowywać ich w tych samych polach co posty? Przy odczycie nie będzie żadnej różnicy. Tekst to tekst, to co zaś tekstem nie jest nie wymaga raczej tłumaczenia 😉

          Pozdrawiam!

Możliwość komentowania jest wyłączona.