Aplikacje Qt4 i mechanizm tłumaczenia

Wstęp

Wiele aplikacji, które używamy na codzień mają interfejs dostępny w języku polskim, choć domyślnie nie zostały w nim napisane. Dzisiaj zajmiemy się mechanizmem tłumaczeń w aplikacjach korzystających z biblioteki Qt4, co - jak się przekonamy - nie jest wcale takie trudne.

Jak to działa?

Metoda działania jest bardzo prosta i można ją podzielić na kilka punktów:

  1. Programista umieszcza wszystkie napisy, które mają być przetłumaczalne w specjalnej metodzie tr().

  2. Programista dodaje kod odpowiedzialny za ładowanie tłumaczeń w funkcji main().

  3. Programista dodaje odpowiednie wpisy dla tłumaczeń w pliku projektu aplikacji.

  4. Programista generuje pliki źródłowe tłumaczeń (.ts).

  5. Osoba tłumacząca uzupełnia plik źródłowy (.ts) przetłumaczonymi komunikatami.

Tajemnicze tr()

Każda klasa, która dziedziczy pośrednio lub bezpośrednio po QObject posiada zaimplementowaną metodę tr() (skrót od translate). Posiada ona następujący prototyp:


QString tr( const char *tekstŹródłowy, const char *komentarz = 0, int n = -1 )


Pierwszy argument jest obowiązkowy i jest nim tekst, który chcemy przetłumaczyć. Drugi to komentarz, który będzie wyświetlany osobie tłumaczącej. Komentarze są bardzo przydatne, ponieważ wiele języków, w tym język polski, posiadają różne formy np. przymiotnika. Jeśli mielibyśmy do przetłumaczenia napis "blue", bylibyśmy zdezorientowani, ponieważ możemy go przetłumaczyć na różne sposoby - np. "niebieski", ale też "niebieska", czy "niebieskie". Dzięki komentarzowi możemy poznać kontekst, w jakim został użyty wyraz oraz czego dotyczy, dzięki czemu możemy jednoznacznie przetłumaczyć komunikat:


tr("blue", "colour of an eye");


Ostatni argument (n) jest liczbą całkowitą dodatnią (n >= 0). Każde wystąpienie w tekście napisu %n zostanie zastąpione przez wartość podanego argumentu n. Na przykład, gdy chcemy uzyskać komunikat informujący o ilości wysłanych wiadomości:


int ileWiadomosci = 3;
tr("Messages sent: %n.", "%n is number of sent messages", ileWiadomosci);

Powyższy komunikat po przetłumaczeniu na język polski będzie wyglądał następująco: Wysłano wiadomości: 3.


Jeśli nasza klasa nie dziedziczy po QObject lub używamy tekstu, który ma być przetłumaczalny np. w funkcji main(), możemy użyć metody tr() poprzedzając ją akcesorem dostępu do klasy QObject, ponieważ ta metoda jest jedną z metod statycznych klasy QObject:


int main(int argc. char *argv[])
{
// Część kodu pominięta

QString napis = QObject::tr("A string");

// Część kodu pominięta
}


Klasa QString

Klasa QString jest podstawową klasą Qt4 do reprezentacji i zarządzania napisami. Posiada wiele przydatnych metod do łatwego manipulowania napisami. Dokładniejszym jej opisem zajmiemy się w kolejnych częściach.



Ładowanie tłumaczeń

Aby załadować tłumaczenia dla odpowiedniego języka będziemu musieli posłużyć się dodatkowymi klasami: QLocale odpowiedzialną za ustawienia językowe oraz QTranslator odpowiedzialną za odszukiwanie przetłumaczonych napisów. Oto niezbędny kod do załadowania pliku z tłumaczeniami:


#include <QApplication>
#include <QLocale>
#include <QTranslator>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QString kodJezyka = QLocale::system().name(); // 1.

QTranslator tlumacz; // 2.
tlumacz.load(QString("NazwaAplikacji_") + kodJezyka); // 3.

app.installTranslator(&tlumacz); // 4.

// Dalsza część kodu...
}

  1. Statyczna metoda system() klasy QLocale zwraca jej instancję (obiekt) zawierający konfigurację językową systemu, zaś metoda name() zwraca kod języka jako napis (QString), czyli dla polskiego będzie to pl_PL

  2. Tworzymy obiekt odpowiedzialny za załadowane tłumaczenia.

  3. Ładujemy plik z tłumaczeniami. Metoda load() jako pierwszy i obowiązkowy argument przyjmuje nazwę pliku do załadowania. Domyślnie plik jest wyszukiwany w katalogu z aplikacją, jednak można podać inną lokację, jako drugi argument metody. Jeśli używanym przez nas językiem byłby polski, to obiekt tlumacz próbowałby załadować plik NazwaAplikacji_pl_PL.qm. Rozszerzenie .qm jest automatycznie dodawane (pliki .qm są binarnymi plikami z tłumaczeniami).

  4. Metoda installTranslator instaluje obiekt tłumacza dla aplikacji. Od tego momentu, jeśli plik z tłumaczeniami został poprawnie wczytany, wszystkie przetłumaczalne napisy będą zastępowane tymi z pliku tłumaczeń. Wspomniana metoda przyjmuje jako argument wskaźnik do obiektu QTranslator.


Modyfikacja pliku projektu


Aby można było automatycznie wygenerować pliki źródłowe tłumaczeń, niezbędna jest modyfikacja pliku projektu naszej aplikacji. Należy w nim dodać zmienną TRANSLATIONS:


TRANSLATIONS += translations/MojaAplikacja_pl.ts \
translations/MojaAplikacja_fr.ts

W powyższym przypadku będziemy mogli wygenerować pliki tłumaczeń dla dwóch języków - polskiego i francuskiego, a pliki tłumaczeń znajdą się w folderze translations (można podać dowolny inny folder, jak również można nie podawać żadnego - wtedy tłumaczenia zostaną wygenerowane w folderze, gdzie znajduje się plik projektu).


Przy okazji możemy zobaczyć, jak można rozdzielać na wiele linii listę argumentów zmiennej - po ostatnim argumencie w linii należy dodać spację i ukośnik, po czym kolejny argument podajemy już w kolejnej linii. Po ostatnim argumencie nie stawiamy już spacji i ukośnika.



Generowanie tłumaczeń

Do generowania tłumaczeń służy narzędzie lupdate, dla którego, jako argument podajemy ścieżkę pliku projektu, dla którego chcemy wygenerować tłumaczenia. Jeśli pliki z tłumaczeniami już istnieją, zostają one zaktualizowane, przy czym stare tłumaczenia nie są usuwane. Tak więc, by wygenerować pliki źródłowe tłumaczeń dla przykładowego projektu MojaAplikacja, przy założeniu, że znajdujemy się w terminalu w folderze tego projektu, wydajemy polecenie:


lupdate ./MojaAplikacja.pro



Tłumaczenie komunikatów

Do przetłumaczenia komunikatów zawarych w pliku źródłowym tłumaczeń służy aplikacja QtLinguist dostarczana razem z Qt4. Dla przykładu posłużymy się prostą aplikacją pobierającą od użytkownika liczbę od 0 do 10, następnie wyświetlającą komunikat z podaną liczbą:


// Plik main.cpp:
#include <QApplication>
#include <QLocale>
#include <QTranslator>
#include <QInputDialog>
#include <QMessageBox>

int main(int argc, char *argv[])
{
QApplication aplikacja(argc, argv);

// Załadowanie tłumaczenia:
QString kodJezyka = QLocale::system().name();
QTranslator tlumacz;
tlumacz.load(QString("Translations_") + kodJezyka);

// Instalacja obiektu tłumacza:
aplikacja.installTranslator(&tlumacz);

// Pobranie danych od użytkownika:
int integer = QInputDialog::getInteger(0, QObject::tr("Books", "Dialog title"),
QObject::tr("How many books have you read last month?"), 0, 0, 10);
// Wyświetlenie komunikatu:
QMessageBox::information(0, QObject::tr("Result"),
QObject::tr("You have read %n book(s) last week.", 0, integer));

return 0;
}

# Plik projektu dla aplikacji Translations:

TEMPLATE = app
TARGET = Translations
CONFIG += qt
QT += gui

SOURCES += main.cpp

# Dodajemy wpis dla polskiego tłumaczenia:
TRANSLATIONS += Translations_pl.ts


Przy założeniu, że projekt nazwaliśmy i zapisaliśmy w folderze Translations, generujemy plik źródłowy tłumaczenia dla naszej aplikacji - będąc w terminalu w folderze projektu:


lupdate ./Translations.pro


Po wygenerowaniu tłumaczenia i uruchomieniu aplikacji QtLinguist (linguist lub linguist-qt4 - w zależności od systemu) otwieramy w niej wygenerowany plik Translations_pl.ts:



  1. Dokowalne okno z listą kontekstów tłumaczeń - kontekst jest nazwą klasy, w której tłumaczenie zostało użyte.

  2. Lista tłumaczeń wybranego kontekstu.

  3. Tekst źródłowy i tekst przetłumaczony.

  4. Okno, w którym zależnie od wybranej zakładki pokazywane są ostrzeżenia o błędach, podgląd kodu źródłowego aktualnego tłumaczenia lub podpowiedzi.


QtLinguist automatycznie sprawdza, czy użyte znaczniki kodu html, klawiszy skrótów, znaki interpunkcyjne na końcu linii się zgadzają. W przypadku, gdy przetłumaczyliśmy komunikat i wszystko się zgadza, obok tłumaczenia na liście pojawia się żółty znak zapytania, który, gdy klikniemy zatwierdza tłumaczenie, jako gotowe. Jeśli coś się nie zgadza, obok tłumaczenia pojawi się czerwony wykrzyknik (jak na zrzucie) - informacja o tym, co się nie zgadza dostępna jest w zakładce ostrzeżenia, lub w podpowiedzi po najechaniu kursorem myszy na tłumaczenie na liście. Walidację poszczególnych rzeczy można wyłączyć, i tak oto po wyłączeniu sprawdzania znaków interpunkcyjnych na końcu linii (poprzez menu Walidacja), wykrzyknik zamieniłby się w znak zapytania i moglibyśmy zatwierdzić tłumaczenie.



Jeśli w napisie, który ma być przetłumaczalny programista dodałby komentarz, byłby on widoczny zaraz pod tekstem źródłowym na niebieskim tle (w naszej przykładowej aplikacji pojawia się jeden komentarz, więc można to łatwo sprawdzić).



Obsługa Lingwisty Qt (QtLinguist) jest bardzo intuicyjna i nie ma potrzeby szczegółowo opisywać wszystkich funkcji. Zainteresowani mogą przejrzeć tutorial Lingwisty Qt po wybraniu odpowiedniej opcji spod menu Pomoc. Należy jeszcze wspomnieć o tłumaczeniach z argumentem liczbowym: Gdy komunikat zawiera liczbę jako argument - w naszym przypadku You have read %n book(s) last week. - Lingwista Qt daje nam możliwość podania różnych form przetłumaczonego tekstu w zależności od wybranego języka. Aby zmienić język dla tłumaczenia na polski wystarczy udać się do pozycji Ustawienia pliku z tłumaczeniami... z menu Edycja. W naszym tłumaczeniu będziemy mogli podać trzy różne formy:


  1. Forma liczby pojedynczej.

  2. Pierwsza forma liczby mnogiej (dla 2, 3, 4 w liczbach, np. dla: 2, 24, 33).

  3. Druga forma liczby mnogiej (pozostałe przypadki, np. dla: 0, 5, 6, 30).


Gotowy plik z tłumaczeniem możemy wydać w postaci binarnego pliku .qm z menu Plik:


  • Wydaj - tworzy finalny plik tłumaczeń w tym samym folderze, w którym znajduje się plik źródłowy

  • Wydaj jako... - tworzy finalny plik tłumaczeń w wybranym przez nas folderze


Po wydaniu tłumaczenia dla naszej przykładowej aplikacji i po jej uruchomieniu, zobaczymy interfejs w języku polskim:

Czytaj całość...

Podstawowe narzędzia Qt4 - qmake.

Wstęp

W poprzedniej części przedstawiłem mechanizm sygnałów i slotów, który bez wątpienia jest jedną z najlepszych cech Qt4. Zanim jednak na dobre przystąpimy do programowania, musimy zapoznać się z podstawowymi narzędziami Qt4, jak na przykład systemem budowania qmake, dzięki któremu będziemy mogli łatwo zarządzać naszymi projektami.



Plik projektu .pro

Qt posiada własny system budowania qmake, który na podstawie plików projektów potrafi wygenerować niezbędne pliki potrzebne do kompilacji na danej platformie, np. w przypadku Linuksa będą to pliki Makefile. Pliki projektów qmake posiadają rozszerzenie .pro, a ich składnia jest bardzo prosta i nikt nie powinien mieć problemów z jej zapamiętaniem. Najlepiej będzie, jeśli posłużymy się przykładem. Załóżmy, że nasz projekt składa się z pięciu plików z kodem źródłowym: PlikNaglowkowy1.h, PlikNaglowkowy2.h, Implementacja1.cpp, Implementacja1.cpp oraz main.cpp. Plik projektu dla nich będzie wyglądał następująco:


# Mój pierwszy projekt "aplikacja.pro".

TEMPLATE = app
TARGET = aplikacja.bin
CONFIG += qt
QT += gui network

HEADERS += PlikNaglowkowy1.h PlikNaglowkowy2.h
SOURCES += Implementacja1.cpp Implementacja2.cpp main.cpp

Jak możemy zobaczyć w pierwszej linijce, komentarze rozpoczynamy znakiem kratki. Komentarz obowiązuje aż do końca linii.



TEMPLATE = app

Słowo kluczowe TEMPLATE służy do wyboru szablonu projektu. Do wyboru mamy:


  • app - szablon dla aplikacji (domyślny)

  • lib - szablon dla biblioteki

  • subdirs - szablon dla projektu zawierającego podprojekty (każdy folder z podprojektem musi zawierać własny plik .pro)


Wybór szablonu jest bardzo ważny, ponieważ w przypadku gdybyśmy chcieli stworzyć bibliotekę, a jako szablon wybralibyśmy szablon dla aplikacji, to podczas kompilacji linker zgłosiłby błąd o braku funkcji main(), która nie występuje w bibliotece.


TARGET = aplikacja.bin



Słowo kluczowe TARGET określa nazwę wynikowego pliku binarnego, czyli w naszym przykładzie plik ze skompilowaną aplikacją będzie nosił nazwę aplikacja.bin. Jeśli podalibyśmy np. bin/aplikacja.bin, wtedy plik wynikowy zostałby utworzony w podfolderze bin




CONFIG += qt
QT += gui network



W tym miejscu określamy dodatkowe moduły biblioteki Qt4, z jakich korzysta nasza aplikacja, a co za tym idzie, z jakimi ma być zlinkowana. W powyższym przykładzie są to odpowiednio QtGui i QtNetwork. Linia CONFIG += qt jest niezbędna w projektach korzystających z Qt4.



Zwróćmy uwagę na operatory = i +=. Pierwszy z nich przypisuje nową wartość do danej zmiennej, drugi natomiast dodaje ją, nie usuwając starych wartości. Spójrzmy na przykład



QT += gui
QT += network


Jak widzimy, dzięki temu jedną instrujcję możemy rozłożyć na dwie oddzielne linie.



HEADERS += PlikNaglowkowy1.h PlikNaglowkowy2.h
SOURCES += Implementacja1.cpp Implementacja2.cpp main.cpp


HEADERS jako wartości przyjmuje nazwy plików nagłówkowych projektu, natomiast SOURCES nazwy plików z implementacją.



Istnieje oczywiście wiele więcej zmiennych dla plików projektów, jednakże nie są one często używane w małych projektach. Zainteresowanych odsyłam do dokumentacji Qt4 (W indeksie: qmake).


Instrukcje warunkowe w plikach projektów

Może zajść taka sytuacja, że dla systemu Mac OS X będziemy posiadali inny plik z implementacją, niż dla Windowsa, czy Linuksa. Z pomocą przychodzą bloki z instrukcjami warunkowymi, dzięki którym możemy zamieścić specjalne instrukcje dla poszczególnych platform.

Na przykład musimy zaimplementować klasę SuperKlasa, jednak implementacja dla systemu Linux będzie się znacznie różniła, niż dla Mac OS X i Windowsa. Tworzymy więc odpowiednie bloki w pliku projektu:


#Instrukcje dla platformy Windows:
win32 {
SOURCES += SuperKlasa_win32.cpp
}

#Instrukcje dla Mac OS X:
macx {
SOURCES += SuperKlasa_osx.cpp
}

#Instrukcje dla Linuksa:
linux* {
SOURCES += SuperKlasa_linux.cpp
}


W zależności od tego na której z platworm będzie budowany projekt, odpowiednie instrukcje zostaną wykonane.


Załóżmy inną sytuację - chcemy sprawdzić, czy w folderze z naszym projektem znajduje się plik main.cpp. W przypadku gdy go nie ma, zgłaszamy błąd. Do wykonania tych operacji posłużą nam funkcje exists() i error():


!exists( main.cpp ) {
error( "Nie odnaleziono wymaganego pliku main.cpp!" )
}


Jak łatwo się domyśleć, wykrzyknik (!) neguje wartość wyrażenia, czyli nasz blok zostanie wykonany, gdy funkcja exists() nie zwróci prawdy. Jeśli chcielibyśmy zgłosić tylko komunikat o braku pliku main.cpp i nie przerywać działania qmake, możemy zamiast funkcji error() użyć funkcję message(). Więcej funkcji zostało opisane w dokumentacji Qt4.


Ready, set, go!

Gdy mamy gotowy plik projektu i chcemy zbudować naszą aplikację, wystarczy w terminalu przejść do folderu zawierającego nasz projekt i wykonać polecenie qmake z pełną nazwą pliku projektu jako argument, np.:



qmake ./aplikacja.pro



Zależnie od platformy zostaną wygenerowane odpowiednie pliki niezbędne do kompilacji naszego programu. Np. na Linuksie zostaną wygenerowane odpowiednie pliki Makefile, dzięki czemu do szczęścia brakuje nam tylko wydanie polecenia make, które rozpocznie kompilację projektu.



Zakończenie

Dzisiaj przedstawiłem najbardziej podstawowe informacje potrzebne do samodzielnego zarządzania projektami. Jutro zajmiemy się mechanizmem tłumaczenia aplikacji, a w kolenjnej części nareszcie rozpoczniemy zabawę z QtCreatorem i już właściwym programowaniem ;)


Czytaj całość...