/* PIP - Platform Independent Primitives Translator Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "picli.h" #include "pidir.h" #include "pifile.h" #include "piiostream.h" #include "pijson.h" #include "pivaluetree.h" #include "pivaluetree_conversions.h" #include "ts_file.h" using namespace PICoutManipulators; const char help_string[] = "Read input files or entire directories as C++ sources\n" "and create QtLinguist translation file (*.ts).\n" "Search for next strings:\n" " * PITranslator::tr(\"msg\"[, \"ctx\"])\n" " * piTr(\"msg\"[, \"ctx\"])\n" " * \"msg\"_tr\n" "Output file can be translated by QtLinguist and used\n" "by PIP ?????? to translate strings.\n" ""; void header() { piCout << Bold << "PIP Translator"; piCout << Cyan << "Version" << Bold << PIPVersion() << NewLine; piCout << Green << Bold << "Usage:" << Default << "\"pip_tr [-hHqn] -l -o [] [] [...]\"" << NewLine; } void usage() { header(); piCout << Green << Bold << "Details:"; piCout << Bold << "Debug control"; piCout << "-h " << Green << "- display this message and exit"; piCout << "-H " << Green << "- display details help"; piCout << "-q " << Green << "- quiet, no debug output to console"; piCout << ""; piCout << Bold << "Output control"; piCout << "-l " << Green << "- translation language (e.g. en_US, ru_RU)"; piCout << "-n, --no-obsolete " << Green << "- drop unused translations in output file"; piCout << "-o " << Green << "- output file for translation (QtLinguist *.ts file)"; piCout << ""; piCout << Bold << "Input control"; piCout << " " << Green << "- add file or dir translation"; } void help() { header(); piCout << help_string; } PIString fromCode(const PIString & in) { return in.replacedAll("\\n", '\n').replaceAll("\\r", '\r').replaceAll("\\t", '\t'); } void gatherStrings(TSFile::Content & content, const PIString & file, const PIString & file_loc) { PIString source, context; int pos = -1, ppos = 0, line = -1; int len = 0; auto isCLetter = [](const PIChar c) { return c.isAlpha() || c.isDigit() || c == '_'; }; static const PIStringList keyword({"PITranslator::tr", "piTr"}); for (const auto & kw: keyword) { for (;;) { source.clear(); context.clear(); pos = file.find(kw, pos + 1); if (pos < 0) break; if (pos > 0) { auto pc = file[pos - 1]; if (isCLetter(pc) || pc == ':') { pos += kw.size_s(); continue; } } pos += kw.size_s(); if (pos < file.size_s() - 1) { auto pc = file[pos]; if (isCLetter(pc) || pc == ':') { pos += kw.size_s(); continue; } } // piCout << "PITranslator" << pos; pos = file.find('(', pos); if (pos < 0) break; // piCout << "(" << pos; pos += 1; pos = file.findRange('"', '"', '\\', pos, &len); if (pos < 0) break; // piCout << "\"" << pos; source = file.mid(pos, len); pos += len + 1; line = file.lineNumber(pos); ppos = pos; while (pos < file.size_s()) { if (!file[pos].isSpace() && file[pos] != '\n' && file[pos] != '\r') break; ++pos; } if (pos < file.size_s()) { // piCout << "check comma" << file[pos]; if (file[pos] == ',') { pos += 1; pos = file.findRange('"', '"', '\\', pos, &len); // piCout << "check range" << file.mid(pos, len); if (pos >= 0) { context = file.mid(pos, len); pos += len + 1; ppos = pos; } } } content[context].confirm(fromCode(source), file_loc, line); pos = ppos; piCout << "Context = \"" << context << "\", message = \"" << source << "\""; } } auto & ec(content[""]); pos = -1; for (;;) { source.clear(); pos = file.find('"', pos + 1); if (pos < 0) break; pos = file.findRange('"', '"', '\\', pos, &len); if (pos < 0) break; // piCout << "\"" << pos; source = file.mid(pos, len); pos += len + 1; if (file.mid(pos, 3) == "_tr") { pos += 3; if (pos < file.size_s() - 1) { auto pc = file[pos]; if (isCLetter(pc) || pc == ':') { continue; } } ec.confirm(fromCode(source), file_loc, file.lineNumber(pos)); piCout << "_tr = \"" << source << "\""; } } } int main(int argc, char * argv[]) { PICLI cli(argc, argv); // piCout << cli.rawArguments(); cli.setOptionalArgumentsCount(-1); cli.addArgument("output", true); cli.addArgument("language", true); cli.addArgument("help"); cli.addArgument("Help"); cli.addArgument("quiet"); cli.addArgument("no-obsolete"); if (cli.hasArgument("Help")) { help(); return 0; } if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.argumentValue("language").isEmpty() || cli.optionalArguments().isEmpty()) { usage(); return 0; } piDebug = !cli.hasArgument("quiet"); PIString out_path = cli.argumentValue("output"); PIStringList files; const static PIStringList ext({"h", "hpp", "cpp", "cxx"}); for (const PIString & a: cli.optionalArguments()) { if (PIDir::isExists(a)) { auto dl = PIDir(a).allEntries(); for (const auto & i: dl) { if (!i.isFile()) continue; if (ext.contains(i.extension().toLowerCase().trim())) files << i.path; } } else { if (PIFile::isExists(a)) files << a; } } piCout << Cyan << Bold << "Reading ts file ..."; auto content = TSFile::read(out_path); piCout << Cyan << Bold << "Reading done"; piCout << Cyan << Bold << "Read" << files.size_s() << "files ..."; PIDir out_dir(PIFile::FileInfo(out_path).dir()); for (const auto & p: files) { PIString file_loc = out_dir.relative(p); PIFile f(p, PIIODevice::ReadOnly); gatherStrings(content, PIString::fromUTF8(f.readAll()), file_loc); } piCout << Cyan << Bold << "Reading done"; piCout << Cyan << Bold << "Writing ts file ..."; if (!TSFile::write(out_path, content, cli.argumentValue("language"), cli.hasArgument("no-obsolete"))) return 1; piCout << Cyan << Bold << "Writing done"; return 0; }