Qml组件化编程4-i18n动态国际化



  • 简介

    本文是《Qml组件化编程》系列文章的第四篇,涛哥将教大家,如何在Qml中实现动态国际化。

    i18n 是 internationalization(国际化) 的首尾字符加中间的 18 个字符。随着产品越做越大,要推向国际的时候,国际化这一步

    是必不可少的。i18n 的方案有很多,这里只讨论在Qt/Qml中的方案。

    文章主要发布在涛哥的博客知乎专栏-涛哥的Qt进阶之路

    效果预览

    看一下最终效果

    预览

    目前支持的语言包括:

    • 中文
    • 英文
    • 日文
    • 韩文
    • 法文
    • 俄文
    • 德文
    • 西班牙文
    • 葡萄牙文
    • 意大利文
    • 越南文
    • 阿拉伯文

    (其中阿拉伯文是从右往左写的,qml默认的处理看起来不完全正确。

    涛哥对阿拉伯也不是很熟,就不深入研究了。)

    源码中输出中文

    先说一下源码中输出中文的问题。

    在文件本身的编码是utf-8的前提下,以下三种方式都可以直接输出中文。

        qInfo() << u8"山有木兮木有枝,心悦君兮君不知。";
    
        qInfo() << QStringLiteral("黄河远上白云间,一片孤城万仞山。");
    
        qInfo() << QString::fromLocal8Bit("人生若只如初见,何事秋风悲画扇。");
    

    u8是c++11标准支持的字符串字面量写法,可以参考https://zh.cppreference.com/w/cpp/language/string_literal

    QStringLiteral是Qt特有的宏,用来在编译期生成字符串字面量,效果和u8类似。

    QString::fromLocal8Bit可以在运行过程中,动态处理中文字符串。

    (题外话:有一部分人给涛哥提过 “源码中不该写中文,应该全部做进翻译文件”的思想,但是涛哥在实践中发现这个思想并不能很好地执行。

    一方面并没有编译器之类的约束,另一方面也不方便。

    涛哥这里提出反问:文件本身是utf8的,utf8就是支持中文的,凭啥就不能写中文了?再者说,我翻译的目标语言正确了,翻译源文是什么语言又有什么关系呢?

    俄罗斯人代码写的挺好的,github上就有不少带俄文注释的代码)

    Qt本身的国际化

    Qt的国际化, 相关文章太多了,Qt帮助文档也有,涛哥这里列出重点

    1. C++代码中的字符串使用QObject::tr()包起来,类本身是QObject的子类时可以省略作用域“QObject::”,直接写tr
    2. qml代码中使用qsTr把字符串包起来
    3. pro文件中添加一句TRANSLATIONS += trans_zh.qs ,这个名字起什么无所谓,关键是‘_zh’要有。
    4. 调用lrelease工具,扫描项目并生成trans_zh.qs 文件。这个文件是xml格式的,未经过翻译的,需要为这个文件做一些翻译工作。
      后面说怎么做翻译。
    5. 翻译做好后,调用lupdate工具,生成trans_zh.qm文件。这个文件就是把xml变成了二进制。
    6. 将qm文件放在运行路径,或者资源文件里。
    7. 切换语言时, Qt/C++代码中使用QTranslater加载qm文件,QCoreApplication卸载旧的QTranslater,并安装新的QTranslater。调用
      QmlEngine::retranslate函数

    在5.10以前的版本,Qt是不能直接动态切换语言的,要么重新启动程序,要么把所有的text都set一遍,retranslate是5.10才有的接口。

    涛哥这次的方案,以retranslate为主,5.10以前还有各路大神的动态切换语言方案,不在本次文章的讨论范围。

    翻译工作

    Qt提供了可视化的工具,即QTDIR/bin路径下的linguist.exe,可以直接打开我们拿到的trans_zh.qs文件。这个工具可以由程序员之外不懂xml的专业翻译人士使用。

    涛哥在这里就使用自动化的翻译工具来做这个事情,比如使用 百度翻译API 或 有道翻译API。

    为此,涛哥使用go语言写了一个小工具,能接入百度和有道的翻译API,并且能读取Qt的qs文件、完成翻译后再写回qs文件。

    小工具已经开源 Qt-Transer

    当然这种小工具Qt也能做,涛哥使用go纯属兴趣。

    实现动态翻译

    加载翻译文件

    需要在Qt/C++代码中实现,并提供给qml调用。这里以中文和英文为例,涛哥直接写在了自定义QQuivkView的代码里。

    //TaoView.h
    
    class TaoView : public QQuickView
    {
        Q_OBJECT
    public:
        explicit TaoView(QWindow *parent = nullptr);
        Q_INVOKABLE void reTrans(const QString &lang);  //这是通过invokable导出的函数
    
    public slots:
    private:
        QTranslator m_enTrans;      //英文的翻译
        QTranslator m_zhTrans;      //中文的翻译
        QString m_lang;             //记录当前语言
    };
    
    
    // TaoView.cpp
    #include "stdafx.h"
    
    #include "TaoView.h"
    
    #include <QTranslator>
    #include <QQmlEngine>
    TaoView::TaoView(QWindow *parent) : QQuickView(parent)
    {
        setFlag(Qt::FramelessWindowHint);
        setResizeMode(SizeRootObjectToView);
        setColor(QColor(Qt::transparent));
        
        //构造函数直接加载翻译文件
    
        bool ok = m_enTrans.load("trans_en.qm");
        bool ok2 = m_zhTrans.load("trans_zh.qm");
        qWarning() << ok << ok2;
    
        //默认安装中文
        QCoreApplication::installTranslator(&m_zhTrans);
        m_lang = QStringLiteral("中文简体");
    }
    
    void TaoView::reTrans(const QString &lang)
    {
        if (m_lang == lang)
        {
            return;
        }
        //切换语言
        m_lang = lang;
        if ( lang == QStringLiteral("中文简体"))
        {
            QCoreApplication::removeTranslator(&m_enTrans);
            QCoreApplication::installTranslator(&m_zhTrans);
            engine()->retranslate();
        } else if (lang == "English") {
            QCoreApplication::removeTranslator(&m_zhTrans);
            QCoreApplication::installTranslator(&m_enTrans);
            engine()->retranslate();
        }
    }
    
    

    Qml中切换语言

    和文章3 Qml组件化编程3-动态切换皮肤 类似, 在标题栏按钮中做一个切换按钮

    // TitleBar.qml
    TImageBtn {
        width: 20
        height: 20
        anchors.verticalCenter: parent.verticalCenter
        imageUrl: containsMouse ? "qrc:/Image/Window/lang_white.png" : "qrc:/Image/Window/lang_gray.png"
        onClicked: {
            pop.show()
        }
        TPopup {
            id: pop
            barColor: gConfig.reserverColor
            backgroundWidth: 100
            backgroundHeight: 80
            contentItem: ListView {
                id: langListView
                anchors.fill: parent
                anchors.margins: 2
                model: ["中文简体", "English"]      //语言列表,这里不需要qsTr
                delegate: TTextBtn {
                    width: langListView.width
                    height: 36
                    text: modelData
                    color: containsMouse ? "lightgray" : pop.barColor
                    onClicked: {
                        pop.hide()
                        //调用Q_INVOKABLE导出的函数,reTrans
                        view.reTrans(modelData)
                    }
                }
            }
        }
    }
    
    

    多国语言版本

    前面的代码只有中文和英文,已经可以说明问题了。

    这里再把最终版本的多国语言的贴出来。

    这是头文件

    //TaoView.h
    #pragma once
    
    #include <QQuickView>
    #include <memory>
    
    class TaoView : public QQuickView
    {
        Q_OBJECT
        Q_PROPERTY(QStringList languageList READ languageList NOTIFY languageListChanged)
    public:
        explicit TaoView(QWindow *parent = nullptr);
        Q_INVOKABLE void reTrans(const QString &lang);
        const QStringList &languageList() const
        {
            return m_languageList;
        }
    
    signals:
        void reTransed();
        void languageListChanged();
    
    private:
        QString m_lang;
        QMap<QString, std::shared_ptr<QTranslator>> m_transMap;
        QTranslator *m_pLastLang = nullptr;
        QStringList m_languageList;
    };
    

    这是cpp

    //TaoView.cpp
    #include "stdafx.h"
    
    #include "TaoView.h"
    
    #include <QTranslator>
    #include <QQmlEngine>
    TaoView::TaoView(QWindow *parent) : QQuickView(parent)
    {
        setFlag(Qt::FramelessWindowHint);
        setResizeMode(SizeRootObjectToView);
        setColor(QColor(Qt::transparent));
    
        m_languageList << u8"中文简体"
                       << u8"English"
                       << u8"日本語"
                       << u8"한국어"
                       << u8"Français"
                       << u8"Español"
                       << u8"Portugués"
                       << u8"In Italiano"
                       << u8"русский язык"
                       << u8"Tiếng Việt"
                       << u8"Deutsch"
                       << u8" عربي ، ";
        QStringList fileList;
        fileList << "trans_zh.qm"
                << "trans_en.qm"
                << "trans_ja.qm"
                << "trans_ko.qm"
                << "trans_fr.qm"
                << "trans_es.qm"
                << "trans_pt.qm"
                << "trans_it.qm"
                << "trans_ru.qm"
                << "trans_vi.qm"
                << "trans_de.qm"
                << "trans_ar.qm";
    
        for (auto i = 0; i < m_languageList.length(); ++i)
        {
            auto trans = std::make_shared<QTranslator>();
            bool ok = trans->load(fileList.at(i));
            qWarning() << m_languageList.at(i) << fileList.at(i) << ok;
            m_transMap[m_languageList.at(i)] = trans;
        }
        m_pLastLang = m_transMap[m_languageList.at(0)].get();
        QCoreApplication::installTranslator(m_pLastLang);
        m_lang = m_languageList.at(0);
        emit languageListChanged();
    }
    
    void TaoView::reTrans(const QString &lang)
    {
        if (m_lang == lang)
        {
            return;
        }
        m_lang = lang;
    
        QCoreApplication::removeTranslator(m_pLastLang);
        m_pLastLang = m_transMap[lang].get();
        QCoreApplication::installTranslator(m_pLastLang);
        engine()->retranslate();
        emit reTransed();
    }
    

    还有qml

        ....
            TImageBtn {
                width: 20
                height: 20
                anchors.verticalCenter: parent.verticalCenter
                imageUrl: containsMouse ? "qrc:/Image/Window/lang_white.png" : "qrc:/Image/Window/lang_gray.png"
                onClicked: {
                    pop.show()
                }
                TPopup {
                    id: pop
                    barColor: gConfig.reserverColor
                    backgroundWidth: 100
                    backgroundHeight: 400
                    contentItem: ListView {
                        id: langListView
                        anchors.fill: parent
                        anchors.margins: 2
                        model: view.languageList        //这里换成了list属性
                        delegate: TTextBtn {
                            width: langListView.width
                            height: 36
                            text: modelData
                            color: containsMouse ? "lightgray" : pop.barColor
                            onClicked: {
                                pop.hide()
                                view.reTrans(modelData)
                            }
                        }
                    }
                }
            }
        ....
    

    转载声明

    文章出自涛哥的博客 -- 点击这里查看涛哥的博客
    文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作 © 涛哥

    联系方式


    作者 涛哥
    开发理念 弘扬鲁班文化,传承工匠精神
    博客 https://jaredtao.github.io
    知乎 https://www.zhihu.com/people/wentao-jia
    邮箱 jared2020@163.com
    微信 xsd2410421
    QQ 759378563

    请放心联系我,乐于提供咨询服务,也可洽谈商务合作相关事宜。

    打赏

    weixin
    zhifubao


    如果觉得涛哥写的还不错,还请为涛哥打个赏,您的赞赏是涛哥持续创作的源泉。



Log in to reply
 

走马观花

最近的回复

  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more
  • 843143141.jpg
    闲下来了,我又开始大规模地学习了。
    最近开始学习内存模型和无锁结构。因为这个是和操作系统密切相关的,懂得这些对于编写C++服务端应用程序
    有着非常好的帮助。之前我对内存模型以及无锁结构几乎没有什么了解,我就询问群里的大佬看看有没有可以参考的资料。
    大佬很高兴,并且推荐了我一本名为《Memory Model》的电子书。这本电子书虽然页数不多,但是从起源到发展,
    从源码到汇编,都给我们详细地介绍了。看了一遍,不是非常理解,但是依然尝试将自己的理解写下来,以便日后翻阅。
    首先因为多核处理器成为主流,多线程的程序已经非常常见,因此我们不可避免地要处理多线程程序的同步问题。
    然后,因为编译器默认都对源码进行了优化,在单核处理器中这通常不是什么问题,但是在多核处理器中,就会因为编译器
    对其进行了乱序处理而导致程序出现问题。由此深入地探讨内存模型。
    内存模型主要分为:
    载-载 顺序(load-load order)
    载-存 顺序(load-store order)
    存-载 顺序(store-load order)
    存-存 顺序(store-store order)
    依赖载入顺序(dependent loads order)

    通过内存栅栏(memory barrier)能够避免编译器对指令的乱序。Linux中有

    READ_ONCE( x, value ) WRITE_ONCE( x )

    避免这些读写被编译器乱序或者是优化掉。

    这里谈到volatile关键字。在另外一篇博客上说,volatile具有“易变性、不可优化性、顺序性”。简单说,由于
    被volatile声明的变量,指令须从内存读取,并且不能被编译器乱序以及优化。在Java(语言扩展)和MSVC(系统兼容)上,
    还附带了Accquire()和Release()语义,因此可部分用于多线程环境。但多数情况下,还是慎用volatile,
    因为不同架构的处理器,它的内存模型是千变万化的,不能一而概之。

    至于C++11,它提供了std::atomic<T>这个模板类,相当于提供了很多方式来实现不同内存模型的原子操作。
    它的load()和store()方法,第二个参数有以下几个选项:

    std::memory_order_relaxed std::memory_order_seq_cst std::memory_order_acq_rel std::memory_order_acquire std::memory_order_release std::memory_order_consume

    我们最常用来实现RCpc(Release Consistency、Processor Consistency)是使用

    std::memory_order_acquire std::memory_order_release

    这两对。

    作为例子,在实现自旋锁时使用std::atomic<T>是这样的:

    struct SpinLock2 { void lock( ) { for ( ; ; ) { while ( lock_.load( std::memory_order_relaxed ) ); if ( !lock_.exchange( true, std::memory_order_acquire ) ) break; } } void unlock( ) { lock_.store( false, std::memory_order_release ); } std::atomic<bool> lock_ = { false }; };

    read more

关注我们

微博
QQ群