=> Главная База Знаний Qt Создание переводимого интерфейса приложения


Создание переводимого интерфейса приложения

Создание переводимого интерфейса приложения

Если мы хотим иметь многоязыковую версию нашего приложения, мы должны сделать две вещи:

• убедиться, что все строки, которые видит пользователь, проходят через функцию tr();

• загрузить файл перевода (.qm) при запуске приложения.

Ничего подобного не надо делать, если приложения никогда не будут переводиться на другой язык. Однако применение функции tr() почти не требует дополнительных усилий и оставляет дверь открытой для их перевода когда-нибудь в будущем.

Функция tr() является статической функцией, определенной в классе QObject и переопределяемой в каждом подклассе, в котором встречается макрос Q_OBJECT. При ее использовании в рамках подкласса QObject мы можем вызывать tr() без ограничений. Вызов tr() возвращает перевод строки, если он имеется, и первоначальный текст в противном случае.

Для подготовки файлов переводов мы должны запустить утилиту Qt lupdate. Эта утилита собирает все строковые константы, которые встречаются в вызовах tr(), и формирует файлы переводов, содержащие все эти подготовленные к переводу строки. Эти файлы могут затем быть переданы переводчику для добавления к ним перевода строк. Эта процедура рассматривается позже в данной главе в разделе «Перевод приложений».

В общем виде вызов tr() имеет следующий синтаксис:

Контекст::tr(исходныйТекст, комментарий)

Здесь Контекст — имя подкласса QObject, в котором используется макрос Q_OBJECT. Нам не требуется его указывать, если мы вызываем tr() в функции—члене рассматриваемого класса. Аргумент исходныйТекст — текстовая константа, которую нужно будет переводить. Аргумент комментарий является необязательным, и он может использоваться для предоставления переводчику дополнительной информации.

Ниже приводится несколько примеров:

01 RockyWidget::RockyWidget(QWidget *parent)

02 : QWidget(parent)

03 {

04 QString str1 = tr("Letter");

05 QString str2 = RockyWidget::tr("Letter");

06 QString str3 = SnazzyDialog::tr("Letter");

07 QString str4 = SnazzyDialog::tr("Letter", "US paper size");

08 }

Первые два вызова tr() выполняются в контексте объекта RockyWidget (скалистый виджет), а вторые два — в контексте объекта SnazzyDialog (притягательное диалоговое окно). В качестве исходного текста во всех четырех случаях используется слово «Letter» (буква). Последний вызов имеет также комментарий, помогающий переводчику точнее понять смысл исходного текста.

Строки в различных контекстах (классах) переводятся независимо друг от друга. Переводчики, как правило, одновременно работают только с одним контекстом, причем часто при этом работает приложение и на экране отображается виджет или диалоговое окно, которые необходимо перевести.

Когда мы вызываем tr() из глобальной функции, мы должны явно указать контекст. Любой подкласс QObject может использоваться в приложении в качестве контекста. Если такого подкласса нет, мы всегда можем использовать сам класс QObject. Например:

01 int main(int argc, char *argv[])

02 {

03 QApplication app(argc, argv);

04 QPushButton button(QObject::tr("Hello Qt!"));

05 button.show();

06 return app.exec();

07 }

До сих пор во всех примерах контекст задавался именем класса. Это удобно, поскольку мы почти всегда можем опустить его, но на самом деле это не так. Наиболее общий способ перевода строки в Qt заключается в использовании функции QApplication::translate(), которая принимает три аргумента: контекст, исходный текст и необязательный комментарий. Например, ниже приводится другой способ перевода «Hello Qt!»:

QApplication::translate("Global Stuff", "Hello Qt!");

На этот раз мы поместили текст в контекст «Global Stuff» (глобальное вещество — ну нихрена себе перевод :) ).

Функции tr() и translate() играют двоякую роль: они являются маркерами, которые утилита lupdate использует для поиска видимых пользователем строк, и одновременно они являются функциями С++, которые переводят текст. Это отражается на том, как следует записывать программный код. Например, следующий программный код не сработает:

// НЕПРАВИЛЬНО

const char *appName = "OpenDrawer 2D";

QString translated = tr(appName);

Проблема состоит в том, что утилита lupdate не сможет извлечь строковую константу «OpenDrawer 2D», поскольку она не входит в вызов функции tr(). Это означает, что переводчик не будет иметь возможность перевести эту строку. Эта проблема часто возникает и при построении динамических строк:

// НЕПРАВИЛЬНО

statusBar()->showMessage(tr("Host " + hostName + " found"));

Здесь значение строки, которую мы передаем функции tr(), меняется в зависимости от значения hostName, и поэтому мы не можем ожидать, что перевод функцией tr() будет выполнен правильно.

Решение заключается в применении функции QString::arg():

statusBar()->showMessage(tr("Host %1 found").arg(hostName));

Обратите внимание на то, как это работает: строковый литерал «Host %1 found» (хост %1 найден) передается функции tr(). Если загружен файл перевода на французский язык, tr() возвратит что-то подобное «Нфtе %1 trouvй». Параметр «%1» замещается на содержимое переменной hostName.

Хотя в целом не рекомендуется вызывать tr() для переменной, это может сработать. Мы должны использовать макрос QT_TR_NOOP() для пометки тех строковых литералов, перевод которых должен быть выполнен до их присваивания переменной. Это лучше всего делать для статических массивов строк. Например:

01 void OrderForm::init()

02 {

03 static const char * const flowers[] = {

04 QT_TR_NOOP("Medium Stem Pink Roses"),

05 QT_TR_NOOP("One Dozen Boxed Roses"),

06 QT_TR_NOOP("Calypso Orchid"),

07 QT_TR_NOOP("Dried Red Rose Bouquet"),

08 QT_TR_NOOP("Mixed Peonies Bouquet"),

09 0

10 };

11 for (int i = 0; flowers[i]; ++i)

12 comboBox->addItem(tr(flowers[i]));

13 }

Макрос QT_TR_NOOP() просто возвращает свой аргумент. Но утилита lupdate обнаружит все строки, заданные в виде аргумента макроса QT_TR_NOOP(), и поэтому они смогут быть переведены. При использовании позже этой переменной мы вызываем, как обычно, tr() для выполнения перевода. Несмотря на передачу функции tr() переменной, перевод все-таки будет выполнен.

Существует также макрос QT_TRANSLATE_NOOP(), который работает подобно макросу QT_TR_NOOP(), но для него, кроме того, задается контекст. Этот макрос удобно использовать для инициализации переменных вне класса:

static const char * const flowers[] = {

QT_TRANSLATE_NOOP("OrderForm", "Medium Stem Pink Roses"),

QT_TRANSLATE_NOOP("OrderForm", "One Dozen Boxed Roses"),

QT_TRANSLATE_NOOP("OrderForm", "Calypso Orchid"),

QT_TRANSLATE_NOOP("OrderForm", "Dried Red Rose Bouquet"),

QT_TRANSLATE_NOOP("OrderForm", "Mixed Peonies Bouquet"),

0

};

Здесь аргумент контекста должен совпадать с контекстом при будущем вызове функции tr() или translate().

Когда мы начинаем использовать в приложении функцию tr(), легко можно забыть в каких-то случаях о необходимости задавать видимые пользователем строки через вызов функции tr() (особенно если это делается впервые). Эти пропущенные строки фактически могут быть обнаружены переводчиком или, еще хуже, пользователями переведенного приложения, когда некоторые строки будут отображаться с применением первоначального языка. Чтобы не допустить этого, мы можем указать Qt на необходимость запрета неявных преобразований с типа const char * на тип QString. Это делается путем определения препроцессорного символа QT_NO_CAST_FROM_ASCII перед включением любого заголовочного файла Qt. Наиболее простой способ обеспечения установки этого символа состоит в добавлении следующей строки в файл .pro:

DEFINES += QT_NO_CAST_FROM_ASCII

Это заставит нас каждый строковый литерал использовать через вызов tr() или QLatin1String() в зависимости от того, надо ли его переводить или нет. Строки, которые не будут заданы именно таким образом, приведут к выводу сообщения об ошибке компилятора и заставят нас восполнить пропущенные вызовы функций tr() или QLatin1String().

После заключения всех видимых пользователем строк в вызовы функций tr() для обеспечения перевода нам остается только загрузить файл перевода. Обычно мы это делаем в функции приложения main(). Например, ниже показано, как можно попытаться загрузить файл перевода, который зависит от пользовательской локализации приложения:

01 int main(int argc, char *argv[])

02 {

03 QApplication app(argc, argv);

04 QTranslator appTranslator;

05 appTranslator.load("myapp_" + QLocale::system().name(),

06 qApp->applicationDirPath());

07 app.installTranslator(&appTranslator);

08 …

09 return app.exec();

10 }

Функция QLocale::system() возвращает объект QLocale, который содержит информацию о пользовательской локализации. Обычно имя локализации является частью имени файла .qm. Локализации можно задавать более или менее точно; например, fr задает европейский французский язык, fr_CA задает канадский французский язык, a fr_CA.ISO8859-15 задает канадский французский язык с использованием кодировки ISO 8859-15 (которая поддерживает символы «^», «КЬ», «№» и «Ы» — в исходном бумажном издании французский куда-то подевался %) ).

Если локализацией является fr_CA.ISO8859-15, функция QTranslator::load() сначала попытается загрузить файл myapp_fr_CA.ISO8859-15.qm. Если этого файла нет, функция load() на следующем шаге попытается загрузить файл myapp_fr_CA.qm, затем myapp_fr.qm и, наконец, myapp.qm, и это будет последней попыткой. В обычных случаях нам необходимо предоставить только файл myapp_fr.qm, содержащий перевод на стандартный французский язык, но если нам нужен другой файл перевода для говорящих на французском в Канаде, мы можем также обеспечить файл myapp_fr_CA.qm, и он будет использован для локализации fr_CA.

Второй аргумент функции QTranslator::load() является каталогом, где функция load() будет искать файл перевода. В данном случае мы предполагаем, что файлы переводов размещаются в том же каталоге, где находится исполняемый модуль.

В самих библиотеках Qt содержится несколько строк, которые необходимо перевести. Компания «Trolltech» располагает переводы на французский, немецкий и упрощенный китайский языки в каталоге Qt translations. Имеются переводы также на другие языки, но они выполнены пользователями Qt и официально не поддерживаются. Необходимо также загрузить файл перевода библиотек Qt:

QTranslator qtTranslator;

qtTranslator.load("qt_" + QLocale::system().name(),

qApp->applicationDirPath());

app.installTranslator(&qtTranslator);

Объект QTranslator может работать одновременно только с одним файлом перевода, и поэтому мы используем отдельный QTranslator для перевода приложения Qt. Возможность применения только одного файла для перевода не составляет проблемы, поскольку мы можем установить любое необходимое нам их количество. QApplication будет рассматривать все такие файлы при поиске перевода.

Некоторые языки, такие как арабский и иврит, используют запись справа налево, а не слева направо. Для таких языков общая компоновка приложения должна быть изменена на зеркальную, что делается при помощи вызова функции QApplication::setLayoutDirection(Qt::RightToLeft). Файлы перевода Qt содержат специальный маркер типа «LTR», указывающий Qt на направление записи используемого языка — слева направо или справа налево, и поэтому нам обычно не приходится самим вызывать функцию setLayoutDirection().

Для наших пользователей может быть более удобно, если файлы перевода будут встраиваться в исполняемый модуль приложения, используя ресурсную систему Qt. Это не только снижает количество файлов в дистрибутиве приложения, но при этом снижается риск случайной потери или удаления файлов переводов.

Предположим, что файлы .qm располагаются в подкаталоге translations исходного дерева, тогда файл myapp.qrc будет содержать следующие строки:

<!DOCTYPE RCC><RCC version="1.0">

<qresource>

<file>translations/myapp_de.qm</file>

<file>translations/myapp_fr.qm</file>

<file>translations/myapp_zh.qm</file>

<file>translations/qt_de.qm</file>

<file>translations/qt_fr.qm</file>

<file>translations/qt_zh.qm</file>

</qresource>

</RCC>

Файл .pro будет иметь следующий элемент:

RESOURCES = myapp.qrc

Наконец, в функции main() мы должны указать :/translations в качестве пути к файлам переводов. Начальное двоеточие говорит о том, что это путь к ресурсу, а не к файлу, размещенному в файловой системе.

Теперь нами рассмотрено все, что необходимо для обеспечения перевода приложения на другие языки. Но язык и направление записи не единственное, что отличает различные страны и культуры. Интернационализация программы должна также учитывать местные форматы дат и времени, денежных единиц, чисел и упорядоченность букв. Qt содержит класс QLocale, обеспечивающий локализованные форматы чисел и даты/времени. Для получения другой информации, характерной для данной местности, мы можем использовать стандартные функции С++ setlocale() и localeconv().

Поведение некоторых классов и функций Qt зависит от локализации:

• сравнение, которое осуществляет функция QString::localeAwareCompare(), зависит от локализации. Этой функцией удобно пользоваться для упорядочивания элементов, которые видит пользователь;

• функция toString() для объектов QDate, QTime и QDateTime возвращает строку в локализованном формате, если вызывается с аргументом Qt::LocalDate;

• по умолчанию виджеты QDateEdit и QDateTimeEdit представляют даты в локализованном формате.

Наконец, в переведенном приложении может потребоваться применение пиктограмм, отличных от используемых в оригинальной версии приложения. Например, стрелки влево и вправо на кнопках Back и Forward (назад и вперед) веб-браузера необходимо поменять местами для языка с записью справа налево. Мы можем это сделать следующим образом:

if (QApplication::isRightToLeft())

{

backAction->setIcon(forwardIcon);

forwardAction->setIcon(backIcon);

} else {

backAction->setIcon(backIcon);

forwardAction->setIcon(forwardIcon);

}

Обычно приходится переводить пиктограммы, содержащие буквы алфавита. Например, буква «I» на кнопке панели инструментов, отображающая опцию Italic (курсив) текстового процессора, должна быть заменена буквой «С» для испанского языка (Cursivo) и буквой «К» для языков датского, голландского, немецкого, норвежского и шведского (Kursiv). Ниже показано, как это можно просто сделать:

if (tr("Italic")[0] == 'C') {

italicAction->setIcon(iconC);

} else if (tr("Italic")[0] == 'K') {

italicAction->setIcon(iconK);

} else {

italicAction->setIcon(iconI);

}

Можно поступить по-другому и использовать средства ресурсной системы, обеспечивающие поддержку нескольких локализаций. В файле .qrc мы можем определять локализацию для ресурса, используя атрибут lang. Например:

<qresource>

<file>italic.png</file>

</qresource>

<qresource lang="es">

<file alias="italic.png">cursivo.png</file>

</qresource>

<qresource lang="sv">

<file alias="italic.png'">kursiv.png</file>

</qresource>

Если пользовательской локализацией является es (Espanol), :/italic.png становится ссылкой на изображение cursivo.png. Если пользовательской локализацией является sv (Svenska), используется изображение kursiv.png. Для других локализаций используется italic.png.