9#include <trlib/metastrings.h>
10#include <trlib/trparser.h>
16#include <QRegularExpression>
18using namespace Qt::StringLiterals;
24constexpr QLatin1String uiElement =
"<ui "_L1;
25constexpr QLatin1String stringElement =
"<string"_L1;
26constexpr QLatin1String stringListElement =
"<stringlist"_L1;
28QString escapeForCppStringLiteral(
const QString &str)
31 result.replace(
"\\",
"\\\\");
32 result.replace(
"\"",
"\\\"");
36QString textMetaString(
const QString &indentation,
const QString &sourceText)
38 const QStringList lines = sourceText.split(
'\n');
40 for (
int i = 0; i < lines.size(); i++) {
41 metaString += indentation +
"//" + MetaStrings::sourceTextAnotation +
" \""
42 + escapeForCppStringLiteral(lines[i]);
43 if (i < lines.size() - 1)
44 metaString +=
"\\n\"\n";
51QString labelMetaString(
const QString &indentation,
const QString &label)
53 return indentation +
"//" + MetaStrings::labelAnotation +
' ' + label +
'\n';
56QString idMetaString(
const QString &indentation,
const QString &id)
58 return indentation +
"//" + MetaStrings::extraAnotation +
" meta-id " + id +
'\n';
61QRegularExpression idMetaStringRegex(
const QString &id)
63 const QString e = QRegularExpression::escape(id);
64 const QString pat = QStringLiteral(R"(^[ \t]*//~ meta-id\s*%1[ \t]*(?:\r?\n|$))").arg(e);
65 return QRegularExpression(pat, QRegularExpression::MultilineOption);
68QString idBasedFunc(
int trFunc,
const QString &id,
const QString &pluralArg)
71 case TrFunctionAliasManager::Function_trUtf8:
72 case TrFunctionAliasManager::Function_tr:
73 case TrFunctionAliasManager::Function_translate:
74 return "qtTrId(\"" + id +
"\"" + pluralArg +
")";
75 case TrFunctionAliasManager::Function_qsTr:
76 case TrFunctionAliasManager::Function_qsTranslate:
77 return "qsTrId(\"" + id +
"\"" + pluralArg +
")";
78 case TrFunctionAliasManager::Function_QT_TR_NOOP:
79 case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8:
80 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP:
81 case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8:
82 return "QT_TRID_NOOP(\"" + id +
"\")";
83 case TrFunctionAliasManager::Function_QT_TR_N_NOOP:
84 case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP:
85 return "QT_TRID_N_NOOP(\"" + id +
"\")";
91int getTrFunction(
const QString &expr)
96 while (i < expr.size()) {
97 if (QChar c = expr[i++]; (c.isLetterOrNumber() || c ==
'_'_L1) && depth == 0)
100 while (c.isSpace() && i < expr.size())
109 }
else if (c ==
')'_L1)
111 else if (c ==
"\""_L1 || c ==
"'"_L1) {
112 const QChar quotation = c;
113 while (i < expr.size()) {
117 else if (c == quotation)
127QString getPluralArg(
const QString &fn,
bool plural)
132 int pos = fn.size() - 2;
133 while (paren > 0 && pos >= 0) {
134 if (fn[pos] ==
'('_L1)
136 else if (fn[pos] ==
')'_L1)
138 if (paren == 1 && fn[pos] ==
','_L1)
139 return ' ' + fn.sliced(pos).removeLast().simplified();
145void transformMessageNoLocation(
TranslatorMessage &msg,
const RecordDirectory &records,
146 QSet<QString> &ids,
Translator &transformedTor,
bool labels)
148 if (msg.id().isEmpty()) {
149 const QString id = records.calculateId(msg);
151 msg.setLabel(msg.context());
160void transformMessageWithLocation(
TranslatorMessage &msg,
const RecordDirectory &records,
161 QSet<QString> &ids,
Translator &transformedTor,
bool labels)
165 for (
const TranslatorMessage::Reference &r : msg.allReferences()) {
166 TranslatorMessage::Reference ur{ r.fileName(),
168 + records.addedLines(r.fileName(), r.lineNumber()),
169 r.startOffset(), r.endOffset() };
170 if (records.isNonSupported(ur.fileName(), ur.lineNumber()))
171 nonsupportedRefs.append(ur);
173 normalRefs.append(ur);
176 if (!nonsupportedRefs.isEmpty()) {
181 if (QString ctx = msg.context(); !ctx.isEmpty() && !normalRefs.empty()) {
183 const QString id = records.id(msg);
191 }
else if (!normalRefs.empty()) {
197bool makeFormIdBased(QStringList &lines,
const QString &filename,
const QString &label)
199 auto itr = lines.begin();
202 pos = itr++->indexOf(uiElement);
203 while (pos < 0 && itr != lines.end());
206 printErr(
"ltext2id: no root element in the "
207 "ui file %1. Ignoring."_L1.arg(filename));
212 if (!label.isEmpty() && itr->indexOf(
"label=") < 0)
213 itr->insert(pos + uiElement.size(),
"label=\"" + label +
"\" ");
215 if (itr->indexOf(
"idbasedtr") < 0)
216 itr->insert(pos + uiElement.size(),
"idbasedtr=\"true\" ");
218 itr->replace(
"idbasedtr=\"false\"",
"idbasedtr=\"true\"");
227const QSet<QString> FileTransformer::cppExtensions{
"c"_L1,
"c++"_L1,
"cc"_L1,
"cpp"_L1,
228 "cxx"_L1,
"ch"_L1,
"h"_L1,
"h++"_L1,
229 "hh"_L1,
"hpp"_L1,
"hxx"_L1 };
232 "jui"_L1,
"ui"_L1,
"js"_L1,
"mjs"_L1,
"qml"_L1,
236 : m_records(records), m_labels(labels), m_quiet(quiet)
242 for (
const auto &[filename, messages] : m_records.messageLocations().asKeyValueRange()) {
243 if (filename.endsWith(
".ui", Qt::CaseInsensitive)) {
244 QStringList lines = readLines(filename);
249 printOut(
"ltext2id: processing source file %1"_L1.arg(filename));
251 makeFormIdBased(lines, filename, (*messages.begin())->context);
253 for (
const std::shared_ptr<MessageItem> &msg : messages) {
254 QString &line = lines[msg->lineNo - 1];
255 qsizetype pos = line.indexOf(stringElement);
256 qsizetype size = stringElement.size();
257 if (pos < 0 || line.size() < pos + size
258 || (line.at(pos + size) !=
'>' && !line.at(pos + size).isSpace())) {
259 pos = line.indexOf(stringListElement);
260 size = stringListElement.size();
262 if (pos < 0 || line.size() < pos + size
263 || (line.at(pos + size) !=
'>' && !line.at(pos + size).isSpace())) {
264 printErr(
"ltext2id error: could not find the "
265 "expected translatable string in %1:%2.\n"_L1.arg(filename)
267 m_records.recordError(filename, msg->lineNo,
268 "please use id %1"_L1.arg(msg->id));
272 line.insert(pos,
" id=\"" + msg->id +
"\"");
274 writeLines(filename, lines);
281 for (
const auto &[filename, messages] : m_records.messageLocations().asKeyValueRange()) {
282 if (!filename.endsWith(
".ui", Qt::CaseInsensitive)) {
284 printOut(
"ltext2id: processing source file %1"_L1.arg(filename));
286 QFile file(filename);
287 if (!file.open(QIODevice::ReadOnly)) {
288 printErr(
"ltext2id error: failed to open file %1 for reading.\n"_L1.arg(filename));
291 QTextStream in(&file);
292 const QString code = in.readAll();
296 newCode.reserve(code.size());
297 qsizetype lastPos = 0;
299 for (
const std::shared_ptr<MessageItem> &msg : messages) {
300 if (msg->startOffset >= 0 && msg->endOffset > msg->startOffset
301 && msg->endOffset <= code.size() && msg->startOffset >= lastPos) {
302 int lastLinePos = code.lastIndexOf(
'\n', msg->startOffset) + 1;
303 if (lastLinePos < lastPos)
304 lastLinePos = msg->startOffset;
305 const QString lastLine =
306 code.sliced(lastLinePos, msg->startOffset - lastLinePos);
308 const QString indentation = getIndentation(lastLine);
309 QString fnId = textMetaString(indentation, msg->sourceText);
311 fnId += labelMetaString(indentation, msg->context);
312 QString fn = code.sliced(msg->startOffset, msg->endOffset - msg->startOffset);
313 int trFunc = getTrFunction(fn);
314 QString idBasedFn = idBasedFunc(trFunc, msg->id, getPluralArg(fn, msg->plural));
315 if (idBasedFn.isEmpty()) {
316 msg->lineNo += addedLines;
318 m_records.recordNonSupported(filename, msg->lineNo);
320 m_records.recordError(filename, msg->lineNo,
321 "Could not detect any translation calls here"_L1);
324 fnId += lastLine + std::move(idBasedFn);
326 QString codePiece = code.sliced(lastPos, lastLinePos - lastPos);
327 if (msg->hasMetaId) {
328 codePiece.remove(idMetaStringRegex(msg->id));
331 newCode += codePiece;
334 addedLines += fnId.count(
'\n');
335 m_records.recordAddedLines(filename, msg->lineNo, addedLines);
336 msg->lineNo += addedLines;
337 if (
const int origMsgLines = fn.count(
'\n'); origMsgLines) {
338 addedLines -= origMsgLines;
339 m_records.recordAddedLines(filename, msg->lineNo - addedLines + 1,
342 lastPos = msg->endOffset;
344 m_records.recordError(
345 filename, msg->lineNo,
346 QString(
"Invalid location offsets for the translation call: %1-%2")
347 .arg(msg->startOffset)
348 .arg(msg->endOffset));
351 newCode += code.sliced(lastPos);
353 QFile outFile(filename);
354 if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
355 printErr(
"ltext2id error: failed to open file %1 for writing.\n"_L1.arg(filename));
358 QTextStream out(&outFile);
370 for (
const QString &tsFile : std::as_const(translations)) {
372 printOut(
"ltext2id: processing TS file %1"_L1.arg(tsFile));
374 tor.load(tsFile, cd,
"ts");
375 if (!cd.errors().empty()) {
379 tor.makeFileNamesAbsolute(QFileInfo(tsFile).absoluteDir());
381 Translator transformedTor;
383 for (qsizetype i = 0; i < tor.messageCount(); i++) {
384 TranslatorMessage &msg = tor.message(i);
385 if (
const QString filename = msg.fileName();
386 filename.isEmpty() || !m_records.containsFile(filename))
387 transformMessageNoLocation(msg, m_records, ids, transformedTor, m_labels);
389 transformMessageWithLocation(msg, m_records, ids, transformedTor, m_labels);
391 transformedTor.save(tsFile, cd,
"ts");
392 if (!cd.errors().empty()) {
394 "ltext2id error: error in processing translation files\n%1"_L1.arg(cd.error()));
399 printOut(
"ltext2id: verifying TS file %1"_L1.arg(tsFile));
401 FileVerifier verifier(m_records, m_quiet);
402 if (!verifier.verifyTs(tsFile, ids)) {
403 printErr(
"ltext2id: verifying TS file %1 failed."_L1.arg(tsFile));
412 for (
const auto &[filename, messages] : m_records.messageLocations().asKeyValueRange()) {
413 if (!filename.endsWith(
".ui", Qt::CaseInsensitive)) {
415 printOut(
"ltext2id: processing source file %1"_L1.arg(filename));
417 QStringList lines = readLines(filename);
419 for (
auto itr = messages.cbegin(); itr != messages.cend(); itr++) {
420 const MessageItem &m = **itr;
421 QString &line = lines[m.lineNo - 1];
422 const QString indentation = getIndentation(lines[m.lineNo - 1]);
423 line = idMetaString(indentation, m.id) + line;
424 m_records.recordAddedLines(filename, m.lineNo, ++addedLines);
426 writeLines(filename, lines);
435 for (
const QString &tsFile : std::as_const(translations)) {
437 printOut(
"ltext2id: processing TS file %1"_L1.arg(tsFile));
439 tor.load(tsFile, cd,
"ts");
440 if (!cd.errors().empty()) {
444 tor.makeFileNamesAbsolute(QFileInfo(tsFile).absoluteDir());
445 Translator transformedTor;
447 for (qsizetype i = 0; i < tor.messageCount(); i++) {
448 TranslatorMessage &msg = tor.message(i);
449 TranslatorMessage::References refs;
450 for (
const TranslatorMessage::Reference &r : msg.allReferences()) {
451 if (
const QString filename = r.fileName();
452 !filename.isEmpty() && m_records.containsFile(filename)) {
453 TranslatorMessage::Reference ur{
454 filename, r.lineNumber() + m_records.addedLines(filename, r.lineNumber()),
455 r.startOffset(), r.endOffset()
460 msg.clearReferences();
461 msg.setReferences(refs);
462 msg.setExtra(meta_id_key, m_records.id(msg));
463 transformedTor.append(msg);
466 transformedTor.save(tsFile, cd,
"ts");
467 if (!cd.errors().empty()) {
469 "ltext2id error: error in processing translation files\n%1"_L1.arg(cd.error()));
474 printOut(
"ltext2id: verifying TS file %1"_L1.arg(tsFile));
void setReferences(const References &refs)
QList< Reference > References
void append(const TranslatorMessage &msg)
TrFunctionAliasManager trFunctionAliasManager