summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@nokia.com>2009-07-03 11:28:16 (GMT)
committerOswald Buddenhagen <oswald.buddenhagen@nokia.com>2009-07-06 11:54:42 (GMT)
commit20d73ef1bf23569b09ca862f6c5e5971098613d6 (patch)
tree88f3aa79ab7827281b3e244ad18c10397f3e2f72
parente446729d99bebaf7fac0110fcf22fc867d7229cb (diff)
downloadQt-20d73ef1bf23569b09ca862f6c5e5971098613d6.zip
Qt-20d73ef1bf23569b09ca862f6c5e5971098613d6.tar.gz
Qt-20d73ef1bf23569b09ca862f6c5e5971098613d6.tar.bz2
support for id-based translations
unlike in an earlier attempt, ids are textual this time. the developer is able to provide a template for the string. when lupdate and lrelease are integrated into the build process, this makes it possible to avoid a round-trip to a dedicated string designer during the early development stage. Requirement-id: QT-435
-rw-r--r--doc/src/snippets/code/src_corelib_global_qglobal.cpp24
-rw-r--r--src/corelib/global/qglobal.cpp56
-rw-r--r--src/corelib/global/qglobal.h12
-rw-r--r--src/corelib/kernel/qcoreapplication.cpp6
-rw-r--r--tests/auto/linguist/lrelease/tst_lrelease.cpp13
-rw-r--r--tests/auto/linguist/lupdate/testdata/good/parsecpp/main.cpp12
-rw-r--r--tests/auto/linguist/lupdate/testdata/good/parsecpp/project.ts.result18
-rw-r--r--tools/linguist/lrelease/main.cpp19
-rw-r--r--tools/linguist/lupdate/cpp.cpp70
-rw-r--r--tools/linguist/shared/qm.cpp52
-rw-r--r--tools/linguist/shared/translator.h2
11 files changed, 267 insertions, 17 deletions
diff --git a/doc/src/snippets/code/src_corelib_global_qglobal.cpp b/doc/src/snippets/code/src_corelib_global_qglobal.cpp
index 287181a..50052c3 100644
--- a/doc/src/snippets/code/src_corelib_global_qglobal.cpp
+++ b/doc/src/snippets/code/src_corelib_global_qglobal.cpp
@@ -358,6 +358,30 @@ QString global_greeting(int type)
//! [36]
+//! [qttrid]
+ //% "%n fooish bar(s) found.\n"
+ //% "Do you want to continue?"
+ QString text = qtTrId("qtn_foo_bar", n);
+//! [qttrid]
+
+
+//! [qttrid_noop]
+static const char * const ids[] = {
+ //% "This is the first text."
+ QT_TRID_NOOP("qtn_1st_text"),
+ //% "This is the second text."
+ QT_TRID_NOOP("qtn_2nd_text"),
+ 0
+};
+
+void TheClass::addLabels()
+{
+ for (int i = 0; ids[i]; ++i)
+ new QLabel(qtTrId(ids[i]), this);
+}
+//! [qttrid_noop]
+
+
//! [37]
qWarning("%s: %s", qPrintable(key), qPrintable(value));
//! [37]
diff --git a/src/corelib/global/qglobal.cpp b/src/corelib/global/qglobal.cpp
index f7a97e1..ad4868d 100644
--- a/src/corelib/global/qglobal.cpp
+++ b/src/corelib/global/qglobal.cpp
@@ -2459,6 +2459,62 @@ int qrand()
*/
/*!
+ \fn QString qtTrId(const char *id, int n = -1)
+ \relates <QtGlobal>
+ \reentrant
+ \since 4.6
+
+ Returns a translated string identified by \a id.
+ If no matching string is found, the id itself is returned. This
+ should not happen under normal conditions.
+
+ If \a n >= 0, all occurrences of \c %n in the resulting string
+ are replaced with a decimal representation of \a n. In addition,
+ depending on \a n's value, the translation text may vary.
+
+ Meta data and comments can be passed as documented for QObject::tr().
+ In addition, it is possible to supply a source string template like that:
+
+ \tt{//% <C string>}
+
+ or
+
+ \tt{\begincomment% <C string> \endcomment}
+
+ Example:
+
+ \snippet doc/src/snippets/code/src_corelib_global_qglobal.cpp qttrid
+
+ Creating QM files suitable for use with this function requires passing
+ the \c -idbased option to the \c lrelease tool.
+
+ \warning This method is reentrant only if all translators are
+ installed \e before calling this method. Installing or removing
+ translators while performing translations is not supported. Doing
+ so will probably result in crashes or other undesirable behavior.
+
+ \sa QObject::tr(), QCoreApplication::translate(), {Internationalization with Qt}
+*/
+
+/*!
+ \macro QT_TRID_NOOP(id)
+ \relates <QtGlobal>
+ \since 4.6
+
+ Marks \a id for dynamic translation.
+ The only purpose of this macro is to provide an anchor for attaching
+ meta data like to qtTrId().
+
+ The macro expands to \a id.
+
+ Example:
+
+ \snippet doc/src/snippets/code/src_corelib_global_qglobal.cpp qttrid_noop
+
+ \sa qtTrId(), {Internationalization with Qt}
+*/
+
+/*!
\macro QT_POINTER_SIZE
\relates <QtGlobal>
diff --git a/src/corelib/global/qglobal.h b/src/corelib/global/qglobal.h
index a522bcf..a139a55 100644
--- a/src/corelib/global/qglobal.h
+++ b/src/corelib/global/qglobal.h
@@ -2198,6 +2198,18 @@ inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase
#define QT_TRANSLATE_NOOP_UTF8(scope, x) (x)
#define QT_TRANSLATE_NOOP3(scope, x, comment) {x, comment}
#define QT_TRANSLATE_NOOP3_UTF8(scope, x, comment) {x, comment}
+
+#ifndef QT_NO_TRANSLATION // ### This should enclose the NOOPs above
+
+// Defined in qcoreapplication.cpp
+// The better name qTrId() is reserved for an upcoming function which would
+// return a much more powerful QStringFormatter instead of a QString.
+Q_CORE_EXPORT QString qtTrId(const char *id, int n = -1);
+
+#define QT_TRID_NOOP(id) id
+
+#endif // QT_NO_TRANSLATION
+
#define QDOC_PROPERTY(text)
/*
diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp
index e2708c3..054be70 100644
--- a/src/corelib/kernel/qcoreapplication.cpp
+++ b/src/corelib/kernel/qcoreapplication.cpp
@@ -1675,6 +1675,12 @@ QString QCoreApplication::translate(const char *context, const char *sourceText,
return result;
}
+// Declared in qglobal.h
+QString qtTrId(const char *id, int n)
+{
+ return QCoreApplication::translate(0, id, 0, QCoreApplication::UnicodeUTF8, n);
+}
+
bool QCoreApplicationPrivate::isTranslatorInstalled(QTranslator *translator)
{
return QCoreApplication::self
diff --git a/tests/auto/linguist/lrelease/tst_lrelease.cpp b/tests/auto/linguist/lrelease/tst_lrelease.cpp
index 512987d..ff90b3c 100644
--- a/tests/auto/linguist/lrelease/tst_lrelease.cpp
+++ b/tests/auto/linguist/lrelease/tst_lrelease.cpp
@@ -55,6 +55,7 @@ private slots:
void translate();
void mixedcodecs();
void compressed();
+ void idbased();
void dupes();
private:
@@ -191,6 +192,18 @@ void tst_lrelease::compressed()
}
+void tst_lrelease::idbased()
+{
+ QVERIFY(!QProcess::execute("lrelease -idbased testdata/idbased.ts"));
+
+ QTranslator translator;
+ QVERIFY(translator.load("testdata/idbased.qm"));
+ qApp->installTranslator(&translator);
+
+ QCOMPARE(qtTrId("test_id"), QString::fromAscii("This is a test string."));
+ QCOMPARE(qtTrId("untranslated_id"), QString::fromAscii("This has no translation."));
+}
+
void tst_lrelease::dupes()
{
QProcess proc;
diff --git a/tests/auto/linguist/lupdate/testdata/good/parsecpp/main.cpp b/tests/auto/linguist/lupdate/testdata/good/parsecpp/main.cpp
index 9fb43fe..735e4cd 100644
--- a/tests/auto/linguist/lupdate/testdata/good/parsecpp/main.cpp
+++ b/tests/auto/linguist/lupdate/testdata/good/parsecpp/main.cpp
@@ -175,3 +175,15 @@ class TestingTake17 : QObject {
tr("even more cool");
}
};
+
+
+
+
+//: again an extra comment, this time for id-based NOOP
+//% "This is supposed\tto be quoted \" newline\n"
+//% "backslashed \\ stuff."
+QT_TRID_NOOP("this_a_id")
+
+//~ some thing
+//% "This needs to be here. Really."
+QString test = qtTrId("this_another_id", n);
diff --git a/tests/auto/linguist/lupdate/testdata/good/parsecpp/project.ts.result b/tests/auto/linguist/lupdate/testdata/good/parsecpp/project.ts.result
index 5bd7525..97d3bce 100644
--- a/tests/auto/linguist/lupdate/testdata/good/parsecpp/project.ts.result
+++ b/tests/auto/linguist/lupdate/testdata/good/parsecpp/project.ts.result
@@ -2,6 +2,24 @@
<!DOCTYPE TS>
<TS version="2.0">
<context>
+ <name></name>
+ <message id="this_a_id">
+ <location filename="main.cpp" line="185"/>
+ <source>This is supposed to be quoted &quot; newline
+backslashed \ stuff.</source>
+ <extracomment>again an extra comment, this time for id-based NOOP</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message id="this_another_id" numerus="yes">
+ <location filename="main.cpp" line="189"/>
+ <source>This needs to be here. Really.</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ </translation>
+ <extra-some>thing</extra-some>
+ </message>
+</context>
+<context>
<name>Dialog2</name>
<message numerus="yes">
<location filename="main.cpp" line="29"/>
diff --git a/tools/linguist/lrelease/main.cpp b/tools/linguist/lrelease/main.cpp
index 86b7866..5fbecac 100644
--- a/tools/linguist/lrelease/main.cpp
+++ b/tools/linguist/lrelease/main.cpp
@@ -70,6 +70,8 @@ static void printUsage()
"format into the 'compiled' .qm format used by QTranslator objects.\n\n"
"Options:\n"
" -help Display this information and exit\n"
+ " -idbased\n"
+ " Use IDs instead of source strings for message keying\n"
" -compress\n"
" Compress the .qm files\n"
" -nounfinished\n"
@@ -99,7 +101,7 @@ static bool loadTsFile(Translator &tor, const QString &tsFileName, bool /* verbo
static bool releaseTranslator(Translator &tor, const QString &qmFileName,
bool verbose, bool ignoreUnfinished,
- bool removeIdentical, TranslatorSaveMode mode)
+ bool removeIdentical, bool idBased, TranslatorSaveMode mode)
{
Translator::reportDuplicates(tor.resolveDuplicates(), qmFileName, verbose);
@@ -121,6 +123,7 @@ static bool releaseTranslator(Translator &tor, const QString &qmFileName,
ConversionData cd;
cd.m_verbose = verbose;
cd.m_ignoreUnfinished = ignoreUnfinished;
+ cd.m_idBased = idBased;
cd.m_saveMode = mode;
bool ok = tor.release(&file, cd);
file.close();
@@ -136,7 +139,7 @@ static bool releaseTranslator(Translator &tor, const QString &qmFileName,
}
static bool releaseTsFile(const QString& tsFileName, bool verbose,
- bool ignoreUnfinished, bool removeIdentical, TranslatorSaveMode mode)
+ bool ignoreUnfinished, bool removeIdentical, bool idBased, TranslatorSaveMode mode)
{
Translator tor;
if (!loadTsFile(tor, tsFileName, verbose))
@@ -151,7 +154,7 @@ static bool releaseTsFile(const QString& tsFileName, bool verbose,
}
qmFileName += QLatin1String(".qm");
- return releaseTranslator(tor, qmFileName, verbose, ignoreUnfinished, removeIdentical, mode);
+ return releaseTranslator(tor, qmFileName, verbose, ignoreUnfinished, removeIdentical, idBased, mode);
}
int main(int argc, char **argv)
@@ -164,6 +167,7 @@ int main(int argc, char **argv)
bool verbose = true; // the default is true starting with Qt 4.2
bool ignoreUnfinished = false;
+ bool idBased = false;
// the default mode is SaveEverything starting with Qt 4.2
TranslatorSaveMode mode = SaveEverything;
bool removeIdentical = false;
@@ -175,6 +179,9 @@ int main(int argc, char **argv)
if (args[i] == QLatin1String("-compress")) {
mode = SaveStripped;
continue;
+ } else if (args[i] == QLatin1String("-idbased")) {
+ idBased = true;
+ continue;
} else if (args[i] == QLatin1String("-nocompress")) {
mode = SaveEverything;
continue;
@@ -232,7 +239,7 @@ int main(int argc, char **argv)
qPrintable(args[i]));
} else {
foreach (const QString &trans, translations)
- if (!releaseTsFile(trans, verbose, ignoreUnfinished, removeIdentical, mode))
+ if (!releaseTsFile(trans, verbose, ignoreUnfinished, removeIdentical, idBased, mode))
return 1;
}
} else {
@@ -243,7 +250,7 @@ int main(int argc, char **argv)
}
} else {
if (outputFile.isEmpty()) {
- if (!releaseTsFile(args[i], verbose, ignoreUnfinished, removeIdentical, mode))
+ if (!releaseTsFile(args[i], verbose, ignoreUnfinished, removeIdentical, idBased, mode))
return 1;
} else {
if (!loadTsFile(tor, args[i], verbose))
@@ -254,7 +261,7 @@ int main(int argc, char **argv)
if (!outputFile.isEmpty())
return releaseTranslator(tor, outputFile, verbose, ignoreUnfinished,
- removeIdentical, mode) ? 0 : 1;
+ removeIdentical, idBased, mode) ? 0 : 1;
return 0;
}
diff --git a/tools/linguist/lupdate/cpp.cpp b/tools/linguist/lupdate/cpp.cpp
index 35b41d4..58e094b 100644
--- a/tools/linguist/lupdate/cpp.cpp
+++ b/tools/linguist/lupdate/cpp.cpp
@@ -199,7 +199,7 @@ private:
enum {
Tok_Eof, Tok_class, Tok_friend, Tok_namespace, Tok_using, Tok_return,
- Tok_tr = 10, Tok_trUtf8, Tok_translate, Tok_translateUtf8,
+ Tok_tr = 10, Tok_trUtf8, Tok_translate, Tok_translateUtf8, Tok_trid,
Tok_Q_OBJECT = 20, Tok_Q_DECLARE_TR_FUNCTIONS,
Tok_Ident, Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, Tok_ColonColon,
Tok_Equals,
@@ -553,6 +553,8 @@ uint CppParser::getToken()
return Tok_Q_DECLARE_TR_FUNCTIONS;
if (yyIdent == QLatin1String("QT_TR_NOOP"))
return Tok_tr;
+ if (yyIdent == QLatin1String("QT_TRID_NOOP"))
+ return Tok_trid;
if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP"))
return Tok_translate;
if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP3"))
@@ -588,6 +590,10 @@ uint CppParser::getToken()
if (yyIdent == QLatin1String("namespace"))
return Tok_namespace;
break;
+ case 'q':
+ if (yyIdent == QLatin1String("qtTrId"))
+ return Tok_trid;
+ break;
case 'r':
if (yyIdent == QLatin1String("return"))
return Tok_return;
@@ -1328,6 +1334,7 @@ void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions)
QString comment;
QString extracomment;
QString msgid;
+ QString sourcetext;
TranslatorMessage::ExtraData extra;
QString prefix;
#ifdef DIAGNOSE_RETRANSLATABILITY
@@ -1514,6 +1521,9 @@ void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions)
case Tok_trUtf8:
if (!results->tor)
goto case_default;
+ if (!sourcetext.isEmpty())
+ qWarning("%s:%d: //%% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n",
+ qPrintable(yyFileName), yyLineNo);
utf8 = (yyTok == Tok_trUtf8);
line = yyLineNo;
yyTok = getToken();
@@ -1611,6 +1621,9 @@ void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions)
case Tok_translate:
if (!results->tor)
goto case_default;
+ if (!sourcetext.isEmpty())
+ qWarning("%s:%d: //%% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n",
+ qPrintable(yyFileName), yyLineNo);
utf8 = (yyTok == Tok_translateUtf8);
line = yyLineNo;
yyTok = getToken();
@@ -1660,6 +1673,27 @@ void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions)
msgid.clear();
extra.clear();
break;
+ case Tok_trid:
+ if (!results->tor)
+ goto case_default;
+ if (!sourcetext.isEmpty()) {
+ if (!msgid.isEmpty())
+ qWarning("%s:%d: //= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n",
+ qPrintable(yyFileName), yyLineNo);
+ //utf8 = false; // Maybe use //%% or something like that
+ line = yyLineNo;
+ yyTok = getToken();
+ if (match(Tok_LeftParen) && matchString(&msgid) && !msgid.isEmpty()) {
+ bool plural = match(Tok_Comma);
+ recordMessage(line, QString(), sourcetext, QString(), extracomment,
+ msgid, extra, false, plural);
+ }
+ sourcetext.clear();
+ }
+ extracomment.clear();
+ msgid.clear();
+ extra.clear();
+ break;
case Tok_Q_DECLARE_TR_FUNCTIONS:
case Tok_Q_OBJECT:
namespaces.last()->hasTrFunctions = true;
@@ -1689,6 +1723,40 @@ void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions)
int k = yyComment.indexOf(QLatin1Char(' '));
if (k > -1)
extra.insert(yyComment.left(k), yyComment.mid(k + 1).trimmed());
+ } else if (yyComment.startsWith(QLatin1Char('%'))) {
+ int p = 1, c;
+ forever {
+ if (p >= yyComment.length())
+ break;
+ c = yyComment.unicode()[p++].unicode();
+ if (isspace(c))
+ continue;
+ if (c != '"') {
+ qWarning("%s:%d: Unexpected character in meta string\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ forever {
+ if (p >= yyComment.length()) {
+ whoops:
+ qWarning("%s:%d: Unterminated meta string\n",
+ qPrintable(yyFileName), yyLineNo);
+ break;
+ }
+ c = yyComment.unicode()[p++].unicode();
+ if (c == '"')
+ break;
+ if (c == '\\') {
+ if (p >= yyComment.length())
+ goto whoops;
+ c = yyComment.unicode()[p++].unicode();
+ if (c == '\n')
+ goto whoops;
+ sourcetext.append(QLatin1Char('\\'));
+ }
+ sourcetext.append(c);
+ }
+ }
} else {
comment = yyComment.simplified();
if (comment.startsWith(QLatin1String(MagicComment))) {
diff --git a/tools/linguist/shared/qm.cpp b/tools/linguist/shared/qm.cpp
index ec61cb6..05d8e12 100644
--- a/tools/linguist/shared/qm.cpp
+++ b/tools/linguist/shared/qm.cpp
@@ -173,6 +173,7 @@ public:
bool save(QIODevice *iod);
void insert(const TranslatorMessage &msg, bool forceComment);
+ void insertIdBased(const TranslatorMessage &message);
void squeeze(TranslatorSaveMode mode);
@@ -436,6 +437,16 @@ void Releaser::insert(const TranslatorMessage &message, bool forceComment)
insertInternal(message, forceComment, false);
}
+void Releaser::insertIdBased(const TranslatorMessage &message)
+{
+ QStringList tlns = message.translations();
+ for (int i = 0; i < tlns.size(); ++i)
+ if (tlns.at(i).isEmpty())
+ tlns[i] = message.sourceText();
+ ByteTranslatorMessage bmsg("", originalBytes(message.id(), false), "", tlns);
+ m_messages.insert(bmsg, 0);
+}
+
void Releaser::setNumerusRules(const QByteArray &rules)
{
m_numerusRules = rules;
@@ -689,11 +700,17 @@ static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData
int finished = 0;
int unfinished = 0;
int untranslated = 0;
+ int missingIds = 0;
+ int droppedData = 0;
for (int i = 0; i != translator.messageCount(); ++i) {
const TranslatorMessage &msg = translator.message(i);
TranslatorMessage::Type typ = msg.type();
if (typ != TranslatorMessage::Obsolete) {
+ if (cd.m_idBased && msg.id().isEmpty()) {
+ ++missingIds;
+ continue;
+ }
if (typ == TranslatorMessage::Unfinished) {
if (msg.translation().isEmpty()) {
++untranslated;
@@ -706,19 +723,34 @@ static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData
} else {
++finished;
}
- // Drop the comment in (context, sourceText, comment),
- // unless the context is empty,
- // unless (context, sourceText, "") already exists or
- // unless we already dropped the comment of (context,
- // sourceText, comment0).
- bool forceComment =
- msg.comment().isEmpty()
- || msg.context().isEmpty()
- || translator.contains(msg.context(), msg.sourceText(), QString());
- releaser.insert(msg, forceComment);
+ if (cd.m_idBased) {
+ if (!msg.context().isEmpty() || !msg.comment().isEmpty())
+ ++droppedData;
+ releaser.insertIdBased(msg);
+ } else {
+ // Drop the comment in (context, sourceText, comment),
+ // unless the context is empty,
+ // unless (context, sourceText, "") already exists or
+ // unless we already dropped the comment of (context,
+ // sourceText, comment0).
+ bool forceComment =
+ msg.comment().isEmpty()
+ || msg.context().isEmpty()
+ || translator.contains(msg.context(), msg.sourceText(), QString());
+ releaser.insert(msg, forceComment);
+ }
}
}
+ if (missingIds)
+ cd.appendError(QCoreApplication::translate("LRelease",
+ "Dropped %n message(s) which had no ID.", 0,
+ QCoreApplication::CodecForTr, missingIds));
+ if (droppedData)
+ cd.appendError(QCoreApplication::translate("LRelease",
+ "Excess context/disambiguation dropped from %n message(s).", 0,
+ QCoreApplication::CodecForTr, droppedData));
+
releaser.squeeze(cd.m_saveMode);
bool saved = releaser.save(&dev);
if (saved && cd.isVerbose()) {
diff --git a/tools/linguist/shared/translator.h b/tools/linguist/shared/translator.h
index ac824f3..fb17fd1 100644
--- a/tools/linguist/shared/translator.h
+++ b/tools/linguist/shared/translator.h
@@ -65,6 +65,7 @@ public:
m_ignoreUnfinished(false),
m_sortContexts(false),
m_noUiLines(false),
+ m_idBased(false),
m_saveMode(SaveEverything)
{}
@@ -97,6 +98,7 @@ public:
bool m_ignoreUnfinished;
bool m_sortContexts;
bool m_noUiLines;
+ bool m_idBased;
TranslatorSaveMode m_saveMode;
};