PySide で多言語対応してみる。
maya_test/main_window.py at master · hal1932/maya_test · GitHub
そのうち必要になる気がするので手順をメモっておく。
Qt Linguist のインストール
普段 Qt Designer とかを使わない*1人なので各種バイナリを一切インストールしておらず。。仕方ないので Qt の SDK をいれることに。とりあえずどのビルドでもいいからインストールすれば Linguist も一緒についてくる。
多言語対応の UI を組む
まず最初に、Linguist に対応できるように UI を組んでやる必要がある。Qt Designer を使う場合は何も考えなくていいんだけど、コードで書くときは QObject.tr() 経由で文字列を設定する*2必要がある。
class QObject(__Shiboken.Object): def tr(self, *args, **kwargs): # real signature unknown pass
class MainWindow(QMainWindow, MayaQWidgetBaseMixin): def __init__(self): ... # 諸々初期化 label1 = QLabel(self.tr('label1')) layout.setWidget(label1)
pyside-lupdate
コードができたら、.pro ファイル*3をこんなかんじで用意する。
# 多言語対応の対象にするソースコード SOURCES = ..\main_window.py # 対応言語 TRANSLATIONS = ja_JP.ts en_US.ts
UI を定義してるソースコードを Python インストールディレクトリにある Scripts/pyside2-lupdate *4 に渡してやる。例えば Windows ならこんなかんじでバッチ化すればいい。
ここでは Python37 の pip でインストールしたやつを使ってるけど、PySide のバージョンさえ合わせてあれば問題なし。
pushd %~dp0 C:\Python37\Scripts\pyside2-lupdate.exe i18n_test_app.pro popd pause
ここまでやると言語ごとに .ts ファイルが生成されるので、Liguist で読み込んで編集してやればいい。終わったら Linguist の「リリース」メニューから .ts のビルドして、言語ごとに .qm ファイルが生成されれば完了。
lupdate は UI の中にある tr() や translate() を検出して、そこに記述されてる情報を拾ってくるようになってる。既に .ts ファイルがある場合は差分を上書き更新してくれるから、気軽に叩けばいい。
言語の反映
.qm ファイルを QTranslator.load してから、QApplication.installTranslator するだけ。Maya 上から実行する場合はカレントディレクトリが Maya.exe の場所になってるので、load() のオプションに directory を指定するのを忘れないこと。*5
app = QApplication.instance() translator = QTranslator() translator.load('ja_JP', directory=os.path.join(os.path.dirname(__file__), 'i18n')) # ./i18n/ja_JP.qm が存在する想定 app.installTranslator(translator)
動的な言語切り替え
QApplication.installTranslator() に従って QObject.tr() の返り値が変わるので、動的に切り替えたい場合は installTranslator 後に tr が再度呼び出されるように組んでやればいい。pyside-uic を使っている場合は retranslateUi() が自動生成されてるので、installTranslator の直後にそれを叩けばいい。
UI をコードで書いてる場合も同じで、例えば簡易的な実装だけどこんなのでも別に構わない。*6
class MayaMainWindowBase(QMainWindow, MayaQWidgetBaseMixin): def setup_ui(self): # type: () -> MayaMainWindowBase widget = self.centralWidget() if widget is not None: widget.deleteLater() widget = QWidget() self.setCentralWidget(widget) self._setup_ui(widget) return self def _setup_ui(self, central_widget): # type: (QWidget) -> NoReturn pass class MayaAppBase(object): def execute(self): app = QApplication.instance() self._initialize(app) self._window = self._create_window() if self._window is not None: self._window.setup_ui().show()
class MainWindow(MayaMainWindowBase): lang_switch_requested = Signal(str) def _setup_ui(self, central_widget): # type: (QWidget) -> NoReturn layout = QVBoxLayout() switch_button = QPushButton(self.tr('switch_lang')) switch_button.clicked.connect(lambda: self.lang_switch_requested('ja_JP')) layout.addWidget(switch_button) central_widget.setLayout(layout)
class MayaApp(MayaAppBase): def __init__(self): super(MayaApp, self).__init__() self.__translator = QTranslator() def _initialize(self, app): # type: (QApplication) -> NoReturn self.__switch_languages('ja_JP') app.installTranslator(self.__translator) def _create_window(self): # type: () -> MayaMainWindowBase window = MainWindow() window.lang_switch_requested.connect(lambda lang: self.__switch_languages(lang)) return window def __switch_languages(self, lang_name): # type: (str) -> NoReturn self.__translator.load(lang_name, directory=os.path.join(os.path.dirname(__file__), 'i18n'))
*1:このへんは人によって色々な考え方があると思うので諸々割愛。
*2:QObject.tr() 自体は QObject.translate() のラッパーとして実装されてる。translate を使ってもいいんだけど、PySide で組む程度の UI なら tr が使えれば十分かな。
*3:Qt Creator 用のプロジェクトファイル。PySide の場合は Creator を使わないので手書きする。
*4:PySide の場合は pyside-lupdate
*5:ちょっと話はそれるけど……、そもそもの話として余程の強い仮定でもない限りは "暗黙のカレントディレクトリ" なんてものを信用してはいけない。実行コンテキストを確実に掌握すること。
*6:実戦投入するならこんな雑なやり方はせずに、自動で setText() しなおしてくれる仕組みをちゃんと設計すべき。