Введение
Международное приложение должно предоставлять справку на языке пользователя, а не только на одном, изначально написанном. Qt Help Framework не имеет встроенного «автоматического перевода» контента — локализация справки означает создание полностью отдельных .qch-файлов для каждого поддерживаемого языка, с собственным namespace, и программную логику выбора нужного файла на основе текущей локали приложения. В статье разберём, как организовать структуру проекта для многоязычной справки и переключение между языками в рантайме.
Концепция
Каждый язык справки компилируется в собственный .qch с отдельным namespace, включающим код языка (com.mycompany.myapp.help.ru, com.mycompany.myapp.help.en) — это технически независимые справочники, и переключение языка означает снятие регистрации текущего языкового .qch и регистрацию нового, а не какую-то «перезагрузку» контента внутри одного файла. Поскольку TOC, индекс и ключевые слова — это текст на конкретном языке, для каждого языка обычно ведётся параллельная, но самостоятельная структура .qhp-файлов и HTML-контента — переводчики работают именно с этими файлами, а не с единым «мультиязычным» исходником.
Пример кода: Локализация справки
docs/
├── ru/
│ ├── source/
│ │ ├── index.html
│ │ └── getting_started.html
│ └── myapp_ru.qhp # namespace: com.mycompany.myapp.help.ru
├── en/
│ ├── source/
│ │ ├── index.html
│ │ └── getting_started.html
│ └── myapp_en.qhp # namespace: com.mycompany.myapp.help.en
└── build_all.sh
#!/usr/bin/env bash
# build_all.sh — компиляция справки для всех поддерживаемых языков
set -e
for lang in ru en; do
echo "Сборка справки для языка: $lang"
qhelpgenerator "$lang/myapp_$lang.qhp" -o "../resources/docs/myapp_$lang.qch"
done
#pragma once
#include <QHelpEngine>
#include <QLocale>
#include <QDebug>
class LocalizedHelpManager
{
public:
explicit LocalizedHelpManager(QHelpEngine &engine) : m_engine(engine) {}
bool switchToLocale(const QString &languageCode)
{
const QString newNamespace = QString("com.mycompany.myapp.help.%1").arg(languageCode);
if (m_currentNamespace == newNamespace) {
return true; // уже выбран этот язык
}
// Снимаем регистрацию предыдущего языкового справочника, если был выбран
if (!m_currentNamespace.isEmpty()
&& m_engine.registeredDocumentations().contains(m_currentNamespace)) {
m_engine.unregisterDocumentation(m_currentNamespace);
}
const QString qchPath = QString(":/docs/myapp_%1.qch").arg(languageCode);
if (!m_engine.registerDocumentation(qchPath)) {
qWarning() << "Не удалось зарегистрировать справку для языка"
<< languageCode << ":" << m_engine.error();
// Откат к английскому как языку по умолчанию при отсутствии перевода
if (languageCode != "en") {
return switchToLocale("en");
}
return false;
}
m_currentNamespace = newNamespace;
qDebug() << "Справка переключена на язык:" << languageCode;
return true;
}
QString currentNamespace() const { return m_currentNamespace; }
private:
QHelpEngine &m_engine;
QString m_currentNamespace;
};
// Использование: выбор языка справки на основе системной локали при старте
#include "localizedhelpmanager.h"
#include <QLocale>
void initializeHelpForCurrentLocale(QHelpEngine &engine)
{
static LocalizedHelpManager helpManager(engine);
const QString systemLanguage = QLocale::system().name().split('_').first(); // "ru", "en"...
helpManager.switchToLocale(systemLanguage);
}
Пояснения к коду
build_all.sh компилирует параллельную структуру .qhp-проектов (по одному на язык) в отдельные .qch-файлы с языковым суффиксом в имени файла — это простой и явный способ организации многоязычной справки без необходимости в сложной инфраструктуре, аналогичной Qt Linguist для переводов интерфейса (хотя в крупных проектах перевод текста самих HTML-страниц справки часто действительно ведётся через отдельный процесс, параллельный переводу UI).
LocalizedHelpManager::switchToLocale() реализует переключение между языковыми справочниками: снимает регистрацию текущего (если был выбран) и регистрирует .qch для нового языка; при отсутствии перевода для запрошенного языка (попытка регистрации проваливается, потому что соответствующий ресурсный файл не существует) происходит откат к английскому как языку по умолчанию — это предотвращает ситуацию, когда пользователь с не полностью переведённой локалью остаётся вовсе без справки.
Подводные камни
- Неполный перевод справки для некоторых языков, не покрытый явной логикой fallback. Если для конкретного языка переведена только часть страниц (например, только
index.html, но неgetting_started.html), а структура .qhp всё равно ссылается на непереведённые страницы, пользователь увидит либо ошибку загрузки страницы, либо (хуже) страницу на языке, отличном от выбранного, без какого-либо предупреждения — стоит явно отслеживать полноту перевода и либо не включать недопереведённые страницы в финальную сборку для этого языка, либо явно помечать их как «страница доступна только на английском». - Расхождение структуры TOC между разными языковыми версиями справки. Если переводчики или авторы контента для разных языков со временем независимо меняют структуру разделов (добавляют/удаляют темы) без синхронизации, разные языковые версии справки начинают предлагать разную навигацию — это создаёт несогласованный опыт для пользователей, переключающихся между языками (например, в международной команде), и затрудняет поддержку контента.
- Производительность при частом переключении языка (если пользователь меняет язык интерфейса приложения «на ходу» несколько раз) — каждое переключение требует снятия и повторной регистрации справочника, что не бесплатно по времени, особенно для больших .qch-файлов; для приложений с частыми переключениями языка стоит измерить, не создаёт ли эта операция заметную задержку в интерфейсе.
- Жёстко закодированный список поддерживаемых языков (
for lang in ru enв скрипте сборки) без автоматической проверки, что для каждого объявленного языка действительно существует полный и непустой набор исходных файлов — добавление нового языка в скрипт сборки без соответствующего реального контента приведёт либо к ошибке компиляцииqhelpgenerator, либо, что хуже, к успешной компиляции пустого или почти пустого справочника, который окажется незаметно неполным до момента реального использования пользователем этого языка.