Эта статья дает ответы на наиболее часто задаваемые вопросы по интернационализации Qt-приложений. Она охватывает такие темы, как распространение файлов переводов, обратный перевод и динамическое переключение между различными языками.
Неофициальный перевод статьи Internationalization Q & A выполнен с любезного разрешения Trolltech.
Обычно двоичные файлы переводов (файлы .qm) помещают в один из каталогов системы и загружают их во время работы. Однако если программа не сможет их обнаружить, она будет работать без перевода.
Чтобы избежать этой проблемы, вы можете включить .qm-файлы в двоичный код программы с помощью инструмента qembed, который можно найти в каталоге Qt tools. Например:
qembed myapp_de.qm myapp_fr.qm > qm_files.h
В результате qembed сгенерирует статический константный массив данных, который вы можете включить в ваше приложение следующим образом:
#include "qm_files.h"
int main(int argc, char *argv[])
{
...
QTranslator translator;
translator.load(myapp_de_qm_data, myapp_de_qm_len);
app.installTranslator(&translator);
...
}
Используемая здесь функция load() является перегруженной и работает не с файлами, а непосредственно с .qm-данными. Она впервые появилась в Qt 3.2.
Чтобы файл qm_files.h оставался актуальным, необходимо вызывать qembed всякий раз, когда изменяются файлы .qm. В новых версиях Qt выполнение этой работы может быть автоматизировано с помощью qmake, который уже сейчас поддерживает встраивание изображений с помощью механизма "коллекций изображений". С работой механизма коллекций изображений вы можете ознакомиться в статье Иконография.
Ни Qt Designer, ни Qt Linguist не позволяют просматривать переведенный интерфейс. Однако программу для их просмотра можно легко написать, используя класс динамического создания виджетов из ui-файлов QWidgetFactory:
#include <qapplication.h>
#include <qtranslator.h>
#include <qwidgetfactory.h>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (argc < 3) {
qWarning("Usage: lpreview " "file.qm form1.ui...");
return 1;
}
QTranslator translator;
translator.load(argv[1]);
app.installTranslator(&translator);
for (int i = 2; i < argc; ++i) {
QWidget *widget = QWidgetFactory::create(argv[i]);
widget->show();
}
QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
return app.exec();
}
В качестве аргументов lpreview принимает имя файла переводов и список ui-файлов для отображения. При компиляции программа должна быть скомпонована с библиотекой Qt qui. Для этого в файл проекта lpreview.pro необходимо добавить следующую строку:
LIBS += -lqui
Такая кроссплатформенная форма записи строки LIBS поддерживается начиная с Qt 3.2. Если у вас более старая версия библиотеки, то вам необходимо написать иначе:
unix:LIBS += -lqui
win:LIBS += $(QTDIR)/lib/qui.lib
В основе механизма tr() лежит принцип, что в одно и то же время используется лишь один язык. Однако можно загрузить несколько файлов переводов в различные экземпляры QTranslator и использовать для получения перевода метод QTranslator::findMessage() вместо tr(). Вот пример:
QTranslator thaiTranslator;
thaiTranslator.load("myapp_th.qm");
QTranslator urduTranslator;
urduTranslator.load("myapp_da.qm");
thaiStr = thaiTranslator.findMessage("MainForm", "Help").translation();
urduStr = urduTranslator.findMessage("MainForm", "Help").translation();
Для этого вам нужно наследовать QTranslator и переопределить функцию findMessage(). Вот соответствующий пример:
class DBTranslator : public QTranslator
{
public:
QTranslatorMessage findMessage(const char *context,
const char *sourceText, const char *comment);
};
QTranslatorMessage DBTranslator::findMessage(const char *context,
const char *sourceText, const char *comment)
{
QSqlQuery query;
query.prepare("SELECT translation FROM message "
"WHERE context=? AND sourcetext=? AND comment=?");
query.addBindValue(context);
query.addBindValue(sourceText);
query.addBindValue(comment);
query.exec();
if (query.next()) {
return QTranslatorMessage(context, sourceText, comment, query.value(0).toString());
}
return QTranslatorMessage();
}
А в функции main() напишите следующее:
int main(int argc, char *argv[])
{
...
DBTranslator translator;
app.installTranslator(&translator);
...
}
XML ts-файлы ("translation source"), которые генерирует lupdate, используют кодировку UTF-8. Это стандартная для XML-формата кодировка, которая несовместима с другими популярными 8-битными кодировками Западной Европы, такими как ISO 8859-1 (Latin-1).
Чтобы сконвертировать ts-файл, недостаточно прочитать его в одной кодировке и сохранить в другой. Нужно также соответствующим образом исправить его XML-объявление:
<?xml version="1.0" encoding="ISO-8859-1"?>
Вы можете написать программу, выполняющую такое конвертирование:
int main(int argc, char *argv[])
{
if (argc < 3) {
qWarning("Usage: lencode encoding file1.ts...");
return 1;
}
QTextCodec *codec = QTextCodec::codecForName(argv[1]);
if (!codec) {
qWarning("Unknown encoding: %s", argv[1]);
return 1;
}
for (int i = 2; i < argc; ++i)
encodeFile(codec, argv[i]);
return 0;
}
В качестве параметров командной строки lencode принимает название кодировки и список файлов, которые необходимо переконвертировать. Основная работа выполняется функцией encodeFile():
void encodeFile(QTextCodec *codec, const char *fileName)
{
QFile file(fileName);
QDomDocument doc;
if (!file.open(IO_ReadOnly | IO_Translate))
; // обработка ошибки
if (!doc.setContent(&file, true))
; // обработка ошибки
if (doc.firstChild().isProcessingInstruction() && doc.firstChild().nodeName() == "xml")
doc.removeChild(doc.firstChild());
QDomNode node = doc.createProcessingInstruction("xml",
QString("version=\"1.0\" encoding=\"") + codec->mimeName() + "\"");
doc.insertBefore(node, doc.firstChild());
file.close();
if (!file.open(IO_WriteOnly | IO_Translate))
; // обработка ошибки
QTextStream out(&file);
doc.save(out, 4);
}
Функция encodeFile() загружает XML-файл в оперативную память и создает дерево DOM. Затем она заменяет элемент <?xml?> в начале файла новым c указанной кодировкой. В завершении она вызывает QDomNode::save() для сохранения DOM-дерева на диск. Для сохранения QDomNode::save() использует кодировку, определенную в <?xml?>-теге, если такой существует, и UTF-8 - в противном случае (для краткости мы опустили код обработки ошибок).
Было бы неплохо, если бы любое Qt-приложение могло мгновенно реагировать на смену локальных настроек Windows без дополнительных усилий при этом со стороны Qt-разработчиков. К сожалению, это невозможно, потому что Qt-виджеты не имеют доступа к оригинальным строкам, а только к переведенным. Например, следующая строка:
tr("Host %1 found").arg(host)
может быть представлена как "HТte www.troll.no trouvИ", и, фактически, у виджета нет возможности определить, что она была получена из "Host %1 found" и затем перевести ее как "Rechner %1 gefunden".
Тем не менее, реализовать динамическое переключение языков в Qt достаточно легко. Основная идея заключается в том, чтобы создать функцию, которая определяет все строки для формы. Например:
MyDialog::MyDialog(QWidget *parent, const char *name)
: QDialog(parent, name)
{
label = new QLabel(this);
lineEdit = new QLineEdit(this);
label->setBuddy(lineEdit);
okButton = new QPushButton(this);
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
retranslateStrings();
}
void MyDialog::retranslateStrings()
{
label->setText(tr("&Name:"));
okButton->setText(tr("OK"));
}
Затем, когда вам понадобится сделать повторный перевод формы, просто вызовите retranslateStrings() (например, по событию LanguageChange). Более подробно этот вопрос рассматривается в разделе "Internationalization" в C++ GUI Programming with Qt 3.
Да. Несмотря на то, что QTranslator не предоставляет прямого API для выполнения обратного поиска, вы можете вызвать QTranslator::message() для получения списка записей файла переводов и найти в нем необходимую исходную строку:
QCString reverseLookup(const QTranslator &translator,
const char *context, const QString &translation)
{
typedef QValueList<QTranslatorMessage> MessageList;
MessageList messages = translator.messages();
MessageList::iterator it = messages.begin();
while (it != messages.end()) {
if ((*it).translation() == translation && (*it).context() == QCString(context))
return (*it).sourceText();
++it;
}
return "";
}
Учтите, что такой подход пригоден только для работы с несжатыми qm-файлами переводов. Если используются сжатые qm-файлы, то возвращаемое функцией QTranslator::message() значение не определено. Для генерации несжатых файлов переводов выполните lrelease с параметром -nocompress. Эта опция поддерживается начиная с Qt 3.2.