Qt 是一個很方便的 C++ 應用開發框架(或許如今要加上 Qt Quick 開發框架?),不單單是程序編寫方便麪,它提供了不少方便的類庫,並且它也提供了很方便的國際化方案(也就是翻譯成各國語言的方案)。node
咱們先來講說在 Qt 中實現多國語言翻譯須要使用的基本流程。首先咱們須要在編寫代碼的時候就要使用 Qt 提供的翻譯相關的函數來"包裹"住全部的須要翻譯的字符串。程序員
你說哪些纔是須要翻譯的字符串呢?就是任何會在用戶界面上顯示的字符串,若是不會顯示天然就不須要翻譯了。json
若是你使用的是 C++ 代碼,那麼翻譯用的函數就是QObject::tr
函數。大多數時候,咱們看到用到這個函數的時候可能都只是一個tr
,由於都是在一個繼承自 QObject 的類中,因此能夠直接調用父類的成員函數了。若是是在自由函數中想使用的話就要把完整的函數名QObject::tr
寫全了。api
若是你寫的是 Qt Quick 的程序,這個函數就變成了 qsTr 或者是 qsTranslate 、qsTranslateNoOp ...app
代碼寫完以後,須要在工程文件 (.pro 文件) 中添加須要翻譯的源代碼文件。框架
SOURCES += \ main.cpp \ mainwindow.cpp TRANSLATIONS = \ English.ts \ Japanese.ts
在工程目錄下運行如下命令dom
lupdate project.pro
執行完後,目錄下會出現 English.ts 和 Japanese.ts 兩個文件,這兩個文件就是由上文中工程文件中制定的文件名。函數
而後就可使用 Qt 提供的 lingust 工具打開上文生成的 .ts 文件,將對應的詞條翻譯成目標語言就行了。工具
當 linguist 翻譯完全部的詞條以後,就須要生成最終給應用程序使用的二進制翻譯文件了。
使用以下命令便可生成。ui
lrelease English.ts
執行完畢,目錄下會多出一個 English.qm 文件,這個文件就是最終須要放在應用程序中分發給用戶的。
使用上面的流程就完成了一次基本的程序國際化的流程。通常來講程序的翻譯工做和編碼工做是由不一樣人來完成的,可能實際完成翻譯的人不熟悉或者不肯去熟悉使用 linguist 的使用方法,他們提供給程序員的詞條翻譯可能就是一個 Excel 文件,這個 Excel 文件一般一列是待翻譯的詞條,另外一列是相應的譯文。
因而咱們可能須要一個將 Qt 的翻譯文件與 Excel 文件互相轉換的小工具,這樣咱們能夠把 Qt 翻譯文件轉換成 Excel 文件交給翻譯人員,而後將翻譯人員翻譯好的 Excel 文件再轉換爲 Qt 的翻譯文件。
要作文件格式轉換,那麼咱們首先得了解一下兩種文件格式有什麼特色。
Qt 的 TS 翻譯文件其實就是一個 XML 文件。咱們先來看一個翻譯文件的例子。
<!DOCTYPE TS> <TS> <context> <name>QPushButton</name> <message> <source>Hello world!</source> <translation type="unfinished"></translation> </message> </context> </TS>
這是一個還未翻譯的文件,詞條原文是"Hello world!"。在 ts 文件中,每一個須要翻譯的詞條都是一個 message,message 包含須要翻譯的原文和已翻譯的譯文。
翻譯完成後須要去除 translation 的 type="unfinished"
屬性。
Excel 的文件格式是 Microsoft ® 的私有格式,Excel 2007 以前的格式是二進制格式, Excel 2007 以後的是 OOXML 格式。
在這裏咱們不用太細的追究它的內部細節,由於它的內部細節很是複雜,不像 ts 文件就是一個簡單的文本文件。如今有許多的能夠用來讀寫 Excel 文件的庫,咱們只需調用這些庫的讀寫函數就能完成咱們所需的功能了。
在本文中,我將使用 C# 語言以及 NPOI 庫來讀寫 Excel 文件。
private void convertQtFile2ExcelFile(string qtFileName, string excelFileName) { XmlDocument xmlDoc = new XmlDocument(); XmlReaderSettings settings = new XmlReaderSettings(); settings.IgnoreComments = true; settings.DtdProcessing = DtdProcessing.Ignore; IWorkbook workbook = new XSSFWorkbook(); ISheet sheet = workbook.CreateSheet("worksheet"); int rowCnt = 0; IRow headRow = sheet.CreateRow(rowCnt); rowCnt++; ICell chCell = headRow.CreateCell(0); chCell.SetCellValue("源語言"); ICell enCell = headRow.CreateCell(1); enCell.SetCellValue("目標語言"); XmlReader reader = XmlReader.Create(qtFileName, settings); xmlDoc.Load(reader); XmlNode rootNode = xmlDoc.SelectSingleNode("TS"); XmlNodeList xnl = rootNode.ChildNodes; foreach (XmlNode node in xnl) { XmlNodeList nodeList = node.ChildNodes; string src = ""; string dst = ""; foreach (XmlNode n in nodeList) { foreach (XmlNode cn in n.ChildNodes) { if (cn.Name == "source") { src = cn.InnerText; } if (cn.Name == "translation") { dst = cn.InnerText; IRow row = sheet.CreateRow(rowCnt); rowCnt++; ICell firstCell = row.CreateCell(0); firstCell.SetCellValue(src); ICell secondCell = row.CreateCell(1); secondCell.SetCellValue(dst); //Console.WriteLine ("Src = " + src + " dst = " + dst); } } } } reader.Close(); using (FileStream fs = File.Create(excelFileName)) { workbook.Write(fs); } }
private bool convertExcelFile2QtFile(string excelFileName, string qtFileName) { try { using (var fs = File.OpenRead(excelFileName)) { var workBook = new XSSFWorkbook(fs); var sheet = workBook.GetSheetAt(0); var translateMap = new Dictionary<string, string>(); for (int i = 1; i < sheet.LastRowNum; i++) { IRow row = sheet.GetRow(i); if (row == null) { continue; } var srcCell = row.GetCell(0); var dstCell = row.GetCell(1); if (srcCell == null) { continue; } if (dstCell == null) { continue; } string src = srcCell.ToString(); string translated = dstCell.ToString(); if (translateMap.ContainsKey(src) == false) { translateMap.Add(src, translated); } } var document = new XmlDocument(); document.Load(qtFileName); XmlNodeList xnl = document.SelectNodes("/TS/context"); if (xnl != null) { foreach (XmlNode ctxNode in xnl) { var msgList = ctxNode.SelectNodes("message"); foreach (XmlNode msg in msgList) { string src = ""; foreach (XmlNode cn in msg.ChildNodes) { if (cn.Name == "source") { src = cn.InnerText; } else if (cn.Name == "translation") { if (translateMap.ContainsKey(src)) { cn.InnerText = translateMap[src]; var attrs = cn.Attributes; attrs.Remove(attrs["type"]); } } } } } } document.Save(qtFileName); } return true; } catch (Exception e) { MessageBox.Show("Exception:" + e.Message); return false; } }
其實瞭解了 TS 文件的結構以後,咱們還能作一件事,那就是直接調用網上的翻譯接口,自動將全部的詞條翻譯成對應的語言,下面就是一個簡單的例子。
import xml.etree.ElementTree as ET import os import httplib import md5 import urllib import random import json import threading import requests from PyQt4.QtCore import * from PyQt4.QtGui import * import sys reload(sys) sys.setdefaultencoding('utf-8') ## 下面填寫你本身的百度翻譯 API 的 appid 和 secretKey appid = '' secretKey = '' myurl = '/api/trans/vip/translate' def translate(string, targetLanguage): if string is None: return "" salt = random.randint(32768, 65536) sign = appid + string + str(salt) + secretKey m1 = md5.new() m1.update(sign) sign = m1.hexdigest() encoded_str = urllib.quote(str(string)) localurl = myurl + '?appid=' + appid + '&q=' + \ encoded_str + '&from=auto&to=' + \ targetLanguage + '&salt=' + str(salt) + '&sign=' + sign try: r = requests.get('http://api.fanyi.baidu.com/' + localurl) if r.status_code == 200: response = r.text objResponse = json.loads(response) return objResponse["trans_result"][0]["dst"] except Exception, e: print e return string class MainWidget(QWidget): def __init__(self, parent=None): super(QWidget, self).__init__(parent) self.setWindowTitle(u"UI Auto Translator") self.labelTargetLanguage = QLabel(u"目標語言:") self.comboTargetLanguage = QComboBox() self.comboTargetLanguage.addItem(u"中文", u"zh") self.comboTargetLanguage.addItem(u"英語", u"en") self.comboTargetLanguage.addItem(u"粵語", u"yue") self.comboTargetLanguage.addItem(u"文言文", u"wyw") self.comboTargetLanguage.addItem(u"日語", u"jp") self.comboTargetLanguage.addItem(u"韓語", u"kor") self.comboTargetLanguage.addItem(u"法語", u"fra") self.comboTargetLanguage.addItem(u"西班牙語", u"spa") self.comboTargetLanguage.addItem(u"泰語", u"th") self.comboTargetLanguage.addItem(u"阿拉伯語", u"ara") self.comboTargetLanguage.addItem(u"俄語", u"ru") self.comboTargetLanguage.addItem(u"葡萄牙語", u"pt") self.comboTargetLanguage.addItem(u"德語", u"de") self.comboTargetLanguage.addItem(u"意大利語", u"it") self.comboTargetLanguage.addItem(u"希臘語", u"el") self.comboTargetLanguage.addItem(u"荷蘭語", u"nl") self.comboTargetLanguage.addItem(u"波蘭語", u"pl") self.comboTargetLanguage.addItem(u"保加利亞語", u"bul") self.comboTargetLanguage.addItem(u"愛沙尼亞語", u"est") self.comboTargetLanguage.addItem(u"丹麥語", u"dan") self.comboTargetLanguage.addItem(u"芬蘭語", u"fin") self.comboTargetLanguage.addItem(u"捷克語", u"cs") self.comboTargetLanguage.addItem(u"羅馬尼亞語", u"rom") self.comboTargetLanguage.addItem(u"斯洛文尼亞語", u"slo") self.comboTargetLanguage.addItem(u"瑞典語", u"swe") self.comboTargetLanguage.addItem(u"匈牙利語", u"hu") self.comboTargetLanguage.addItem(u"繁體中文", u"cht") self.comboTargetLanguage.addItem(u"越南語", u"vie") self.labelSrcFile = QLabel(u"源文件") self.inputSrcFile = QLineEdit() self.inputSrcBtn = QPushButton(u"打開文件") self.connect(self.inputSrcBtn, SIGNAL( "clicked()"), self.onInputSrcBtnClicked) self.labelTargetFile = QLabel(u"保存文件") self.inputTargetFile = QLineEdit() self.inputTargetBtn = QPushButton(u"選擇文件名") self.connect(self.inputTargetBtn, SIGNAL( "clicked()"), self.onInputTargetBtnClicked) self.cvtBtn = QPushButton(u"開始翻譯") self.connect(self.cvtBtn, SIGNAL("clicked()"), self.onCvtBtnClicked) self.gridLayout = QGridLayout() self.gridLayout.addWidget(self.labelTargetLanguage, 0, 0) self.gridLayout.addWidget(self.comboTargetLanguage, 0, 1) self.gridLayout.addWidget(self.labelSrcFile, 1, 0) self.gridLayout.addWidget(self.inputSrcFile, 1, 1) self.gridLayout.addWidget(self.inputSrcBtn, 1, 2) self.gridLayout.addWidget(self.labelTargetFile, 2, 0) self.gridLayout.addWidget(self.inputTargetFile, 2, 1) self.gridLayout.addWidget(self.inputTargetBtn, 2, 2) self.vLayout = QVBoxLayout() self.vLayout.addLayout(self.gridLayout) self.vLayout.addWidget(self.cvtBtn) self.setLayout(self.vLayout) @pyqtSlot() def onInputSrcBtnClicked(self): fileName = QFileDialog.getOpenFileName( self, u"打開要翻譯的文件", u".", u"*.ts") if fileName.isEmpty(): QMessageBox.warning(self, u"警告", u"未選擇要翻譯的文件", QMessageBox.Ok) return self.inputSrcFile.setText(fileName) @pyqtSlot() def onInputTargetBtnClicked(self): fileName = QFileDialog.getSaveFileName( self, u"選擇另存爲的文件名", u".", u"*.ts") if fileName.isEmpty(): QMessageBox.warning(self, u"警告", u"未選擇要保存爲的文明名", QMessageBox.Ok) return self.inputTargetFile.setText(fileName) def process_file(self, src, dst, lang): self.cvtBtn.setText(u"正在翻譯中...") tree = ET.parse(src) root = tree.getroot() source = "" for ctx in root: for msg in ctx: if msg.tag == 'message': for tag in msg: if tag.tag == 'source': source = tag.text if tag.tag == 'translation': tag.text = translate(source, lang) print("source = {0}, translate = {1}".format(source, tag.text)) tag.set('type', '') tree.write(dst) self.cvtBtn.setText(u"開始翻譯") @pyqtSlot() def onCvtBtnClicked(self): targetLang = self.comboTargetLanguage.itemData( self.comboTargetLanguage.currentIndex()).toString() srcFileName = self.inputSrcFile.text() if srcFileName.isEmpty(): QMessageBox.warning(self, u"警告", u"未選擇要翻譯的文件", QMessageBox.Ok) return targetFileName = self.inputTargetFile.text() if targetFileName.isEmpty(): QMessageBox.warning(self, u"警告", u"未選擇要保存爲的文明名", QMessageBox.Ok) return t = threading.Thread(target=self.process_file, args=(srcFileName, targetFileName, str(targetLang))) t.setDaemon(True) t.start() if __name__ == "__main__": app = QApplication(sys.argv) widget = MainWidget() widget.show() app.exec_()