QGIS二次开发汉化全流程:从Qt翻译机制到插件中文化实践 QGIS二次开发做了几个项目之后我最大的感触是功能写完了不算完界面能不能让使用者看懂才是决定这工具能不能真正落地的关键。很多开发者在做QGIS插件或独立应用时前期埋头写业务逻辑最后交付时才发现满屏英文菜单和提示客户直接皱眉。于是“QGIS二次开发汉化”这件事变成了绕不开的收尾工作。我刚开始处理汉化时也天真地以为把代码里的字符串全换成中文就行。实际试过才发现QGIS基于Qt框架它的翻译机制是一整套完整体系如果不懂这套机制的运作原理你会出现“改了代码但界面纹丝不动”的诡异问题。这篇内容我会从机制原理讲到实操流程再结合我踩过的坑把QGIS二次开发中的汉化翻译问题完整拆一遍。适合正在写QGIS插件但不知道如何让界面变中文的开发者也适合准备做源码级二次修改、需要给定制版QGIS做整体中文化的团队参考。内容尽量说人话把每一步为什么要这么做讲清楚。1. 先搞懂QGIS的翻译机制才知道汉化该从哪里下手很多人在汉化卡壳根本原因是没搞明白QGIS的界面文字是从哪来的。QGIS虽然是C写的但它用的是Qt框架而Qt自带一套成熟的国际化方案简称i18n。QGIS官方支持几十种语言靠的就是这套机制。二次开发的插件、独立程序只要跑在QGIS环境里同样得遵循这套规则。1.1 Qt翻译机制的核心角色Qt的翻译体系里有几个关键角色对应不同的文件格式.ts文件这是翻译源文件XML格式里面记录了程序里所有需要翻译的原文、上下文、翻译结果。你可以把它理解成一张“待翻译清单”。.qm文件这是编译后的二进制翻译文件程序运行时实际加载的是它。它由.ts文件通过工具编译生成体积小、加载快但不适合人类直接阅读和编辑。QTranslator类这是Qt运行时负责加载.qm文件、把原文替换为译文的核心组件。没有它就算你翻译文件做得再完整程序也不会认。tr()函数这是代码里标记“这段字符串需要翻译”的入口。代码中的字符串本身是英文tr()负责在运行时去翻译文件里查找对应的译文。用一句生活化的话解释这套流程代码里的英文字符串是“身份证号”.ts文件是“户口登记表”.qm文件是“户口本”QTranslator就是“户籍民警”。程序运行时tr()把身份证号报给户籍民警户籍民警去户口本里查查到对应的中文名就显示中文查不到就显示原来的英文。1.2 为什么不能直接把代码里的字符串改成中文我第一次做插件汉化图省事想着直接把代码里的“Buffer Creation”改成“创建缓冲区”不就行了。后果是界面确实显示中文了但项目一换环境或者需要出多语言版本的时候问题立刻爆发。更关键的是直接改源码字符串和QGIS的生态是脱节的因为QGIS有大量基于翻译上下文translation context的机制例如针对特定语境对同一个英文单词给出不同翻译。如果你直接改死字符串等于绕过了整个翻译系统后续维护体验极差。正确的做法是保持源码中的字符串是英文或者源语言把翻译结果放到.ts/.qm文件里通过运行时加载实现汉化。这样做的好处很明显代码逻辑和界面文案解耦改翻译不需要动代码出多语言版本也是顺带的事。1.3 QGIS二次开发中翻译工具链的组成QGIS二次开发分两个主流方向一是用Python写插件二是用C做深度开发。两条路线的翻译工具链稍有不同但核心机制一致Python插件依赖PyQt5/PyQt6中的pylupdate5或pylupdate6工具用于从.py文件中提取可翻译字符串。C插件和源码级开发依赖lupdate工具提取字符串。翻译文件统一用Qt Linguist语言家软件进行人工翻译这个软件在安装Qt时会自带。这套工具链相当于一个“翻译流水线”提取原文→人工翻译→发布编译→运行时加载。后续的操作说白了就是把这条流水线完整走通。2. 插件汉化全流程实操从提取词条到发布qm现阶段大部分QGIS二次开发工作集中在Python插件上。下面这套流程我实际跑过很多遍照着走基本不会出错。2.1 环境准备确认你的工具链是否齐全首先确认你已经安装了QGIS。QGIS安装目录下自带Python环境同时也带了一部分开发和翻译工具。但pylupdate5不一定在默认路径。我在Windows上遇到过一次python插件需要的工具在QGIS自带的OSGeo4W Shell里能直接调用但在系统cmd里找不到。建议直接在OSGeo4W Shell里操作或者把QGIS安装目录下的bin路径加进系统PATH。接下来用命令验证工具是否可用pylupdate5 -version lrelease -version如果提示找不到命令去QGIS安装目录找一下通常在C:\Program Files\QGIS 3.x\bin下面。找到后可以直接用绝对路径调用。2.2 代码里需要做的准备工作想让pylupdate5正确提取字符串你得在代码里“标记”这些字符串。规则很简单凡是用户界面要显示的字符串尽量用self.tr()包裹而不是直接写死。凡是需要格式化的动态字符串用占位符%1而不是拼字符串。举个对比# 不推荐无法被翻译系统捕获 label.setText(Buffer Creation) # 推荐可以被tr()捕获 label.setText(self.tr(Buffer Creation))带参数的字符串也建议用占位符# 不推荐 self.setWindowTitle(已创建 str(count) 个要素) # 推荐 self.setWindowTitle(self.tr(已创建 %1 个要素).arg(count))这样做的原因是pylupdate5只会提取被tr()包裹的字符串。直接写在界面文件.ui里的字符串也会被提取但需要额外的步骤确认UI文件被加入了提取列表。2.3 生成.ts翻译源文件假设你的插件目录结构大致如下my_plugin/ __init__.py my_plugin.py resources.py gui/ dialog.py在插件根目录创建或修改一个.pro文件用于告诉pylupdate5要扫描哪些Python文件。我给插件配置的.pro文件长这样SOURCES __init__.py \ my_plugin.py \ gui/dialog.py FORMS gui/dialog.ui TRANSLATIONS i18n/my_plugin_zh.ts然后执行pylupdate5 my_plugin.pro -ts i18n/my_plugin_zh.ts执行后会在i18n目录生成一个my_plugin_zh.ts文件。用文本编辑器打开它你会看到类似这样的结构context nameMyPlugin/name message sourceBuffer Creation/source translation typeunfinished/translation /message /context这里name是上下文source是原文translation typeunfinished是待翻译的译文。所有的汉化工作都将在这个文件上进行。2.4 用Qt Linguist完成翻译Qt Linguist是一个带图形界面的翻译工具装过Qt的人对它不陌生。打开方式Windows下在开始菜单找Qt目录下的Linguist也可以在OSGeo4W Shell里输入linguist打开Qt Linguist后用它打开刚才生成的.ts文件左侧是待翻译词条列表右侧是原文和译文输入框。翻译完一条按CtrlEnter跳到下一条。这里的坑点来了如果你看原文列表觉得信息来源太杂建议打开“模糊”标记。有些词条是从.ui文件提取的上下文信息可能和预期的不一样。先按上下文分组翻译比逐个乱翻效率高得多。翻译过程中有几个细节值得注意保留占位符原文里的%1、%2是运行时替换的参数译文也必须保留否则运行时参数会消失。保留HTML标签按钮、标签里可能带有HTML富文本比如b、a href翻译时不要破坏标签结构。快捷键冲突英文菜单和按钮常带File这种写法后面的字母是快捷键。中文翻译里不建议保留因为中文字符无法直接作为快捷键建议删掉或换一种方式提示。2.5 发布.qm文件并验证翻译完成并保存.ts文件后用lrelease把.ts编译成.qmlrelease my_plugin.pro没有.pro时也可以直接指定文件lrelease i18n/my_plugin_zh.ts -qm i18n/my_plugin_zh.qm.qm文件是程序运行时真正加载的文件。到这里插件的翻译文件就制作完成了。但光有.qm文件还不够程序启动时必须主动加载它。这一步是在插件的__init__.py或主模块里完成的。QGIS插件加载翻译文件的通用代码模板如下import os from qgis.core import QgsApplication from qgis.PyQt.QtCore import QTranslator, QLocale from qgis.PyQt.QtWidgets import QApplication class MyPlugin: def __init__(self, iface): self.iface iface self.plugin_dir os.path.dirname(__file__) # 初始化翻译 self.translator QTranslator() locale QLocale.system().name() # 例如 zh_CN qm_path os.path.join( self.plugin_dir, i18n, fmy_plugin_{locale}.qm ) if os.path.exists(qm_path): self.translator.load(qm_path) QApplication.instance().installTranslator(self.translator)注意这里的QLocale.system().name()拿到的是系统区域设置不同机器可能是zh_CN、zh_TW、en_US等。如果只做中文汉化文件名统一为my_plugin_zh_CN.qm也行。关键是加载逻辑里文件路径必须和实际文件名对得上。如果不确定系统语言变量到底取到了什么值可以在QGIS的Python控制台里临时打印一下from qgis.PyQt.QtCore import QLocale print(QLocale.system().name())这一步排查很实用很多插件汉化失效都是因为这个细节。2.6 在QGIS中加载插件并验证翻译把整个插件目录拷贝到QGIS的插件目录下或通过“安装来自ZIP的插件”方式安装然后在“管理并安装插件”中勾选启用。打开插件的主窗口如果一切正常界面应该显示中文了。如果还是英文按下面的优先顺序排查.qm文件路径是否正确文件名是否包含正确的区域代码。QTranslator.load是否成功可以在load后加一行print(self.translator.isEmpty())如果输出False说明加载成功。是否调用installTranslator以及调用时机是否在窗口创建之前。.ts里是否真的翻译了对应词条且lrelease后生成的.qm是最新的。这套排查逻辑我每次都用基本能定位90%的问题。3. C二次开发中不得不注意的翻译细节Python插件汉化相对简单C层面的二次开发又是另一番风景。如果你在做QGIS源代码级修改、C插件或者自定义QGIS独立应用翻译方式会更贴近Qt传统开发流程但坑也更多。3.1 C代码里的tr()与上下文C环境中字符串提取靠的是lupdate工具机制和pylupdate5类似但更硬核。关键区别在于上下文C里tr()函数属于具体的类翻译上下文就是类名。同一个英文字符串在不同类里可以有不同的翻译。比如“OK”按钮在主窗口里可能翻译成“确定”在设置对话框里可能也翻译成“确定”但如果你遇到某些语境化有差异的词就需要利用上下文区分。QGIS源码级别的翻译文件在GitHub上对应的是qgis_zh.ts完整文件非常大有几万条词条翻译团队通过Transifex平台协作维护。普通二次开发不需要动这个巨型文件但如果你给某个自定义类写界面就需要在自己的.ts文件里新增对应上下文。C里面正确写法示例class MyTool : public QObject { Q_OBJECT public: explicit MyTool(QObject *parent nullptr); void setStatusMessage(); }; void MyTool::setStatusMessage() { // 使用tr()包裹上下文自动关联到MyTool类 QString msg tr(Creating buffer...); QgsMessageLog::logMessage(msg); }3.2 QObject::tr和QCoreApplication::translate的使用边界在类内部直接用tr()最方便。但在一些静态函数、工具类、或无法继承QObject的场景下tr()不可用或上下文不明确就得用QCoreApplication::translate(Context, Source)代替。举例说明// 没有this指针可用的静态函数里 QString getDisplayName() { // 第一个参数是上下文必须和.ts里的name对应 return QCoreApplication::translate(MyTool, Buffer Creation); }这里最容易被坑的是上下文不一致。你在代码里写的是QCoreApplication::translate(MyTool, Buffer Creation)但.ts文件里提取出来的上下文可能是完整类路径比如MyPlugin::MyTool。两边对不上翻译就失效。遇到这种问题打开.ts文件看一下实际的name字段再回来对照调整。3.3 动态拼接字符串的翻译策略C里经常有人这么写QString message 共生成 QString::number(featureCount) 个要素;这种写法在翻译系统里是死路一条。因为lupdate提取的是静态字符串运行时拼装的字符串是无法被识别的。正确做法同样是占位符QString message tr(共生成 %1 个要素).arg(featureCount);不光是数量日期、文件名、路径这类动态内容都应该用占位符。这样翻译人员不需要知道运行时出现的具体值只需要翻译模板本身。我看到有些团队的翻译文件里出现“%1个缓冲区创建于%2”看原文时一头雾水就是因为翻译人员不知道%1和%2代表什么。这里也建议开发者在.ts文件里顺手加comment注释说明占位符含义message source%1 buffer(s) created at %2/source comment%1 is the buffer count, %2 is the output path/comment translation已在 %2 创建 %1 个缓冲区/translation /message这对后续维护和团队协作帮助极大。3.4 C项目中批量处理翻译文件C项目的翻译流程通常借助CMake或qmake管理。CMake方式下可以用qt5_create_translation或qt6_create_translation命令自动生成.ts和.qm。假设项目里有一个CMakeLists.txt可以这样配置find_package(Qt6 COMPONENTS LinguistTools REQUIRED) set(TS_FILES translations/myapp_zh_CN.ts translations/myapp_en.ts ) qt6_create_translation(QM_FILES ${TS_FILES})这样在构建时会自动调用lupdate提取翻译源再调用lrelease生成.qm免去了手工敲命令的麻烦。同时把.qm作为项目资源嵌入可执行文件还能避免路径查找问题。4. 翻译质量与界面细节机器翻译靠不住这些坑必须手动规避很多团队图省事把.ts文件丢给机器翻译。短词条机器翻译还能应付长句子、涉及时态和行业术语的句子往往翻得离谱。QGIS是GIS专业软件术语错误会直接影响工作流。以下是我在几个项目里实打实踩过的质量坑。4.1 GIS专业术语必须建立对照表QGIS里的部分术语在常规语境下很好理解但在GIS领域有指定含义。随手举例layer在GIS里是“图层”不是“层”feature在GIS里是“要素”不是“特征”buffer在GIS里是“缓冲区”不是“缓冲”extent在GIS里是“范围/空间范围”不是“程度”join在GIS里常指“连接/关联属性表”不是“加入”raster在GIS里是“栅格”不能被翻译成“光栅”或“网格”这些术语错了会让从业者觉得很业余。建议在项目早期就建立一份术语对照表团队内部统一标准。我自己的习惯是把术语表放在插件仓库的docs目录格式很简单英文中文使用场景layer图层通用feature要素矢量数据buffer缓冲区分析工具raster栅格数据格式attribute table属性表数据操作Qt Linguist里面也支持术语表和词典功能可以在翻译时实时查询避免同一个词在不同词条里翻译得五花八门。4.2 长度和布局问题按钮文字变形中文翻译通常比英文短但有时也会出现长词条比如“配置高级符号系统选项”塞进一个原本给英文文本设计的按钮里很容易换行或溢出。插件对话框的布局是固定的文字一变长UI就可能乱掉。这个问题并没有万能解法只能靠人工检查和布局配合。经验法则如下按钮文字控制在6到10个汉字以内如果直译太长换成语义更精练的表述。对话框的QSizePolicy设置成Preferred或Expanding给控件留出伸缩空间。关键界面在翻译后逐语言截图对比。对文本长度要求严格的场景可以设置setMinimumWidth或使用QFormLayout自动换行。如果空间紧张可以把按钮提示文字放到setToolTip里按钮本身用短文本。4.3 编码问题中文乱码的元凶早期我在Windows上用记事本编辑.ts文件保存时不小心带上了BOM头导致某些词条在运行时不显示甚至直接显示乱码。Qt Linguist和lupdate对UTF-8有严格要求建议中文内容统一使用UTF-8无BOM编码。用代码检查文件编码可以这样file -bi i18n/my_plugin_zh.ts输出应该是text/xml; charsetutf-8。如果看到charsetutf-8加BOM可以手动转一下。Python脚本处理也行content open(my_plugin_zh.ts, encodingutf-8-sig).read() with open(my_plugin_zh.ts, w, encodingutf-8) as f: f.write(content)另外QGIS插件里有时会用到配置文件或帮助文档里的中文内容这些文件同样建议使用UTF-8无BOM。4.4 动态语言切换不生效有些插件提供语言切换功能期待用户点一下按钮就全界面切换语言。这个效果需要额外处理不能只靠安装一次QTranslator就完事。正确的做法是监听语言切换事件并重新设置界面文字。在Qt中窗口类需要重写changeEventvoid MyDialog::changeEvent(QEvent *event) { if (event-type() QEvent::LanguageChange) { retranslateUi(this); // 重新设置所有控件的文本 } QDialog::changeEvent(event); }retranslateUi通常会单独封装成一个函数把所有setText、setWindowTitle、setToolTip都放在里面。这样无论是初始加载还是语言切换都能保证界面正确更新。Python插件里也可以实现同样思路在动态切换语言时重新构建对话框的文案或者干脆重新实例化对话框。但很多插件并不会做实时切换只跟随系统语言启动这种情况下只需要在初始化时加载一次翻译即可。4.5 单位、坐标、日期格式的本地化汉化不仅仅是文字翻译还涉及数字格式问题。QGIS默认使用英文环境下的小数点和日期格式但在中文环境下用户可能习惯用“3.14159”而不是“3,14159”。QGIS本身会跟随系统区域设置自动调整但你在二次开发中如果自己格式化数字就得注意和系统保持一致。建议使用QLocale或QgsSettings中的本地化设置而不是硬编码分隔符double value 3.14159; QString text QLocale().toString(value); // 跟随系统区域设置另外QGIS中的坐标单位可能显示为“degrees”在中文界面上应该显示为“度”。这部分在QGIS源码的翻译文件里已经覆盖了但如果你自己写了测量工具或坐标显示控件记得用统一机制翻译单位字符串。5. 常见问题与排查技巧实录汉化这种活做得多了总会遇到各种奇怪问题。下面这张排查表是我在多个插件和二次开发项目中反复用到的建议收藏。现象可能原因解决思路界面全是英文一个字没变.qm路径不对或未被加载检查插件里load和installTranslator代码确认路径存在部分词条翻译了但没生效.ts改完后忘了重新lrelease重新跑lrelease确认.qm时间戳更新翻译越改越乱出现同一词条多译文上下文不一致导致词条多次出现打开.ts查看name上下文统一上下文命名中文显示为乱码.ts文件编码不是UTF-8无BOM用UTF-8无BOM重新保存按钮文字溢出或被截断译文长度超过控件设计缩短译文或调整layout策略弹窗标题翻译了但菜单栏没翻译翻译文件未覆盖所有上下文重新提取全部源文件补充缺失词条动态语言切换不生效未处理LanguageChange事件重写changeEvent调用retranslateUi部分字符串找不到原文字符串没有被tr()包裹无法被提取在代码中把界面字符串全部用tr()包裹中文翻译生效但有些词语明显不对术语表缺失或机器翻译产物建立术语对照表逐条人工校对同一个插件在不同电脑上语言不同跟随系统语言文件命名和判断条件不一致统一.qm文件命名明确加载策略5.1 最容易被忽略的先翻译还是后翻译代码还在频繁改动时就开始翻译往往会造成大量返工。我建议先把功能写完、界面冻结后再启动翻译工作否则每改一次界面文案就要重新提取、重新翻译、重新发布效率很低。但如果项目周期紧张需要边开发边翻译那建议引入版本管理配合每次lupdate提取后用ts文件里的location标签确认词条来源区分新增和改动。Qt Linguist会标记出新词条旧翻译还保留这样增量翻译比较清晰。5.2 如何排查“明明翻译了却不显示”这个问题我在帮朋友看一个插件时遇到过找了一下午才发现是区域代码缀错了。插件加载的qm文件名是my_plugin_zh_CN.qm系统返回的区域代码却是zh不带国家后缀导致拼接出的路径不存在加载直接失败。排查方法很简单在启动加载时加一段调试日志qm_path os.path.join( self.plugin_dir, i18n, fmy_plugin_{locale}.qm ) QgsMessageLog.logMessage(fTry load translation: {qm_path}, MyPlugin)看日志里输出的实际路径对比磁盘上的文件名问题立刻现形。这比瞎猜快得多。还有一种情况是语言文件明明加载成功了但界面文字还是英文。这种时候可以检查一下是否有多个QTranslator实例互相覆盖或者插件代码里在窗口创建之后才安装translator导致窗口初始化时已经渲染了英文文字。对于后者最简单的解法是把translator的加载放到插件类的最开始确保在任何UI创建之前就准备好翻译。5.3 插件里混合C和Python时的翻译协调有些大型插件底层用C编译成二进制上层用Python调用。这种插件做汉化时C部分的词条和Python部分的词条会被分别提取到不同的源文件。加载时需要同时安装两个翻译器或者把两个.ts合并成一个后再lrelease。实际项目中我更倾向于分开维护C部分一个tsPython部分一个ts加载时分别load两个QTranslator实例。注意安装顺序先装C的再装Python的避免同名词条被后安装的覆盖掉。5.4 关于“源码级汉化”的提醒有些人会问如果我只是自己用不想搞翻译文件这么麻烦直接修改QGIS源码里的英文字符串为中文再编译一个中文版QGIS行不行理论上可行但我强烈不建议这么做。因为QGIS官方源码里的大量字符串本身就带翻译上下文你改了源码后续升级时很容易被覆盖而且每次上游更新代码你都要重新面对合并灾难。正确姿势是跟着官方翻译体系走用自定义.qm文件覆盖或补充翻译这种做法的维护成本低得多。6. 翻译文件的团队协作与版本管理最后聊一个很多独立开发者容易忽略的问题翻译文件在多人协作和版本迭代时怎么管理。6.1 .ts文件能否放进Git当然可以。.ts本身是XML文本可以被方便的diff和merge。但.qm是二进制文件建议不要频繁提交或者提交时保持每次由最新的.ts生成。你可以把.qm加入.gitignore让构建过程自动化生成这样仓库里始终只维护一个可读的.ts源文件。一个常见的目录规划my_plugin/ i18n/ my_plugin_zh_CN.ts # 源文件提交到Git my_plugin_zh_CN.qm # 构建产物可不提交 scripts/ generate_translations.sh # 一键生成脚本脚本内容也很简单#!/bin/bash cd $(dirname $0)/.. pylupdate5 my_plugin.pro lrelease my_plugin.pro echo Translations generated.6.2 多人协作时如何避免翻译冲突如果有多个翻译人员同时编辑同一个.ts文件很容易产生冲突。缓解办法有两种按上下文切分ts把不同模块的词条拆到不同ts文件里最后合并加载。适合模块边界清晰的插件。约定流程用Qt Linguist翻译完成后立即提交避免长时间霸占文件。Git的merge对于.ts这种结构化的格式通常也能自动合并但人工审查仍然必要。6.3 翻译文件的自动化检查如果你对质量要求比较高可以写一个简单的CI脚本检查.ts文件里是否存在typeunfinished的条目如果有说明有词条还没翻译完。检查方法可以直接用grepif grep -q typeunfinished i18n/my_plugin_zh_CN.ts; then echo There are unfinished translations. exit 1 fi这个检查可以挂进Git的pre-commit hook或CI流水线保证合并到主分支的翻译文件是完整可用的。6.4 面向多语言的架构考量虽然标题是汉化但架构设计上最好考虑未来出其他语言。翻译文件名里的区域代码、加载逻辑里的fallback机制、术语表的可扩展性这些最初多花一点心思后面收益巨大。一个简单的fallback逻辑示例def load_translation(self, locale): candidates [ fmy_plugin_{locale}.qm, fmy_plugin_{locale.split(_)[0]}.qm, # 例如 zh_CN 回退到 zh my_plugin_en.qm ] for name in candidates: path os.path.join(self.plugin_dir, i18n, name) if os.path.exists(path): self.translator.load(path) QApplication.instance().installTranslator(self.translator) return这样即使某个语言的文件缺失程序也不会白屏最多回退到英文或更粗粒度的中文区域。7. 我实际用下来的一些心得汉化这项工作技术难度并不高难的是把事情做系统化。如果你只是临时给一个内部工具做中文界面翻译文件的老路子可能显得繁琐但只要你还在持续迭代这个工具这套标准的翻译流程就会越用越省心。我见过太多人草草把字符串改死等到维护时哭天喊地最后只能推倒重来。我自己实际用下来建议从第一个版本就把翻译体系搭好。哪怕只有几十个词条也走一遍pylupdate、Linguist、lrelease的完整流程。这就像代码里的注释起初觉得多余后面才知道值钱。另外一个建议是汉化完成后一定要找真正的业务用户试用一遍。开发者的直觉和用户习惯差距很大翻译得“信达雅”不等于用户用着顺手。某些提示信息在开发者眼里很清晰在业务人员看来可能一脸懵。这种问题只有真用起来才能发现。最后如果你在汉化过程中遇到具体问题建议优先看.ts文件本身。打开它看清上下文、原文、译文、状态80%的问题都能在文件内容里找到答案。把翻译机制吃透之后你会发现QGIS的国际化体系设计得相当优雅照着规则来几乎没有做不成的界面汉化。