Привет, земляне, в этой статье я хочу затронуть вопрос интернационализации Qt-приложений, поделиться своим опытом, показать некоторые неочевидные моменты, которые могут возникнуть и непосредственно то, как легко работать с предоставленными инструментами. Выступаю сторонником того, чтобы софт был доступен совершенно для всех людей, поэтому его нужно локализировать, ведь не каждому посчастливилось владеть русским языком, да и самым распространенным языком таким как английский владеет от силы каждый пятый.
Данная статья будет полезна для любителей и начинающих Qt-разработчиков, которые желают разобраться с механизмом интернационализации в фреймворке Qt, чтобы в будущем добавлять локализации в свои приложения. Примеры из данной статьи выполнены на языке Python с применением фреймворка PySide2, это обертка для pyQt5 так что, отличия минимальны, но и применение Qt на других языках не имеет принципиальных отличий. Материал статьи основан на моем личном опыте, который был получен в процессе разработки утилиты для частного применения, которая выполняет пакетную конвертацию изображений и видео в GIF-файлы – GIF Builder и самостоятельного изучения упомянутого фреймворка. В тексте я упомяну о трудностях, которые возникли на пути и о способах их решения. Лишний раз напомню, что в данном деле я всего лишь любитель, поэтому опытным товарищам, возможно, лучше воздержаться от чтения. Я предупредил.
Типичный порядок действий при разработке
Итак, для начала кратко опишу обычный порядок действий, которые необходимо преодолеть, чтобы иметь возможность делать локализации программы. Список состоит из следующий действий:
написание/генерация кода: модули с классами пользовательского интерфейса (как правило это окна, виджеты) и классы/модули, которые будут хранить текстовую информацию, которая подлежит переводу на другие языки;
конвертация программного кода в файлы переводов с помощью утилиты pylupdate5, или обновление уже существующих;
перевод сгенерированных файлов перевода на целевые языки;
написание кода, который отвечает за реализацию перевода интерфейса и служебной текстовой информации.
Пожалуй, перейдем уже к делу. Специально для этого я подготовил демонстрационный проект, вот его исходный код на GitHub, откройте его в отдельной вкладке, потому что я будут на него ссылаться в дальнейшем.
Проект имеет следующую структуру:
?Multilanguage_Qt_App
?forms
└Spaceship Control Panel.ui
?Translates
└?Deutsch
| └qtbase_de.qm
| └spacehip_gui_de.qm
└?English
| └qtbase_en.qm
| └spacehip_gui_en.qm
└?Русский
└qtbase_ru.qm
?Translation
| └fix_coding.py
| └spaceship_gui_de.ts
| └spaceship_gui_en.ts
| └TS creation.ps1
ui_spaceship_control_panel.py
ui_spaceship_control_panel2.py
В папке ?forms:
Spaceship Control Panel.ui - исходный макет главного окна, созданный в QtDesigner, работа в этой программе выходит за рамки этой статьи.
В папке ?Translates, в отдельных папках находятся словари с переводами для каждого конкретного языка. В данному случае: русский, английский, немецкий. Файл qtbase_<код языка>.qm – словарь с переводами, встроенного в Qt текста, просто скопировано из библиотеки PySide2; spaceship_gui_<код языка>.qm – словарь, содержащий переводы для текстовых данных из нашего кода (в папке ?Русский нет этого файла потому, текстовые данные в программе написаны на этом языке).
В папке ?Translation:
fix_coding.py – скрипт для исправления кодировки в коде, сгенерированном QtDesigner, это актуально в случаях, когда язык оригинала имеет символы отличные от латиницы;
файлы spaceship_gui_<код языка>.ts - файлы словарей (en -английский, de - немецкий);
TS creation.ps1 – скрипт PowerShell для конвертации исходного кода в файлы перевода.
В корневой папке?:
constants.py – содержит языковые константы, которые будут задействованы в коде;
main.py – основной код программы;
ui_spaceship_control_panel.py- код класса главного окна после экспорта из QtDesigner;
ui_spaceship_control_panel2.py - код класса главного окна после исправления, об этом моменте будет сказано далее.
Разработка формы главного окна
Хочу предупредить, что я решил попробовать нестандартный подход, и написал исходный текст интерфейса на русском языке. Если текст написан не латиницей, то программа QtDesigner при генерации кода заменит эти символы на коды юникод (вроде этого- \u1234).
Для демонстрации возможностей интернационализации я сделал приложение, отдаленно напоминающее консоль управления космическим кораблем.
На рисунке выше показано окно QtDesigner. При выборе виджетов, которые содержат текстовую информацию на панели «Редактора свойств» у некоторых свойств есть параметр переводимый, если он установлен, то это свойство при генерации словаря будет в него включено; параметр уточнение является вспомогательной информацией от разработчика, например, в английском в отличие от русского нет родов и падежей, поэтому в некоторых случаях нужно довести некоторую информацию до переводчика, чтобы он сделал корректный перевод. В Qt, кроме отображаемого текста у виджетов есть свойства, такие как: всплывающие подсказки, статусные подсказки, горячие клавиши и пр., которые тоже могут подлежать переводу.
После завершения редактирования формы нужно конвертировать его в python-код, вызвав функцию Форма –>Показать код Python, затем сохраняем код на диске. Код из примера в файле ui_spaceship_control_panel.py. Заметьте, что весь переводимый текст обернут в метод translate (подробнее о нем в следующем разделе), также все операции записи текста находятся в теле метода retranslateUi класса окна MainWindow, этот метод нужно вызывать после установки в приложение новых словарей, чтобы перевести исходный текст. Так как текст напечатан кириллицей, то строки имеют нечитаемый вид, если использовать латинский алфавит, то все будет в порядке. Чтобы привести строки в приличный вид, я написал скрипт fix_coding.py, код закомментирован, поэтому разобраться в нем будет несложно.
Написание классов/модулей с текстовой информацией
Не только в классах форм используется отображаемая текстовая информация, подлежащая переводу на другие языки. Хранить эту информацию можно многими способами, но она должна быть оформлена надлежащим образом. Чтобы объявить переменную с информацией, подлежащей переводу следует воспользоваться методом translate(context, key[, disambiguation=None[, n=-1]]) -> str класса QCoreApplication (PySide2.QtCore), он возвращает перевод исходного текста, используя установленные в приложении словари, он имеет следующие параметры:
context – строка содержащая контекст, обычно это имя класса, но может любой, служит для группировки строк;
key – исходный текст (он будет впоследствии переводится);
disambiguation – служебная информация для переводчика (необязательный параметр).
Метод translate() переводит исходный текст, переданный через параметр key, поиск перевода производится в словарях, которые установлены в приложении (экземпляре класса QApplication).
Класс-хранилище языковых констант
Один из вариантов хранения текстовых констант – это класс. В приведенном примере класс имеет единственный метод retranslate(), выполняющий перевод исходной текстовой информации.
from PySide2.QtCore import QCoreApplication as QA
class <Имя класса>:
''' Класс с языковыми константами'''
@classmethod
def retranslate(cls):
''' Метод переводит языковые константы, используя установленные в приложении словари'''
cls.<Имя константы1> = QA.translate("<контекст>", "< исходный текст 1>", [" <комментарий разработчика>"])
cls.<Имя константыN> = QA.translate("<контекст>", "< исходный текст N>", [" <комментарий разработчика>"])
Модуль-хранилище языковых констант
Можно константы объявлять прямо в теле модуля, только нужно учесть следующие нюансы: константы нельзя импортировать с помощью инструкции from import; чтобы перевести текст нужно перезагрузить модуль функцией reload из модуля importlib, тогда ее объявлять следует так:
from PySide2.QtCore import QCoreApplication as QA
<Имя константы1> = QA.translate("<контекст>", "< исходный текст 1>", [" <комментарий разработчика>"])
<Имя константыN> = QA.translate("<контекст>", "< исходный текст N>", [" <комментарий разработчика>"])
На мой взгляд, использование модуля удобнее, поскольку IDE (в моем случае PyCharm) будет выдавать подсказки при вводе, что не работает при использовании статического класса. Также можно объявить константы в методе __init__() и просто использовать экземпляр класса, а для перевода просто вызвать его снова.
Сборка файлов перевода
Когда работа над исходным кодом завершена, наступает этап сборки файлов перевода (файлы с расширением .ts). В этом нам поможет утилита pylupdate5.exe, все действия производились в ОС Windows. Чтобы собрать файлы из исходного кода нужно в терминале ввести команду (в демопроекте есть файл TS cretation.ps1):
<путь к pylupdate5.exe > <список путей к файлам с исходным кодом> -ts <список путей к файлам переводов, которые будут созданы/обновлены>
список путей к файлам с исходным кодом – указать пути в файлам с исходным кодом, которые будут проанализированы и текст, переданный в метод translate() будет внесен в файлы с переводом; пути разделяются пробелом;
список путей к файлам переводов, которые будут созданы/обновлены – тут следует ввести пути к файлам, которые будут созданы, если файлы существуют, то информация в них будет обновлена; ее придется обновлять, ведь программу придется исправлять и дополнять; пути разделяются пробелом.
Имена файлов с переводами могут быть любыми, но желательно соблюдать следующее соглашение имен: <имя словаря>_<код языка>.ts, например, main_en.ts.
Перевод текста
Итак, переводимый текст собран в специальных файлах, теперь надо его перевести на целевой язык. Запускаем утилиту Qt Linguist и открываем наши ts-файлы. При первой загрузке нужно выбрать исходный язык и язык перевода. У утилиты есть одна фишка - можно открыть сразу несколько словарей, чтобы сразу выполнить перевод на несколько языков, такое поведение может быть несколько непривычным.
В разделе Контекст мы видим, что все текстовые данные сгруппированы в соответствии с тем текстом, который был перед методу translate() через аргумент context. В разделе Строки выделяем строку и в поле перевод на целевой язык вводим соответственно перевод исходного текста и жмем Alt+Enter, тогда строка будет помечена зеленой галочкой. Проводим эту операцию над всем строками, если некоторые из них не будут иметь перевода, то текст останется оригинальным – все просто.
После завершения перевода нужно скомпилировать файлы словарей, для этого нужно выбрать одну из команд Скомпилировать в разделе Файл главного верхнего меню.
Также есть возможность скомпилировать файлы переводов утилитой lrelease, можно прочитать информацию по ней здесь.
Использование словарей в коде программы
Наконец-то пришло время поговорить о том, как же использовать словари в своей программе, все предельно просто, смотрим код из демо: main.py, акцентирую свое внимание только на тех операциях, которые необходимы для решения поставленной задачи.
У нас имеется класс MainWindow, в методе __init__() запускается метод _init_lang(). В _init_lang() происходит следующее:
загружается список имен папок, находящихся в папке Translates (такой подход позволяет добавлять новую локализацию просто добавлением новой папки со словарями);
полученный список папок устанавливается в виджет выпадающего списка comboBox_language (QComboBox), устанавливается текущий элемент;
создается переменная, которая будет содержать словари (_translators);
запускается процедура обновления текущего языка (on_lang_changed()).
Метод on_lang_changed() выполняет загрузку словарей и перевод текста, он также привязан к сигналу currentTextChanged comboBox_language (QComboBox), чтобы он выполнялся, когда пользователь выбирает другой язык.
Чтобы полученные словари использовать их нужно установить, поэтому для каждого словаря создается экземпляр класса QTranslator (PySide2.QtCore) и тот заносится в список _translators, затем файл словаря загружается вызовом метода load(<путь к QM-файлу словаря>). Словари в приложение (экземпляр класса QApplication (PySide2. QtWidgets), каждое Qt-приложение его содержит) устанавливаются методом installTranslator(<экземпляр QTranslator>). В случае, когда словари уже установлены и нужно установить другие, то нужно их сперва удалить методом removeTranslator(<экземпляр QTranslator>). После установки нужно обновить интерфейс и языковые константы, за это отвечают эти две строчки:
self.ui.retranslateUi(self) # перевести текст в элементах интерфейса
LC.retranslate() # в классе с языковыми константами
Резюме
Все переводимые текстовые данные в коде должны быть обернуты в метод translate(context, key[, disambiguation=None[, n=-1]]) -> str класса QCoreApplication (PySide2.QtCore), это метод переводит исходный текст, переданный через параметр key, поиск перевода производится в установленных в приложении словарях.
Файлы перевода (имеют расширение .TS) создаются из файлов исходного кода утилитой pylupdate5.exe, текст в них переводится с помощью программы QtLinguist и компилируются в словари (имеют расширение .QM).
Словари загружаются в экземпляры класса QTranslator методом load(), затем экземпляр устанавливается в приложение (экземпляр класса QApplication) при помощи метода installTranslator(<экземпляр QTranslator>). Ранее установленные словари удаляются методом removeTranslator(<экземпляр QTranslator>).
Отображаемая информация обновляется. У классов, сгенерированных QtDesigner обновление производится методом retranslateUi().