Lokalizacja UI z wykorzystaniem plików XML i mechanizmu refleksji – Część 3

W poprzednim artykule zajęliśmy się częścią odpowiedzialną za wczytywanie i przechowywanie słownika w pamięci. Teraz opiszę w jaki sposób będziemy tłumaczyć UI i oznaczać, które elementy mają temu podlegać. Przedstawię również prostą aplikację napisaną w Windows Forms podsumowującą wszystko o czym mówiliśmy.

Wiązanie kluczy z elementami interfejsu

Pierwszym krokiem będzie powiązanie kluczy ze słownika pliku XML z konkretnymi elementami UI. Wykorzystamy do tego specyficzne tagi zwane atrybutami. Stworzymy klasę dziedziczącą z System.Attribute z dwiema właściwościami: Key oraz Default. Oba typu string. Dodatkowo zaznaczymy, że ten atrybut będzie można wykorzystać tylko w przypadku właściwości i tylko jednokrotnie (dla każdej).

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class TranslatableAttribute : System.Attribute
{
    private readonly string key;
    public string Key { get { return key; } }

    private readonly string @default;
    public string Default { get { return @default; } }

    public TranslatableAttribute(string key) : this(key, "") { }

    public TranslatableAttribute(string key, string @default)
        : base()
    {
        this.key = key;
        this.@default = @default;
    }
}

Właściwość Key to nic innego jak klucz z naszego słownika. Wartość Default będzie opcjonalna i pozwoli nam na zdefiniowanie wyświetlanego tekstu bezpośrednio w kodzie programu. Dlaczego? Pliki mają tę własność, że potrafią się w niewytłumaczalny sposób zgubić lub uszkodzić (np. za sprawą działań użytkownika). Nie chcemy chyba, żeby nasz użytkownik w takim wypadku pozostał z całkowicie niezdatnym do pracy interfejsem? Wartość Default będzie wstawiona w tekst tych elementów, których klucze nie zostały zidentyfikowane. W ekstremalnych warunkach – gdy nie znaleziono pliku XML. Poza tym rozwiązanie to ma tę zaletę, że nie musimy cały czas nadzorować zawartość słownika aby wygodnie pracować nad naszym programem. W moim wypadku, zazwyczaj pod wartościami Default zapisuje wersję angielską wyświetlanych tekstów.

Wiemy już jak wygląda specjalnie przygotowany atrybut. Jak go teraz zastosować? To już zależy w znacznej mierze od technologii naszego UI. W przypadku Windows Forms może to wyglądać następująco:

[Translatable("Form1.ExitButton", "Exit")]
public string ExitButtonLabel
{
    set
    {
        exitButton.Text = value;
    }
}

 Tłumaczenie wskazanych elementów

Pozostał nam ostatni element całej układanki. Musimy wyszukać we wskazanych obiektach właściwości opatrzonych atrybutem Translatable. Następnie przypisujemy wartość:

  • jeżeli istnieje klucz z atrybutu w słowniku przypisz jego wartość z paczki językowej
  • jeżeli nie istnieje taki klucz, przypisz wartość Default
  • jeżeli nie wykorzystano wartości Default wstaw string „(???)” (lub dowolny inny, według uznania)

W tym celu stworzymy nową klasę LanguageManager, która będzie zawierała aktualną paczkę językową oraz metodę Translate przyjmującą jako parametr obiekty klasy object. Przedstawię tylko fragment klasy, cały kod dostępny jest do pobrania na końcu tego artykułu.

public void Translate(params object[] objects)
{
    foreach (object o in objects)
    {
        foreach (PropertyInfo property in o.GetType().GetProperties())
        {
            foreach (object it in property.GetCustomAttributes(false))
            {
                if (it is TranslatableAttribute)
                {
                    TranslatableAttribute a = it as TranslatableAttribute;
                    if (property.PropertyType == typeof(string))
                    {
                        try
                        {
                            property.SetValue(o, languagePack.GetByKey(a.Key), null);
                        }
                        catch
                        {
                            try { property.SetValue(o, a.Default, null); }
                            catch { property.SetValue(o, "(???)", null); }
                        }
                    }
                }
            }
        }
    }
}

W powyższym kodzie zastosowaliśmy się do wcześniej podanych kroków. Klasa PropertyInfo znajduje się w przestrzeni System.Reflection. W pierwszej pętli przechodzimy po wszystkich obiektach podanych jako argument metody. Dalej przyglądamy się wszystkich właściwością danego obiektu. Jeżeli znajdziemy nasz atrybut próbujemy przypisać wartość z paczki językowej (linia 16). Dalszej części chyba nie muszę wyjaśniać.

Łączymy wszystko w jedną całość

Nadszedł czas na połączenie wszystkich elementów w jeden program. Jak wcześniej pisałem, będzie to aplikacja w Windows Forms. Przedstawię tylko kilka ciekawszych fragmentów. Zachęcam do zapoznania się z całym kodem.

Program jest bardzo prosty i jedyne co robi to umożliwia zmianę języka interfejsu użytkownika. Oto screen:

Program wyszukuje w bieżącym katalogu pliki XML, następnie sprawdza które są paczkami językowymi.

foreach (string file in Directory.GetFiles(".", "*.xml"))
{
    try
    {
        LanguagePack p = LanguagePack.Load(file);
        languages.Add(file, p);
        languageComboBox.Items.Add(file);
    }
    catch { }
}

Obiekt languages jest słownikiem o typu <string, LanguagePack>. Kluczem jest nazwa pliku paczki, wartością pakiet językowy. Z kolei languageComboBox wyświetla nazwy plików. Gdy użytkownik wybierze plik A, to wybierana jest paczka ze słownika o kluczu A.

Zanim użytkownik dokona jakiegokolwiek wyboru ustawiane są wartości domyślne.

manager.Translate(this, myListView1);

this odnosi się do obiektu Form1 (kod wykonywany jest w zdarzeniu Load). Dodatkowo stworzyłem własną wersję kontrolki ListView, która posiada właściwości tłumaczące nagłówki poszczególnych kolumn. Powyższa linijka służy do tłumaczenia interfejsu w przypadku gdy LanguageManager ma już wybrany pakiet językowy:

manager.SetLanguageFile(languages[languageComboBox.SelectedItem.ToString()]);
manager.Translate(this, myListView1);

Podsumowanie

To wszystko jeśli chodzi o ten program jak i mini serię artykułów. Przeanalizowanie kodu źródłowego na pewno wiele wyjaśni w kwestii działania całego systemu. Załączyłem dwa pakiety językowe. Zachęcam do dołączenia kolejnych i sprawdzenia jak program sobie z nimi radzi (bez ponownej kompilacji). Wszelkie pytania proszę zadawać w komentarzach.

{filelink=1}

Share

2 myśli nt. „Lokalizacja UI z wykorzystaniem plików XML i mechanizmu refleksji – Część 3

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Spam protection by WP Captcha-Free