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:
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:
- 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.
... // // 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.