Pull to refresh

Локализация Qt-приложений

Reading time9 min
Views8.6K

Привет, земляне, в этой статье я хочу затронуть вопрос интернационализации 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

constants.py

main.py

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). 

Для демонстрации возможностей интернационализации я сделал приложение, отдаленно напоминающее консоль управления космическим кораблем.

Главное окно проекта в программе Qt  Designer
Главное окно проекта в программе Qt Designer

На рисунке выше показано окно QtDesigner. При выборе виджетов, которые содержат текстовую информацию на панели «Редактора свойств» у некоторых свойств есть параметр переводимый, если он установлен, то это свойство при генерации словаря будет в него включено; параметр уточнение является вспомогательной информацией от разработчика, например, в английском в отличие от русского нет родов и падежей, поэтому в некоторых случаях нужно довести некоторую информацию до переводчика, чтобы он сделал корректный перевод. В Qt, кроме отображаемого текста у виджетов есть свойства, такие как: всплывающие подсказки, статусные подсказки, горячие клавиши и пр., которые тоже могут подлежать переводу.

После завершения редактирования формы нужно конвертировать его в python-код, вызвав функцию Форма –>Показать код Python, затем сохраняем код на диске. Код из примера в файле ui_spaceship_control_panel.py. Заметьте, что весь переводимый текст обернут в метод translate (подробнее о нем в следующем разделе), также все операции записи текста находятся в теле метода retranslateUi класса окна MainWindow, этот метод нужно вызывать после установки в приложение новых словарей, чтобы перевести исходный текст. Так как текст напечатан кириллицей, то строки имеют нечитаемый вид, если использовать латинский алфавит, то все будет в порядке. Чтобы привести строки в приличный вид, я написал скрипт fix_coding.py, код закомментирован, поэтому разобраться в нем будет несложно.

Генератор кода Qt Designer не хочет отображать кириллицу корректно
Генератор кода Qt Designer не хочет отображать кириллицу корректно

Написание классов/модулей с текстовой информацией

Не только в классах форм используется отображаемая текстовая информация, подлежащая переводу на другие языки. Хранить эту информацию можно многими способами, но она должна быть оформлена надлежащим образом. Чтобы объявить переменную с информацией, подлежащей переводу следует воспользоваться методом 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() происходит следующее:

  1. загружается список имен папок, находящихся в папке Translates (такой подход позволяет добавлять новую локализацию просто добавлением новой папки со словарями);

  2. полученный список папок устанавливается в виджет выпадающего списка comboBox_language (QComboBox), устанавливается текущий элемент;

  3. создается переменная, которая будет содержать словари (_translators);

  4. запускается процедура обновления текущего языка (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().

 

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 6: ↑5 and ↓1+4
Comments10

Articles