Pull to refresh

Qt — трудности перевода

Reading time 6 min
Views 61K
Вы написали программу на Qt и хотите перевести ее на другие языки, что бы сделать ее полезной для людей в других странах. Сделать это не просто, а очень просто. Для этого нам потребуется сделать всего три простых шага.

Исходная программа


Пусть у нас есть простая программа:

  1. #include <QtGui>
  2. #include <QtCore>
  3.  
  4. int main(int argc, char *argv[]) {
  5.   QApplication app(argc, argv);
  6.   QLabel label("Hello, World!");
  7.   label.show();
  8.   return app.exec();
  9. }
* This source code was highlighted with Source Code Highlighter.


Все что она делает — создает окно с надписью «Hello,World!». Сделаем для нее перевод на русский язык.

Шаг 1. Указание всех строк, для которых требуется сделать перевод


Все строки, которые увидит пользователь должны быть обработаны функциями QObject::tr() или QcoreApplication::translate().
У всех классов Qt, наследуемых от QObject, есть функция-член tr(). Так как мы работаем со строкой в глобальной функции которая не относится ни к какому классу, то используем функцию translate(), которая позволяет указать контекст перевода, то есть класс, к которому он относится (в данном случае — QLabel).

  1. #include <QtGui>
  2. #include <QtCore>
  3.  
  4. int main(int argc, char *argv[]) {
  5.   QApplication app(argc, argv);
  6.   QLabel label(app.translate("QLabel", "Hello, World!"));
  7.   label.show();
  8.   return app.exec();
  9. }
* This source code was highlighted with Source Code Highlighter.


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

  1.    static const char *greeting_strings[] = {
  2.      QT_TR_NOOP("Hello"),
  3.      QT_TR_NOOP("Goodbye")
  4.    };
  5. static const char *greeting_strings[] = {
  6.    QT_TRANSLATE_NOOP("HelloWidget", "Hello"),
  7.    QT_TRANSLATE_NOOP("HelloWidget", "Goodbye")
  8. };
* This source code was highlighted with Source Code Highlighter.


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

  1. void FileCopier::showProgress(int done, int total,
  2.                 const QString &currentFile)
  3. {
  4.    label.setText(tr("%1 of %2 files copied.\nCopying: %3")
  5.           .arg(done)
  6.           .arg(total)
  7.           .arg(currentFile));
  8. }
* This source code was highlighted with Source Code Highlighter.


Если потребуется изменить порядок аргументов при переводе, то при переводе надо будет поменять переменные с символом % местами, например, вот так:

«Копируем файл %3. %1 из %2 файлов скопировано»


Переписывать программу из-за этого не потребуется.

Шаг 2. Создание перевода


После того как мы использовали tr() по всему приложению, надо создать перевод текста. Перевод текста так же содержит 3 шага:
  1. Запуск lupdate для извлечения переводимого текста из исходного кода приложения Qt на C++, создавая файл сообщений для переводчиков (файл .ts). Утилита распознает конструкторы tr() и translate() и макросы QT_TR*_NOOP(), описанные выше, и производит файлы .ts (обычно один на каждый язык).
  2. Обеспечение переводов для исходных текстов в файле .ts, используя Qt Linguist. Так как файлы .ts в формате XML, их можно также отредактировать вручную.
  3. Запуск lrelease для получения легкого файла сообщений (файл .qm) из файла .ts, удобного только для конечного пользования. Думайте о файлах .ts как об «исходных файлах», и о файлах .qm как об «объектных файлах». Переводчик редактирует файлы .ts, но пользователям нашего приложения требуются только файлы .qm. Оба типа файлов не зависят от платформы и локали.


Обычно надо повторять эти шаги для каждого выпуска приложения. Утилита lupdate делает все возможное по повторному использованию переводов от предыдущих релизов.
Перед запуском lupdate, вам потребуется подготовить файл проектов. Вот как будет выглядеть наш файл проекта (файл helloworld.pro):

TEMPLATE = app
TARGET = release
DEPENDPATH +=.
INCLUDEPATH +=.
SOURCES += main.cpp
TRANSLATIONS += helloworld_ru.ts

Когда вы запускаете lupdate или lrelease, вы должны предоставить имя файла проекта в качестве аргумента командной строки.

Шаг 3. Загрузка файлов переводов в приложении.


В нашем приложении мы должны загрузить QTranslator::load() файлы используя QcoreApplication::installTranslator(). Окончательная версия программы примет вид:

  1. #include <QtGui>
  2. #include <QtCore>
  3.  
  4. int main(int argc, char *argv[]) {
  5.   QApplication app(argc, argv);
  6.   QTranslator myTranslator;
  7.   myTranslator.load("helloworld_" + QLocale::system().name());
  8.   app.installTranslator(&myTranslator);
  9.   QLabel label(app.translate("QLabel", "Hello, World!"));
  10.   label.show();
  11.   return app.exec();
  12. }
* This source code was highlighted with Source Code Highlighter.


Мы создаем объект QTranslator, загружаем в него файл перевода с помощью функции load(). В ней мы указываем начало имени файла нашего перевода. По умолчанию файлы переводов ищутся в папке с программой, но можно указать любую директорию, передав ее имя в качестве второго параметра функции. Расширение ".qm" будет добавлено автоматически. Функция Qlocale::system().name() возвращает имя текущей локали, в моем случае это было ru_RU.UTF-8. Порядок поиска файла переводов функцией load() будет следующим:

  1. helloworld_ru_RU.UTF-8.qm
  2. helloworld_ru_RU.UTF-8
  3. helloworld_ru_RU.qm
  4. helloworld_ru_RU
  5. helloworld_ru.qm
  6. helloworld_ru
  7. helloworld.qm
  8. helloworld


Правда когда я попробовал загрузить таким образом файл переводов в Windows XP, у меня ничего не вышло. Оказалось, что (по крайней мере у меня так) функция Qlocale::system().name() все время возвращала значение «С». Поэтому стоит предусмотреть дополнительный способ указания языка интерфейса приложения, например, через диалог настроек программы или параметры командной строки.

Вот и все, наше приложение переведено на русский язык, но есть еще парочка моментов, о которых стоит упомянуть в данной статье.

Дополнительные текстовые строки


Qt содержит внутри около 400 строк, которые так же должны быть переведены на языки которые нам необходимы. В директории $QTDIR/translations можно найти файлы переводов для французского, немецкого и упрощенного китайского, так же как и шаблоны для перевода на другие языки. (Эта директория так же содержит некоторые дополнительные неподдерживаемые переводы, которые могут быть полезны, например, перевод на русский язык).
Обычно эти переводы подгружают так же в фунции main():

  1. int main(int argc, char *argv[])
  2. {
  3.   ...
  4.    QTranslator qtTranslator;
  5.    qtTranslator.load("qt_" + QLocale::system().name(),
  6.        QLibraryInfo::location(QLibraryInfo::TranslationsPath));
  7.    app.installTranslator(&qtTranslator);
  8.   ...
  9. }
* This source code was highlighted with Source Code Highlighter.


Обратите внимание на использование QLibraryInfo::location() для обнаружения переводов Qt. Разработчик должен запросить путь к переводам обрабатывая QLibraryInfo::TranslationsPath для этой функции вместо использования переменной среды QTDIR в своих приложениях. Хотя в случаях, когда мы не уверены что у пользователя стоит полная версия Qt, имеет смысл поставлять этот файл переводов вместе с программой и загружать его из директории программы (или любой другой на выбор).

Динамический перевод


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

  1. void MyWidget::changeEvent(QEvent *event)
  2. {
  3.    if (e->type() == QEvent::LanguageChange) {
  4.      titleLabel->setText(tr("Document Title"));
  5.      ...
  6.      okPushButton->setText(tr("&OK"));
  7.    } else
  8.      QWidget::changeEvent(event);
  9. }
* This source code was highlighted with Source Code Highlighter.


Все остальные события изменения должны быть обработаны вызовом реализации по умолчанию данной функции.
Список установленных переводов может быть изменен в реакции на событие LocaleChange, или приложение может предоставлять интерфейс пользователю, который позволит ему изменить текущий язык приложения.
Обработчик событий по умолчанию для подклассов QWidget отвечает на событие QEvent::LanguageChange и вызовет эту функцию при необходимости; в других компонентах приложения можно так же заставить виджеты обновить себя отправив им событие LanguageChange.
UPD: Как подсказывает intellinside, классы графического интерфейса пользователя, сгенерированные Qt Designer'ом, имеют функцию retranslateUi(), которую можно вызвать для динамического изменения языка приложения.
Tags:
Hubs:
+35
Comments 6
Comments Comments 6

Articles