Qt文字编码

时间:2023-03-08 22:08:30
Qt文字编码

(internationalization and localization)

旨在使应用程序适用于不同的语言, 不同的区域文化, 不同目标市场的技术需求。

internationalization 是指程序可以在不改变设计的前提下适用于多种语言和地区; localization 是指让针对全球的程序通过添加特定的区域化组件(如日期、时间和数字格式)和翻译文字来应用于特定的地区。

internationalization

在一些案例中国际化可能是非常简单的, 比如把一个 美语版的程序翻译为澳大利亚版本, 可能只需要一些拼写上的改动即可。 但是如果把它翻译为日本版, 或者把一个韩文应用翻译为德文版,不仅仅需要让程序改变语言, 还需要使用不同的输入技术, 字符编码, 以及外观的改变。Qt 尽量减轻开发者的国际化工作。 几乎所有语言都提供了内建的输入组件和文字绘制方法。 内建的字体引擎可以在不同的书写系统中渲染出正确的、漂亮的文字。

Qt 支持的语言可以参考 Qt Mannual-Internationalization with Qt 所述。

很多语言都有自己的特性, 例如:

  • 换行规则:一些亚洲文字的词素之间没有空格。换行可能发生于任意一个字符之后(特殊情况下) 如中文,日文, 韩文; 也有可能发生于一个逻辑词素界限之后, 如泰语。

  • 双向书写: 阿拉伯语和希伯来语自右至左书写,而文字中出现的英文、数字等却还是自左至右。

  • 标记: 非空格标记(词素结束)和额外的标记(部分欧洲语系中有音标等) 一些语系中广泛使用这些标记, 而且有时候一个词素中可能有多个标记以标明发音。

  • 连字: 在一些环境中, 几个字符会按照一定的规则连到一起。 比如 美语中的 fi 和 fl。

通常情况下, Qt 已经针对这些特性做了处理, 并不需要在使用或者继承一些输入控件时自己处理。对语言的支持对于开发者来说是透明的。 它被封装于 Qt Text Engine 中。也就是说在编写某种语系的应用程序时, 你没必要了解这门语言的规则。

当然, 也有例外。QPainter::drawText(int x, int y, const QString &str) 从左往右绘制字符串, 这在阿拉伯语和希伯来语中是错误的。 可以使用 drawText 的 QRect版本, 指定文字方向。

如果你需要编写自己的输入控件, 使用 QTextLayout。 在印度语系中, 字符图元的大小会随着上下文变化, 这在 QTextLayout 中是支持的。

QTextCodec

Qt 使用 Unicode 存储、绘制、操作字符串, QTextCodec 能够用于unicode 和其他编码转换。

QTextCodec能够支持用户输入/输出数据的编码。 当程序启动时, 系统的本地语言系统会决定如何为内部的8位用户数据编码。 而QTextCodec::codecForLocale() 能够返回一个可以使本地语言系统和unicode互相转换的类。而 codeForName 则根据指定编码生成一个和unicode 互相转换的类。

QString string = ...; // some Unicode text
QTextCodec *codec =QTextCodec::codecForName("ISO 8859-5");
QByteArray encodedString = codec->fromUnicode(string);

如果要操作不同编码的文件, 在 QTextSteam 中使用 setCodec

在处理大块文字的编码转换时, 需要小心。 比如网络传输时, 多字节字符可能会被拆成2块, 这在最好的情形下可能仅仅是文字丢失, 但是在最坏的情况下, 可能使整个转换工作的失败。 处理这个问题的办法是使用 QTextDecoder

QTextCodec*codec =QTextCodec::codecForName("Shift-JIS");
QTextDecoder*decoder = codec->makeDecoder();
QString string;
while (new_data_available()) {
  QByteArray chunk = get_new_data();
  string += decoder->toUnicode(chunk);
}
delete decoder;

QTextDecoder 能够记录当前块的状态,因此可以很好的解决这个问题。

也可以通过继承 QTextCodec 定义新的编码。纯虚函数用于描述解码器和编码器。 它们可用于不同的文本文件格式(QTextSteam), x11系统, 和本地化的输入输出。Qt Mannual 中列举出了需要重新实现的几个方法。

Qt Linguist

在国际化应用程序中, 许多在应用程序中出现的文字比如窗口标题, 菜单项, 工具提示, pushbutton,radiobutton checkbutton的文字, 无论是一个单词, 还是短语, 都需要被翻译。

开发者需要在源程序中使用简单的声明来表示这些文字是需要被翻译的。 Qt tools 提供了每一条短语的信息来帮助开发者、翻译者协作完成翻译。

Release Manager 能够根据源代码文件生成一系列翻译文件并传递给翻译者。 翻译者 使用 Qt Linguist 打开这些文件, 编辑, 并保存, 然后返回给 Release Manager。 然后 Release Manager 生成能够被应用程序使用的较快的版本。

这个工具在程序更新时能够重复使用, 保留已有翻译并很容易添加新的翻译规则。Qt Linguist 提供了能够在不同程序之间保证翻译的一致性的语法书。

由于人类语言的微妙与复杂, 开发者和翻译者必须添加一系列的规则。

一个词素根据上下文不同可能会有多种翻译, 比如 open 可以翻译为 打开, 打开文件,打开网络连接, open file等等。

快捷键可能会发生变化, 比如 &Quit 在挪威语中是 Avslutt , 它没有 Q; 定义快捷键时必须注意不要使用已经被使用了的字母, 除非你修改了多个快捷键。

短语中包含变量, 需要在运行时替换为数字时, 需要重新加载这个短语。 因为数字的顺序问题。

这些规则在 Qt Linguist 中能够很简便的得到解决。

Qt Linguist 能够导入和导出 XML Localization Interchange File Format (XLIFF) 文件。 Translator 中有描述。

Release Manager

Release Manager 里有两个工具: lupdate, lrelease。

在 pro 中添加 TRANSLATION 变量, 然后使用 lupdate 生成 ts 文件, 在 Qt Linguist 中编辑以后, 再使用 lrelease 命令去生成qm 文件。

Translation 文件提供了所有用户可见的文字、Ctrl快捷键, 以及它们的译文。

这些基本近于设计者的工作范围, 并且可以在 Qt Mannual - translator 一章中找到。

developer

使用Qt创建一个可以在运行时切换语言的应用是可能的, 但是需要开发者比较多的工作, 并会带来一定的性能代价。

Qt 为每个 Window 创建了一个一个语法书, 以降低翻译所带来的性能代价。翻译工作只会在窗口的初始化时占用系统资源。 因此对于那些在应用中只会创建一次的 MainWindow, 以及仅仅在需要时才会弹出的 Dialog, Qt Linguist 都是最好的选择。 但是对于那些会经常存在 创建和和销毁动作的类, 可能会存在性能问题。

一个典型的多语言应用的 pro 文件需要加上 TRANSLATIONS 选项。

TRANSLATIONS = arrowpad_fr.ts \
               arrowpad_nl.ts

在自定义的 ts 文件名中加上 _languagename 可以在运行期确定使用哪一个文件(有待验证)。

Lupdate 读取 pro 文件指定的 头文件和源文件, 以确定需要被翻译的文字。 这也意味着 只有在 SOURCES HEADERS 变量中包含的文件才能被翻译。但是这个导致的问题是, qml 和 js 无法被翻译。 解决办法是, 采用块语句

lupdate_only {
SOURCES += main.qml \
          MainPage.qml
}

虽然, Qt为国际化提供了很方便的翻译机制, 但是开发者还是要注意以下几项

  • 在应用程序中查找和加载合适的翻译文件

  • 把用户可见的文字,以及 Ctrl 快捷键标注为翻译对象

  • 为要翻译的文字提供上下文(context 属性)

  • 消除文字二义性

  • 使用 QString 的 %n 替代某些参数, 以实现运行时翻译

  • 注意数字、日期、时间和货币的国际化

  • Mark data text strings outside functions translatable.

翻译文件的内容会包括 类型名, 以及它在文件中出现的位置, 这样可以消除二义。除此之外, 开发者可能还需要添加一些其它信息。

Qt 提供了一些 qm 文件用于翻译 Qt 类(Qt 派生类中的 文字需要自己编写 qm文件), 在Qt4中,每一个 locale 对应一个 qm 文件; 在 Qt5中, qm文件根据 module 区分。 如 对于德国地区, qtscript 模块 有 qtscript_de.qm, qtbase 有 qtbase_de.qm , assistant 有 assistant_de.qm等等。 当然, 这只是 Qt 的一个愿景,因为在一个特定的 application中, 并不需要对所有模块的翻译。 不过在Qt5.50 中, 中文仍然是 qt_zh_CN.qm.

可以使用 lconvert 命令, 根据多个模块的翻译文件生成一个。

例如

lconvert -o installation_folder/qt_de.qm qtbase_de.qm qtdeclarative_de.qm

一些 例子

Hello tr()

Arrow Pad

Troll Print

Internationalization

Text Id

在一个应用程序中只能使用 纯文本, 或者 text id 函数。这个机制可以保证一个单词在不同上下文中被正确翻译成不同的意思。可以参看 Qt Mannual - qstr qstrid

QLocale

QLocale 支持 default locale 的概念。 默认的Locale 是在应用程序启动时刻加载的系统Locale信息。Static 成员函数 setDefault 可以改变默认的 locale。

QLocale 的构造函数中包括一个 language/country 对, 每当一个language/country 对确定时, Qt 会进行如下操作

  • 如果 它 能在数据库中被找到, 则使用它;
  • 如果这个language没有找到,但是 country 找到了, 或者country 是 AnyCountry, 则language 会使用现有的、与这个country 相关度最大的语言。
  • 如果language 和 country 都没有被找到, 则系统会使用 default locale。
  • 可使用 language() country()函数查看实际的语言 国家信息。

改变default locale 有以下几个影响:

  • 如果 QLocale 实例使用 默认构造函数生成, 它会使用 default locale
  • QString::toInit, QString::toDouble 使用default locale 翻译文字, 如果失败, 他返回 “C” locale 的翻译结果。
  • QString::arg , 如果它的位置标识符里有 ‘L’, 例如 ‘%L1’时, 它使用 default locale 格式化数字。

一个更简单的方法是使用 locale-name 来定义 QLocale

在代码中处理翻译工作

  • Qt Quick

    在 QML 代码中, 用qsTr(),qsTranslate(), qsTrId(),QT_TR_NOOP(), QT_TRANSLATE_NOOP(), 和 QT_TRID_NOOP() 标记的字符串可以被翻译。

    添加自定义上下文信息:

    Text {

    id: txt1;

    // This user interface string is only used here

    //: The back of the object, not the front

    //~ Context Not related to back-stepping

    text: qsTr("Back");

    }

在上文中, //: 以后的文字用于翻译。 //~ 以后的文字是可选项; 注意第一个词语是一个标识符(未验证)。

消除歧义: qstr 的第二个参数作为一个 id, 可以保证相同的源码在不同的语境中能被正确的翻译为不同的意思。

Text {
    id: txt1;
    // This user interface string is used only here
    //: The back of the object, not the front
    //~ Context Not related to back-stepping
    text: qsTr("Back","not front");
}

使用使用 %n 在string中插入参数: 因为不同语言对于词素的放置顺序不同, 所以把字符串硬编码可能不正确。

使用 %Ln 在string中插入数字: 对于数字而言, 不同的书写系统可能有不同的展示。 因此把它作为参数传入 qstr 中, 可以实现本地化的展示。

Text {
    text: qsTr("%L1").arg(total)
}

货币、日期、时间的本地化: 没有像上述的简单方法转换货币等符号。需要使用系统的 Locale 。

Text {
    text: qsTr("Date %1").arg(Date().toLocaleString(Qt.locale()))
}

使用QT_TR_NOOP() 翻译字符串文本数据:如果系统的 locale 信息发生了变化, 但是没有重启, 那么 array、list、model中的string数据可能不会自动更新。 QT_TR_NOOP() 使每次使用这些数据填充展示类(widgets等)时,都要显式的去查找文本数据的翻译文字。例如

ListModel {
    id: myListModel;
    ListElement {
        //: Capital city of Finland
        name: QT_TR_NOOP("Helsinki");
        }
    }
...
Text {
    text: qsTr(myListModel.get(0).name); // get the translation of the name property in element 0
    }

使用 Locale 信息扩展应用: 如果需要指定不同 Locale 下的特性, 可以如下使用 Qt.Locale()

Component.onCompleted: {
    switch (Qt.locale().name.substring(0,2)) {
        case"en":   // show the English-language icon
            languageIcon ="../images/language-icon_en.png";
            break;
        case"fi":   // show the Finnish language icon
            languageIcon ="../images/language-icon_fi.png";
            break;
        default:     // show a default language icon
            languageIcon ="../images/language-icon_default.png";
    }
}

Qt 和 QML 使用相同的Locale 信息, 使用相同的翻译工具(lupdate, lrelease, linguist等)。 可以在一个应用中同时在c++ 和 QML 中使用翻译机制, lupdate 能够在同一个 ts 文件中生成翻译信息。

Lupdate 会遍历 SOURCES 和 HEADERS 变量中的所有文件。 因此如果有需要翻译的 QML 或者 js 文件, 需要使用 lupdate_only 做特殊处理, 这样可以通知 qmake 忽略它们。如下

lupdate_only{
SOURCES = main.qml \
          MainPage.qml
}

也可以使用通配符声明, 但是注意这里的查找不是递归的, 因此需要指明所有的目录。

lupdate_only{
SOURCES =*.qml \
          *.js \
          content/*.qml \
          content/*.js
}

Qt C++

  • 使用 QString: QString 使用 Unicode 编码。因此所有语言都可以使用QString; 少数情况下必须使用 char* 或者 QByteArray类型, 比如 QObject name, 以及 file format 信息;如果在程序中需要使用 其它编码, 可以使用 QTextCodec 做相应转换。

  • 使用 tr : 如果程序中有需要展示给客户的文字, 应该使用 QCoreApplication::translate 函数。 它的简化版是 tr(在QObject 的子类中使用)。 是否使用tr 也可以把程序中是否需要翻译的文字区分开。

  • 对于自定义类, 如果是 QObject 的子类, 使用 Q_OBJECT 宏可以使能翻译; 如果不是 QObject 的子类, 可以使用 Q_DECLARE_TR_FUNCTIONS(MyClass) 宏, 使这个可以使用tr 翻译文字。

对于作为文本数据提供的字符串, 可以使用QT_TR_NOOP 标记, 然后使用 tr 引用; 或者使用 QT_TRANSLATE_NOOP 标记, 然后使用 tr 或者 使用QCoreApplication::translate 函数引用

标准C++

类似"abc汉字"这样的字符串是以utf-8编码。

C++ 的 cout执行的操作是把参数发送给stdout,因此如果终端支持utf-8, 汉字可以使用cout打印。

比较好的办法是使用unicode, 例如, wchar_t wc = L"abc汉字", 字符串前面加 L, 显式声明unicode, 然后使用

    wcout.imbue(locale("chs"));
    wcout\<\<s2\<\<endl;

可以对unicode 和 utf-8 进行转码, ascii 提供了

    wint_t btowc (int c);
    int wctob (wint_t wc);

微软提供了

    int MultiByteToWideChar(
    UINT CodePage,
    DWORD dwFlags,
    LPCSTR lpMultiByteStr,
    int cchMultiByte,
    LPWSTR lpWideCharStr,
    int cchWideChar
    );

    int WideCharToMultiByte(
    UINT CodePage, //指定执行转换的代码页
    DWORD dwFlags, //允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
    LPCWSTR lpWideCharStr, //指定要转换为宽字节字符串的缓冲区
    int cchWideChar, //指定由参数lpWideCharStr指向的缓冲区的字符个数
    LPSTR lpMultiByteStr, //指向接收被转换字符串的缓冲区
    int cchMultiByte, //指定由参数lpMultiByteStr指向的缓冲区最大值
    LPCSTR lpDefaultChar, //遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符
    LPBOOL pfUsedDefaultChar //至少有一个字符不能转换为其多字节形式,函数就会把这个变量设为TRUE
    );

Qt Localization

internationalization 表示在不做编码工作的前提下, 让软件运行于不同的语言环境之中;Localization 表示添加区域化组件(如时间、数字格式等), 以及该区域的文字翻译组件, 以实现internationalization 在该区域的实现。

未完成

附录

Qt Linguist 相关类

类名 作用
QCollator 根据比较算法比较字符串
QCollatorSortKey 用于提高字符串比较速度
QLocale 用于转换数字、日期等的字符串格式。
QTextCodec 不同编码间转换
QTextDecoder 字符串状态相关的编码转换-解码
QTextEncoder 字符串状态相关的编码转换-编码
QTranslator 字符串输出的国际化的支持