Введение
Локализация (поддержка нескольких языков, разобранная в основном цикле статей про CMake/Boost и Qt Linguist) и доступность на первый взгляд кажутся независимыми задачами, но на практике они тесно пересекаются: текст для screen reader должен переводиться так же, как и видимый текст интерфейса, направление текста (RTL для арабского/иврита) влияет на логику клавиатурной навигации и tab order, а длина переведённого текста на разных языках может радикально отличаться, что влияет на расчёты размеров, разобранные в предыдущей статье. В заключительной статье цикла разберём эти точки пересечения.
Концепция
Каждый вызов setAccessibleName()/setAccessibleDescription() с захардкоженной русской или английской строкой ломает локализацию приложения для других языков точно так же, как захардкоженный видимый текст интерфейса — эти строки должны проходить через тот же механизм перевода tr(), что и обычный видимый текст, и попадать в те же файлы .ts для переводчиков (статья 39 основного цикла). Для языков с направлением письма справа налево (RTL) Qt автоматически отражает большинство layout’ов через QGuiApplication::setLayoutDirection(), но порядок tab order и логика навигации стрелками в кастомных компонентах должны быть явно проверены и, возможно, скорректированы для этого случая, поскольку «следующий» логический элемент при RTL может физически располагаться слева, а не справа.
Пример кода: Совместное проектирование локализации
#include <QPushButton>
#include <QApplication>
// ПРОБЛЕМНО: accessibleName на фиксированном языке игнорирует локализацию,
// делая интерфейс недоступным для screen reader пользователей других языков
void createButtonBadPractice(QWidget *parent)
{
auto *button = new QPushButton(QObject::tr("Сохранить"), parent);
button->setAccessibleName("Сохранить текущий документ"); // не переведено!
}
// ЛУЧШЕ: accessibleName проходит через тот же механизм перевода tr(),
// что и видимый текст — попадает в тот же .ts файл для переводчиков
void createButtonGoodPractice(QWidget *parent)
{
auto *button = new QPushButton(QObject::tr("Сохранить"), parent);
button->setAccessibleName(QObject::tr("Сохранить текущий документ"));
}
// Учёт направления письма (RTL) при расчёте кастомной клавиатурной навигации
#include <QGuiApplication>
#include <QKeyEvent>
class CustomCarousel : public QWidget
{
Q_OBJECT
protected:
void keyPressEvent(QKeyEvent *event) override
{
const bool isRtl = QGuiApplication::layoutDirection() == Qt::RightToLeft;
// Логическое значение "следующий элемент" зависит от направления письма:
// при RTL клавиша "влево" должна перемещать к логически СЛЕДУЮЩЕМУ элементу,
// а не к предыдущему, как было бы при LTR
bool moveToNext = false;
if (event->key() == Qt::Key_Right) {
moveToNext = !isRtl;
} else if (event->key() == Qt::Key_Left) {
moveToNext = isRtl;
} else {
QWidget::keyPressEvent(event);
return;
}
if (moveToNext) {
selectNextItem();
} else {
selectPreviousItem();
}
}
private:
void selectNextItem();
void selectPreviousItem();
};
// QML-layout, автоматически адаптирующийся к RTL без ручной логики
import QtQuick
// LayoutMirroring автоматически отражает anchors (left/right) при RTL,
// что снимает необходимость в ручной проверке направления для большинства
// простых случаев — но НЕ для логики клавиатурной навигации, как в примере выше
Item {
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
Row {
anchors.left: parent.left // автоматически становится "anchors.right" при RTL
Text { text: qsTr("Имя пользователя") }
}
}
Пояснения к коду
createButtonGoodPractice() оборачивает строку accessible name в тот же tr(), что используется для видимого текста кнопки — это гарантирует, что переводчики, работающие с файлами .ts (статья 39), увидят и переведут эту строку наравне с остальным видимым текстом интерфейса, и screen reader пользователь, работающий на другом языке, услышит корректно переведённое описание, а не оригинальный русский текст независимо от выбранного языка интерфейса.
CustomCarousel::keyPressEvent() явно проверяет QGuiApplication::layoutDirection() и инвертирует логику навигации стрелками для RTL-языков — это критично для кастомных компонентов, реализующих собственную логику навигации (в отличие от стандартных layout’ов и виджетов Qt, которые в большинстве случаев автоматически адаптируются к RTL через встроенные механизмы). QML-пример показывает LayoutMirroring — встроенный механизм автоматического зеркалирования простых анкоров без необходимости в ручной проверке направления, но явно отмечает, что такое автоматическое зеркалирование не покрывает кастомную императивную логику навигации, которую нужно проверять и адаптировать вручную, как в примере с CustomCarousel.
Подводные камни
- Захардкоженные accessible-строки, забытые при первоначальной локализации приложения, обнаруживаются значительно позже, чем обычные пропуски в переводе видимого текста — поскольку они не видны зрячему тестировщику переключающему язык интерфейса визуально, такие пропуски пропускаются стандартным процессом QA и обнаруживаются только при специальном тестировании с screen reader на нерусском/неанглийском языке интерфейса.
- Существенная разница в длине переведённого текста между языками, ломающая расчёты размеров элементов, рассчитанных под один конкретный язык (часто — английский или русский исходник) — немецкий перевод того же текста в среднем заметно длиннее английского оригинала, а перевод на китайский — заметно компактнее; жёстко зафиксированные размеры кнопок и полей, не учитывающие эту вариативность (см. предыдущую статью про масштабирование), могут одинаково плохо работать как для увеличенного шрифта, так и для просто более длинного перевода на другой язык интерфейса.
LayoutMirroringавтоматически отражает геометрию, но не порядок логической навигации Tab/Focus между элементами в кастомных компонентах с собственноручно прописанной логикой обхода фокуса (например, через явныйsetTabOrder(), как разбиралось в статье 202) — простое включениеLayoutMirroring.enabledне гарантирует автоматическую корректность tab order для RTL без отдельной проверки и, возможно, явной адаптации этой логики.- Тестирование RTL-поддержки только визуально, без проверки реального направления навигации с клавиатуры или screen reader. Интерфейс может выглядеть визуально корректно отражённым для RTL-языка (текст, иконки, layout зеркалированы правильно), но при этом клавиатурная навигация может остаться «залипшей» в LTR-логике (как было бы без явной проверки
layoutDirection()вCustomCarousel), что незаметно при чисто визуальной проверке, но сразу проявляется при реальном использовании клавиатуры носителем RTL-языка.