QT Linguist Tools 中 QT_TR_NOOP和QT_TRANSLATE_NOOP 以及QObject::tr() 的区别

2023年5月9日 3532点热度 1人点赞 0条评论

前言

​ 本篇博文主要针对一下几个问题展开讨论:

  1. QT_TR_NOOP和QT_TRANSLATE_NOOP 以及QObject::tr() 的区别。
  2. 正常加载.qm文件,但无法正常翻译字符。
  3. 没有继承QObject的类如何使用国际化功能

框架结构解析

.ts .qm文件

​ .ts文件本质上是一个xml文件,它保存了以下信息:

  1. 需要翻译的字符串

  2. 字符串的上下文标记(context),一般是类名,也可以自定义

  3. 字符串所在文件,以及在文件中所在的行号

  4. 字符串被翻译之后的文本

    ​ 其中1、2很重要,这里先按下不表。

    ​ .qm文件是将.ts文件中的一些关键信息发布为二进制之后的文件,主要是为了节省空间,这没啥好说的

​ 在代码中需要用QT_TR_NOOP、QT_TRANSLATE_NOOP和QObject::tr()标记需要翻译的字符串文本,这样

Linguist Tools才能收集这些文本的信息生成.ts文件。

QCoreApplication::translate

​ 为了方便理解下面的内容,需要知道QT中有这么一个函数

QString QCoreApplication::translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1)

​ 这个函数用于查找最近安装的.qm文件中的字符串翻译文本并返回。

​ context参数为字符串所在的上下文,说人话就是一个标识,用于标记字符串文本在哪里被使用,通常是类的类名,也可以自定义。

​ sourceText参数是需要翻译的字符串。

​ disambiguation 参数也是一个标记,用于同一个上下文中,出现了多个相同的需要翻译的字符串,用于区分,不过一般没有那么多道道,都是使用默认参数。

​ n 用于与字符串中的%n结合,生成新的字符串,这个与QString中的arg()函数用法类似,可以自行翻阅文档。

​ 从该函数以及.ts文件中可以窥得QT这套框架的一角,可以猜测,QT中通过某种方法将.ts文件中的收集的字符串、翻译文本,类名(context)信息建立了一个可以快速查找的表,在文本需要翻译的时候,调用translate来查询翻译文本的内容,实现多种语言的功能。具体的实现方法请继续往下看。

QT_TR_NOOP、QT_TRANSLATE_NOOP的区别

​ 先来介绍QT_TR_NOOP和QT_TRANSLATE_NOOP,这是两个宏定义,在QT源码中可以找到他们的定义:

#define QT_TR_NOOP(x) x
#define QT_TRANSLATE_NOOP(scope, x) x

​ 可以看到,这两个宏定义其实什么也没有做,直接是标记的字符串本身。

​ 在QT文档中可以看到这两个宏的介绍,QT_TR_NOOP 仅用于标记文本,lupdate通过这些标记来生成.ts文件,这个宏的作用仅止步于此。QT_TRANSLATE_NOOP则是在这个基础上增加了自定义context标记的功能,传入的第一个参数scope会在lupdate收集文本数据时作为context使用。

​ 这两个宏一般用于延迟翻译,即在使用这两个宏标记了要翻译的字符串数据之后,再在真正要使用到翻译文本的地方调用QCoreApplication::translate来实现翻译的功能。

tr函数

​ 在QObject源码中可以找到这样的声明:

static QString QObject::tr(const char *sourceText, const char *disambiguation = nullptr, int n = -1)

​ 这个函数和QCoreApplication::translate类似,只是少了context参数的传入。因为是一个静态函数,如果不是在QObject类的派生类中,可以显示的使用QObject::tr来调用。

​ 但是我们真正在使用的tr函数则不一定是在QObject类中的。

​ 需要先了解Q_OBJECT宏,这个宏熟悉qt的朋友一定知道,它提供了很多qt元编译器需要的内容,这个宏中的内容很多,这里不展开,只说其中一个,就是QT_TR_FUNCTIONS,它的展开是这样的:

    static inline QString tr(const char *s, const char *c = nullptr, int n = -1) \
        { return staticMetaObject.tr(s, c, n); }

​ 也就是说在使用Q_OBJECT宏的时候,已经在类中定义了一个静态的tr函数了,如果在类中直接调用tr,调用到的就是这个函数。

​ staticMetaObject的类型是一个QMetaObject,然而在QT的元编译器生成MOC文件的时候,在QMetaObject对象中是保存了当前类的名称的,说道这里你是不是恍然大悟了?

​ 虽然我并没有在源码里找到QMetaObject::tr的实现,但是可以肯定的是它一定是使用类名调用QCoreApplication::translate来查找翻译文本的内容。

​ tr()函数与QT_TR_NOOP和QT_TRANSLATE_NOOP的区别就在于tr会实时的返回翻译文本的内容,不用在自己去调用QCoreApplication::translate来实现翻译的功能。

解答开篇的问题

QT_TR_NOOP和QT_TRANSLATE_NOOP 以及QObject::tr() 的区别?

​ 三者皆可用于标记字符串文本,然后生成.ts文件,但是QT_TR_NOOP和QT_TRANSLATE_NOOP只有标记的功能,不能实现翻译内容。tr()函数可以实时的返回翻译文本的字符串实现翻译功能。

正常加载.qm文件,但无法正常翻译字符

    1. 没有继承QObject 类

        2. 没有使用Q_OBJECT 宏,在tr()函数的介绍中提到的问题,要实现翻译要依赖于Q_OBJECT宏中声明的tr函数,并且需要用到元编译器生成的类名,如果context无法正确的传递,就无法返回正确的翻译文本内容。

没有继承QObject的类如何使用国际化功能

​ 这种情况需要使用延迟翻译,即使用QT_TR_NOOP和QT_TRANSLATE_NOOP标记文本后,在真正使用文本的地方调用QCoreApplication::translate来实现翻译,官方给出的示例代码:

QT_TRANSLATE_NOOP示例

static const char *greeting_strings[] = {
    QT_TRANSLATE_NOOP("FriendlyConversation", "Hello"),
    QT_TRANSLATE_NOOP("FriendlyConversation", "Goodbye")
};

QString FriendlyConversation::greeting(int type)
{
    return tr(greeting_strings[type]);
}

QString global_greeting(int type)
{
    return qApp->translate("FriendlyConversation",
                           greeting_strings[type]);
}

QT_TR_NOOP示例

QString FriendlyConversation::greeting(int type)
{
    static const char *greeting_strings[] = {
        QT_TR_NOOP("Hello"),
        QT_TR_NOOP("Goodbye")
    };
    return tr(greeting_strings[type]);
}

​ 在QT_TRANSLATE_NOOP示例中使用QCoreApplication::translate来实现对内容的翻译,这种使用方法就不需要对QObject继承,但是如果每次都按照官方示例这么去写是要折磨死人的。因为QT_TRANSLATE_NOOP只是做文本标记的功能,所以可以用这一点,修改QT_TRANSLATE_NOOP的宏定义,来实现实时翻译的功能。

#define QT_TRANSLATE_NOOP(scope, x) QCoreApplication::translate(scope,x)

​ 在使用QT_TRANSLATE_NOOP之前将宏定义修改为上述代码,就可以在代码中像使用tr函数一样了,得到的效果也是一样的。

​ 如果想使用QT_TR_NOOP实现这样的功能会更麻烦,因为它默认使用类名来作为translate函数的context参数,在宏定义中暂时没有办法或许到准确的类名,所以只能手动去调用translate函数。

关于这个问题,再扩展一种情况,就是当继承了QObject,但是不想使用Q_OBJECT宏的时候,想实现多语言翻译的功能

​ 对于这种情况下,QT提供了Q_DECLARE_TR_FUNCTIONS宏,它的定义是

#define Q_DECLARE_TR_FUNCTIONS(context) \
public: \
    static inline QString tr(const char *sourceText, const char *disambiguation = Q_NULLPTR, int n = -1) \
        { return QCoreApplication::translate(#context, sourceText, disambiguation, n); } \
    QT_DECLARE_DEPRECATED_TR_FUNCTIONS(context) \

​ 可以看到这个宏为类添加了一个tr函数,tr函数的实现就是去利用传入的类名,去调用了translate函数。不想使用Q_OBJECT宏时可以使用这个宏,一样可以实现使用tr函数来翻译文本的功能。

大脸猫

这个人虽然很勤快,但什么也没有留下!

文章评论