15.3. Динамическое переключение языков.

В большинстве приложений, определение предпочитаемого пользователем языка производится в функции main() и затем выполняется загрузка соответствующего файла с переводом. Чаще всего этого бывает достаточно, но иногда возникают ситуации, когда нужно быстро переключить интерфейс приложения на другой язык, без перезапуска приложения. В качестве примера можно привести приложения для операторов международных телефонных центров, для переводчиков, выполняющих синхронный перевод или для операторов отделений банков, когда становится насущной необходимость в быстром переключении между языками.

Чтобы приложение позволяло выполнять быстрый переход от языка к языку, нужно сделать немного больше, чем просто загрузить единственный файл с переводом, но это не так сложно, как может показаться на первый взгляд. Чтобы реализовать возможность выбора языка во время исполнения приложения, нужно:

Рассмотрим отдельные части исходного кода приложения, которое поддерживает возможность быстрой смены языка интерфейса. Язык по-умолчанию -- Английский.

Рисунок 15.1. Меню Language.


Поскольку заранее не известно, какой из языков предпочтет пользователь, приложение запускается с языком по-умолчанию. Загрузка файла с переводом будет производиться динамически, по мере необходимости. Таким образом, весь код, поддерживающий перевод, должен размещаться в классах главного окна и диалогов. Рассмотрим класс главного окна приложения-примера (Call Center), производного от класса QMainWindow: MainWindow::MainWindow(QWidget *parent, const char *name) : QMainWindow(parent, name) { journalView = new JournalView(this); setCentralWidget(journalView); qmPath = qApp->applicationDirPath() + "/translations"; appTranslator = new QTranslator(this); qtTranslator = new QTranslator(this); qApp->installTranslator(appTranslator); qApp->installTranslator(qtTranslator); createActions(); createMenus(); retranslateStrings(); } В конструкторе, виджет JournalView (наследник класса QListView) назначается центральным. Затем настраиваются переменные-члены, которые имеют отношение к переводу: В конце вызываются createActions() и createMenus(), которые создают систему меню. И наконец вызывается функция retranslateStrings(), которая настраивает первоначальный перевод по-умолчанию. void MainWindow::createActions() { newAct = new QAction(this); connect(newAct, SIGNAL(activated()), this, SLOT(newFile())); ... aboutQtAct = new QAction(this); connect(aboutQtAct, SIGNAL(activated()), qApp, SLOT(aboutQt())); } Функция createActions() создает обычные объекты QAction, правда без указания надписей и горячих комбинаций клавиш. Эти действия будут выполнены в функции retranslateStrings(). void MainWindow::createMenus() { fileMenu = new QPopupMenu(this); newAct->addTo(fileMenu); openAct->addTo(fileMenu); saveAct->addTo(fileMenu); exitAct->addTo(fileMenu); ... createLanguageMenu(); } Функция createMenus() создает меню, но не вставляет их в полосу меню. Эти действия так же будут выполнены в функции retranslateStrings(). В конце функции вызывается createLanguageMenu(), которая заполняет меню списком поддерживаемых языков. Мы вернемся к этой функции чуть позже, а сейчас заглянем в исходный код функции retranslateStrings(): void MainWindow::retranslateStrings() { setCaption(tr("Call Center")); newAct->setMenuText(tr("&New")); newAct->setAccel(tr("Ctrl+N")); newAct->setStatusTip(tr("Create a new journal")); ... aboutQtAct->setMenuText(tr("About &Qt")); aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); menuBar()->clear(); menuBar()->insertItem(tr("&File"), fileMenu); menuBar()->insertItem(tr("&Edit"), editMenu); menuBar()->insertItem(tr("&Reports"), reportsMenu); menuBar()->insertItem(tr("&Language"), languageMenu); menuBar()->insertItem(tr("&Help"), helpMenu); } В функции retranslateStrings() сосредоточены все вызовы tr() для класса MainWindow. Она вызывается из конструктора класса, а так же всякий раз, когда пользователь изменяет язык интерфейса приложения, из меню Language.

Здесь в пункты меню записывается текст и строки подсказки. Затем меню вставляются в полосу меню, с уже переведенными надписями.

Функция createMenus(), которая упоминалась выше, вызывает функцию createLanguageMenu(), чтобы заполнить меню Language списком поддерживаемых языков:

void MainWindow::createLanguageMenu() { QDir dir(qmPath); QStringList fileNames = dir.entryList("callcenter_*.qm"); for (int i = 0; i < (int)fileNames.size(); ++i) { QTranslator translator; translator.load(fileNames[i], qmPath); QTranslatorMessage message = translator.findMessage("MainWindow", "English"); QString language = message.translation(); int id = languageMenu->insertItem( tr("&%1 %2").arg(i + 1).arg(language), this, SLOT(switchToLanguage(int))); languageMenu->setItemParameter(id, i); if (language == "English") languageMenu->setItemChecked(id, true); QString locale = fileNames[i]; locale = locale.mid(locale.find('_') + 1); locale.truncate(locale.find('.')); locales.push_back(locale); } } Вместо того, чтобы жестко зашивать список языков в приложение, здесь создается один пункт меню для каждого файла с переводом (.qm). Для простоты примера, предполагается, что англоязычный вариант так же находится в отдельном файле .qm. Как альтернатива -- когда пользователь выбирает пункт меню English, очищать QTranslator методом clear().

Единственная сложность тут состоит в том, чтобы представить названия языков в меню в достаточно удобочитаемом виде. Если просто показывать en, вместо English, или de, вместо Deutsch, то мы можем привести в замешательство отдельных пользователей. Поэтому, createLanguageMenu() проверяет перевод строки "English" в контексте "MainWindow". Эта строка должна быть переведена как "Deutsch" в немецком переводе, как "Francais" -- во французским и как "" -- в японском.

Пункты меню создаются вызовом QPopupMenu::insertItem(). Они соединяются со слотом switchToLanguage(int) главного окна, который мы рассмотрим чуть ниже. Аргумент слота switchToLanguage(int) -- это значение, установленное функцией setItemParameter(). Примерно то же самое мы делали в Главе 3, когда в приложении Spreadsheet создавали пункты меню, соответствующие названиям недавно открывавшихся документов.

В конце, название локали добавляется в список locales, который будет использоваться функцией switchToLanguage().

void MainWindow::switchToLanguage(int param) { appTranslator->load("callcenter_" + locales[param], qmPath); qtTranslator->load("qt_" + locales[param], qmPath); for (int i = 0; i < (int)languageMenu->count(); ++i) languageMenu->setItemChecked(languageMenu->idAt(i), i == param); retranslateStrings(); } Слот switchToLanguage() отрабатывает, когда пользователь выбирает какой либо пункт в меню Language. Сначала слот загружат файлы с переводами для приложения и для библиотеки Qt. Затем обновляет состояние маркеров пунктов меню. И наконец вызывает retranslateStrings(), которая выполняет перевод всех надписей в главном окне.

В Microsoft Windows, дополнительно к обслуживанию меню Language, можно предусмотреть реакцию приложения на событие LocaleChange, которое возникает, когда изменяются региональные настройки среды. Этот тип событий существует во всех версиях Qt, но актуален только для Microsoft Windows. Для обработки этого события необходимо перекрыть обработчик QObject::event() следующим образом:

bool MainWindow::event(QEvent *event) { if (event->type() == QEvent::LocaleChange) { appTranslator->load(QString("callcenter_") + QTextCodec::locale(), qmPath); qtTranslator->load(QString("qt_") + QTextCodec::locale(), qmPath); retranslateStrings(); } return QMainWindow::event(event); } Если пользователь изменит региональные настройки во время работы приложения, то будет произведена попытка загрузить соответствующий файл с переводом и последующим вызовом retranslateStrings() будет обновлен интерфейс приложения.

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

На этом мы завершаем обзор класса MainWindow и переходим к одному из виджетов главного окна -- JournalView, чтобы показать, какие изменения необходимо внести, чтобы он так же поддерживал возможность изменения языка во время работы приложения.

JournalView::JournalView(QWidget *parent, const char *name) : QListView(parent, name) { ... retranslateStrings(); } Класс JournalView порожден от класса QListView. В конце конструктора класса вызывается его метод retranslateStrings(). Это очень похоже на то, что мы делали в классе главного окна. bool JournalView::event(QEvent *event) { if (event->type() == QEvent::LanguageChange) retranslateStrings(); return QListView::event(event); } Обработчик событий виджета вызывает retranslateStrings(), при поступлении события LanguageChange.

Qt генерирует событие LanguageChange, при изменении содержимого QTranslator. В нашем приложении эта ситуация возникает, когда вызывается функция load(), для загрузки файлов перевода в appTranslator и qtTranslator.

Не следует путать события LanguageChange и LocaleChange. Событие LocaleChange как бы говорит приложению: "Необходимо загрузить другой файл с переводом", а событие LanguageChange: "Необходимо выполнить перевод всех строк".

В классе MainWindow у нас не возникало необходимости реагировать на событие LanguageChange, поскольку функция retranslateStrings() итак вызывалась сразу же вслед за загрузкой нового файла перевода.

void JournalView::retranslateStrings() { for (int i = columns() - 1; i >= 0; --i) removeColumn(i); addColumn(tr("Time")); addColumn(tr("Priority")); addColumn(tr("Phone Number")); addColumn(tr("Subject")); } Функция retranslateStrings() пересоздает заголовки столбцов в QListView, с новыми надписями в них. Для этого, сначала все заголовки удаляются, а потом создаются новые. Эта операция воздействует только на заголовки столбцов и никак не влияет на содержимое QListView.

Для диалогов и виджетов, создаваемых в визуальном построителе Qt Designer, утилита uic сама создает функции, похожие на retranslateStrings(), которая автоматически вызывается в ответ на событие LanguageChange. Все что нам остается сделать -- это загрузить соответствующий файл с переводом, когда пользователь изменяет язык приложения.