我的Qt应用程序支持动态翻译IE,用户可以在应用程序运行时更改语言
现在我需要找到翻译过的字符串的英文等价物,似乎无法找到方法
例如 给定QString s = tr(“Hello”); 我需要能够在翻译完成后从s获得“Hello”。
之前是否有人这样做过,或者对如何(如果)实现这一点有任何想法
由于
答案 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 语言环境。