06/07/2011

Londyn - początek

Home

Życie podąża różnymi ścieżkami, a mnie ostatnio przywiało do Londynu. Zawsze lubiłem czytać o życiu i pracy w innych krajach dlatego postanowiłem, że korzystając z okazji będę dzielił się swoimi wrażeniami na blogu.

Zacznę od tego, że jestem w podróży służbowej, a więc jestem w tej komfortowej sytuacji, że przyjechałem na gotowe. Z tego powodu nie mogę jednak opisać z autopsji jak wygląda szukanie pracy czy mieszkania w Londynie. Skupię się, więc na innych rzeczach: ile kosztuje mnie życie z wyłączeniem kosztów około mieszkaniowych, jak wyglądają dojazdy do pracy, czy miasto mi się podoba itd. Tym samym mocno odbiegnę od zwyczajnej tematyki bloga ale mam nadzieję, że się spodoba. Posty na ten temat chciałbym publikować co kilka dni ale będzie to raczej zlepek luźno powiązanych z sobą wpisów niż z góry zaplanowana seria.

Na początek kilka zdań o tym jak idzie mi dogadywanie się z autochtonami. Wyjeżdżając do Londynu miałem z jednej strony wiele powodów sądzić, że nie będę miał z tym większych problemów ale miałem również trochę obaw. Jeśli ktoś chciałby porównać swój poziom znajomości angielskiego z moim to uczę się go od czasów podstawówki przy czym intensywną naukę zacząłem tak naprawdę w liceum. Mam też zdany egzamin CAE. Do tej pory angielskiego używałem głównie do czytania dokumentacji technicznej, książek, forów czy blogów. Rzadziej pisałem po angielsku, a już bardzo rzadko miałem okazję rozmawiać po angielsku, w szczególności na tematy inne niż techniczne.

Przechodząc do setna to po tych kilku dniach pobytu muszę powiedzieć, że jest dobrze. W przeważającej liczbie przypadków nie mam żadnych albo prawie żadnych problemów z porozumiewaniem się po angielsku. Równocześnie zdarzyło się jednak, że w czasie rozmowy, pomimo poproszenia o powtórzenie, z powodu akcentu nie byłem w stanie wyłowić z wypowiedzi bardzo wielu słów i mogłem zrozumieć tylko ogólny sens wypowiedzi. Mam jednak nadzieję, że to tylko kwestia czasu i osłuchania. Muszę również przyznać, że jestem mile zaskoczony ponieważ większość do tej pory spotkanych przeze mnie osób nie mówi z bardzo silnym akcentem, który utrudniałby zrozumienie.

Jeśli chodzi o mówienie to otrzymałem nawet komplement od rodowitego Anglika, że mój angielski jest bardzo dobry :) Co prawda moim zdaniem rozmawiając z rzeczonym Anglikiem popełniłem sporo błędów gramatycznych i czasem brakowało mi słów ale to pokazuje, że najważniejsze to żeby się nie bać i mówić, mówić i jeszcze raz mówić. Dla mieszkańców danego kraju, w tym przypadku dla Brytyjczyków, najważniejsze jest przecież to, że mówi się w ich języku, a więc nie powinniśmy być dla siebie zbyt surowi. To przypomina mi anegdotę jaką opowiedział mi kolega z Polski. Otóż kiedy ubiegał się o pracę to odbył rozmowę z headhunter'em, który stwierdził, że z tym jego angielskim to mogło by być dużo lepiej, że popełnia błędy itd. Pomimo sceptycyzmu headhunter'a odbył rozmowę z Anglikami, a oni nie mieli zastrzeżeń i go zatrudnili. Dodam jeszcze, że jego angielski jest moim zdaniem lepszy od mojego.

Na koniec osobom, które wyjeżdżają za granicę i podobnie jak ja rzadko rozmawiają po angielsku polecam przed samym wyjazdem wykupić kilka godzin konwersacji z lektorem aby się rozgadać. Ja tak zrobiłem i sądzę, że pomogło.

09/06/2011

Bardzo wymagająca rekrutacja 2

Home

W ostatnim poście opisałem przebieg pewnej rekrutacji, w której uczestniczyłem, aż do rozmowy z kierownikiem projektu. Post ten stanowi dokończenie tego tematu, a w szczególności zawiera odpowiedź na pytania jakie pojawiły się w komentarzach.

Po jakimś czasie po rozmowie z kierownikiem projektu, w tej chwili już nie pamiętam szczegółów, zostałem zaproszony do kolejnego etapu rekrutacji. Tym razem musiałem pofatygować się do innego miasta na serię rozmów z przedstawicielami firmy z zagranicy. O takiej konieczności zostałem zresztą poinformowany dużo wcześniej i dlatego również na każdym z wcześniejszych etapów rekrutacji była sprawdzana moja znajomość angielskiego. Nie ukrywam, że taka podróż nie do końca mi się podobała ale ponieważ praca wyglądała bardzo obiecująca to zdecydowałem się na wyjazd.

Na rozmowę pojechałem samochodem ze względu na elastyczność, jadę kiedy chcę, nie przejmuję się godziną odjazdu pociągu... Decyzja była dobra i zła. Zła ponieważ 6 godzin jazdy to męcząca sprawa, a dobra bo pociągiem nie byłoby wiele krócej, a musiałbym jeszcze płacić za taksówkę itd. Na rozmowę pojechałem dzień wcześniej aby się porządnie wyspać. Nocleg miałem prawie za darmowo, a za benzynę zwrócono mi pieniądze. Tutaj dodam, że z perspektywy uważam, że po tak długiej jeździe samochodem, a chyba nawet pociągiem porządny odpoczynek to konieczność. Innymi słowy nie ma sensu umawiać się na rozmowę o pracę tuż po długiej podróży. Zapewne można wypaść dobrze ale jestem przekonany, że zawsze będzie to gorzej niż kiedy będziemy wypoczęci.

Rozmowy z przedstawicielami z zagranicy trwały 3 godziny. W sumie odbyłem trzy rozmowy. Każda z nich była mocno techniczna ale dotyczyła trochę innych rzeczy. Rozmawiałem o algorytmach np.: programowanie dynamiczne, implementacji funkcji wirtulanych w C++, złożoności obliczeniowej, a także o rzeczach bardziej biznesowych. Muszę przyznać, że po tych rozmowach nabyłem większej pewności co do mojego mówionego języka angielskiego. To jednak co innego rozmawiać po angielsku na wczasach czy prowadzić luźną rozmowę, a co innego mieć kilkugodzinną rozmowę o pracę w tym języku z ludźmi, z którymi w inny sposób sie nie dogadasz.

Z przebiegu tych rozmów byłem zadowolony, a moje wrażenie zostało wkrótce potwierdzone bo zaproponowano mi pracę. Tutaj dodam, bo zapomniałem o tym wcześniej napisać, że rekrutacja dotyczyła stanowiska programisty czy jak to się nazywało w nomenklaturze tejże firmy. Z tą istotną różnicą, że było to stanowisku tzw. programisty algorytmicznego czyli takiego, który zajmuje się na co dzień przede wszystkim implementacją i analizą algorytmów np.: data miningowych, sortowania itp., a nie realizacją typowo biznesowych wymagań.

Dalszy etap rekrutacji to oczywiście negocjacje, które trwały dość długo (dwa spotkania, rozmowy telefoniczne). Jak się skończyły? Finalnie odrzuciłem ofertę pracy. Czemu? Wbrew pozorom nie chodziło o kwestie finansowe, a na pewno nie miały one decydującej roli. Głównym argumentem przeciw były powody natury rodzinno osobistej. Co tu dużo mówić, przeprowadzka do innego miasta kiedy całe życie spędziło się w Warszawie, ma się tutaj rodzinę, przyjaciół, pracę, mieszkanie i jest się zadowolony z życia to bardzo ciężka decyzja. Pozostaje mi tylko żałować, że oferta nie dotyczyła pracy w Warszawie.

Jako podsumowanie chciałbym wymienić te cechy/zalety tego procesu rekrutacyjnego, które spowodowały, że go zapamiętałem:
  • Bardzo kompetentne osoby sprawdzające wiedzę techniczną.
  • Dyskusja, a nie tylko ocena przedstawionych przeze mnie rozwiązań.
  • Praca domowa do zrobienia.
  • Sprawdzenie znajomości algorytmów i rozwiązywania zadań algorytmicznych.
  • Prowadzenie części rozmowy w języku angielskim.
  • Szczerość w odpowiedziach na moje pytania nawet jeśli odpowiadający wiedział, że taka odpowiedź może mnie zniechęcić.
  • Jeśli firma wkłada tyle wysiłku w znalezienie pracowników to znaczy, że planuje dłuższą współpracę.
  • Jeśli firma wkłada tyle wysiłku w znalezienie pracowników to znaczy, że potencjalni współpracownicy są bardzo kompetentni.
Opisana rekrutacja jest dla mnie wzorem rekrutacji dobrze sprawdzającej umiejętności i wiedzę technologiczną kandydata i porównuję do niej inne. Nie twierdzę jednak, że zawsze powinno to wyglądać w taki sposób. Nie każdej firmy na to stać, a po drugie nie zawsze potrzebne jest tak gruntowne sprawdzenie potencjalnego pracownika. Każdy proces rekrutacyjny na stanowisko programistyczne powinien jednak charakteryzować się kilkoma rzeczami (łatwymi do osiągnięcia):
  • Pisanie jakiegoś kodu przez kandydata.
  • Test z wiedzy technicznej.
  • Rozmowa z osobą techniczną i biznesową.
  • Sprawdzenie znajomość języka angielskiego przynajmniej w stopniu pozwalającym czytać dokumentację.

06/06/2011

Bardzo wymagająca rekrutacja

Home

W swojej dotychczasowej karierze wziąłem udział w wielu rekrutacjach. W przeważającej liczbie przypadków zostałem zaproszony na rozmowę ale czasami skończyło się na wysłaniu CV. Bardzo często zaproszenie na rozmowę poprzedzone było wywiadem telefonicznym. Wielokrotnie rozwiązywałem różnego rodzaju testy, dużo rzadziej byłem proszony o wykonanie pracy domowej. Część rekrutacji organizowana była przez firmy HR'owe inne bezpośrednio przez zatrudniającą firmę. Spośród tych wszystkich rekrutacji kilka zapadło mi w pamięci, a szczególnie jedna niezwykle wymagająca. Aby nie budzić wątpliwości napiszę, że rekrutacja ta odbyła się już sporo czasu temu i finalnie nie podjąłem współpracy z tą firmą. Chciałbym jednak opisać jak wyglądała ponieważ dużo się dzięki niej nauczyłem i jest dla mnie dzisiaj wzorem, do którego porównuję inne rekrutacje. Post ze względu na jego długość postanowiłem rozbić na dwie części, tutaj prezentuję pierwszą.

Wszystko rozpoczęło się w sposób standardowy od zapytania na portalu internetowym czy jestem zainteresowany taką, a taką ofertą pracy. Propozycja była na tyle interesująca, że odpowiedziałem na nią pozytywnie. Po jakimś czasie zadzwoniła do mnie Pani z HR'ów aby potwierdzić moje zainteresowanie i umówić się na dłuższą rozmowę telefoniczną.

O ile mnie pamięć nie myli rozmowa odbyła się w ciągu kolejnego tygodnia i trwała około półgodziny. W tym czasie zostałem poproszony o opisanie swojego dotychczasowego doświadczenia, w jakich projektach brałem udział, jakich technologii używałem... jednym słowem standard. Padło również pytanie o oczekiwania finansowe, na które to mimo starań i prób aby to druga strona wyszła najpierw z propozycją w końcu udzieliłem odpowiedzi. Rozmowę wyróżnia trochę to, że już na tym etapie została zweryfikowana moja znajomość języka angielskiego. Z drugiej strony taka praktyka jest chyba coraz powszechniejsza. Na koniec dowiedziałem się, że dalszy etap rekrutacji będzie polegał na rozwiązaniu dwóch zadań algorytmicznych i jednego biznesowego. Przy czym większą uwagę miałem zwrócić na zadania algorytmiczne.

Następnego dnia otrzymałem drogą mailową treść zadań. Niestety ze względu na klauzulę poufności nie przytoczę ich tutaj, chociaż chciałbym ponieważ były bardzo ciekawe. Pierwsze zadanie algorytmiczne rozwiązałem następnego wieczoru. Moje pierwsza implementacja bazowała na przeszukiwaniu przestrzeni stanów i dawało poprawne wyniki ale kiedy już miałem je wysłać zauważyłem, że do problemu można podejść w zupełnie innych sposób. Finalne rozwiązanie zajmowało kilka linii kodu zamiast kilkuset! Za drugie zadanie zabrałem się następnego dnia i doszedłem do wniosku, że to pytanie z hakiem ponieważ przedstawiony problem należy do klasy NP, co też napisałem w odpowiedzi.

Rozwiązanie pierwszego zadania została zaakceptowane. Co do drugiego to zostałem poproszony żebym się jeszcze mu przyjrzał ponieważ postawiony problem można rozwiązać w czasie wielomianowym. Początkowo byłem przekonany, że to układający/oceniający zadanie pomylił się. Błąd tkwił jednak po mojej stronie. Na czym polegał? Przedstawiony problem można było sprowadzić do znalezienie ścieżki Hamiltona w grafie i to rzeczywiście jest problem NP. Ja zapomniałem jednak o tym, że wśród wszystkich możliwych grafów są takie ich odmiany dla, których problem znalezienia ścieżki Hamiltona można sprowadzić do znalezienia ścieżki Eulera, a to można zrobić w czasie wielomianowym. Co do zadania biznesowego to w końcu z braku czasu go nie rozwiązałem ale tak jak pisałem nie było ono bardzo istotne.

Co dalej? Zostałem zaproszony na rozmowę, połączoną z testem. Test składał się z 20 otwartych pytań i na jego rozwiązanie było około 30 minut. Pytania były tak ułożone, że można na nie było odpowiedzieć w jednym/dwóch zadaniach. Czy były trudne? Dla mnie raczej nie, chociaż nad niektórymi musiałem się dłużej zastanowić. Pomimo, że pytań było tylko 20 sądzę, że dobrze weryfikowały ogólną znajomość platformy .NET. Było coś o zwalnianiu zasobów, użyciu słowa kluczowego using, synchronizacji, LINQ'u itd. Te 30 minut starczyło dokładnie na tyle aby napisać odpowiedzi, ktoś nie orientujący się w temacie miałby z tym problem.

Po rozwiązaniu testu przyszła kolej na rozmowę z dwoma "technicznymi" osobami. Rozmowa odbyła się za pośrednictwem Skype. Początkowo zapraszano mnie na wizytę do innego miasta ale kiedy powiedziałem, że będzie z tym ciężko zorganizowano mi video konferencję. Rozmowa miała być połączona z omówieniem testu ale widać wypadłem dobrze ponieważ zadano mi raptem 1 albo 2 pytania na ten temat. Całą rozmowę zapamiętałem z 2 powodów. Po pierwsze trwała bite 3 godziny, a po drugie zadawano mi naprawdę trudne pytania. Pytania nad którymi musiałem się dogłębnie zastanowić przed podaniem odpowiedzi. W gruncie rzeczy nie były to po prostu pytania ale spore problemy do rozwiązania. Fajne było to, że nawet po podaniu prawidłowej odpowiedzi mówiono mi żebym się zastanowił bo można to zrobić jeszcze lepiej, bardziej optymalnie. W czasie tej rozmowy powtórnie zweryfikowano moją znajomość języka angielskiego tyle, że tym razem rozmawialiśmy o zagadnieniach technicznych. Po tych 3 godzinach byłem równie zmęczony jak po całym dniu pracy.

W czasie tej rozmowy miałem też okazję zadać kilka pytań, na które w miarę możliwości udzielono mi odpowiedzi. Jak zwykle w takich przypadkach poprosiłem również o feedback. Powiedziano mi, że pełnego raportu raczej nie otrzymam ale na pewno dostanę informację zwrotna czy było coś nie tak, a jeśli tak to co. Dwa dni później dostałem zaproszenie na następną rozmowę, tym razem z kierownikiem projektu. Rozmowa ta również odbyła się za pośrednictwem video konferencji i trwała około półgodziny. I tym razem sprawdzono moją znajomość języka angielskiego i zadano kilka "biznesowych" pytań. Miałem też okazję dopytać o interesujące rzeczy. Na koniec kierownik projektu powiedział, że w razie jakichś wątpliwości zaprasza do kontaktu. Z możliwości tej skorzystałem kilkukrotnie i za każdym razem otrzymałem wyczerpującą odpowiedź...

05/06/2011

Aplikacje wielojęzyczne - WPF

Home

Przystępując do tłumaczenia aplikacji WPF miałem dokładny plan jak się za to zabrać. Mianowicie postanowiłem użyć narzędzia LocBaml, o którym dowiedziałem się z training kit'a do egzaminu 70-502. Opis całej procedury można znaleźć tutaj. Praktyka pokazała jednak, że narzędzie to pozostawia bardzo dużo do życzenia. Dalej opiszę kolejne kroki pracy z LocBaml wraz z komentarzem jak to wygląda w praktyce.

UIDs

Mechanizm wielojęzyczności aplikacji w WPF koncepcyjnie zbliżony jest to tego co znamy z ASP.NET. W szczególności każda kontrolka, która posiada jakieś zasoby do przetłumaczenia powinna mieć odpowiedni identyfikator tzw. UID. Identyfikatory te można nadać automatycznie przy pomocy polecenia:

msbuild /t:updateuid NAZWA_PROJEKTU.csproj

Wszystkie pliki XAML, które mają zostać przetworzone przez msbuild powinny być checkoutowane (brzmi okropnie ale nie przychodzi mi do głowy dobry polski odpowiednik). Narzędzie to działa i dobrze i źle. Dobrze bo rzeczywiście wygeneruje dla wszystkich kontrolek UID. Źle bo zrobi to dla wszystkich kontrolek, a właściwie powinienem napisać dla wszystkich elementów dokumentu XAML. msbuild nie wykonuje jakiejkolwiek analizy przetwarzanych elementów. Skutkuje to dużym bałaganem. Po przeprowadzeniu tej operacji w plikach XAML pojawi się bardzo dużo, niepotrzebnych, śmieciowych UID'ów, na przykład po co nadawać identyfikator UID kontrolce Border, Line lub StackPanel. Potem aż bolą oczy jak się patrzy na tak przetworzony XAML. Moja rada jest taka. Jeśli chcemy stosować LocBaml to zawczasu, tworząc interfejs użytkownika, powinniśmy nadawać kontrolkom UID'y.

Wyciąganie zasobów do przetłumaczenia

Po wygenerowaniu UID'ów przechodzimy do następnego kroku czyli używamy LocBaml aby wyciągnać zasoby do przetłumaczenia do pliku CSV. Służy do tego takie polecenie:

LocBaml.exe /parse NAZWA_PROJEKTU.resources.dll /out:NAZWA_PLIKU_WYJSCIOWEGO.CSV

Skompilowane zasoby czyli pliki dll umieszczane są w podkatalogach o nazwach zgodnych z kulturą (językiem) zasobów np.: en-US, pl-PL itp. Jeśli nasz projekt nazywa isę SimpleApplication to plik dll z zasobami będzie nazywał się SimpleApplication.resources.dll. W praktyce wydanie tego polecenia skończy się błędem:

Could not load file or assembly '...' or one of its dependencies. The system cannot find the file specified.

Rozwiązanie jest proste ale trochę upierdliwe. Otóż plik LocBaml.exe, plik z zasobami NAZWA_PROJEKTU.resources.dll oraz plik exe/dll powstały po skompilowaniu aplikacji/biblioteki muszą znajdować się w tym samym folderze. Najszybciej to oskryptować. Podobny ale trochę inny komunikat o błędzie:

Could not load file or assembly '...' or one of its dependencies. An attempt was made to load a program with an incorrect format.

Otrzymamy jeśli nasza aplikacja ma ustawioną docelową platformę na x86. W takim wypadku możemy zmienić ustawienia naszej aplikacji, przekompilować LocBaml na x86 albo użyć narzędzia corflags o czym już zresztą pisałem tutaj lub tutaj.

Tak wygenerowany plik CSV powinniśmy teraz przetłumaczyć. Jeśli użyliśmy msbuild do wygenerowania UID'ów to plik ten będzie zawierał dla dużej aplikacji nawet kilkanaście tysięcy wierszy. W moim przypadku było to około 13 tysięcy wierszy z czego jakieś 10% wymagało przetłumaczenia!!! Po prawdzie tak duża liczba wierszy jest również związana ze sposobem działania LocBaml. Otóż dla każdej kontrolki z UID'em w pliku CSV znajdziemy wiele wierszy. Na przykład dla kontrolki TextBlock możemy zlokalizować, co oczywiste, właściwość Text ale również mniej sensowne jak Foreground czy Margin.

Stworzenie pliku dll z przetłumaczonymi zasobami

To jest chyba najtrudniejszy, a zarazem najmniej wygodny krok w całym procesie. W teorii jest to proste. Bierzemy przetłumaczony plik CSV i używamy LocBaml do wygenerowania dll'ki z zasobami o nazwie NAZWA_PROJEKTU.resources.dll, a następnie umieszczamy ją w odpowiednim katalogu aplikacji np.: en-US. Za wczytanie odpowiedniej wersji zasobów odpowiada już silnik WPF, a decyzje podejmuje podobnie jak w ASP.NET czy WinForms na podstawie właściwości Thread.CurrentThread.CurrentUICulture.

Problem pierwszy związany jest z bugiem w implementacji LocBaml, który objawia się tym, że identyfikatory zasobów w pliku CSV mogą się powtarzać. W związku z tym proces odwrotny czyli wygenerowanie dll'ki na podstawie pliku CSV się nie powiedzie. Rozwiązanie problemu można znaleźć tutaj i jest ono trywialne ale wymaga rekompilacji projektu. Można też, ale tego nie próbowałem, zapewnić, że wszystkie pliki XAML w naszym projekcie mają inne nazwy nawet jeśli znajdują się w innych katalogach.

Pierwszy problem w porównaniu z drugim to nic. Wyobraźmy sobie taką sytuację. Mamy projekt aplikacji WPF i grzecznie, zgodnie z zasadami wszystkie komunikaty wyświetlane użytkownikowi trzymamy w plikach zasobów resx. Po jakim czasie chcemy przygotować angielską wersję aplikacji. Zaczynamy od przetłumaczenia tych zasobów, a więc plik np.: msg.resx kopiujemy i zmieniamy mu nazwę na msg.en-US.resx, a następnie tłumaczymy.

Teraz zabieramy się za XAML. Nadajemy kontrolką UID'y, wyciągamy zasoby do pliku CSV, tłumaczymy i ponownie używamy narzędzie LocBaml do wygenerowania dll'ki z zasobami. Wszystko poszło jak po maśle i zadowoleni chcemy skopiować świeżutką dll'ke z przetłumaczonymi zasobami do katalogu en-US, a tu figa z makiem. W katalogu znajduje się już dll'ka o takiej nazwie. Skąd się wzięła? Została wygenerowana przez VS i zawiera komunikaty, które umieściliśmy w pliku msg.en-US.resx. Mamy więc dwie dll'ki o takiej samej nazwie, jedną z przetłumaczonymi komunikatami, a drugą z przetłumaczonym GUI i musimy umieścić je w tym samym katalogu.

I tutaj zaczynają się schody, wysokie, wąskie, ciemne i niewygodne. Nie pozostaje nic innego jak użyć linkera al.exe. Da się to zrobić ale tak jak powiedziałem jest to bardzo niewygodne. Nie będę tutaj tego opisywał bo moim zdaniem jest to strasznie nudne. Jeśli ktoś tego potrzebuje to zapraszam do kontaktu.

Inne

Takie podejście do lokalizowania aplikacji WPF ma również inne wady. Tak jak wspomniałem wersja zasobów jaka zostanie wczytana zależy od właściwości Thread.CurrentThread.CurrentUICulture. Trzeba więc pamiętać aby ustawić ją na odpowiednią kulturę zanim zaczniemy robić cokolwiek z interfejsem użytkownika. W przeciwnym wypadku możemy doprowadzić do wczytania złej wersji zasobów i okaże się, że cześć okien będzie w języku polskim, a część w angielskim. Po drugie, modyfikacja Thread.CurrentThread.CurrentUICulture nie powoduje automatycznego przełączenia się pomiędzy jedną, a drugą wersją zasobów. Dopiero zamknięcie okna i ponowne jego wyświetlenie spowoduje pobranie odpowiedniej wersji językowej zasobów.

Podsumowanie

LocBaml można użyć, pytanie czy z wszystkimi wadami i ograniczeniami tego narzędzia warto. W omawianym przypadku skończyło się to użyciem wyszukanej przez kolegę biblioteki WPFLocalizeExtension. Też ma swoje wady, też nie jest idealna, w szczególności w żaden sposób nie automatyzuje procesu lokalizowania aplikacji ale nie wymaga UID'ów, nie trzeba pisać skryptów, linkować itd. Tutaj zresztą kłania się to co już pisałem dwa razy:

Jeśli chcemy aby nasza aplikacja miała wiele wersji językowych to przygotowujmy się do tego od pierwszej linijki tej aplikacji.


26/05/2011

Aplikacje wielojęzyczne - WinForms

Home

Przyszła pora wrócić do tematu aplikacji wielojęzycznych. Tym razem skupię się na WinForms. Zacznę od tego, że część rzeczy, o których pisałem we wcześniejszym poście na temat aplikacji ASP.NET można zastosować do innych technologii, w szczególności do WinForms. Dla przypomnienia:
  • Jeśli chcemy aby nasza aplikacja miała wiele wersji językowych to przygotowujmy się do tego od pierwszej linijki tej aplikacji.
  • Stałe znakowe w kodzie są złe, bardzo złe, niewyobrażalnie złe... Stałe zawierające komunikaty dla użytkownika itp. powinny znajdować się w zasobach aplikacji, a pozostałe, nazwijmy je techniczne powinny zostać zdefiniowany w jednym konkretnym miejscu np.: klasie o nazwie Constans.
Tyle tytułem wstępu. Kiedy przystępowałem do prac nad aplikacją WinForms byłem o tyle w gorszej sytuacji w porównaniu do wcześniejszych prac nad aplikacją ASP.NET, że nie wiedziałem o żadnych narzędziach wbudowanych w Visual Studio wspomagających lokalizację WinForms. Co do samego mechanizmu przełączania się pomiędzy różnymi wersjami językowymi zasobów to wygląda to tak samo jak w ASP.NET. Mamy więc odpowiednio ponazywane pliki np.: Resources.resx, Resources.en.resx itd. zawierające zasoby dla poszczególnych języków (kultur). Teraz w zależności od tego jaką kulturę ustawimy na właściwości Thread.CurrentThread.CurrentUICulture taka wersja zasobu zostanie wczytana. Na samym początku pomyślałem więc aby przejrzeć kod wygenerowany przez designer i w nim pozamieniać stałe znakowe na odwołania do zasobów. Podobnie postąpiłem przecież dla ASP.NET. Czyli poniższy kod:
...
// 
// cancelButton
// 
this.cancelButton.Location = new System.Drawing.Point(85, 103);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(75, 23);
this.cancelButton.Text = "Anuluj";
...
Zamienić na taki:
...
// 
// cancelButton
// 
this.cancelButton.Location = new System.Drawing.Point(85, 103);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(75, 23);
this.cancelButton.Text = Resources.Cancel;
...
To nie jest jednak dobry pomysł przynajmniej z kilku powodów. Kod generowany przez designer WinForms nie jest przeznaczony do samodzielnej modyfikacji. Podejście to zadziała ale wystarczy, że ktoś użyje designera aby zmienić położenie kontrolki lub zrobić coś równie prostego, a kod zostanie ponownie wygenerowany, a nasze zmiany usunięte. Po drugie przeglądanie kodu designera w poszukiwaniu stałych znakowych to żmudna, nudna i błędogenna robota. W następnej kolejności pomyślałem więc o innym rozwiązaniu:
public Form1()
{
  InitializeComponent();
  cancelButton.Text = Resources.Cancel;
  ...
}
Czyli zanim wyświetlimy formę, pobieramy zasoby i lokalizujemy GUI. Podejście bardzo proste, wręcz prymitywne. Można je trochę udoskonalić na przykład dodać do bazowej klasy metodę LocalizeGUI:
public class BaseForm : Form
{
  protected override void OnShown(EventArgs e)
  {
    LocalieGUI();
    base.OnShown(e);
  }

  protected virtual void LocalieGUI()
  {}
}
...
public partial class Form1 : BaseForm
{
  protected override void LocalieGUI()
  {
    cancelButton.Text = Resources.Cancel;
    ...
  }
}
Również bardzo proste rozwiązanie i można jeszcze dużo w nim zmienić. Zasadniczy problem polega jednak na tym, że takie rzeczy sprawdzają się kiedy używa się ich od początku. Ja dostałem gotową aplikację i jak wyobraziłem sobie przeglądanie całego kodu w poszukiwaniu etykiet, przycisków itd., a następnie przenoszenie ustawiania tekstu wyświetlanego użytkownikowi do metody LocalizeGUI to mi się odechciało.

Na szczęście istnieje dużo lepsze rozwiązanie problemu. Otóż Visual Studio posiada wsparcie dla wielojęzycznych WinForms i w przeciwieństwie do narzędzia Tools->Generate Local Resources dla ASP.NET działa całkiem dobrze. Używa się go bardzo prosto:
  • Otwieramy designer dla formy (kontrolki użytkownika).
  • Otwieramy okno właściwości (Properties) dla formy (kontrolki użytkownika).
  • Pole Localizable z kategorii Design ustawiamy na True i zapisujemy zmiany. W tym momencie designer wyniesie do pliku o nazwie NAZWA_FORMY.resx wszystkie stałe znakowe, które podlegają lokalizacji. Jeśli zajrzymy teraz do kodu generowanego przez designer to będzie on wyglądał trochę inaczej niż wcześniej. W szczególności nie znajdziemy tam kodu takie jak this.cancelButton.Text = "Anuluj";, a taki resources.ApplyResources(this.cancelButton, "cancelButton"); gdzie resources to obiekt klasy ComponentResourceManager. Co bardzo ważne dla kontrolek, które dodamy do formy później będzie to wyglądało tak samo.
  • Wracamy do okna właściwości (Properties).
  • Zmieniamy pole Language z Default na docelowy język np.: English.
  • Przechodzimy do okna designera i dokonujemy tłumaczenia. Czyli przycisk 'Anuluj' zamieniamy na 'Cancel', a etykietę 'Nazwa' na 'Name' itd.
  • Zapisujemy zmiany. W projekcie pojawi się nowy plik z zasobami o nazwie NAZWA_FORMY.JEZYK.resx.
  • Przywracamy poprzednią wartość pola Language czyli Default. Zawartość wszystkich zlokalizowanych kontrolek powinna wrócić do stanu wyjściowego.
  • Proces powtarzamy dla innych języków.
Z narzędziem tym pracuje się naprawdę przyjemnie. Trzeba tylko pamiętać, że w designerze na pierwszy rzut oka nie widać wszystkich rzeczy do przetłumaczenia na przykład pozycji menu. Praktyka pokazało również, że narzędzie to współpracuje z kontrolkami zewnętrznych dostawców. Mam tylko dwa zastrzeżenia. W jednym przypadku zmodyfikowany przez designer kod nie chciał się potem kompilować i musiałem go poprawić. Po drugie, o czym pisałem już w poście dotyczącym ASP.NET, jeśli nasza aplikacja składa się z wielu okien to otrzymamy wiele plików z zasobami. Moim zdaniem wprowadza to niestety bałagan do projektu i zmusza nas do wielokrotnego tłumaczenie tych samych tekstów.