15.2. Разработка приложений, подготовленных к переводу.

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

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

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

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

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

Context::tr(sourceText, comment) Часть имени Context -- это имя класса, производного от QObject. Если функция вызывается в контексте класса, то указание имени класса не обязательно. sourceText -- это строка символов, которая должна быть переведена. comment -- не обязательный аргумент, может использоваться для предоставления дополнительной информации переводчику.

Еще один пример:

BlueWidget::BlueWidget(QWidget *parent, const char *name) : QWidget(parent, name) { QString str1 = tr("Legal"); QString str2 = BlueWidget::tr("Legal"); QString str3 = YellowDialog::tr("Legal"); QString str4 = YellowDialog::tr("Legal", "US paper size"); } Первые два вызова производятся в контексте класса BlueWidget, последние два -- YellowDialog. Все четыре вызова получают строку "Legal" в качестве исходной, кроме того, последний из них имеет дополнительный комментарий, который поможет переводчику понять смысл исходной строки.

Перевод строк в разных контекстах выполняется независимо друг от друга. Переводчики обычно учитывают контекст в своей работе, часто выполняя пробные запуски приложения и оценивая качество перевода.

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

int main(int argc, char *argv[]) { QApplication app(argc, argv); ... QPushButton button(QObject::tr("Hello Qt!"), 0); app.setMainWidget(&button); button.show(); return app.exec(); } Очень часто полезной оказывается следующая методика, которая может быть применена к переводу названия приложения: вместо того, чтобы всякий раз набивать строки с именем приложения и вынуждать переводчика переводить их для каждого из контекстов, в котором они используются, более удобным будет определить его в виде макроса APPNAME, поместить макрос в заголовочный файл и использовать его по мере необходимости: #define APPNAME MainWindow::tr("OpenDrawer 2D") До сих пор, в качестве контекста мы рассматривали имя класса. Это довольно удобно, поскольку в большинстве случаев мы можем не указывать контекст перевода явно, при вызове функции tr(). Но более универсальный способ подготовки строк к переводу состоит в использовании функции QApplication:: translate(), которая принимает три аргумента: контекст, исходный текст и необязательный комментарий. Например, еще один способ определения макроса APPNAME: #define APPNAME qApp->translate("Global Stuff", "OpenDrawer 2D") На этот раз текст помещен в контекст "Global Stuff".

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

// НЕВЕРНО const char *appName = "OpenDrawer 2D"; QString translated = tr(appName); Проблема состоит в том, что lupdate не сможет отыскать строку "OpenDrawer 2D", поскольку она явно не передается функции tr(). Эта проблема очень часто проявляется при работе с динамическими строками: // НЕВЕРНО statusBar()->message(tr("Host " + hostName + " found")); Здесь строка изменяется динамически, в зависимости от значения переменной hostName, таким образом мы не можем требовать от tr() корректного перевода.

Как одно из решений проблемы -- используйте QString::arg():

statusBar()->message(tr("Host %1 found").arg(hostName)); Остановимся в этом месте чуть подробнее: функции tr() передается строка символов "Host %1 found". Допустим, что приложение загрузило файл с русским переводом, тогда функция tr() должна вернуть примерно такую строку: "Обнаружен узел сети %1". После этого аргумент '%1' замещается содержимым переменной hostName. В результате мы получаем вполне корректный перевод сообщения, которое демонстрируется русскоговорящему пользователю.

В случае, когда необходимо записать перевод строки в переменную, следует использовать макрос QT_TR_NOOP(). Чаще всего этот прием используется при создании статических массивов строк, например:

void OrderForm::init() { static const char * const flowers[] = { QT_TR_NOOP("Medium Stem Pink Roses"), QT_TR_NOOP("One Dozen Boxed Roses"), QT_TR_NOOP("Calypso Orchid"), QT_TR_NOOP("Dried Red Rose Bouquet"), QT_TR_NOOP("Mixed Peonies Bouquet"), 0 }; int i = 0; while (flowers[i]) { comboBox->insertItem(tr(flowers[i])); ++i; } } Макрос QT_TR_NOOP() фактически ничего не делает, но он служит маркером для lupdate. Строки, передаваемые этому макросу попадут в файл перевода и затем 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(), которая будет выполнять перевод этих строк.

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

DEFINES += QT_NO_CAST_ASCII В результате, каждая строка, которая не пропускается через вызов tr() или QString:: fromAscii() (в зависимости от того, должна строка подвергаться переводу или нет), будет вызывать ошибку времени компиляции.

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

int main(int argc, char *argv[]) { QApplication app(argc, argv); QTranslator appTranslator; appTranslator.load(QString("app_") + QTextCodec::locale(), qApp->applicationDirPath()); app.installTranslator(&appTranslator); ... return app.exec(); } Функция QTextCodec::locale() возвращает строку -- имя локали пользователя, запустившего приложение. Локаль может быть определена более или менее точно, например, ru определяет русскую локаль, ru_RU -- русскую локаль для России, ru_RU.KOI8-R -- русскую локаль для России, с кодировкой символов KOI8-R.

Предположим, что приложение получило строку с именем локали -- ru_RU.KOI8-R, тогда load() попытается сначала загрузить файл app_ru_RU.KOI8-R.qm. Если этот файл отсутствует, то load() попытается загрузить файл app_ru_RU.qm, затем app_ru.qm и наконец app.qm. Обычно, в таких случаях достаточно создать один файл, с именем app_ru.qm. Однако, если перевод предполагает более точный учет региональных настроек, как например в случае fr_FR (французский язык для Франции) и fr_CA (французский язык для Канады), то может потребоваться создать отдельные файлы с переводом для каждого из регионов.

Второй аргумент функции load() -- это каталог, где находится файл с переводом. Компания Trolltech предоставляет файлы с французским и немецким переводами Qt в каталоге translations. (Переводы на некоторые другие языки так же могут поставляться вместе с библиотекой, но все они выполняются командами добровольцев и официально не поддерживаются.) Так же должен подгружаться библиотечный файл с переводом:

QTranslator qtTranslator; qtTranslator.load(QString("qt_") + QTextCodec::locale(), qApp->applicationDirPath()); app.installTranslator(&qtTranslator); Экземпляр класса QTranslator может хранить только один файл с переводом, поэтому следует использовать различные QTranslator. Но это не является большой проблемой, так как мы можем создать столько экземпляров класса QTranslator, сколько потребуется. Все они будут использоваться приложением при поиске перевода.

В некоторых языках, таких как арабский и иврит, строки пишутся справа-налево. В этих случаях приложению необходимо сообщить о порядке вывода строк вызовом QApplication::setReverseLayout(true). Для таких языков, файл перевода должен содержать специальный маркер -- "LTR", который обеспечивает корректный вывод переведенных строк.

Для пользователей программы может оказаться более удобным вариант, когда файлы перевода внедряются в тело исполняемого файла программы. Мало того, что этот прием уменьшает количество файлов, которые придется распространять вместе сприложением, но это так же сведет к минимуму риск случайной потери файлов с переводами. Для реализации этой возможности, в составе Qt распространяется утилита qembed, которая преобразует файлы с переводами в массивы C++, которые могут передаваться функции QTranslator::load().

Мы описали все, что необходимо сделать, чтобы подготовить приложение к интернационализации. Но язык и направление письма это еще не все, что отличает страны и культуры. Интернационализированная программа должна принимать во внимание формат представления даты, времени, национальной валюты, чисел и порядок сортировки строк. Для этого в Qt 3.2 не существует никаких специальных функций, но мы можем использовать стандартные функции setlocale() и localeconv(). [1]

Некоторые функции и классы Qt адаптируют свое поведение под настройки локали:

Наконец, вместе с переводом, приложение может использовать разные наборы иконок для разных языков. Например, для языков, в которых письмо осуществляется справа-налево, в web-браузере логичнее было бы поменять местами кнопки "Назад" и "Вперед". Сделать это можно следующим образом: if (QApplication::reverseLayout()) { backAct->setIconSet(forwardIcon); forwardAct->setIconSet(backIcon); } else { backAct->setIconSet(backIcon); forwardAct->setIconSet(forwardIcon); } Иконки, изображение на которых соответствует алфавитным символам, очень часто должны быть адаптированы, в соответствии с конкретными языковыми настройками. Например, в текстовых процессорах, иконка с изображением символа "I" (что означает "Italic" -- Курсив) должна быть заменена на "C" для Испании (Cursivo) или на "K" -- для России (Курсив). Самый простой способ: if (tr("Italic")[0] == 'C') { italicAct->setIconSet(iconC); } else if (tr("Italic")[0] == 'K') { italicAct->setIconSet(iconK); } else { italicAct->setIconSet(iconI); }

Примечания

[1]

Вероятно, в состав Qt 3.3 будет включен класс QLocale, который будет обслуживать представление числовых форматов.