1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Linguist of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtCore/QDir> |
30 | #include <QtCore/QDebug> |
31 | #include <QtCore/QFile> |
32 | #include <QtCore/QByteArray> |
33 | |
34 | #include <QtTest/QtTest> |
35 | |
36 | class tst_lrelease : public QObject |
37 | { |
38 | Q_OBJECT |
39 | |
40 | public: |
41 | tst_lrelease() |
42 | : lrelease(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/lrelease" ) |
43 | , dataDir(QFINDTESTDATA("testdata/" )) |
44 | {} |
45 | |
46 | private: |
47 | |
48 | private slots: |
49 | void translate(); |
50 | void compressed(); |
51 | void idbased(); |
52 | void markuntranslated(); |
53 | void dupes(); |
54 | void noTranslations(); |
55 | |
56 | private: |
57 | void doCompare(const QStringList &actual, const QString &expectedFn); |
58 | |
59 | QString lrelease; |
60 | QString dataDir; |
61 | }; |
62 | |
63 | void tst_lrelease::doCompare(const QStringList &actual, const QString &expectedFn) |
64 | { |
65 | QFile file(expectedFn); |
66 | QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); |
67 | QStringList expected = QString(file.readAll()).trimmed().split(sep: '\n'); |
68 | |
69 | int i = 0, ei = expected.size(), gi = actual.size(); |
70 | for (; ; i++) { |
71 | if (i == gi) { |
72 | if (i == ei) |
73 | return; |
74 | gi = 0; |
75 | break; |
76 | } else if (i == ei) { |
77 | ei = 0; |
78 | break; |
79 | } else if (!QRegExp(expected.at(i)).exactMatch(str: actual.at(i))) { |
80 | while ((ei - 1) >= i && (gi - 1) >= i && |
81 | (QRegExp(expected.at(i: ei - 1)).exactMatch(str: actual.at(i: gi - 1)))) |
82 | ei--, gi--; |
83 | break; |
84 | } |
85 | } |
86 | QString diff; |
87 | for (int j = qMax(a: 0, b: i - 3); j < i; j++) |
88 | diff += expected.at(i: j) + '\n'; |
89 | diff += "<<<<<<< got\n" ; |
90 | for (int j = i; j < gi; j++) { |
91 | diff += actual.at(i: j) + '\n'; |
92 | if (j >= i + 5) { |
93 | diff += "...\n" ; |
94 | break; |
95 | } |
96 | } |
97 | diff += "=========\n" ; |
98 | for (int j = i; j < ei; j++) { |
99 | diff += expected.at(i: j) + '\n'; |
100 | if (j >= i + 5) { |
101 | diff += "...\n" ; |
102 | break; |
103 | } |
104 | } |
105 | diff += ">>>>>>> expected\n" ; |
106 | for (int j = ei; j < qMin(a: ei + 3, b: expected.size()); j++) |
107 | diff += expected.at(i: j) + '\n'; |
108 | QFAIL(qPrintable("Output for " + expectedFn + " does not meet expectations:\n" + diff)); |
109 | } |
110 | |
111 | void tst_lrelease::translate() |
112 | { |
113 | QVERIFY(!QProcess::execute(lrelease, QStringList() << (dataDir + "translate.ts" ))); |
114 | |
115 | QTranslator translator; |
116 | QVERIFY(translator.load(dataDir + "translate.qm" )); |
117 | qApp->installTranslator(messageFile: &translator); |
118 | |
119 | QCOMPARE(QObject::tr("\nnewline at the start" ), QString("\nNEWLINE AT THE START" )); |
120 | QCOMPARE(QObject::tr("newline at the end\n" ), QString("NEWLINE AT THE END\n" )); |
121 | QCOMPARE(QObject::tr("newline and space at the end\n " ), QString("NEWLINE AND SPACE AT THE END\n " )); |
122 | QCOMPARE(QObject::tr("space and newline at the end \n" ), QString("SPACE AND NEWLINE AT THE END \n" )); |
123 | QCOMPARE(QObject::tr("\ttab at the start and newline at the end\n" ), QString("\tTAB AT THE START AND NEWLINE AT THE END\n" )); |
124 | QCOMPARE(QObject::tr("\n\tnewline and tab at the start" ), QString("\n\tNEWLINE AND TAB AT THE START" )); |
125 | QCOMPARE(QObject::tr(" \tspace and tab at the start" ), QString(" \tSPACE AND TAB AT THE START" )); |
126 | QCOMPARE(QObject::tr(" string that does not exist" ), QString(" string that does not exist" )); |
127 | |
128 | QCOMPARE(QCoreApplication::translate("CubeForm" , "Test" ), QString::fromLatin1("BBBB" )); |
129 | QCOMPARE(QCoreApplication::translate("" , "Test" , "Empty context" ), QString("AAAA" )); |
130 | |
131 | // Test plurals |
132 | QString txed = QCoreApplication::translate(context: "Plurals" , key: "There are %n houses" , disambiguation: 0, n: 0); |
133 | QCOMPARE(QString::fromLatin1("[%1]" ).arg(txed), QString("[There are 0 houses]" )); |
134 | QCOMPARE(QCoreApplication::translate("Plurals" , "There are %n houses" , 0, 1), QString("There is 1 house" )); |
135 | QCOMPARE(QCoreApplication::translate("Plurals" , "There are %n houses" , 0, 2), QString("There are 2 houses" )); |
136 | QCOMPARE(QCoreApplication::translate("Plurals" , "There are %n houses" , 0, 3), QString("There are 3 houses" )); |
137 | |
138 | |
139 | // More plurals |
140 | QCOMPARE(tr("There are %n cars" , "More Plurals" , 0) , QString("There are 0 cars" )); |
141 | QCOMPARE(tr("There are %n cars" , "More Plurals" , 1) , QString("There is 1 car" )); |
142 | QCOMPARE(tr("There are %n cars" , "More Plurals" , 2) , QString("There are 2 cars" )); |
143 | QCOMPARE(tr("There are %n cars" , "More Plurals" , 3) , QString("There are 3 cars" )); |
144 | |
145 | |
146 | QCOMPARE(QCoreApplication::translate("no_en" , "Kj\xc3\xb8r K\xc3\xa5re, kj\xc3\xa6re" ), QString::fromUtf8("Drive K\xc3\xa5re, dear" )); |
147 | QCOMPARE(QCoreApplication::translate("en_no" , "Drive K\xc3\xa5re, dear" ), QString::fromUtf8("Kj\xc3\xb8r K\xc3\xa5re, kj\xc3\xa6re" )); |
148 | QCOMPARE(QCoreApplication::translate("en_ch" , "Chinese symbol:" ), QString::fromUtf8("Chinese symbol:\xe7\xb0\x9f" )); |
149 | |
150 | // printf("halo\r\nhallo"); |
151 | // QCOMPARE(tr("This\r\nwill fail"), QString("THIS\nWILL FAIL")); // \r\n = 0d 0a |
152 | |
153 | QCOMPARE(tr("Completely random string" ), |
154 | QString::fromLatin1("Super-lange Uebersetzung mit Schikanen\x9c" |
155 | "Mittlere Uebersetung\x9c" |
156 | "Kurze Uebers." )); |
157 | |
158 | qApp->removeTranslator(messageFile: &translator); |
159 | } |
160 | |
161 | void tst_lrelease::compressed() |
162 | { |
163 | QVERIFY(!QProcess::execute(lrelease, QStringList() << "-compress" << (dataDir + "compressed.ts" ))); |
164 | |
165 | QTranslator translator; |
166 | QVERIFY(translator.load(dataDir + "compressed.qm" )); |
167 | qApp->installTranslator(messageFile: &translator); |
168 | |
169 | QCOMPARE(QCoreApplication::translate("Context1" , "Foo" ), QString::fromLatin1("in first context" )); |
170 | QCOMPARE(QCoreApplication::translate("Context2" , "Bar" ), QString::fromLatin1("in second context" )); |
171 | |
172 | QCOMPARE(QCoreApplication::translate("Action1" , "Component Name" ), QString::fromLatin1("translation in first context" )); |
173 | QCOMPARE(QCoreApplication::translate("Action2" , "Component Name" ), QString::fromLatin1("translation in second context" )); |
174 | QCOMPARE(QCoreApplication::translate("Action3" , "Component Name" ), QString::fromLatin1("translation in third context" )); |
175 | |
176 | } |
177 | |
178 | void tst_lrelease::idbased() |
179 | { |
180 | QVERIFY(!QProcess::execute(lrelease, QStringList() << "-idbased" << (dataDir + "idbased.ts" ))); |
181 | |
182 | QTranslator translator; |
183 | QVERIFY(translator.load(dataDir + "idbased.qm" )); |
184 | qApp->installTranslator(messageFile: &translator); |
185 | |
186 | QCOMPARE(qtTrId("test_id" ), QString::fromLatin1("This is a test string." )); |
187 | QCOMPARE(qtTrId("untranslated_id" ), QString::fromLatin1("This has no translation." )); |
188 | } |
189 | |
190 | void tst_lrelease::markuntranslated() |
191 | { |
192 | QVERIFY(!QProcess::execute(lrelease, QStringList() << "-markuntranslated" << "#" << "-idbased" << (dataDir + "idbased.ts" ))); |
193 | |
194 | QTranslator translator; |
195 | QVERIFY(translator.load(dataDir + "idbased.qm" )); |
196 | qApp->installTranslator(messageFile: &translator); |
197 | |
198 | QCOMPARE(qtTrId("test_id" ), QString::fromLatin1("This is a test string." )); |
199 | QCOMPARE(qtTrId("untranslated_id" ), QString::fromLatin1("#This has no translation." )); |
200 | } |
201 | |
202 | void tst_lrelease::dupes() |
203 | { |
204 | QProcess proc; |
205 | proc.start(program: lrelease, arguments: QStringList() << (dataDir + "dupes.ts" ), mode: QIODevice::ReadWrite | QIODevice::Text); |
206 | QVERIFY(proc.waitForFinished()); |
207 | QVERIFY(proc.exitStatus() == QProcess::NormalExit); |
208 | doCompare(actual: QString(proc.readAllStandardError()).trimmed().split(sep: '\n'), expectedFn: dataDir + "dupes.errors" ); |
209 | } |
210 | |
211 | void tst_lrelease::noTranslations() |
212 | { |
213 | QProcess proc; |
214 | proc.start(program: lrelease, arguments: { dataDir + "no-translations.pro" }); |
215 | QVERIFY(proc.waitForFinished()); |
216 | QCOMPARE(proc.exitStatus(), QProcess::NormalExit); |
217 | QCOMPARE(proc.exitCode(), 0); |
218 | auto stderrOutput = proc.readAllStandardError(); |
219 | QVERIFY(stderrOutput.contains("lrelease warning: Met no 'TRANSLATIONS' entry in project file" )); |
220 | } |
221 | |
222 | QTEST_MAIN(tst_lrelease) |
223 | #include "tst_lrelease.moc" |
224 | |