目标:让软件可以切换中文、英文。
核心工具:
tr():最常用QCoreApplication.translate():tr()的底层真身
如果你软件不需要切换多语言,这整套东西你完全不用写。
1. 没有翻译是什么样子
普通代码,写死文字:
python
from PyQt5.QtWidgets import *
class Win(QWidget):
def __init__(self):
super().__init__()
btn = QPushButton("确定") # 硬编码写死中文
layout = QVBoxLayout(self)
layout.addWidget(btn)
app = QApplication([])
w = Win()
w.show()
app.exec_()
问题:程序写死中文,想变成英文,必须改源码。
2. 第一步:给字符串打上「需要翻译标记」
Qt 的思路:不直接写死字符串,告诉 Qt:这个文字以后可以被替换成别的语言。
方式A:在QWidget/QObject类里面,用 self.tr("文字")
python
class Win(QWidget):
def __init__(self):
super().__init__()
btn = QPushButton(self.tr("确定")) # ✅打上翻译标记
layout = QVBoxLayout(self)
layout.addWidget(btn)
self.tr("确定")- 如果没加载翻译文件:直接返回字符串
"确定",和原来一模一样。 - 如果加载了英文翻译文件:返回
"OK"。
- 如果没加载翻译文件:直接返回字符串
tr()只能在继承 QWidget / QObject 的类里面,有self才能调用。
方式B:不在类里面,没有 self,用底层函数 translate
比如全局函数、普通python类,没有self,不能用tr(),就要手动调用底层。
tr() 本质等价:
python
# self.tr("确定")
QCoreApplication.translate("Win", "确定")
第一个参数:上下文名字(一般填类名),第二个:原始字符串。
示例全局位置使用:
python
from PyQt5.QtCore import QCoreApplication
# 不在任何QWidget类里面
text = QCoreApplication.translate("Global", "确定")
一句话:
self.tr()= 简写,自动把当前类名当做第一个参数传给translate()。
translate()= 底层原始函数,没有self的时候用。
3. tr / translate 只是标记,本身不会自动翻译
非常关键:
只写
self.tr("确定"),程序不会自动变成英文!tr 只是做标记,真正翻译效果,靠加载外部 .qm 文件。
整个流程分 4 步:
- 代码中所有界面文字,用
tr()/translate()包裹字面字符串。 - 工具扫描代码,提取所有被标记的文字,生成
.ts文件(xml,翻译底稿) - 人去编辑ts文件,写好对应的英文译文,编译输出
.qm(二进制翻译包) - 程序运行时加载 qm 文件,此时 tr/translate 才输出翻译后的文字。
4. 演示加载翻译文件
有一个编译好的 en.qm(英文翻译包)
python
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTranslator, QCoreApplication
app = QApplication([])
# 1. 创建翻译器对象
trans = QTranslator(app)
# 2. 加载翻译二进制文件
trans.load("en.qm")
# 3. 把翻译器安装到app,全局生效
app.installTranslator(trans)
# 现在 tr() 就会生效
class Win(QWidget):
def __init__(self):
super().__init__()
btn = QPushButton(self.tr("确定"))
layout = QVBoxLayout(self)
layout.addWidget(btn)
w = Win()
w.show()
app.exec_()
加载 qm 之后:self.tr("确定") 返回 "OK"。
不加载 qm:返回原始 "确定"。
5. 重要硬性限制(踩坑最多)
tr / translate 只能接收写死的字面字符串,不能传变量!
❌错误,工具提取不到,翻译失效:
python
s = "确定"
btn.setText(self.tr(s))
✅正确:
python
btn.setText(self.tr("确定"))
原因:提取工具是静态扫描源码,看引号里面的字面内容;运行时的变量它看不到。
6. 命令行工具(生成ts、生成qm)
pyqt5 使用 pylupdate5
bash
# 扫描py代码,生成翻译底稿 ts
pylupdate5 main.py -o my_en.ts
得到 my_en.ts,这是xml文本,交给 Qt Linguist 软件,一行行填译文。
填完译文后,生成二进制qm:
bash
lrelease my_en.ts -o en.qm
把 en.qm 放到程序运行目录,代码中 load 它。
7. 什么时候用 translate,什么时候用 tr
| 场景 | 使用 |
|---|---|
| 在QWidget/QObject子类里面,有self | 用 self.tr("xxx")(优先) |
| 普通函数、全局代码、普通Python类,没有self | 用 QCoreApplication.translate("上下文名","xxx") |
8. 极简总结
self.tr()是给界面字符串加翻译标记;底层调用QCoreApplication.translate(类名,文本)translate是底层原始接口,无self的时候手动调用,第一个参数填上下文名称。- 只写tr不会自动翻译,必须外部加载
.qm翻译文件才会替换文字。 - 参数必须是写死字面字符串,不能传变量。
- 不需要多语言切换,完全不要碰这套API,直接写普通字符串。
各个 MainWindow 区分对照表
结合你的 .ui xml文件、pyuic5生成的Python代码
| 出现位置 | 写法 | 本质 | 含义说明 |
|---|---|---|---|
ui文件xml <class>MainWindow</class> |
<class>MainWindow</class> |
字符串,翻译上下文context | 给翻译工具pylupdate5用;生成代码_translate("MainWindow",...)第一个参数就取自这里;ts文件的<name>MainWindow</name>和它对应,和Python类无关 |
ui文件xml <widget class="QMainWindow" name="MainWindow"> |
name="MainWindow" |
控件的objectName属性(字符串) | Qt对象的名字,MainWindow.setObjectName("MainWindow")就是把这个值设置给窗口;用于findChild查找控件,信号槽auto连接 |
ui文件xml class="QMainWindow" |
class="QMainWindow" |
Qt内置C++类名 | 指明这个控件是Qt提供的主窗口类QMainWindow,不要和上面两个字符串混淆 |
Python函数形参 def setupUi(self, MainWindow): |
MainWindow |
Python变量,QMainWindow实例对象(QObject) | 外部传进来真实窗口对象,是真正干活的Qt窗口实例,可以调用.resize()、.setCentralWidget()等Qt接口 |
Python函数形参 def retranslateUi(self, MainWindow): |
MainWindow |
Python变量,QMainWindow实例对象(QObject) | 和上面是同一个对象,只是传到retranslateUi函数里用来设置标题等属性 |
Python代码 _translate("MainWindow", "MainWindow") 第一个参数 |
"MainWindow" |
字面字符串,翻译context上下文 | 直接复制来自ui的<class>MainWindow</class>,用来在qm翻译库查表;固定写死,不受继承子类影响 |
Python代码 _translate("MainWindow", "MainWindow") 第二个参数 |
"MainWindow" |
字面字符串,原始显示文本 | 窗口要显示的原始标题文本,对应ui里<property name="windowTitle"><string>MainWindow</string></string> |
Python类名 class Ui_MainWindow(object): |
Ui_MainWindow |
Python普通类名 | pyuic5自动生成的UI装配工具类,注意:是Ui_MainWindow,带Ui_前缀,和上面全部不是一回事! |
重点摘录对应关系
- ui:
<class>MainWindow</class>→ 生成代码:_translate("MainWindow", ...)的第一个参数字符串(翻译上下文) - ui:
name="MainWindow"→ 生成代码:MainWindow.setObjectName("MainWindow")(设置Qt对象名字) - ui:
class="QMainWindow"→ Qt系统类,Python中from PyQt5.QtWidgets import QMainWindow def setupUi(self, MainWindow):里面的MainWindow:只是一个局部Python变量,接收外部传入的QMainWindow()实例。
举个改动例子帮你彻底分清
如果你在Qt Designer,把顶层窗口的 objectName 修改为MyWin:
- ui xml:
<class>MyWin</class>,<widget class="QMainWindow" name="MyWin"> - pyuic5重新生成后:
python
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setObjectName("MyWin")
MainWindow.setWindowTitle(_translate("MyWin", "MainWindow"))
注意:函数参数变量名依旧叫
MainWindow(pyuic5生成的函数参数名字不会变),但是translate第一个参数字符串、setObjectName的值已经变成MyWin。
极简记忆
- 带引号
"MainWindow":大多是字符串,翻译上下文 / objectName名字; - 不带引号
MainWindow(函数参数):是Qt窗口对象实例; QMainWindow:Qt库的类;Ui_MainWindow:自动生成的UI装配工具类。
我这边没法直接生成带标注的图片文件,但我给你做文本可视化示意图 ,把 XML‑UI 和 Python 代码分段,用 【标注】直接贴在对应位置,复制就能看,效果等价图片标注。
① ui 文件(xml)可视化标注
xml
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class> 【标注1:翻译上下文context字符串,py代码 _translate("MainWindow",...)第一个参数来源】
<widget class="QMainWindow" name="MainWindow">
<!-- ↑Qt系统类名 ↑【标注2:objectName对象名字符串;对应py MainWindow.setObjectName("MainWindow")】 -->
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>726</width>
<height>592</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string> 【标注3:原始窗口标题文本,对应 _translate(..., "MainWindow")第二个参数】
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>490</x>
<y>110</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>按钮</string> 【标注4:按钮原始文字,对应 _translate(..., "按钮")第二个参数】
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>726</width>
<height>28</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
② pyuic5生成的python代码可视化标注
python
class Ui_MainWindow(object): #【标注A:UI装配工具类,普通Python object,不是QObject;前缀Ui_不要忽略】
def setupUi(self, MainWindow): #【标注B:MainWindow 是函数局部变量!接收外部传进来真实QMainWindow窗口实例(QObject)】
MainWindow.setObjectName("MainWindow") #【标注C:设置Qt对象objectName,来自ui name="MainWindow"】
MainWindow.resize(726, 592)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(490, 110, 93, 28))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 726, 28))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow) #【标注D:把窗口实例继续传给retranslateUi】
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow): #【标注E:MainWindow仍然是那个QMainWindow窗口对象变量】
_translate = QtCore.QCoreApplication.translate
# ↓【标注F:第一个参数字符串context,取自ui <class>MainWindow</class>】
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
# ↑【标注G:第二个参数原始文本,ui windowTitle字符串】
# ↓【标注H:context上下文,固定字符串,不受子类继承影响】
self.pushButton.setText(_translate("MainWindow", "按钮"))
# ↑【标注I:第二个参数原始文本,ui按钮text】
对应关系速查(把上面标注一一对应)
- ui【标注1】 → py【标注F】【标注H】:
"MainWindow"翻译上下文 - ui【标注2】 → py【标注C】:objectName
- ui【标注3】 → py【标注G】:窗口标题原始文字
- ui【标注4】 → py【标注I】:按钮原始文字
- py【标注B】【标注D】【标注E】:不带引号
MainWindow= Python变量,QMainWindow实例对象 Ui_MainWindow【标注A】只是装配工具,不是Qt窗口
核心记忆:
- 带双引号
"MainWindow":字符串(上下文/对象名/显示文字)- 不带引号
MainWindow:Python变量,Qt窗口实例对象Ui_MainWindow:带Ui_前缀,普通object工具类QMainWindow:Qt系统类
。