Форум программистов, компьютерный форум, киберфорум
iamvic
Войти
Регистрация
Восстановить пароль
Карта форума Блоги Сообщество Поиск Заказать работу  
Путевые заметки в процессе познания Python и PyQt/PySide.
Помни - только тег CODE не портит код добавлением пробела в начало пустой строки.
Рейтинг: 3.00. Голосов: 1.

К вопросу о разделении труда (часть 1).

Запись от iamvic размещена 07.11.2022 в 23:20
Обновил(-а) iamvic 10.11.2022 в 22:46 (не учёл особенности движка Cyberforum)

Понаблюдав тут в одной конторке за необычно оживлённой суетой, возникшей на почве острой необходимости перевода сайта на иностранный язык, подумал "А как это будет выглядеть с PyQt-приложениями?" Ведь все инструменты для безболезненного решения проблемы есть, а опыта нет. Надо пробовать, надо делать демонстратор.

В качестве донора для демонстратора была выбрана простейшая поделка из По итогам определения начального экрана при старте приложения.

Этап 1

В процессе приведения поделки к товарному виду был добавлен ещё один параметр командной строки (пример командной строки под Windows):
Код:
py team_probe.py [-] [params...]
Если первым параметром указан - (знак минус), то переводы стандартных диалогов, кнопок и т.д. средствами Qt выполняться не будут. И, соответственно, будут они на языке оригинала кода Qt, т.е. на английском.

Сам код демонстратора стал выглядеть так:

team_probe.py
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""Team Probe v0.001
 
Пример реализации оконного приложения, соблюдающего соглашения
о пользовательском интерфейсе для окон верхнего уровня в любых
системах.
 
В соглашениях определено, что эти окна должны открываться
на том экране, на котором находится курсор мыши.
В то же самое время, в многоэкранных системах фокус ввода
клавиатуры может находиться на другом экране.
 
Windows строго придерживается соглашений, а Linux соблюдает
их только для виджетов класса QDialog и его подклассов
(включая стандартные диалоги).
 
Во всех остальных случаях Linux открывает окна верхнего уровня
на том экране, на котором находится фокус ввода c клавиатуры
(например, QMainWindow, QSplashScreen и прочие).
"""
 
import sys
 
from PyQt5.QtCore import (
    pyqtSlot,
    QLibraryInfo,
    QLocale,
    QTranslator
    )
from PyQt5.QtGui import (
    QCursor,
    QFont
    )
from PyQt5.QtWidgets import (
    QAction,
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QMessageBox,
    QVBoxLayout,
    QWidget
    )
 
 
APP_NAME = 'Team Probe v0.001'
TRANSLATOR_QT_LIST = ['qtbase_']
 
 
def probe_cursor_screen():
    """Функция определения экрана, на котором находится курсор мыши.
    Возврат: объект класса QScreen.
 
    selected_screen = probe_cursor_screen()
    """
 
    selected_screen = QApplication.screens()[0]
    screens = QApplication.screens()
    if len(screens) > 0:
        for i in range(len(screens)):
            if screens[i].geometry().contains(QCursor.pos()):
                selected_screen = screens[i]
    return selected_screen
 
 
class Main_window(QMainWindow):
    """Главное окно приложения
    """
    def __init__(self, parent=None):
        """Конструктор
        """
        super(Main_window, self).__init__(parent)
 
        self.setWindowTitle(APP_NAME)
        self.statusBar()
 
        orphan_action = QAction(
            'Без родителя',
            self,
            shortcut = 'Ctrl+1',
            statusTip = 'Окно сообщения, у которого нет родителя',
            triggered = self.on_orphan_action_triggered,
            enabled = True
            )
 
        infant_action = QAction(
            'С родителем',
            self,
            shortcut = 'Ctrl+2',
            statusTip = 'Окно сообщения, у которого есть родитель',
            triggered = self.on_infant_action_triggered,
            enabled = True
            )
 
        quit_action = QAction(
            'Выход',
            self,
            shortcut = 'Ctrl+Q',
            statusTip = 'Завершить работу приложения',
            triggered = self.on_quit_action_triggered,
            enabled = True
            )
 
        task_menu = self.menuBar().addMenu('Файл')
        task_menu.addAction(orphan_action)
        task_menu.addAction(infant_action)
        task_menu.addAction(quit_action)
 
        info_text = QLabel(
            'Поскольку у главного окна приложения нет родителя,\n'
            'то оно открывается на том экране, на котором\n'
            'в этот момент находится курсор мыши (см. соглашения\n'
            'о пользовательском интерфейсе окон верхнего уровня).\n\n'
            'Ctrl-1 - у окна этого сообщения тоже нет родителя\n'
            'и откроется оно тоже на том экране, на котором\n'
            'в этот момент находится курсор мыши.\n\n'
            'Ctrl-2 - окно сообщения с родителем всегда открывается\n'
            'поверх окна родителя (в данном случае, главного окна).'
            )
        font = info_text.font()
        font.setWeight(QFont.Bold)
        info_text.setFont(font)
 
        hbox = QHBoxLayout(None)
        hbox.addStretch(1)
        hbox.addWidget(info_text)
        hbox.addStretch(1)
 
        main_map = QVBoxLayout(None)
        main_map.addStretch(1)
        main_map.addLayout(hbox)
        main_map.addStretch(1)
 
        central_widget = QWidget(self)
        central_widget.setLayout(main_map)
        self.setCentralWidget(central_widget)
 
        selected_screen = probe_cursor_screen()
        # определяем экран, где находится курсор мыши,
        # затем центруем и подгоняем начальные размеры окна
        # (для Linux - требуется, а Windows - не портит)
 
        current_geo = selected_screen.availableGeometry()
        self.move(current_geo.x() + current_geo.width() // 4,
                  current_geo.y() + current_geo.height() // 4)
        self.resize(current_geo.width() // 2,
                    current_geo.height() // 2)
 
    @pyqtSlot(bool)
    def on_orphan_action_triggered(self, val):
        """Слот, обрабатывающий сигнал на открытие окна сообщения
        БЕЗ РОДИТЕЛЯ
        """
        QMessageBox.warning(
            None,
            'БЕЗ РОДИТЕЛЯ',
            'Поскольку у окна этого сообщения нет родителя,'
            ' то оно будет открыто на том экране, на котором'
            ' в этот момент находится курсор мыши (см. соглашения'
            ' о пользовательском интерфейсе окон верхнего уровня).',
            QMessageBox.Yes
            )
 
    @pyqtSlot(bool)
    def on_infant_action_triggered(self, val):
        """Слот, обрабатывающий сигнал на открытие окна сообщения
        С РОДИТЕЛЕМ
        """
        QMessageBox.information(
            self,
            'С РОДИТЕЛЕМ',
            'Окно этого сообщения всегда открывается поверх'
            ' окна родителя (в данном случае, главного окна).',
            QMessageBox.YesToAll
            )
 
    @pyqtSlot(bool)
    def on_quit_action_triggered(self, val):
        """Слот, обрабатывающий сигнал на закрытие приложения
        """
        self.close()
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
 
    # Стандартные диалоги QColorDialog, QFileDialog, QFontDialog,
    # QMessageBox, стандартные кнопки и т.д. по умолчанию используют
    # английский язык. Для того, чтобы они заговорили на языке,
    # указанном в текущих настройках операционной системы
    # пользователя (см. системная локаль), например, на русском,
    # требуется создать транслятор, загрузить соответствующий файл
    # переводов и установить его (например, интересующие нас
    # переводы на русский находятся в файле qtbase_ru.qm).
 
    # Но в некоторых системах, например, в OpenSUSE Linux,
    # этого не требуется, поскольку необходимые переводы загружаются
    # и устанавливаются в процессе загрузки операционной системы
    # (и, вероятно, используются тотально).
 
    # Вопрос о целесообразности удаления этих трансляторов, например,
    # с помощью конструкции:
    #
    #    for t in app.findChildren(QTranslator):
    #        app.removeTranslator(t)
    #
    # остаётся открытым :).
 
    tr_qtlist = []
 
    if (len(app.findChildren(QTranslator)) == 0 and
        (QLocale().name()[0:2] != 'en' and
            (len(sys.argv) == 1 or
                (len(sys.argv) > 1 and sys.argv[1] != '-')
                )
            )
        ):
        # Транслятор НЕ БУДЕТ загружен и установлен, если:
        # 1. есть трансляторы, заранее установленные операционной
        #    системой
        # 2. если язык операционной системы пользователя - английский
        # 3. если при старте первым параметром указан - (знак минус)
        #    Пример:
        #       team_probe.py -
        #       team_probe.py - 0
 
        for n in TRANSLATOR_QT_LIST:
            # в принципе, можно было бы загрузить сразу все имеющиеся
            # в PyQt5 переводы на язык, используемый операционной
            # системой пользователя (а там их больше десятка на каждый
            # язык), но зачем? Грузим только нужные из списка...
            f_n = n + QLocale().name()[0:2] + '.qm'
            tr_qtlist.append(QTranslator())
            if tr_qtlist[-1].load(
                f_n,
                QLibraryInfo.location(QLibraryInfo.TranslationsPath)
                ):
 
                app.installTranslator(tr_qtlist[-1])
 
 
    if (len(sys.argv) == 2 and sys.argv[1] != '-') or len(sys.argv) > 2:
        # старт считается аварийным, если есть хоть один параметр,
        # отличный от первого - (знак минус)
        # Пример:
        #   1. безаварийные старты
        #       team_probe.py
        #       team_probe.py -
        #   2. аварийные старты
        #       team_probe.py 0
        #       team_probe.py - 0
        #       team_probe.py 0 -
 
        QMessageBox.critical(
            None,
            'АВАРИЯ (БЕЗ РОДИТЕЛЯ)',
            'Поскольку у окна этого сообщения нет родителя,'
            ' то оно будет открыто на том экране, на котором'
            ' в этот момент находится курсор мыши (см. соглашения'
            ' о пользовательском интерфейсе окон верхнего уровня).',
            QMessageBox.Close
            )
        sys.exit(1)
 
    # нормальный запуск приложения
    mwin = Main_window()
    mwin.show()
    sys.exit(app.exec_())
Можно проверять как это работает.

Нажмите на изображение для увеличения
Название: 001.png
Просмотров: 228
Размер:	16.9 Кб
ID:	7781Нажмите на изображение для увеличения
Название: 001_.png
Просмотров: 203
Размер:	17.0 Кб
ID:	7782

Этап 2

Теперь надо заняться подготовкой кода к переводу. Беда в том, что писать программы я всё равно буду на русском. Ну, не дано! Понимать - понимаю, а сказать правильно, как они, не могу. Поэтому и переводы буду делать с русского на другие языки (спасибо, Google и Yandex). Соответственно, расширяется роль параметра командной строки - (знак минус). Теперь он должен не только отключать перевод стандартных диалогов, но и подсовывать переводы с русского на английский (имитация того, что код изначально написан на английском). А хранить эти ресурсы будем пока в одной куче вместе с кодом.

Тогда код демонстратора приобретает такой вид:

team_probe.py
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""Team Probe v0.002
 
Пример реализации оконного приложения, соблюдающего соглашения
о пользовательском интерфейсе для окон верхнего уровня в любых
системах.
 
В соглашениях определено, что эти окна должны открываться
на том экране, на котором находится курсор мыши.
В то же самое время, в многоэкранных системах фокус ввода
клавиатуры может находиться на другом экране.
 
Windows строго придерживается соглашений, а Linux соблюдает
их только для виджетов класса QDialog и его подклассов
(включая стандартные диалоги).
 
Во всех остальных случаях Linux открывает окна верхнего уровня
на том экране, на котором находится фокус ввода c клавиатуры
(например, QMainWindow, QSplashScreen и прочие).
"""
 
import os, sys
 
from PyQt5.QtCore import (
    pyqtSlot,
    QLibraryInfo,
    QLocale,
    QTranslator
    )
from PyQt5.QtGui import (
    QCursor,
    QFont
    )
from PyQt5.QtWidgets import (
    QAction,
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QMessageBox,
    QVBoxLayout,
    QWidget
    )
 
 
APP_NAME = 'Team Probe v0.002'
TRANSLATOR_QT_LIST = ['qtbase_']
TRANSLATOR_QM_LIST = ['team_probe_']
RESOURCE_QM_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
 
 
def tr_app(context, text):
    """Вспомогательная функция перевода по контексту.
    Возврат: текст, переведённый на язык, указанный в системной локали
 
    result_text = tr_app('__main__', 'Выход')
    """
    return QApplication.instance().translate(context, text)
 
 
def probe_cursor_screen():
    """Функция определения экрана, на котором находится курсор мыши.
    Возврат: объект класса QScreen.
 
    selected_screen = probe_cursor_screen()
    """
 
    selected_screen = QApplication.screens()[0]
    screens = QApplication.screens()
    if len(screens) > 0:
        for i in range(len(screens)):
            if screens[i].geometry().contains(QCursor.pos()):
                selected_screen = screens[i]
    return selected_screen
 
 
class Main_window(QMainWindow):
    """Главное окно приложения
    """
    def __init__(self, parent=None):
        """Конструктор
        """
        super(Main_window, self).__init__(parent)
 
        self.setWindowTitle(APP_NAME)
        self.statusBar()
 
        orphan_action = QAction(
            tr_app('__main__', 'Без родителя'),
            self,
            shortcut = 'Ctrl+1',
            statusTip = tr_app(
                '__main__',
                'Окно сообщения, у которого нет родителя'
                ),
            triggered = self.on_orphan_action_triggered,
            enabled = True
            )
 
        infant_action = QAction(
            tr_app('__main__', 'С родителем'),
            self,
            shortcut = 'Ctrl+2',
            statusTip = tr_app(
                '__main__',
                'Окно сообщения, у которого есть родитель'
                ),
            triggered = self.on_infant_action_triggered,
            enabled = True
            )
 
        quit_action = QAction(
            tr_app('__main__', 'Выход'),
            self,
            shortcut = 'Ctrl+Q',
            statusTip = tr_app(
                '__main__',
                'Завершить работу приложения'
                ),
            triggered = self.on_quit_action_triggered,
            enabled = True
            )
 
        task_menu = self.menuBar().addMenu(tr_app('__main__', 'Файл'))
        task_menu.addAction(orphan_action)
        task_menu.addAction(infant_action)
        task_menu.addAction(quit_action)
 
        info_text = QLabel(tr_app(
            '__main__',
            'Поскольку у главного окна приложения нет родителя,\n'
            'то оно открывается на том экране, на котором\n'
            'в этот момент находится курсор мыши (см. соглашения\n'
            'о пользовательском интерфейсе окон верхнего уровня).\n\n'
            'Ctrl-1 - у окна этого сообщения тоже нет родителя\n'
            'и откроется оно тоже на том экране, на котором\n'
            'в этот момент находится курсор мыши.\n\n'
            'Ctrl-2 - окно сообщения с родителем всегда открывается\n'
            'поверх окна родителя (в данном случае, главного окна).'
            ))
        font = info_text.font()
        font.setWeight(QFont.Bold)
        info_text.setFont(font)
 
        hbox = QHBoxLayout(None)
        hbox.addStretch(1)
        hbox.addWidget(info_text)
        hbox.addStretch(1)
 
        main_map = QVBoxLayout(None)
        main_map.addStretch(1)
        main_map.addLayout(hbox)
        main_map.addStretch(1)
 
        central_widget = QWidget(self)
        central_widget.setLayout(main_map)
        self.setCentralWidget(central_widget)
 
        selected_screen = probe_cursor_screen()
        # определяем экран, где находится курсор мыши,
        # затем центруем и подгоняем начальные размеры окна
        # (для Linux - требуется, а Windows - не портит)
 
        current_geo = selected_screen.availableGeometry()
        self.move(current_geo.x() + current_geo.width() // 4,
                  current_geo.y() + current_geo.height() // 4)
        self.resize(current_geo.width() // 2,
                    current_geo.height() // 2)
 
    @pyqtSlot(bool)
    def on_orphan_action_triggered(self, val):
        """Слот, обрабатывающий сигнал на открытие окна сообщения
        БЕЗ РОДИТЕЛЯ
        """
        QMessageBox.warning(
            None,
            tr_app('__main__', 'БЕЗ РОДИТЕЛЯ'),
            tr_app(
                '__main__',
                'Поскольку у окна этого сообщения нет родителя,'
                ' то оно будет открыто на том экране, на котором'
                ' в этот момент находится курсор мыши (см. соглашения'
                ' о пользовательском интерфейсе окон верхнего уровня).'
                ),
            QMessageBox.Yes
            )
 
    @pyqtSlot(bool)
    def on_infant_action_triggered(self, val):
        """Слот, обрабатывающий сигнал на открытие окна сообщения
        С РОДИТЕЛЕМ
        """
        QMessageBox.information(
            self,
            tr_app('__main__', 'С РОДИТЕЛЕМ'),
            tr_app(
                '__main__',
                'Окно этого сообщения всегда открывается поверх'
                ' окна родителя (в данном случае, главного окна).'
                ),
            QMessageBox.YesToAll
            )
 
    @pyqtSlot(bool)
    def on_quit_action_triggered(self, val):
        """Слот, обрабатывающий сигнал на закрытие приложения
        """
        self.close()
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
 
    # Стандартные диалоги QColorDialog, QFileDialog, QFontDialog,
    # QMessageBox, стандартные кнопки и т.д. по умолчанию используют
    # английский язык. Для того, чтобы они заговорили на языке,
    # указанном в текущих настройках операционной системы
    # пользователя (см. системная локаль), например, на русском,
    # требуется создать транслятор, загрузить соответствующий файл
    # переводов и установить его (например, интересующие нас
    # переводы на русский находятся в файле qtbase_ru.qm).
 
    # Но в некоторых системах, например, в OpenSUSE Linux,
    # этого не требуется, поскольку необходимые переводы загружаются
    # и устанавливаются в процессе загрузки операционной системы
    # (и, вероятно, используются тотально).
 
    # Вопрос о целесообразности удаления этих трансляторов, например,
    # с помощью конструкции:
    #
    #    for t in app.findChildren(QTranslator):
    #        app.removeTranslator(t)
    #
    # остаётся открытым :).
 
    tr_qtlist = []
 
    if (len(app.findChildren(QTranslator)) == 0 and
        (QLocale().name()[0:2] != 'en' and
            (len(sys.argv) == 1 or
                (len(sys.argv) > 1 and sys.argv[1] != '-')
                )
            )
        ):
        # Транслятор НЕ БУДЕТ загружен и установлен, если:
        # 1. есть трансляторы, заранее установленные операционной
        #    системой
        # 2. если язык операционной системы пользователя - английский
        # 3. если при старте первым параметром указан - (знак минус)
        #    Пример:
        #       team_probe.py -
        #       team_probe.py - 0
 
        for n in TRANSLATOR_QT_LIST:
            # в принципе, можно было бы загрузить сразу все имеющиеся
            # в PyQt5 переводы на язык, используемый операционной
            # системой пользователя (а там их больше десятка на каждый
            # язык), но зачем? Грузим только нужные из списка...
            f_n = n + QLocale().name()[0:2] + '.qm'
            tr_qtlist.append(QTranslator())
            if tr_qtlist[-1].load(
                f_n,
                QLibraryInfo.location(QLibraryInfo.TranslationsPath)
                ):
 
                app.installTranslator(tr_qtlist[-1])
 
 
    tr_tslist = []
 
    if  len(sys.argv) > 1 and sys.argv[1] == '-':
        # 1. если первый параметр - (знак минус),
        #    то грузим английский перевод (*_en.qm)
 
        for n in TRANSLATOR_QM_LIST:
            f_n = n + 'en.qm'
            tr_tslist.append(QTranslator())
            if tr_tslist[-1].load(f_n, RESOURCE_QM_DIR):
                app.installTranslator(tr_tslist[-1])
 
    elif QLocale().name()[0:2] != 'ru':
        # 2. иначе, если язык системы не русский,
        #    то грузим переводы на соответствующий язык
        #    или на английский, если их нет
 
        for n in TRANSLATOR_QM_LIST:
            f_n = n + QLocale().name()[0:2] + '.qm'
            f_qtrn = os.path.join(RESOURCE_QM_DIR, f_n)
            if not (os.path.exists(f_qtrn) and os.path.isfile(f_qtrn)):
                # если файл на языке системы пользователя отсутствует,
                # то подсунем ему английский
                f_n = n + 'en.qm'
            tr_tslist.append(QTranslator())
            if tr_tslist[-1].load(f_n, RESOURCE_QM_DIR):
                app.installTranslator(tr_tslist[-1])
 
 
    if (len(sys.argv) == 2 and sys.argv[1] != '-') or len(sys.argv) > 2:
        # старт считается аварийным, если есть хоть один параметр,
        # отличный от первого - (знак минус)
        # Пример:
        #   1. безаварийные старты
        #       team_probe.py
        #       team_probe.py -
        #   2. аварийные старты
        #       team_probe.py 0
        #       team_probe.py - 0
        #       team_probe.py 0 -
 
        QMessageBox.critical(
            None,
            tr_app('__main__', 'АВАРИЯ (БЕЗ РОДИТЕЛЯ)'),
            tr_app(
                '__main__',
                'Поскольку у окна этого сообщения нет родителя,'
                ' то оно будет открыто на том экране, на котором'
                ' в этот момент находится курсор мыши (см. соглашения'
                ' о пользовательском интерфейсе окон верхнего уровня).'
                ),
            QMessageBox.Close
            )
        sys.exit(1)
 
    # нормальный запуск приложения
    mwin = Main_window()
    mwin.show()
    sys.exit(app.exec_())
Запускаем
Код:
pylupdate5 -translate-function tr_app team_probe.py -ts team_probe.ts
Запускаем Qt Linguist, открываем файл team_probe.ts, в качестве языка исходного текста выбираем русский (для любой страны), в качестве языка, на который будем переводить, выбираем английский (для любой страны) и сохраняем как "Исходники переводов" в файл team_probe_en.ts. Затем переводим всё что надо на английский и вновь сохраняем в team_probe_en.ts. У меня после перевода получилось вот такое:

team_probe_en.ts
Код:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en" sourcelanguage="ru">
<context>
    <name>__main__</name>
    <message>
        <location filename="team_probe.py" line="91"/>
        <source>Без родителя</source>
        <translation type="unfinished">Without a parent</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="91"/>
        <source>Окно сообщения, у которого нет родителя</source>
        <translation type="unfinished">A message box that has no parent</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="103"/>
        <source>С родителем</source>
        <translation type="unfinished">With a parent</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="103"/>
        <source>Окно сообщения, у которого есть родитель</source>
        <translation type="unfinished">A message box that has a parent</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="115"/>
        <source>Выход</source>
        <translation type="unfinished">Quit</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="115"/>
        <source>Завершить работу приложения</source>
        <translation type="unfinished">Shut down the application</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="127"/>
        <source>Файл</source>
        <translation type="unfinished">File</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="178"/>
        <source>БЕЗ РОДИТЕЛЯ</source>
        <translation type="unfinished">WITHOUT A PARENT</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="196"/>
        <source>С РОДИТЕЛЕМ</source>
        <translation type="unfinished">WITH A PARENT</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="196"/>
        <source>Окно этого сообщения всегда открывается поверх окна родителя (в данном случае, главного окна).</source>
        <translation type="unfinished">The window of this message always opens on top of the parent window (in this case, the main window).</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="313"/>
        <source>АВАРИЯ (БЕЗ РОДИТЕЛЯ)</source>
        <translation type="unfinished">ACCIDENT (WITHOUT PARENT)</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="132"/>
        <source>Поскольку у главного окна приложения нет родителя,
то оно открывается на том экране, на котором
в этот момент находится курсор мыши (см. соглашения
о пользовательском интерфейсе окон верхнего уровня).

Ctrl-1 - у окна этого сообщения тоже нет родителя
и откроется оно тоже на том экране, на котором
в этот момент находится курсор мыши.

Ctrl-2 - окно сообщения с родителем всегда открывается
поверх окна родителя (в данном случае, главного окна).</source>
        <translation type="unfinished">Since the main application window does not have a parent,
it opens on the screen where the mouse cursor is at that
moment (see the conventions on the user interfac
e of top-level windows).

Ctrl-1 - the window of this message also has no parent
and it will also open on the screen where the mouse
cursor is at that moment.

Ctrl-2 - the parent message window always opens on top
of the parent window (in this case, the main window).</translation>
    </message>
    <message>
        <location filename="team_probe.py" line="313"/>
        <source>Поскольку у окна этого сообщения нет родителя, то оно будет открыто на том экране, на котором в этот момент находится курсор мыши (см. соглашения о пользовательском интерфейсе окон верхнего уровня).</source>
        <translation type="unfinished">Since the window of this message does not have a parent, it will be opened on the screen on which the mouse cursor is at that moment (see the conventions on the user interface of top-level windows).</translation>
    </message>
</context>
</TS>
После чего компилируем как "Скомпилированные файлы перевода" в файл team_probe_en.qm и помещаем этот файл рядом с нашим модулем team_probe.py, а затем проверяем работу.

Нажмите на изображение для увеличения
Название: 002.png
Просмотров: 197
Размер:	17.2 Кб
ID:	7783Нажмите на изображение для увеличения
Название: 002_.png
Просмотров: 203
Размер:	16.2 Кб
ID:	7784

Продолжение следует... (Часть 2)

Приношу извинения всем посетителям!

Возникла непредвиденная ситуация при публикации кода team_probe_en.ts.
Строки 67, 71, 78, 82 должны быть пустыми. Там не должно быть одиночного пробела в начале строки. Удалите его руками, пожалуйста, должен остаться только перевод строки.

Подозреваю, что это особенности движка Cyberforum. Не терпит он пустые строки. Глянул я на другие публикации и там движок везде втыкает одиночные пробелы, если строка пустая. Учту на будущее...


Оказывается вставкой непрошеных пробелов страдает тег XML. Заменил я его на CODE, стало не так удобно выделять и копировать - зато без ошибок. Извините за неудобство ещё раз...
Размещено в Памятка
Показов 2263 Комментарии 3
Всего комментариев 3
Комментарии
  1. Старый комментарий
    Тема Расчёт банка в тему
    Запись от iamvic размещена 26.01.2023 в 11:05 iamvic вне форума
  2. Старый комментарий
    Аватар для Welemir1
    пересмотрите логику probe_cursor_screen, если скринов менее одного то вы упадете на строке 58, а если >=1 то тогда не нужно условие на строке 60

    а если скажут еще перевести на турецкий, испанский и болгарский, то вы в код внесете еще 3 больших элифа с одинаковым циклом for n in TRANSLATOR_QM_LIST: ?
    Просится же код на вынос в общую функцию куда просто передаем ен или ру или ес/тр/бл чтобы программа поискала такой файлик в нужной папочке и если есть подгрузила нужный язык, если нет взяла дефолтный.
    Запись от Welemir1 размещена 31.01.2023 в 07:38 Welemir1 вне форума
  3. Старый комментарий
    Цитата:
    Сообщение от Welemir1 Просмотреть комментарий
    пересмотрите логику probe_cursor_screen, если скринов менее одного то вы упадете на строке 58, а если >=1 то тогда не нужно условие на строке 60
    Спасибо, Welemir1, что обратили внимание на этот тонкий момент с определением количества экранов. В оправдание могу сказать, что это ведь только моделька приложения и условие в функции probe_cursor_screen() действительно было бы логичнее записать в таком виде:
    Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    def probe_cursor_screen():
        """Функция определения экрана, на котором находится курсор мыши.
        Возврат: объект класса QScreen.
     
        selected_screen = probe_cursor_screen()
        """
     
        selected_screen = QApplication.screens()[0]
        screens = QApplication.screens()
        if len(screens) > 1:     # <-- исправлено
            for i in range(len(screens)):
                if screens[i].geometry().contains(QCursor.pos()):
                    selected_screen = screens[i]
        return selected_screen
    в расчёте на то, что уж один-то экран в работающей системе должен быть.

    Но есть вопрос: Может ли вызов QApplication.screens() вернуть пустой список? По моим представлениям, такого быть не может. Там ведь недостаточно отключить все мониторы и выдернуть интерфейсные кабели. А на системах, в которых графический видеоадаптер отсутствует физически, вряд ли получится запустить Qt. Или я чего-то не понимаю?

    Цитата:
    Сообщение от Welemir1 Просмотреть комментарий
    а если скажут еще перевести на турецкий, испанский и болгарский, то вы в код внесете еще 3 больших элифа с одинаковым циклом for n in TRANSLATOR_QM_LIST: ?
    Ну, давайте разберём подробно что делает этот кусок кода (Вы же про него говорите? Может я действительно чего накосячил? Для примера, предположим, что локаль системы it):
    Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
        elif QLocale().name()[0:2] != 'ru':
            # 2. иначе, если язык системы не русский,
            #    то грузим переводы на соответствующий язык
            #    или на английский, если их нет
     
            for n in TRANSLATOR_QM_LIST:
                # --> список TRANSLATOR_QM_LIST содержит шаблоны имен
                # --> наших собственных файлов переводов
                # --> на этапе 2 в списке только один шаблон имени
                # --> TRANSLATOR_QM_LIST = ['team_probe_']
                # -->
                # --> выбираем очередной шаблон имени файла переводов из списка
                # --> на первый раз переменная n получит значение 'team_probe_'
     
                f_n = n + QLocale().name()[0:2] + '.qm'
                # --> формируем имя файла переводов из полученного шаблона
                # --> и имени системной локали
                # --> вызов QLocale().name()[0:2] вернул зачение 'it'
                # --> переменная f_n получает значение 'team_probe_it.qm'
     
                f_qtrn = os.path.join(RESOURCE_QM_DIR, f_n)
                # --> формируем полное имя файла переводов и проверяем его наличие
                if not (os.path.exists(f_qtrn) and os.path.isfile(f_qtrn)):
                    # если файл на языке системы пользователя отсутствует,
                    # то подсунем ему английский
                    f_n = n + 'en.qm'
                    # --> файла team_probe_it.qm нет
                    # --> переменная f_n получает значение 'team_probe_en.qm'
     
                tr_tslist.append(QTranslator())
                # --> добавляем транслятор к списку существующих трансляторов
     
                if tr_tslist[-1].load(f_n, RESOURCE_QM_DIR):
                    # --> загружаем файл переводов в транслятор
                    # --> переменная f_n либо 'team_probe_it.qm', либо 'team_probe_en.qm'
                    # --> если загрузка файла переводов в транслятор успешна
                    app.installTranslator(tr_tslist[-1])
                    # --> то устанавливаем транслятор
                # --> и переходим к выбору следующего шаблона имени файла переводов
                # --> из списка TRANSLATOR_QM_LIST
    То есть, городить-то больше ничего не надо, достаточно положить эти файлы переводов к остальным и они будут использованы на системах с соответствующей локалью.
    Запись от iamvic размещена 01.02.2023 в 00:57 iamvic вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru