Wiki

Интернационализация в вопросах и ответах

Автор: Jasmin Blanchette Перевод: Andi Peredri Эта статья дает ответы на наиболее часто задаваемые вопросы по интернационализации 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. Мы хотим передать ui-файлы вместе с Qt Designer нашему переводчику, но не обнаружили средств для просмотра переведенного интерфейса. Как это можно сделать?

Ни 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() всегда возвращает перевод строки, выполненный на языке, который был загружен последним. Не могли бы вы добавить в функцию tr() параметр "language" для возможности указания используемого файла переводов?

В основе механизма 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();

Мы хотим хранить наши переводы не в qm-файлах, а в базе данных. Как мы можем сообщить об этом функции tr()?

Для этого вам нужно наследовать 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);
    ...
}

Моя программа содержит немецкий текст "Best?tigen", но когда я открываю соответствующий ts-файл в текстовом редакторе, то вижу вместо него "Best??tigen". Вы можете добавить в lupdate поддержку немецкого языка?

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. Затем она заменяет элемент в начале файла новым c указанной кодировкой. В завершении она вызывает QDomNode::save() для сохранения DOM-дерева на диск. Для сохранения QDomNode::save() использует кодировку, определенную в -теге, если такой существует, и 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.

Мы создали выпадающий список с элементами tr("Yes") и tr("No"). Благодаря функции tr(), перевод строк осуществляется легко. Но в других модулях нашей программы ожидаются строки "Yes" и "No" на английском. Есть ли в Qt возможность обратного перевода с tr("Yes") в "Yes"?

Да. Несмотря на то, что 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.