QtMiddleSystem design

Как обрабатывать интернационализацию (i18n) и локализацию в Qt?

Строки помечаются макросом tr(), извлекаются утилитой lupdate в .ts файлы, переводятся в Linguist, компилируются lrelease в .qm и загружаются через QTranslator::load() + QApplication::installTranslator().

Интернационализация и локализация в Qt

Qt предоставляет полный инструментарий для i18n: макрос tr() для пометки строк, утилиты lupdate / lrelease для работы с файлами переводов и класс QTranslator для загрузки перевода во время выполнения.

Шаг 1: пометка строк в коде

#include <QObject>
#include <QString>

class MyDialog : public QDialog {
    Q_OBJECT
public:
    MyDialog(QWidget *parent = nullptr) : QDialog(parent) {
        // tr() — основной макрос для переводимых строк
        setWindowTitle(tr("Settings"));

        // Контекстная подсказка для переводчика
        label->setText(tr("File", "menu item"));

        // Множественное число
        statusLabel->setText(tr("%n file(s) found", "", fileCount));

        // tr() вне класса QObject
        QString msg = QCoreApplication::translate("MyDialog", "Error");
    }
};

Шаг 2: объявление файлов переводов в .pro / CMake

# CMakeLists.txt (Qt 6 + CMake)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets LinguistTools)

set(TS_FILES
    translations/myapp_ru.ts
    translations/myapp_de.ts
)

qt_add_translations(
    myapp                  # target
    TS_FILES ${TS_FILES}
    QM_FILES_OUTPUT_VARIABLE QM_FILES
)

# Ресурсы автоматически встраиваются в бинарник
qt_add_resources(myapp "translations"
    PREFIX "/i18n"
    FILES ${QM_FILES}
)

Шаг 3: извлечение и компиляция переводов

# Извлечь строки из исходников в .ts файлы
lupdate src/ -ts translations/myapp_ru.ts translations/myapp_de.ts

# Открыть GUI-редактор переводов
linguist translations/myapp_ru.ts

# Скомпилировать .ts -> .qm (бинарные файлы перевода)
lrelease translations/myapp_ru.ts -qm translations/myapp_ru.qm

Шаг 4: загрузка перевода в runtime

#include <QApplication>
#include <QTranslator>
#include <QLocale>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QTranslator translator;
    // QLocale::system() определяет язык ОС
    const QString locale = QLocale::system().name(); // например, "ru_RU"

    // Загрузка из ресурсов Qt
    if (translator.load(":/i18n/myapp_" + locale)) {
        app.installTranslator(&translator);
    }

    // Можно загрузить Qt-переводы для стандартных диалогов
    QTranslator qtTranslator;
    qtTranslator.load(":/i18n/qt_" + locale);
    app.installTranslator(&qtTranslator);

    MainWindow w;
    w.show();
    return app.exec();
}

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

void MainWindow::changeLanguage(const QString &locale) {
    static QTranslator *current = nullptr;
    if (current) {
        qApp->removeTranslator(current);
        delete current;
    }
    current = new QTranslator(this);
    if (current->load(":/i18n/myapp_" + locale)) {
        qApp->installTranslator(current);
    }
    // Qt автоматически пошлёт QEvent::LanguageChange всем виджетам
    // Переопределите changeEvent() для повторной установки текстов
}

void MainWindow::changeEvent(QEvent *e) {
    if (e->type() == QEvent::LanguageChange) {
        ui->retranslateUi(this); // если используется Qt Designer
        // или вручную: label->setText(tr("Settings"));
    }
    QMainWindow::changeEvent(e);
}

Форматирование чисел, дат, валют

QLocale locale(QLocale::Russian, QLocale::Russia);

// Числа
qDebug() << locale.toString(1234567.89, 'f', 2); // "1 234 567,89"

// Даты
qDebug() << locale.toString(QDate::currentDate(), QLocale::LongFormat);

// Валюта (Qt не имеет встроенного форматирования валюты — используйте ICU или собственный шаблон)
qDebug() << locale.currencySymbol(); // "руб."

Подводные камни

  • Конкатенация строк вместо %1: tr("Hello") + name — непереводимо; используйте tr("Hello %1").arg(name).
  • tr() вне класса QObject: tr() недоступна в глобальных функциях; используйте QCoreApplication::translate("context", "string").
  • Забытый lupdate после изменений: новые строки не появятся в .ts файле автоматически — нужно перезапускать lupdate.
  • Отсутствие LanguageChange в динамическом переключении: если не переопределить changeEvent(), уже показанные виджеты остаются с текстами предыдущего языка.
  • Жёстко закодированные строки в .ui файлах: Qt Designer помечает строки как переводимые по умолчанию, но если снять галочку — lupdate их пропустит.
  • Множественное число без %n: ручная конкатенация вроде tr("%1 файлов").arg(n) не работает для языков со сложными правилами множественного числа (русский, арабский); используйте только tr("%n", "", n).
  • Кодировка .ts файлов: .ts — это XML в UTF-8; открытие в редакторе с другой кодировкой портит строки с кириллицей.
  • QLocale vs системный язык: QLocale::system() на Windows может вернуть en_US, даже если пользователь выбрал другой язык интерфейса — проверяйте QLocale::system().uiLanguages().

Common mistakes

  • Объяснять i18n и localization только по синтаксису, без жизненного цикла и стоимости.
  • Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
  • Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
  • Смешивать signals/slots с generic callbacks и забывать про thread affinity.

What the interviewer is testing

  • Кандидат формулирует точную модель для i18n и localization, а не только определение.
  • Пример компилируем, безопасен по lifetime и соответствует версии технологии.
  • Названы trade-off, ограничения и диагностируемые симптомы ошибки.
  • Понимает границу между C++ кодом, runtime/framework metadata и editor/UI слоем.

Sources

Related topics