Qt将翻译后的文本转换为英语

时间:2015-07-24 14:37:38

标签: qt

我的Qt应用程序支持动态翻译IE,用户可以在应用程序运行时更改语言

现在我需要找到翻译过的字符串的英文等价物,似乎无法找到方法

例如 给定QString s = tr(“Hello”); 我需要能够在翻译完成后从s获得“Hello”。

之前是否有人这样做过,或者对如何(如果)实现这一点有任何想法

由于

5 个答案:

答案 0 :(得分:1)

无法从Qt中的翻译字符串中获取原始字符串。最后,实际的翻译是由QTranslator类完成的,它不会公开反向查找功能,甚至不作为私有API。

您需要更改代码以避免需要反向字符串查找。一种方法是始终将英文字符串或其他标识符存储在您需要的位置。

这实际上是使用QAction的一种常见情况,这就是QAction提供在QAction::setData()中与翻译后的字符串一起存储任意数据的原因。

答案 1 :(得分:1)

在我的应用程序中,我需要将翻译后的消息写入UI,并将原始消息写入程序日志文件。

我的解决方案是创建可以翻译和原始数据都可以使用的包装器类。

class TS {
public:
    TS(const char* str) {
        init(str);
    }

    TS(const QString& str) {
        init(str.toStdString().c_str());
    }

    TS& arg(const QString& arg1, const bool translate = true) {
        this->orig = this->orig.arg(arg1);
        if (translate) {
            this->tran = this->tran.arg(qApp->translate("", arg1.toStdString().c_str()));
        } else {
            this->tran = this->tran.arg(arg1);
        }
        return *this;
    }

    TS& arg(const int arg1) {
        this->orig = this->orig.arg(arg1);
        this->tran = this->tran.arg(arg1);
        return *this;
    }

    inline const QString& getOrig() const {
        return this->orig;
    }

    inline const QString& getTran() const {
        return this->tran;
    }

private:
    inline void init(const char* str) {
        this->orig = str;
        this->tran = qApp->translate("", str);
    }

private:
    QString orig;
    QString tran;
};

用法:

void info(const TS& rsMsg);

...

m_rLog.info(QT_TRANSLATE_NOOP("", "Plain Text"));
m_rLog.info(TS(QT_TRANSLATE_NOOP("", "Text with argument : %1")).arg( 123 ));

答案 2 :(得分:0)

基于Mykhailo Bryzhaks的回答,我稍微采用了该类,因此我们可以在.ts文件中具有上下文,这对于大型解决方案将使事情变得容易得多

#ifndef TrString_h__
#define TrString_h__

#include <QString>
#include <QRegularExpression>
#include <cassert>

class QObject;

namespace
{
    static QRegularExpression s_findClassNameRegExp("(?<=\\bclass\\s)(\\w+)");
    static QRegularExpression s_findQuotesRegExp("\"([^\"\\\\]|\\\\.)*\"");
}

#define TRStringVoid(QT_TRANSLATE_NOOP_STRING) TrString<void>(#QT_TRANSLATE_NOOP_STRING)
#define TRString(QT_TR_NOOP_STRING) TrString<decltype(this)>(this, #QT_TR_NOOP_STRING)

// Use      //: MyComment
// one line before the call to add a comment for the translator

namespace
{
    const QString kQT_TR_NOOP_Macro = "QT_TR_NOOP(";
    const QString kQT_TRANSLATE_NOOP_Macro = "QT_TRANSLATE_NOOP(";
    const QString kQT_TRANSLATE_NOOP_3_Macro = "QT_TRANSLATE_NOOP3(";
}

// Use TRString or TRStringVoid macro to get an instance of this class
template <class T> class TrString
{
    enum class MacroType { Unknown, QT_TR_NOOP, QT_TRANSLATE_NOOP, QT_TRANSLATE_NOOP3 };

public:
    // used for global functions where T is void
    template <typename SfinaeT = void, typename SfinaeT2 = std::enable_if_t<std::is_void<T>::value, T>>
    TrString(const char* lineString)
        : m_native(lineString)
    {
        m_macroType = getLineStringType(lineString);

        assert(("You must use TRStringVoid(QT_TRANSLATE_NOOP('Context','StringToTranslate')) here because you are not in a class context.",
            (m_macroType == MacroType::QT_TRANSLATE_NOOP || m_macroType == MacroType::QT_TRANSLATE_NOOP3)));

        initStrings();
    }

    // used for calls from within objects
    template <typename SfinaeT = void, typename SfinaeT2 = std::enable_if_t<!std::is_void<T>::value, T>>
    TrString(SfinaeT2 caller, const char* lineString)
        : m_native(lineString)
    {
        m_macroType = getLineStringType(lineString);
        checkIfTemplateHasCorrectType(caller);
        initStrings();
    }

    TrString& operator=(const TrString& other)
    {
        m_scope = other.m_scope;
        m_native = other.m_native;
        m_translated = other.m_translated;
    }

    TrString& arg(const TrString& arg1)
    {
        this->m_native = this->m_native.arg(arg1.native());
        this->m_translated = this->m_translated.arg(qApp->translate(m_scope.toLocal8Bit().data(), arg1.translated().toLocal8Bit().data()));

        return *this;
    }

    TrString& arg(const QString arg1)
    {
        this->m_native = this->m_native.arg(arg1);
        this->m_translated = this->m_translated.arg(arg1);
        return *this;
    }

    TrString& arg(const double arg1)
    {
        this->m_native = this->m_native.arg(arg1);
        this->m_translated = this->m_translated.arg(arg1);
        return *this;
    }

    inline const QString& native() const
    {
        return this->m_native;
    }

    inline const QString& translated() const
    {
        return this->m_translated;
    }

private:
    template<class SfinaeT = T>
    typename std::enable_if<!std::is_polymorphic< std::remove_pointer_t<SfinaeT>>::value, void>::type
        checkIfTemplateHasCorrectType(SfinaeT caller)
    {
        assert(("You must use TRString(QT_TRANSLATE_NOOP('Context', 'StringToTranslate')) here because your class does not derive from QObject.",
            (m_macroType == MacroType::QT_TRANSLATE_NOOP || m_macroType == MacroType::QT_TRANSLATE_NOOP3)));
    }

    template<class SfinaeT = T>
    typename std::enable_if<std::is_polymorphic< std::remove_pointer_t<SfinaeT>>::value, void>::type
        checkIfTemplateHasCorrectType(SfinaeT caller)
    {
        if (!dynamic_cast<QObject*>(caller))
            assert(("You must use TRString(QT_TRANSLATE_NOOP('Context', 'StringToTranslate')) here because your class does not derive from QObject.",
            (m_macroType == MacroType::QT_TRANSLATE_NOOP || m_macroType == MacroType::QT_TRANSLATE_NOOP3)));
        else
            m_isDerivedFromQObject = true;
    }

    MacroType getLineStringType(QString lineString) const
    {
        if (m_native.contains(kQT_TR_NOOP_Macro))
            return MacroType::QT_TR_NOOP;
        else if (m_native.contains(kQT_TRANSLATE_NOOP_3_Macro))
            return MacroType::QT_TRANSLATE_NOOP3;
        else if (m_native.contains(kQT_TRANSLATE_NOOP_Macro))
            return MacroType::QT_TRANSLATE_NOOP;
        else
        {
            assert(("You must you TRString(QT_TR_NOOP('StringToTranslate') or TRString(QT_TRANSLATE_NOOP('Context', 'StringToTranslate')) if you are not in a QObject derived class for this to work.", false));
            return MacroType::Unknown;
        }
    }

    inline void initStrings()
    {
        QRegularExpressionMatchIterator regExpIter = s_findQuotesRegExp.globalMatch(m_native);
        QStringList macroArguments;

        while (regExpIter.hasNext())
        {
            QRegularExpressionMatch match = regExpIter.next();
            QString argumentString = match.captured(0);
            argumentString.remove(0, 1);
            argumentString.remove(argumentString.length() - 1, 1);
            macroArguments.push_back(argumentString);
        }

        if (macroArguments.size() == 1 && m_macroType == MacroType::QT_TR_NOOP)
        {
            m_scope = QRegularExpressionMatch(s_findClassNameRegExp.match(typeid(T).name())).captured(0);
            m_native = macroArguments[0];
        }
        else if ((macroArguments.size() == 2 && m_macroType == MacroType::QT_TRANSLATE_NOOP) || (macroArguments.size() == 3 && m_macroType == MacroType::QT_TRANSLATE_NOOP3))
        {
            m_scope = macroArguments[0];
            m_native = macroArguments[1];
        }
        else
        {
            assert(("You must you TRString(QT_TR_NOOP('StringToTranslate') or TRString(QT_TRANSLATE_NOOP('Context', 'StringToTranslate')) if you are not in a QObject derived class for this to work.", false));
        }

        m_translated = qApp->translate(m_scope.toLocal8Bit().data(), m_native.toLocal8Bit().data());
    }

    MacroType m_macroType = MacroType::Unknown;
    bool m_isDerivedFromQObject = false;
    QString m_native;
    QString m_translated;
    QString m_scope;
};

#endif // TrString_h__

在QObject派生类中的用法:

auto logString = TRString(QT_TR_NOOP("Indian Pale Ale"));
qDebug() << "Native: " << logString.native(); // "Indian Pale Ale"
qDebug() << "Transl: " << logString.translated(); // "Indisches Blass Ale"

不是从QObject派生的类中的用法:

auto logString = TRString(QT_TRANSLATE_NOOP("MyClass", "Log message"));

课外使用:

auto logString = TRStringVoid(QT_TRANSLATE_NOOP("Main", "Log message in main"));

如果您无意中使用了错误的Qt宏或错误的TRString宏,则会创建一个断言,并附带一条有关如何解决此呼叫的消息。

答案 3 :(得分:0)

因此,我遇到了同样的情况,我也想避免在代码中重复某些字符串文字,并且仍然能够向我的应用程序用户打印出一些翻译后的错误消息,然后将文本的原始英文版本记录到日志。

我开始使用jaba的代码,并在使用一些特殊字符(例如“ \ n”和某些Unicode标记)时遇到了一些问题。事实证明,Qt解析由翻译宏标记的字符串文字的方式涉及替换这些特殊字符。转码功能直接取自Qt的Linguist源代码,以便在运行时执行相同的过程。否则,由于存储在转换文件中的内容与编译后的字符串不完全匹配,因此转换功能无法找到该字符串。所以我能想到的是这样的:

#pragma once

#include <QApplication>

#include <QString>
#include <QRegularExpression>
#include <cassert>

class QObject;

//NOTE: enable_if_t is only available in C++14 so we need to define it here
template< bool Condition, typename T = void >
using enable_if_t = typename std::enable_if<Condition, T>::type;

namespace
{
    constexpr char QT_TR_NOOP_MACRO [] = "QT_TR_NOOP(";
    constexpr char QT_TRANSLATE_NOOP_MACRO [] = "QT_TRANSLATE_NOOP(";
    constexpr char QT_TRANSLATE_NOOP_3_MACRO [] = "QT_TRANSLATE_NOOP3(";

    //TODO: Add QT_TR_N_NOOP, QT_TRANSLATE_N_NOOP, QT_TRANSLATE_N_NOOP3
    enum class MacroType { Unknown, QT_TR_NOOP, QT_TRANSLATE_NOOP, QT_TRANSLATE_NOOP3 };

    MacroType getMacroTypeFromString(const QString& str)
    {
        if (str.contains(QT_TR_NOOP_MACRO))
            return MacroType::QT_TR_NOOP;
        else if (str.contains(QT_TRANSLATE_NOOP_3_MACRO))
            return MacroType::QT_TRANSLATE_NOOP3;
        else if (str.contains(QT_TRANSLATE_NOOP_MACRO))
            return MacroType::QT_TRANSLATE_NOOP;
        else
        {
            return MacroType::Unknown;
        }
    }

    QStringList extractArguments(const QString& raw)
    {
        QStringList macroArguments;

        const QRegularExpression findQuotesRegExp("\"([^\"\\\\]|\\\\.)*\"");
        QRegularExpressionMatchIterator regExpIter = findQuotesRegExp.globalMatch(raw);

        while (regExpIter.hasNext())
        {
            QRegularExpressionMatch match = regExpIter.next();
            QString argumentString = match.captured(0);
            argumentString.remove(0, 1); // Remove leading quote
            argumentString.remove(argumentString.length() - 1, 1); // Remove trailing quote
            macroArguments.push_back(argumentString);
        }

        return macroArguments;
    }

    QString transcode(const QString &str)
    {
        static const char tab[] = "abfnrtv";
        static const char backTab[] = "\a\b\f\n\r\t\v";
        // This function has to convert back to bytes, as C's \0* sequences work at that level.
        const QByteArray ba = str.toUtf8();
        std::vector<uchar> in(std::begin(ba), std::end(ba));
        size_t inputLength = in.size();
        QByteArray out;
        out.reserve(static_cast<int>(inputLength));

        for (size_t i = 0; i < inputLength;)
        {
            uchar c = in[i++];
            if (c == '\\')
            {
                if (i >= inputLength)
                    break;

                c = in[i++];

                if (c == '\n')
                    continue;

                if (c == 'x' || c == 'u' || c == 'U')
                {
                    const bool unicode = (c != 'x');
                    QByteArray hex;

                    while (i < inputLength && isxdigit((c = in[i])))
                    {
                        hex += static_cast<char>(c);
                        i++;
                    }

                    if (unicode)
                    {
                        out += QString(QChar(hex.toUInt(nullptr, 16))).toUtf8();
                    }
                    else
                    {
                        out += static_cast<char>(hex.toUInt(nullptr, 16));
                    }
                }
                else if (c >= '0' && c < '8')
                {
                    QByteArray oct;
                    int n = 0;
                    oct += static_cast<char>(c);

                    while (n < 2 && i < inputLength && (c = in[i]) >= '0' && c < '8')
                    {
                        i++;
                        n++;
                        oct += static_cast<char>(c);
                    }

                    out += static_cast<char>(oct.toUInt(nullptr, 8));
                }
                else
                {
                    const char *p = strchr(tab, c);
                    out += !p ? static_cast<char>(c) : backTab[p - tab];
                }
            }
            else
            {
                out += static_cast<char>(c);
            }
        }

        return QString::fromUtf8(out.constData(), out.length());
    }
}

#define TR_STRING_VOID(QT_TRANSLATE_NOOP_STRING) TranslatedString<void>(#QT_TRANSLATE_NOOP_STRING)
#define TR_STRING(QT_TR_NOOP_STRING) TranslatedString<decltype(this)>(#QT_TR_NOOP_STRING)

template <class T> class TranslatedString
{

public:

    explicit TranslatedString(const char* raw)
    {
        init(raw);
    }

    inline const QString& native() const
    {
        return m_Native;
    }

    inline QString translated() const
    {
        return QCoreApplication::translate(qUtf8Printable(m_Scope), qUtf8Printable(m_Native));
    }

private:

    //TODO: The init method should be SFINAE'd such that only the
    //      respective macros for void vs QObject subclass are supported
    //

    inline void init(const QString& raw)
    {
        m_MacroType = getMacroTypeFromString(raw);

        assert(("Unsupported Qt translation Macro Type", (m_MacroType != MacroType::Unknown)));

        QStringList macroArguments = extractArguments(raw);

(macroArguments.size() == 2)));

        if (macroArguments.size() == 1 && m_MacroType == MacroType::QT_TR_NOOP)
        {
            const QRegularExpression findClassNameRegExp("(?<=\\bclass\\s)(\\w+)");
            m_Scope = QRegularExpressionMatch(findClassNameRegExp.match(typeid(T).name())).captured(0);
            m_Native = transcode(macroArguments[0]);
        }
        else if ((macroArguments.size() == 2 && m_MacroType == MacroType::QT_TRANSLATE_NOOP) || (macroArguments.size() == 3 && m_MacroType == MacroType::QT_TRANSLATE_NOOP3))
        {
            m_Scope = macroArguments[0];
            m_Native = transcode(macroArguments[1]);
        }
        else
        {
            //TODO: Figure out what to put here
        }
    }

    MacroType m_MacroType = MacroType::Unknown;
    QString m_Native;
    QString m_Scope;
};

我还重构了代码以使其符合我自己的个人编码风格和命名约定,但这只是表面上的更改。当我重构代码时,我最终摆脱了构造函数的SFINAE东西,一切对于我而言仍然可以正常工作。尽管我不确定这是否会增加其他人不正确使用该类的机会,所以我很想知道init函数是否应具有两个替代版本,如果该类为void,则仅部分翻译宏是有效的,并且将相应地分析参数的数量,而如果该类是QObject子类,则将验证另一组宏。如果我要添加此内容,我发现使用类似以下内容的方法可能会起作用:

std::enable_if_t<std::is_convertible<T, QObject>::value

如果我错了,请纠正我,因为我不确定这是否涵盖了所有引用类型的可能情况。

我也删除了所有“ arg”函数,因为这些函数不允许进行动态翻译,这意味着带有附加参数的任何字符串都不能重新翻译,以考虑将新值作为参数传递或将根据语言可能交换参数的顺序。 Qt在他们的文档中显示了一个示例。

使用类似的宏,如何使用它的一些示例如下:

namespace
{
    enum class ERROR
    {
        COUNT,
        ARGUMENT_SWAP,
    };

    static const std::map<ERROR, TranslatedString<void>> ERROR_STRINGS =
    {
        std::make_pair(ERROR::COUNT, TR_STRING_VOID(QT_TRANSLATE_NOOP("MainWindow", "%1"))),
        // Translated text could be given as "Second Argument: %2,   First Argument: %1"
        std::make_pair(ERROR::ARGUMENT_SWAP, TR_STRING_VOID(QT_TRANSLATE_NOOP("MainWindow", "First Argument: %1,   Second Argument: %2"))),
    };
}

void MainWindow::retranslateUi()
{
    static int count = 0;
    count++;

    // This has the copyright symbol unicode in it.
    auto ts = TR_STRING(QT_TR_NOOP("Test translation. \xC2\xA9"));

    // Both the translated string and the original text are available
    ui->translatedLabel->setText(ts.translated());
    ui->nativeLabel->setText(ts.native());

    ui->translatedCountLabel->setText(ERROR_STRINGS.at(COUNT).translated().arg(count));
    ui->nativeCountLabel->setText(ERROR_STRINGS.at(COUNT).native().arg(count));

    // Note that the arguments need to be provided to each string
    // so that they can be replaced according to the correct order
    // of the translated text which is only available at runtime.
    ui->translatedSwappedArgumentsLabel->setText(ERROR_STRINGS.at(ARGUMENT_SWAP).translated().arg(50.0).arg("String"));
    ui->swappedArgumentsLabel->setText(ERROR_STRINGS.at(ARGUMENT_SWAP).native().arg(50.0).arg("String"));
}

答案 4 :(得分:0)

您可以使用例如python脚本以编程方式将英语翻译回您想要的语言环境:

translate.py

import sys
import xml.etree.cElementTree as ET

tree = ET.ElementTree(file=sys.argv[1])
root = tree.getroot()
root.attrib["language"] = "c"
for context in root:
    for node in context:
        if node.tag != "message":
            continue

        source = (None, None)
        translation = (None, None)

        for sub in node:
            if sub.tag == "source":
                source = (sub, sub.text)
            elif sub.tag == "translation":
                translation = (sub, sub.text)

        source[0].text = translation[1]
        translation[0].text = source[1]

tree.write(sys.argv[2])

在链接前创建翻译:

app.pro

TRANSLATIONS += \
    $$PWD/../en_US.ts \

translate.input = TRANSLATIONS
translate.variable_out = TRANSLATE_OUTPUT
translate.output = $$PWD/../${QMAKE_FILE_BASE}_c.ts
translate.commands = \
    python $$system_path($$PWD/../translate.py) \
        ${QMAKE_FILE_NAME} \
        ${QMAKE_FILE_OUT}
translate.config = no_link target_predeps

QMAKE_EXTRA_COMPILERS += translate

translate.release.input = TRANSLATE_OUTPUT
translate.release.output = $$PWD/../${QMAKE_FILE_BASE}.qm
translate.release.commands = \
    $$system_path($$[QT_INSTALL_BINS]/lrelease) \
        ${QMAKE_FILE_NAME} \
        ${QMAKE_FILE_OUT}
translate.release.config = no_link target_predeps

QMAKE_EXTRA_COMPILERS += translate.release

最后在您的应用中使用 QTranslator 取回源字符串:


auto translator{ QTranslator{} };

translator.load(QLocale{ QLocale::C },
                QLocale{}.name(),
                QLatin1String{ "_" },
                QLatin1String{ ":/translations" });

您可能想用您需要的任何语言环境替换我使用的所有 C 语言环境。