diff --git a/CMakeLists.txt b/CMakeLists.txt index 6264bba8..3eb5d1c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -563,6 +563,8 @@ if(NOT PIP_FREERTOS) add_subdirectory("utils/code_model_generator") add_subdirectory("utils/resources_compiler") add_subdirectory("utils/deploy_tool") + add_subdirectory("utils/qt_support") + add_subdirectory("utils/translator") add_subdirectory("utils/value_tree_translator") if(PIP_UTILS AND (NOT CROSSTOOLS)) add_subdirectory("utils/system_test") diff --git a/libs/main/application/pitranslator.cpp b/libs/main/application/pitranslator.cpp index 874c9a45..674fd4d6 100644 --- a/libs/main/application/pitranslator.cpp +++ b/libs/main/application/pitranslator.cpp @@ -66,6 +66,11 @@ void PITranslator::loadConfig(const PIString & content) { for (const auto & s: cn.children()) c->add(s.name(), s.value().toString()); } + auto c = s->createContextInternal(""); + for (const auto & s: lang.children()) { + if (s.hasChildren()) continue; + c->add(s.name(), s.value().toString()); + } } diff --git a/libs/main/application/pitranslator.h b/libs/main/application/pitranslator.h index 60eea97e..6ccdf47a 100644 --- a/libs/main/application/pitranslator.h +++ b/libs/main/application/pitranslator.h @@ -26,9 +26,10 @@ #ifndef pitranslator_H #define pitranslator_H -#include "pifile.h" -#include "piiostream.h" -#include "pithread.h" +#include "pistring.h" + + +#define piTr PITranslator::tr //! \ingroup Application //! \~\brief @@ -37,7 +38,7 @@ class PIP_EXPORT PITranslator { public: static PIString tr(const PIString & in, const PIString & context = {}); - static PIString tr(const char * in, const PIString & context = {}) { return tr(PIString(in), context); } + static PIString tr(const char * in, const PIString & context = {}) { return tr(PIString::fromUTF8(in), context); } static void clear(); static void loadLang(const PIString & short_lang); @@ -61,4 +62,11 @@ private: PIMap contexts; }; +//! \~\brief +//! \~english PIString from UTF-8 +//! \~russian PIString из UTF-8 +inline PIString operator""_tr(const char * v, size_t sz) { + return PITranslator::tr(PIString::fromUTF8(v, sz)); +} + #endif diff --git a/libs/main/text/pichar.h b/libs/main/text/pichar.h index 0fe96237..1e51d89c 100644 --- a/libs/main/text/pichar.h +++ b/libs/main/text/pichar.h @@ -99,8 +99,8 @@ public: //! \~russian Оператор сравнения bool operator<=(const PIChar & o) const; - //! \~english Returns \b true if symbol is digit ('0' to '9') - //! \~russian Возвращает \b true если символ является + //! \~english Returns \b true if symbol is digit (from '0' to '9') + //! \~russian Возвращает \b true если символ является цифрой (от '0' до '9') bool isDigit() const; //! \~english Returns \b true if symbol is HEX digit ('0' to '9', 'a' to 'f', 'A' to 'F') diff --git a/libs/main/text/pistring.cpp b/libs/main/text/pistring.cpp index dc719ff6..a8d02619 100644 --- a/libs/main/text/pistring.cpp +++ b/libs/main/text/pistring.cpp @@ -1209,6 +1209,17 @@ int PIString::entries(const PIString & str) const { } +int PIString::lineNumber(int pos) const { + if (isEmpty()) return 0; + if (pos < 0 || pos >= size_s()) pos = size_s() - 1; + int line = 1; + for (int i = 0; i < pos; ++i) { + if (at(i) == '\n') ++line; + } + return line; +} + + bool PIString::startsWith(const PIChar c) const { if (isEmpty()) return false; return front() == c; @@ -1736,7 +1747,7 @@ PIString & PIString::setReadableSize(llong bytes) { static const PIString tr_c = "PIString"_a; clear(); if (bytes < 1024) { - *this += (PIString::fromNumber(bytes) + " "_a + PITranslator::tr("B", tr_c)); + *this += (PIString::fromNumber(bytes) + " "_a + PITranslator::tr("B", "PIString")); return *this; } double fres = bytes; @@ -1751,13 +1762,13 @@ PIString & PIString::setReadableSize(llong bytes) { } return false; }; - if (checkRange(PITranslator::tr("KiB", tr_c))) return *this; - if (checkRange(PITranslator::tr("MiB", tr_c))) return *this; - if (checkRange(PITranslator::tr("GiB", tr_c))) return *this; - if (checkRange(PITranslator::tr("TiB", tr_c))) return *this; - if (checkRange(PITranslator::tr("PiB", tr_c))) return *this; - if (checkRange(PITranslator::tr("EiB", tr_c))) return *this; - if (checkRange(PITranslator::tr("ZiB", tr_c))) return *this; + if (checkRange(PITranslator::tr("KiB", "PIString"))) return *this; + if (checkRange(PITranslator::tr("MiB", "PIString"))) return *this; + if (checkRange(PITranslator::tr("GiB", "PIString"))) return *this; + if (checkRange(PITranslator::tr("TiB", "PIString"))) return *this; + if (checkRange(PITranslator::tr("PiB", "PIString"))) return *this; + if (checkRange(PITranslator::tr("EiB", "PIString"))) return *this; + if (checkRange(PITranslator::tr("ZiB", "PIString"))) return *this; checkRange(PITranslator::tr("YiB", tr_c), true); return *this; } diff --git a/libs/main/text/pistring.h b/libs/main/text/pistring.h index b4f5767f..02bec59f 100644 --- a/libs/main/text/pistring.h +++ b/libs/main/text/pistring.h @@ -1282,6 +1282,10 @@ public: //! \endcode int entries(const PIString & str) const; + //! \~english Returns line number of position "pos". Lines starts from 1. + //! \~russian Возвращает номер строки позиции "pos". Строки начинаются с 1. + int lineNumber(int pos) const; + //! \~english Returns if string starts with "c". //! \~russian Возвращает начинается ли строка с "c". bool startsWith(const char c) const { return startsWith(PIChar(c)); } diff --git a/main.cpp b/main.cpp index a3a8adb5..9634f88f 100644 --- a/main.cpp +++ b/main.cpp @@ -8,7 +8,6 @@ #include "pilog.h" #include "pimathbase.h" #include "pip.h" -#include "pitranslator.h" #include "pivaluetree_conversions.h" using namespace PICoutManipulators; @@ -21,12 +20,17 @@ void foo() { int main(int argc, char * argv[]) { + // piCout << PIString::readableSize(50_KiB); + // piCout << PIString::readableSize(1_GB); PITranslator::loadLang("ru"); - piCout << PIString::readableSize(50_KiB); - piCout << PIString::readableSize(1_GB); + PITranslator::loadConfig("[C]\ntest string=\n"_u8); + piCout << "test string1"; + piCout << "test string2"_tr; + piCout << piTr("test string", "C1"); PITranslator::clear(); - piCout << PIString::readableSize(50_KiB); - piCout << PIString::readableSize(1_GB); + piCout << "test string3"; + piCout << "test string4"_tr; + piCout << piTr("test string", "C2"); // PICodeParser parser; // parser.parseFile("cmg_test.h"); /*for (auto m: parser.macros) { diff --git a/utils/qt_support/CMakeLists.txt b/utils/qt_support/CMakeLists.txt new file mode 100644 index 00000000..2b1c6669 --- /dev/null +++ b/utils/qt_support/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SRC "*.h" "*.cpp") +add_library(pip_qt_support STATIC ${SRC}) +target_include_directories(pip_qt_support PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(pip_qt_support pip) diff --git a/utils/qt_support/ts_file.cpp b/utils/qt_support/ts_file.cpp new file mode 100644 index 00000000..f3a26192 --- /dev/null +++ b/utils/qt_support/ts_file.cpp @@ -0,0 +1,175 @@ +/* + PIP - Platform Independent Primitives + 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 "ts_file.h" + +#include "pifile.h" +#include "piiostream.h" + + +void TSFile::Context::confirm(const PIString & msg, const PIString & file, int line) { + if (msg.isEmpty()) return; + bool is_new = !messages.contains(msg); + auto & m(messages[msg]); + m.source = msg; + m.filename = file; + m.line = line >= 0 ? PIString::fromNumber(line) : PIString(); + m.obsolete = false; + if (is_new) m.type = "unfinished"; +} + + +PIString TSFile::mask(const PIString & in) { + static const PIVector> map = { + {"&", "&" }, + {"<", "<" }, + {">", ">" }, + {"'", "'"}, + {"\"", """}, + }; + PIString ret = in; + for (const auto & i: map) + ret.replaceAll(i.first, i.second); + return ret; +} + + +PIString TSFile::unmask(const PIString & in) { + static const PIVector> map = { + {"<", "<" }, + {">", ">" }, + {"'", "'"}, + {"\"", """}, + {"&", "&" }, + }; + PIString ret = in; + for (const auto & i: map) + ret.replaceAll(i.second, i.first); + return ret; +} + + +TSFile::Content TSFile::read(const PIString & path) { + Content ret; + PIFile f(path, PIIODevice::ReadOnly); + if (!f.isOpened()) return ret; + PIIOTextStream ts(&f); + Context * cc = nullptr; + Message 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; + cc = &(ret[line]); + phase = 2; + } + break; + case 2: + if (line == "") { + msg = {}; + phase = 3; + } + break; + case 3: + if (line == "") { + if (cc) cc->messages[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 == "") { + cc = nullptr; + phase = 0; + } + } + return ret; +} + + +bool TSFile::write(const PIString & path, const TSFile::Content & content, const PIString & lang, bool no_obsolete) { + PIFile outf(path, PIIODevice::ReadWrite); + if (!outf.isOpened()) { + piCerr << "Can`t open" << outf.path() << "!"; + return false; + } + outf.clear(); + PIIOTextStream ts(&outf); + auto writeMessage = [&ts](const Message & m) { + if (m.source.isEmpty()) return; + ts << " \n"; + if (m.filename.isNotEmpty()) { + ts << " \n"; + } + ts << " " << mask(m.source) << "\n"; + ts << " " << mask(m.translation) << "\n"; + ts << " \n"; + }; + + ts << "\n"; + ts << "\n"; + ts << "\n"; + auto cit = content.makeIterator(); + while (cit.next()) { + if (cit.value().messages.isEmpty()) continue; + ts << "\n"; + ts << " " << cit.key() << "\n"; + auto mit = cit.value().messages.makeIterator(); + while (mit.next()) { + if (mit.value().obsolete && no_obsolete) continue; + writeMessage(mit.value()); + } + ts << "\n"; + }; + ts << "\n"; + + return true; +} diff --git a/utils/qt_support/ts_file.h b/utils/qt_support/ts_file.h new file mode 100644 index 00000000..8ea6c126 --- /dev/null +++ b/utils/qt_support/ts_file.h @@ -0,0 +1,45 @@ +/* + PIP - Platform Independent Primitives + 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 "pistring.h" + +namespace TSFile { + +struct Message { + PIString source; + PIString translation; + PIString type; + PIString filename; + PIString line; + bool obsolete = true; +}; + +struct Context { + PIMap messages; + void confirm(const PIString & msg, const PIString & file, int line = -1); +}; + +using Content = PIMap; + +PIString mask(const PIString & in); +PIString unmask(const PIString & in); + +Content read(const PIString & path); +bool write(const PIString & path, const Content & content, const PIString & lang, bool no_obsolete); + +} // namespace TSFile diff --git a/utils/translator/CMakeLists.txt b/utils/translator/CMakeLists.txt new file mode 100644 index 00000000..d3ae9f7d --- /dev/null +++ b/utils/translator/CMakeLists.txt @@ -0,0 +1,16 @@ +list(APPEND PIP_UTILS_LIST "pip_tr") +set(PIP_UTILS_LIST ${PIP_UTILS_LIST} PARENT_SCOPE) +import_version(pip_tr PIP) +set_deploy_property(pip_tr + LABEL "PIP Translator" + FULLNAME "${PIP_DOMAIN}.pip_tr" + COMPANY "${PIP_COMPANY}" + INFO "Platform-Independent Primitives") +make_rc(pip_tr _RC) +add_executable(pip_tr "main.cpp" ${_RC}) +target_link_libraries(pip_tr pip_qt_support pip) +if (DEFINED LIB) + install(TARGETS pip_tr DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +else() + install(TARGETS pip_tr DESTINATION bin) +endif() diff --git a/utils/translator/main.cpp b/utils/translator/main.cpp new file mode 100644 index 00000000..819a521d --- /dev/null +++ b/utils/translator/main.cpp @@ -0,0 +1,220 @@ +/* + 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; +} + + +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(source, file_loc, line); + pos = ppos; + piCout << "Context = \"" << context << "\", message = \"" << source << "\""; + } + } + + auto & ec(content[""]); + content[context].confirm(source, file_loc, line); + 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(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; +} diff --git a/utils/value_tree_translator/CMakeLists.txt b/utils/value_tree_translator/CMakeLists.txt index 22139e2f..906082f5 100644 --- a/utils/value_tree_translator/CMakeLists.txt +++ b/utils/value_tree_translator/CMakeLists.txt @@ -8,7 +8,7 @@ set_deploy_property(pip_vtt INFO "Platform-Independent Primitives") make_rc(pip_vtt _RC) add_executable(pip_vtt "main.cpp" ${_RC}) -target_link_libraries(pip_vtt pip) +target_link_libraries(pip_vtt pip_qt_support pip) if (DEFINED LIB) install(TARGETS pip_vtt DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) else() diff --git a/utils/value_tree_translator/main.cpp b/utils/value_tree_translator/main.cpp index ce5747f1..0a3a0cde 100644 --- a/utils/value_tree_translator/main.cpp +++ b/utils/value_tree_translator/main.cpp @@ -20,12 +20,10 @@ #include "picli.h" #include "pidir.h" #include "pifile.h" -#include "piiostream.h" #include "pijson.h" #include "pivaluetree.h" #include "pivaluetree_conversions.h" - -#include +#include "ts_file.h" using namespace PICoutManipulators; using Attribute = PIValueTree::Attribute; @@ -69,132 +67,24 @@ void help() { piCout << help_string; } -void printError(const PIString & msg) { - std::cerr << msg.data() << std::endl; -} + +const PIString contextName = "QAD::PIValueTreeEdit"; -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) { +void gatherStrings(TSFile::Context & context, 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); + context.confirm(c.name(), loc); + context.confirm(c.comment(), loc); for (const auto & a: attrs) { if (c.attributes().contains(a)) { - addString(c.attributes().value(a).toString(), loc); + context.confirm(c.attributes().value(a).toString(), loc); } } - if (!c.isArray()) gatherStrings(c, loc); + if (!c.isArray()) gatherStrings(context, 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); @@ -232,74 +122,28 @@ int main(int argc, char * argv[]) { } } + 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 ..."; + auto & context(content[contextName]); 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 == "conf") gatherStrings(context, 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()); + gatherStrings(context, + PIValueTreeConversions::fromJSON(PIJSON::fromJSON(PIString::fromUTF8(f.readAll()))), + out_dir.relative(p)); + if (ext == "bin") gatherStrings(context, 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"; - + if (!TSFile::write(out_path, content, cli.argumentValue("language"), cli.hasArgument("no-obsolete"))) return 1; piCout << Cyan << Bold << "Writing done"; - return 0; }