/* PIP - Platform Independent Primitives ValueTree 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 using namespace PICoutManipulators; using Attribute = PIValueTree::Attribute; const char help_string[] = "Read input files or entire directories as PIValueTree\n" "according to extension, and create QtLinguist translation\n" "file (*.ts). Extensions:\n" " * conf - PIValueTreeConversions::fromText\n" " * json - PIValueTreeConversions::fromJSON\n" " * bin - binary deserialization\n" "Output file can be translated by QtLinguist and used\n" "by QAD PIValueTreeEdit widget to translate strings.\n" ""; void header() { piCout << Bold << "PIP ValueTree translator"; piCout << Cyan << "Version" << Bold << PIPVersion() << NewLine; piCout << Green << Bold << "Usage:" << Default << "\"pip_vtt [-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; } void printError(const PIString & msg) { std::cerr << msg.data() << std::endl; } PISet strings; PIMap locations; PIString mask(const PIString & in) { static PIVector> map = { {"&", "&" }, {"<", "<" }, {">", ">" }, {"'", "'"}, {"\"", """}, }; PIString ret = in; for (const auto & i: map) ret.replaceAll(i.first, i.second); return ret; } PIString unmask(const PIString & in) { static PIVector> map = { {"<", "<" }, {">", ">" }, {"'", "'"}, {"\"", """}, {"&", "&" }, }; PIString ret = in; for (const auto & i: map) ret.replaceAll(i.second, i.first); return ret; } void addString(const PIString & s, const PIString & loc) { if (s.isEmpty()) return; strings << s; locations[s.hash()] = loc; } void gatherStrings(const PIValueTree & vt, const PIString & loc) { const static PIStringList attrs({Attribute::prefix, Attribute::suffix}); for (const auto & c: vt.children()) { addString(c.name(), loc); addString(c.comment(), loc); for (const auto & a: attrs) { if (c.attributes().contains(a)) { addString(c.attributes().value(a).toString(), loc); } } if (!c.isArray()) gatherStrings(c, loc); } } const PIString context = "QAD::PIValueTreeEdit"; struct TSMessage { PIString source; PIString translation; PIString type; PIString filename; PIString line; }; PIMap readTS(const PIString & path) { PIMap ret; PIFile f(path, PIIODevice::ReadOnly); if (!f.isOpened()) return ret; PIIOTextStream ts(&f); TSMessage msg; int phase = 0; while (!ts.isEnd()) { auto line = ts.readLine().trim(); switch (phase) { case 0: if (line == "") phase = 1; break; case 1: if (line.startsWith("")) { line.cutLeft(6).cutRight(7); if (line == context) phase = 2; } break; case 2: if (line == "") { msg = {}; phase = 3; } break; case 3: if (line == "") { ret[msg.source] = msg; phase = 2; } else if (line.startsWith("")) { line.cutLeft(8).cutRight(9); msg.source = unmask(line); } else if (line.startsWith("').cutLeft(8); while (trs.isNotEmpty()) { PIString t = trs.takeCWord(); trs.cutLeft(1); PIString v = trs.takeRange('\"', '\"'); if (t == "filename") msg.filename = v; if (t == "line") msg.line = v; if (trs.size_s() <= 2) break; } } else if (line.startsWith("').cutLeft(11); while (trs.isNotEmpty()) { PIString t = trs.takeCWord(); trs.cutLeft(1); PIString v = trs.takeRange('\"', '\"'); if (t == "type") msg.type = v; } line.cutRight(14); msg.translation = unmask(line); } break; } if (line == "") phase = 0; } return ret; } 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({"conf", "json", "bin"}); 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 << "Read" << files.size_s() << "files ..."; PIDir out_dir(PIFile::FileInfo(out_path).dir()); for (const auto & p: files) { PIString ext = PIFile::FileInfo(p).extension().toLowerCase().trim(); PIFile f(p, PIIODevice::ReadOnly); if (ext == "conf") gatherStrings(PIValueTreeConversions::fromText(PIString::fromUTF8(f.readAll())), out_dir.relative(p)); if (ext == "json") gatherStrings(PIValueTreeConversions::fromJSON(PIJSON::fromJSON(PIString::fromUTF8(f.readAll()))), out_dir.relative(p)); if (ext == "bin") gatherStrings(piDeserialize(f.readAll()), PIString()); } piCout << Cyan << Bold << "Reading done"; piCout << Cyan << Bold << "Reading ts file ..."; auto old = readTS(out_path); piCout << Cyan << Bold << "Reading done"; piCout << Cyan << Bold << "Writing ts file ..."; PIFile outf(out_path, PIIODevice::ReadWrite); if (!outf.isOpened()) { printError("Can`t open \"" + outf.path() + "\"!"); return 1; } outf.clear(); PIIOTextStream ts(&outf); auto writeTSMessage = [&ts](const PIString & s, const TSMessage & m) { ts << " \n"; ts << " " << mask(s) << "\n"; if (m.filename.isNotEmpty()) { ts << " \n"; } ts << " " << mask(m.translation) << "\n"; ts << " \n"; }; ts << "\n"; ts << "\n"; ts << "\n"; ts << "\n"; ts << " " << context << "\n"; for (const auto & s: strings) { TSMessage m = old.value(s); m.filename = locations.value(s.hash()); writeTSMessage(s, m); old.remove(s); } if (!cli.hasArgument("no-obsolete")) { for (const auto & i: old) { writeTSMessage(i.first, i.second); } } ts << "\n"; ts << "\n"; piCout << Cyan << Bold << "Writing done"; return 0; }