Различные статьи по Qt и c++

Локализация справки: несколько языков и переключение справочников по локали

Введение

Международное приложение должно предоставлять справку на языке пользователя, а не только на одном, изначально написанном. 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, либо, что хуже, к успешной компиляции пустого или почти пустого справочника, который окажется незаметно неполным до момента реального использования пользователем этого языка.