Разработка графического интерфейса с помощью библиотеки Qt3 | ||
---|---|---|
Пред. | Глава 15. Интернационализация | След. |
Если необходимо предусмотреть возможность перевода приложения на разные языки, следует соблюдать следующие положения:
Весь текст, который будет отображаться перед пользователем, должен пропускаться через функцию tr().
На запуске, приложение должно подгружать файл с переводом (.qm).
Функция 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 адаптируют свое поведение под настройки локали:
Функция QString::localeAwareCompare() выполняет сравнение строк в зависимости от настроек локали. Она используется классами QIconView и QListView для выполнения сортировки своих элементов.
Функция toString() используется классами QDate, QTime и QDateTime, возвращающими локализованное представление даты и времени, когда вызываются с аргументом Qt::LocalDate.
По-умолчанию QDateEdit, QTimeEdit и QDateTimeEdit представляют дату и время в локализованном виде.
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, который будет обслуживать представление числовых форматов. |
Пред. | В начало | След. |
Интернационализация | На уровень выше | Динамическое переключение языков. |