add PIString::lineNumber() method
add ""_tr literal to translate string by PITranslator add pip_tr util, now useless, only can generate *.ts add qt_support internal lib, now only works with *.ts file pip_vtt migrate to qt_support
This commit is contained in:
@@ -563,6 +563,8 @@ if(NOT PIP_FREERTOS)
|
|||||||
add_subdirectory("utils/code_model_generator")
|
add_subdirectory("utils/code_model_generator")
|
||||||
add_subdirectory("utils/resources_compiler")
|
add_subdirectory("utils/resources_compiler")
|
||||||
add_subdirectory("utils/deploy_tool")
|
add_subdirectory("utils/deploy_tool")
|
||||||
|
add_subdirectory("utils/qt_support")
|
||||||
|
add_subdirectory("utils/translator")
|
||||||
add_subdirectory("utils/value_tree_translator")
|
add_subdirectory("utils/value_tree_translator")
|
||||||
if(PIP_UTILS AND (NOT CROSSTOOLS))
|
if(PIP_UTILS AND (NOT CROSSTOOLS))
|
||||||
add_subdirectory("utils/system_test")
|
add_subdirectory("utils/system_test")
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ void PITranslator::loadConfig(const PIString & content) {
|
|||||||
for (const auto & s: cn.children())
|
for (const auto & s: cn.children())
|
||||||
c->add(s.name(), s.value().toString());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,10 @@
|
|||||||
#ifndef pitranslator_H
|
#ifndef pitranslator_H
|
||||||
#define pitranslator_H
|
#define pitranslator_H
|
||||||
|
|
||||||
#include "pifile.h"
|
#include "pistring.h"
|
||||||
#include "piiostream.h"
|
|
||||||
#include "pithread.h"
|
|
||||||
|
#define piTr PITranslator::tr
|
||||||
|
|
||||||
//! \ingroup Application
|
//! \ingroup Application
|
||||||
//! \~\brief
|
//! \~\brief
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
class PIP_EXPORT PITranslator {
|
class PIP_EXPORT PITranslator {
|
||||||
public:
|
public:
|
||||||
static PIString tr(const PIString & in, const PIString & context = {});
|
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 clear();
|
||||||
static void loadLang(const PIString & short_lang);
|
static void loadLang(const PIString & short_lang);
|
||||||
@@ -61,4 +62,11 @@ private:
|
|||||||
PIMap<uint, Context *> contexts;
|
PIMap<uint, Context *> 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
|
#endif
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ public:
|
|||||||
//! \~russian Оператор сравнения
|
//! \~russian Оператор сравнения
|
||||||
bool operator<=(const PIChar & o) const;
|
bool operator<=(const PIChar & o) const;
|
||||||
|
|
||||||
//! \~english Returns \b true if symbol is digit ('0' to '9')
|
//! \~english Returns \b true if symbol is digit (from '0' to '9')
|
||||||
//! \~russian Возвращает \b true если символ является
|
//! \~russian Возвращает \b true если символ является цифрой (от '0' до '9')
|
||||||
bool isDigit() const;
|
bool isDigit() const;
|
||||||
|
|
||||||
//! \~english Returns \b true if symbol is HEX digit ('0' to '9', 'a' to 'f', 'A' to 'F')
|
//! \~english Returns \b true if symbol is HEX digit ('0' to '9', 'a' to 'f', 'A' to 'F')
|
||||||
|
|||||||
@@ -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 {
|
bool PIString::startsWith(const PIChar c) const {
|
||||||
if (isEmpty()) return false;
|
if (isEmpty()) return false;
|
||||||
return front() == c;
|
return front() == c;
|
||||||
@@ -1736,7 +1747,7 @@ PIString & PIString::setReadableSize(llong bytes) {
|
|||||||
static const PIString tr_c = "PIString"_a;
|
static const PIString tr_c = "PIString"_a;
|
||||||
clear();
|
clear();
|
||||||
if (bytes < 1024) {
|
if (bytes < 1024) {
|
||||||
*this += (PIString::fromNumber(bytes) + " "_a + PITranslator::tr("B", tr_c));
|
*this += (PIString::fromNumber(bytes) + " "_a + PITranslator::tr("B", "PIString"));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
double fres = bytes;
|
double fres = bytes;
|
||||||
@@ -1751,13 +1762,13 @@ PIString & PIString::setReadableSize(llong bytes) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (checkRange(PITranslator::tr("KiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("KiB", "PIString"))) return *this;
|
||||||
if (checkRange(PITranslator::tr("MiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("MiB", "PIString"))) return *this;
|
||||||
if (checkRange(PITranslator::tr("GiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("GiB", "PIString"))) return *this;
|
||||||
if (checkRange(PITranslator::tr("TiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("TiB", "PIString"))) return *this;
|
||||||
if (checkRange(PITranslator::tr("PiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("PiB", "PIString"))) return *this;
|
||||||
if (checkRange(PITranslator::tr("EiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("EiB", "PIString"))) return *this;
|
||||||
if (checkRange(PITranslator::tr("ZiB", tr_c))) return *this;
|
if (checkRange(PITranslator::tr("ZiB", "PIString"))) return *this;
|
||||||
checkRange(PITranslator::tr("YiB", tr_c), true);
|
checkRange(PITranslator::tr("YiB", tr_c), true);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1282,6 +1282,10 @@ public:
|
|||||||
//! \endcode
|
//! \endcode
|
||||||
int entries(const PIString & str) const;
|
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".
|
//! \~english Returns if string starts with "c".
|
||||||
//! \~russian Возвращает начинается ли строка с "c".
|
//! \~russian Возвращает начинается ли строка с "c".
|
||||||
bool startsWith(const char c) const { return startsWith(PIChar(c)); }
|
bool startsWith(const char c) const { return startsWith(PIChar(c)); }
|
||||||
|
|||||||
14
main.cpp
14
main.cpp
@@ -8,7 +8,6 @@
|
|||||||
#include "pilog.h"
|
#include "pilog.h"
|
||||||
#include "pimathbase.h"
|
#include "pimathbase.h"
|
||||||
#include "pip.h"
|
#include "pip.h"
|
||||||
#include "pitranslator.h"
|
|
||||||
#include "pivaluetree_conversions.h"
|
#include "pivaluetree_conversions.h"
|
||||||
|
|
||||||
using namespace PICoutManipulators;
|
using namespace PICoutManipulators;
|
||||||
@@ -21,12 +20,17 @@ void foo() {
|
|||||||
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
int main(int argc, char * argv[]) {
|
||||||
|
// piCout << PIString::readableSize(50_KiB);
|
||||||
|
// piCout << PIString::readableSize(1_GB);
|
||||||
PITranslator::loadLang("ru");
|
PITranslator::loadLang("ru");
|
||||||
piCout << PIString::readableSize(50_KiB);
|
PITranslator::loadConfig("[C]\ntest string=\n"_u8);
|
||||||
piCout << PIString::readableSize(1_GB);
|
piCout << "test string1";
|
||||||
|
piCout << "test string2"_tr;
|
||||||
|
piCout << piTr("test string", "C1");
|
||||||
PITranslator::clear();
|
PITranslator::clear();
|
||||||
piCout << PIString::readableSize(50_KiB);
|
piCout << "test string3";
|
||||||
piCout << PIString::readableSize(1_GB);
|
piCout << "test string4"_tr;
|
||||||
|
piCout << piTr("test string", "C2");
|
||||||
// PICodeParser parser;
|
// PICodeParser parser;
|
||||||
// parser.parseFile("cmg_test.h");
|
// parser.parseFile("cmg_test.h");
|
||||||
/*for (auto m: parser.macros) {
|
/*for (auto m: parser.macros) {
|
||||||
|
|||||||
4
utils/qt_support/CMakeLists.txt
Normal file
4
utils/qt_support/CMakeLists.txt
Normal file
@@ -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)
|
||||||
175
utils/qt_support/ts_file.cpp
Normal file
175
utils/qt_support/ts_file.cpp
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<PIPair<PIString, PIString>> 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<PIPair<PIString, PIString>> 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 == "<context>") phase = 1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (line.startsWith("<name>")) {
|
||||||
|
line.cutLeft(6).cutRight(7);
|
||||||
|
// if (line == context) phase = 2;
|
||||||
|
cc = &(ret[line]);
|
||||||
|
phase = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (line == "<message>") {
|
||||||
|
msg = {};
|
||||||
|
phase = 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (line == "</message>") {
|
||||||
|
if (cc) cc->messages[msg.source] = msg;
|
||||||
|
phase = 2;
|
||||||
|
} else if (line.startsWith("<source>")) {
|
||||||
|
line.cutLeft(8).cutRight(9);
|
||||||
|
msg.source = unmask(line);
|
||||||
|
} else if (line.startsWith("<location")) {
|
||||||
|
PIString trs = line.takeRange('<', '>').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("<translation")) {
|
||||||
|
PIString trs = line.takeRange('<', '>').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 == "</context>") {
|
||||||
|
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 << " <message>\n";
|
||||||
|
if (m.filename.isNotEmpty()) {
|
||||||
|
ts << " <location filename=\"" << m.filename << "\"";
|
||||||
|
if (m.line.isNotEmpty()) ts << " line=\"" << m.line << "\"";
|
||||||
|
ts << "/>\n";
|
||||||
|
}
|
||||||
|
ts << " <source>" << mask(m.source) << "</source>\n";
|
||||||
|
ts << " <translation";
|
||||||
|
if (m.type.isNotEmpty()) ts << " type=\"" << m.type << "\"";
|
||||||
|
ts << ">" << mask(m.translation) << "</translation>\n";
|
||||||
|
ts << " </message>\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
ts << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||||
|
ts << "<!DOCTYPE TS>\n";
|
||||||
|
ts << "<TS version=\"2.1\" language=\"" << lang << "\">\n";
|
||||||
|
auto cit = content.makeIterator();
|
||||||
|
while (cit.next()) {
|
||||||
|
if (cit.value().messages.isEmpty()) continue;
|
||||||
|
ts << "<context>\n";
|
||||||
|
ts << " <name>" << cit.key() << "</name>\n";
|
||||||
|
auto mit = cit.value().messages.makeIterator();
|
||||||
|
while (mit.next()) {
|
||||||
|
if (mit.value().obsolete && no_obsolete) continue;
|
||||||
|
writeMessage(mit.value());
|
||||||
|
}
|
||||||
|
ts << "</context>\n";
|
||||||
|
};
|
||||||
|
ts << "</TS>\n";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
45
utils/qt_support/ts_file.h
Normal file
45
utils/qt_support/ts_file.h
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pistring.h"
|
||||||
|
|
||||||
|
namespace TSFile {
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
PIString source;
|
||||||
|
PIString translation;
|
||||||
|
PIString type;
|
||||||
|
PIString filename;
|
||||||
|
PIString line;
|
||||||
|
bool obsolete = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
PIMap<PIString, Message> messages;
|
||||||
|
void confirm(const PIString & msg, const PIString & file, int line = -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
using Content = PIMap<PIString, Context>;
|
||||||
|
|
||||||
|
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
|
||||||
16
utils/translator/CMakeLists.txt
Normal file
16
utils/translator/CMakeLists.txt
Normal file
@@ -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()
|
||||||
220
utils/translator/main.cpp
Normal file
220
utils/translator/main.cpp
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 <lang> -o <output_file> <file1/dir1> [<file2/dir2>] [<file3/dir3>] [...]\"" << 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 <lang> " << Green << "- translation language (e.g. en_US, ru_RU)";
|
||||||
|
piCout << "-n, --no-obsolete " << Green << "- drop unused translations in output file";
|
||||||
|
piCout << "-o <output_file> " << Green << "- output file for translation (QtLinguist *.ts file)";
|
||||||
|
piCout << "";
|
||||||
|
piCout << Bold << "Input control";
|
||||||
|
piCout << "<file/dir> " << 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;
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ set_deploy_property(pip_vtt
|
|||||||
INFO "Platform-Independent Primitives")
|
INFO "Platform-Independent Primitives")
|
||||||
make_rc(pip_vtt _RC)
|
make_rc(pip_vtt _RC)
|
||||||
add_executable(pip_vtt "main.cpp" ${_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)
|
if (DEFINED LIB)
|
||||||
install(TARGETS pip_vtt DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
install(TARGETS pip_vtt DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -20,12 +20,10 @@
|
|||||||
#include "picli.h"
|
#include "picli.h"
|
||||||
#include "pidir.h"
|
#include "pidir.h"
|
||||||
#include "pifile.h"
|
#include "pifile.h"
|
||||||
#include "piiostream.h"
|
|
||||||
#include "pijson.h"
|
#include "pijson.h"
|
||||||
#include "pivaluetree.h"
|
#include "pivaluetree.h"
|
||||||
#include "pivaluetree_conversions.h"
|
#include "pivaluetree_conversions.h"
|
||||||
|
#include "ts_file.h"
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using namespace PICoutManipulators;
|
using namespace PICoutManipulators;
|
||||||
using Attribute = PIValueTree::Attribute;
|
using Attribute = PIValueTree::Attribute;
|
||||||
@@ -69,132 +67,24 @@ void help() {
|
|||||||
piCout << help_string;
|
piCout << help_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
void printError(const PIString & msg) {
|
|
||||||
std::cerr << msg.data() << std::endl;
|
const PIString contextName = "QAD::PIValueTreeEdit";
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PISet<PIString> strings;
|
void gatherStrings(TSFile::Context & context, const PIValueTree & vt, const PIString & loc) {
|
||||||
PIMap<uint, PIString> locations;
|
|
||||||
|
|
||||||
PIString mask(const PIString & in) {
|
|
||||||
static PIVector<PIPair<PIString, PIString>> map = {
|
|
||||||
{"&", "&" },
|
|
||||||
{"<", "<" },
|
|
||||||
{">", ">" },
|
|
||||||
{"'", "'"},
|
|
||||||
{"\"", """},
|
|
||||||
};
|
|
||||||
PIString ret = in;
|
|
||||||
for (const auto & i: map)
|
|
||||||
ret.replaceAll(i.first, i.second);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
PIString unmask(const PIString & in) {
|
|
||||||
static PIVector<PIPair<PIString, PIString>> 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});
|
const static PIStringList attrs({Attribute::prefix, Attribute::suffix});
|
||||||
for (const auto & c: vt.children()) {
|
for (const auto & c: vt.children()) {
|
||||||
addString(c.name(), loc);
|
context.confirm(c.name(), loc);
|
||||||
addString(c.comment(), loc);
|
context.confirm(c.comment(), loc);
|
||||||
for (const auto & a: attrs) {
|
for (const auto & a: attrs) {
|
||||||
if (c.attributes().contains(a)) {
|
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<PIString, TSMessage> readTS(const PIString & path) {
|
|
||||||
PIMap<PIString, TSMessage> 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 == "<context>") phase = 1;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
if (line.startsWith("<name>")) {
|
|
||||||
line.cutLeft(6).cutRight(7);
|
|
||||||
if (line == context) phase = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (line == "<message>") {
|
|
||||||
msg = {};
|
|
||||||
phase = 3;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
if (line == "</message>") {
|
|
||||||
ret[msg.source] = msg;
|
|
||||||
phase = 2;
|
|
||||||
} else if (line.startsWith("<source>")) {
|
|
||||||
line.cutLeft(8).cutRight(9);
|
|
||||||
msg.source = unmask(line);
|
|
||||||
} else if (line.startsWith("<location")) {
|
|
||||||
PIString trs = line.takeRange('<', '>').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("<translation")) {
|
|
||||||
PIString trs = line.takeRange('<', '>').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 == "</context>") phase = 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
int main(int argc, char * argv[]) {
|
||||||
PICLI cli(argc, 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 ...";
|
piCout << Cyan << Bold << "Read" << files.size_s() << "files ...";
|
||||||
|
auto & context(content[contextName]);
|
||||||
PIDir out_dir(PIFile::FileInfo(out_path).dir());
|
PIDir out_dir(PIFile::FileInfo(out_path).dir());
|
||||||
for (const auto & p: files) {
|
for (const auto & p: files) {
|
||||||
PIString ext = PIFile::FileInfo(p).extension().toLowerCase().trim();
|
PIString ext = PIFile::FileInfo(p).extension().toLowerCase().trim();
|
||||||
PIFile f(p, PIIODevice::ReadOnly);
|
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")
|
if (ext == "json")
|
||||||
gatherStrings(PIValueTreeConversions::fromJSON(PIJSON::fromJSON(PIString::fromUTF8(f.readAll()))), out_dir.relative(p));
|
gatherStrings(context,
|
||||||
if (ext == "bin") gatherStrings(piDeserialize<PIValueTree>(f.readAll()), PIString());
|
PIValueTreeConversions::fromJSON(PIJSON::fromJSON(PIString::fromUTF8(f.readAll()))),
|
||||||
|
out_dir.relative(p));
|
||||||
|
if (ext == "bin") gatherStrings(context, piDeserialize<PIValueTree>(f.readAll()), PIString());
|
||||||
}
|
}
|
||||||
piCout << Cyan << Bold << "Reading done";
|
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 ...";
|
piCout << Cyan << Bold << "Writing ts file ...";
|
||||||
|
if (!TSFile::write(out_path, content, cli.argumentValue("language"), cli.hasArgument("no-obsolete"))) return 1;
|
||||||
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 << " <message>\n";
|
|
||||||
ts << " <source>" << mask(s) << "</source>\n";
|
|
||||||
if (m.filename.isNotEmpty()) {
|
|
||||||
ts << " <location filename=\"" << m.filename << "\"";
|
|
||||||
if (m.line.isNotEmpty()) ts << " line=\"" << m.line << "\"";
|
|
||||||
ts << "/>\n";
|
|
||||||
}
|
|
||||||
ts << " <translation";
|
|
||||||
if (m.source.isEmpty()) {
|
|
||||||
ts << " type=\"unfinished\"";
|
|
||||||
} else {
|
|
||||||
if (m.type.isNotEmpty()) ts << " type=\"" << m.type << "\"";
|
|
||||||
}
|
|
||||||
ts << ">" << mask(m.translation) << "</translation>\n";
|
|
||||||
ts << " </message>\n";
|
|
||||||
};
|
|
||||||
ts << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
|
||||||
ts << "<!DOCTYPE TS>\n";
|
|
||||||
ts << "<TS version=\"2.1\" language=\"" << cli.argumentValue("language") << "\">\n";
|
|
||||||
ts << "<context>\n";
|
|
||||||
ts << " <name>" << context << "</name>\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 << "</context>\n";
|
|
||||||
ts << "</TS>\n";
|
|
||||||
|
|
||||||
piCout << Cyan << Bold << "Writing done";
|
piCout << Cyan << Bold << "Writing done";
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user