乱谈Qt程序之i18n的实现(从C++到Qt)

嘿嘿,本文只是试图从纯C++的角度告诉你 Qt 的国际化是到底是怎么一回事(注:本文只看一个点,不看面)。而不会一步一步告诉你Qt的国际化/本地化怎么用(这些东西在Qt Manual、论坛 以及 相关书籍中介绍的够多了)。

Qt 国际化所做的就是这点东西:

  • 首先,提取要翻译的字符串,手动翻译,生成一个qm文件,以备使用
  • 其次,程序中使用QTranslator安装翻译文件
  • 最后,tr函数去查找有没有对应的字符串,有则使用,
    • 没有怎么办,就按照某种编码将参数窄字符串变成QString呗

至于动态翻译:点一下菜单,界面文字全改变,这在Qt中是相当容易实现的东西。

其实,根本就没有动态这回事。所谓的动态翻译,就是我们加载了一个新的翻译文件,然后将界面的文字用新的重新设置了一遍。

我们找个超简单的C++的小例子看看,并一步一步让它变的复杂一点点。

例子一

看一个 hello world 的例子:

  • 为了稍后国际化,我们先用一个 tr 宏包住了所有要显示的字符串。
  • 同样为了程序可以在所有平台下运行,我们的输出使用的是宽字符wchar_t

#include <iostream>
#include <string>
#include <locale.h>
#define tr(X) L##X
int main()
{
    setlocale(LC_ALL, "");
    std::wcout<<tr("hello")<<tr(" ")<<tr("world")<<std::endl;
    return 0;
}

恩,编译运行,看到结果

hello world

例子二

如何翻译这个程序呢?

  • 要有 翻译后的文字 吧
  • 要使用 翻译后的文字 吧
  • 要处理 没有翻译的文字 吧

词典?

源单词和目标单词的对应关系,我们就叫它词典好了。

  • 创建3个全局的map,分别用来存在中文、日文、挪威文的翻译
  • 创建一个辅助函数create_map和辅助变量dummy用来初始化这3个map

typedef std::map<std::string, std::wstring> Map;

Map chinese;
Map japanese;
Map norwegian;

int create_maps()
{
    chinese["hello"] = L"你好";
    chinese["world"] = L"世界";

    japanese["hello"] = L"こんにちは";
    japanese["world"] = L"世界";

    norwegian["hello"] = L"hallo";
    norwegian["world"] = L"verden";

    return 0;
}

int dummy = create_maps();

关联

有了翻译的内容,需要安装一下,让我们的程序知道翻译内容的存在吧?

Map * globalMap = 0;
int main()
{
    setlocale(LC_ALL, "");

    globalMap = & chinese; //install

    std::wcout<<tr("hello")<<tr(" ")<<tr("world")<<std::endl;
    return 0;
}

使用

第一个例子中的宏可以丢掉了,我们写一个函数:

  • 如果安装了词典,且存在翻译的内容,使用之
  • 其他,将窄字符串用某种规则直接转成宽字符串

std::wstring tr(const char * text)
{
    if (globalMap && globalMap->count(text)) {
        return (*globalMap)[text];
    }
    wchar_t wcs[100];
    mbstowcs(wcs, text, 99);
    return std::wstring(wcs);
}

拼盘

将3部分合到一块:

#include <iostream>
#include <string>
#include <cstdlib>
#include <map>
#include <locale.h>

typedef std::map<std::string, std::wstring> Map;

Map chinese;
Map japanese;
Map norwegian;

int create_maps()
{
    chinese["hello"] = L"你好";
    chinese["world"] = L"世界";

    japanese["hello"] = L"こんにちは";
    japanese["world"] = L"世界";

    norwegian["hello"] = L"hallo";
    norwegian["world"] = L"verden";

    return 0;
}

int dummy = create_maps();

Map * globalMap = 0;
std::wstring tr(const char * text)
{
    if (globalMap && globalMap->count(text)) {
        return (*globalMap)[text];
    }
    wchar_t wcs[100];
    mbstowcs(wcs, text, 99);
    return std::wstring(wcs);
}
//#define tr(X) L##X

int main()
{
    setlocale(LC_ALL, "");

    globalMap = & chinese;

    std::wcout<<tr("hello")<<tr(" ")<<tr("world")<<std::endl;
    return 0;
}

看看运行结果:

你好 世界

对比Qt

Qt 又做了什么呢?

Qt

我们的例子

 

lupdate/lrelease/...

Map/Map/Map

生成翻译/词典文件

QTranslator

globalMap

安装翻译文件

QObject::tr()
QCoreApplication::translate()

tr()

使用翻译文件

从根本上说,Qt 国际化所做的就是这点东西:

  • 首先,提取要翻译的字符串,手动翻译,生成一个qm文件,以备使用
  • 其次,程序中使用QTranslator安装翻译文件
  • 最后,tr函数去查找有没有对应的字符串,有则使用,
    • 没有怎么办,就按照某种编码将参数窄字符串变成QString呗?

注意,tr就是一个将 const char * 变成 QString 的函数:

QString QObject::tr ( const char * sourceText,...)

对于tr,我们在Qt中translate、tr关系 与中文问题 有了比较详细的讨论,此处就不重复了。

Qt动态翻译

点一下菜单,界面文字全改变,这在Qt中是相当容易实现的东西。也就是大家所说的动态翻译。

其实,根本就没有动态这回事。所谓的动态翻译,就是我们加载了一个新的翻译文件,然后将界面的文字用新的重新设置了一遍。

考虑我们前面的例子,稍微改改:

int main()
{
    setlocale(LC_ALL, "");

    std::wstring welcome = tr("hello"); //1st

    globalMap = & chinese;
    welcome = tr("hello"); //2nd
    
    globalMap = & japanese;
    welcome = tr("hello"); //3rd

    return 0;
}

尽管都是用tr,但3处 welcome 的内容却不相同,动态翻译也就是这回事。

  • 一个button的文字如何改变? 通过 setText
  • 在button上,"hello" ==> "你好" 是改变么? 显然,于是这个过程需要setText

这也是为什么,uic生成的代码 ui_xxx.h 中始终有:

    void retranslateUi(QDialog *Dialog)
    {
        Dialog->setWindowTitle(QApplication::translate("Dialog", "Dialog", 0, QApplication::UnicodeUTF8));
        groupBox->setTitle(QApplication::translate("Dialog", "GroupBox", 0, QApplication::UnicodeUTF8));
    } // retranslateUi

这种函数存在的原因。

当你安装了新的翻译文件以后,只需要重新调用一遍这个函数就行了。

但你安装新的翻译文件后,应用程序会给各个窗口发送一个事件,此时,是调用上述函数的最佳时机

void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

大家对这个函数应该都不陌生,毕竟,Qt Creator会自动为你生成它。不管你到底用还是不用。

你可能感兴趣的:(C++,String,dialog,qt,button,平台)