first release of translation facility
* runtime - loading and translating * design-time - works with *.ts file (pip_tr utility) * compile-time - CMake macro for compile *.ts
This commit is contained in:
@@ -20,8 +20,7 @@
|
||||
#include "picli.h"
|
||||
#include "picodeparser.h"
|
||||
#include "piiostream.h"
|
||||
|
||||
#include <iostream>
|
||||
#include "pitranslator.h"
|
||||
|
||||
using namespace PICoutManipulators;
|
||||
|
||||
@@ -107,10 +106,6 @@ void help() {
|
||||
piCout << help_string;
|
||||
}
|
||||
|
||||
void printError(const PIString & msg) {
|
||||
std::cerr << msg.data() << std::endl;
|
||||
}
|
||||
|
||||
|
||||
PIString toCName(const PIString & s) {
|
||||
PIString ret(s.trimmed());
|
||||
@@ -307,7 +302,7 @@ bool writeClassStreamMembersOut(PIIOTextStream & ts, const PICodeParser::Entity
|
||||
++cnt;
|
||||
if (m.meta.contains("id")) cnt = m.meta.value("id").toInt();
|
||||
if (used_id[cnt]) {
|
||||
printError("Error with \"" + e->name + "\" stream operator: ID " + PIString::fromNumber(cnt) + " already used");
|
||||
piCerr << "Error with \"%1\" stream operator: ID %2 already used"_tr("pip_cmg").arg(e->name).arg(cnt);
|
||||
return false;
|
||||
}
|
||||
if (m.dims.isEmpty()) {
|
||||
@@ -361,7 +356,7 @@ bool writeClassStreamMembersIn(PIIOTextStream & ts, const PICodeParser::Entity *
|
||||
++cnt;
|
||||
if (m.meta.contains("id")) cnt = m.meta.value("id").toInt();
|
||||
if (used_id[cnt]) {
|
||||
printError("Error with \"" + e->name + "\" stream operator: ID " + PIString::fromNumber(cnt) + " already used");
|
||||
piCerr << "Error with \"%1\" stream operator: ID %2 already used"_tr("pip_cmg").arg(e->name).arg(cnt);
|
||||
return false;
|
||||
}
|
||||
used_id << cnt;
|
||||
@@ -496,7 +491,7 @@ bool writeModel(PICodeParser & parser, PICLI & cli, const PIString out, bool met
|
||||
PIFile f(out + ".cpp");
|
||||
f.clear();
|
||||
if (!f.open(PIIODevice::WriteOnly)) {
|
||||
piCout << "Error: can`t open out file" << f.path();
|
||||
piCerr << "Error: can`t open output file \"%1\""_tr("pip_cmg").arg(f.path());
|
||||
return false;
|
||||
}
|
||||
PIIOTextStream ts(&f);
|
||||
@@ -585,7 +580,7 @@ bool writeModel(PICodeParser & parser, PICLI & cli, const PIString out, bool met
|
||||
f.setPath(out + ".h");
|
||||
f.clear();
|
||||
if (!f.open(PIIODevice::WriteOnly)) {
|
||||
piCout << "Error: can`t open out file" << f.path();
|
||||
piCerr << "Error: can`t open output file \"%1\""_tr("pip_cmg").arg(f.path());
|
||||
return false;
|
||||
}
|
||||
ts << "// Generated by \"PIP Code model generator\" " << PIDateTime::current().toString("dd.MM.yyyy hh:mm:ss\n");
|
||||
|
||||
@@ -640,7 +640,7 @@ int main(int argc, char * argv[]) {
|
||||
if (!objdump.isEmpty()) ++etcnt;
|
||||
if (!otool.isEmpty()) ++etcnt;
|
||||
if (etcnt > 1) {
|
||||
piCout << "Can use only one of \"readelf\", \"objdump\" and \"otool\"!";
|
||||
piCerr << "Can use only one of \"readelf\", \"objdump\" and \"otool\"!";
|
||||
return 1;
|
||||
}
|
||||
if (etcnt > 0) is_ldd = false;
|
||||
|
||||
@@ -20,17 +20,19 @@
|
||||
|
||||
#include "pifile.h"
|
||||
#include "piiostream.h"
|
||||
#include "pitranslator.h"
|
||||
|
||||
|
||||
void TSFile::Context::confirm(const PIString & msg, const PIString & file, int line) {
|
||||
if (msg.isEmpty()) return;
|
||||
bool TSFile::Context::confirm(const PIString & msg, const PIString & file, int line) {
|
||||
if (msg.isEmpty()) return false;
|
||||
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";
|
||||
m.type.setFlag(Message::Missing, false);
|
||||
if (is_new) m.type.setFlag(Message::Unfinished);
|
||||
return is_new;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,14 +67,21 @@ PIString TSFile::unmask(const PIString & in) {
|
||||
|
||||
|
||||
TSFile::Content TSFile::read(const PIString & path) {
|
||||
enum Phase {
|
||||
pHeader,
|
||||
pContext,
|
||||
pContextName,
|
||||
pMessage,
|
||||
pMessageBody,
|
||||
};
|
||||
Content ret;
|
||||
PIFile f(path, PIIODevice::ReadOnly);
|
||||
if (!f.isOpened()) return ret;
|
||||
PIIOTextStream ts(&f);
|
||||
Context * cc = nullptr;
|
||||
Message msg;
|
||||
int phase = 0;
|
||||
bool multi_source = false, multi_translation = false;
|
||||
int phase = pHeader;
|
||||
bool multi_source = false, multi_translation = false, multi_comment = false;
|
||||
while (!ts.isEnd()) {
|
||||
auto line = ts.readLine().trim();
|
||||
if (multi_source) {
|
||||
@@ -91,28 +100,45 @@ TSFile::Content TSFile::read(const PIString & path) {
|
||||
msg.translation += "\n" + unmask(line);
|
||||
continue;
|
||||
}
|
||||
if (multi_comment) {
|
||||
if (line.endsWith("</translatorcomment>")) {
|
||||
line.cutRight(20);
|
||||
multi_comment = false;
|
||||
}
|
||||
msg.comment += "\n" + unmask(line);
|
||||
continue;
|
||||
}
|
||||
switch (phase) {
|
||||
case 0:
|
||||
if (line == "<context>") phase = 1;
|
||||
case pHeader:
|
||||
if (line.startsWith("<TS")) {
|
||||
int ind = line.find("language");
|
||||
line.remove(0, ind + 8);
|
||||
line.takeSymbol();
|
||||
ret.lang = line.takeRange('"', '"');
|
||||
// piCout << "lang" << ret.lang;
|
||||
phase = pContext;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case pContext:
|
||||
if (line == "<context>") phase = pContextName;
|
||||
break;
|
||||
case pContextName:
|
||||
if (line.startsWith("<name>")) {
|
||||
line.cutLeft(6).cutRight(7);
|
||||
// if (line == context) phase = 2;
|
||||
cc = &(ret[line]);
|
||||
phase = 2;
|
||||
cc = &(ret.contexts[line]);
|
||||
phase = pMessage;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case pMessage:
|
||||
if (line == "<message>") {
|
||||
msg = {};
|
||||
phase = 3;
|
||||
phase = pMessageBody;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case pMessageBody:
|
||||
if (line == "</message>") {
|
||||
if (cc) cc->messages[msg.source] = msg;
|
||||
phase = 2;
|
||||
phase = pMessage;
|
||||
} else if (line.startsWith("<source>")) {
|
||||
line.cutLeft(8);
|
||||
if (line.endsWith("</source>"))
|
||||
@@ -136,29 +162,41 @@ TSFile::Content TSFile::read(const PIString & path) {
|
||||
PIString t = trs.takeCWord();
|
||||
trs.cutLeft(1);
|
||||
PIString v = trs.takeRange('\"', '\"');
|
||||
if (t == "type") msg.type = v;
|
||||
if (t == "type") {
|
||||
if (v == "unfinished")
|
||||
msg.type.setFlag(Message::Unfinished);
|
||||
else if (v == "vanished")
|
||||
msg.type.setFlag(Message::Missing);
|
||||
}
|
||||
}
|
||||
if (line.endsWith("</translation>"))
|
||||
line.cutRight(14);
|
||||
else
|
||||
multi_translation = true;
|
||||
msg.translation = unmask(line);
|
||||
} else if (line.startsWith("<translatorcomment>")) {
|
||||
line.cutLeft(19);
|
||||
if (line.endsWith("</translatorcomment>"))
|
||||
line.cutRight(20);
|
||||
else
|
||||
multi_comment = true;
|
||||
msg.comment = unmask(line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (line == "</context>") {
|
||||
cc = nullptr;
|
||||
phase = 0;
|
||||
phase = pContext;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool TSFile::write(const PIString & path, const TSFile::Content & content, const PIString & lang, bool no_obsolete) {
|
||||
bool TSFile::write(const PIString & path, const TSFile::Content & content, bool no_obsolete) {
|
||||
PIFile outf(path, PIIODevice::ReadWrite);
|
||||
if (!outf.isOpened()) {
|
||||
piCerr << "Can`t open" << outf.path() << "!";
|
||||
piCerr << "Can`t open file \"%1\"!"_tr("TSFile").arg(outf.path());
|
||||
return false;
|
||||
}
|
||||
outf.clear();
|
||||
@@ -172,23 +210,29 @@ bool TSFile::write(const PIString & path, const TSFile::Content & content, const
|
||||
ts << "/>\n";
|
||||
}
|
||||
ts << " <source>" << mask(m.source) << "</source>\n";
|
||||
if (m.comment.isNotEmpty()) {
|
||||
ts << " <translatorcomment>" << mask(m.comment) << "</translatorcomment>\n";
|
||||
}
|
||||
ts << " <translation";
|
||||
if (m.type.isNotEmpty()) ts << " type=\"" << m.type << "\"";
|
||||
if (m.type[Message::Missing])
|
||||
ts << " type=\"vanished\"";
|
||||
else if (m.type[Message::Unfinished])
|
||||
ts << " type=\"unfinished\"";
|
||||
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();
|
||||
ts << "<TS version=\"2.1\" language=\"" << content.lang << "\">\n";
|
||||
auto cit = content.contexts.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;
|
||||
if (mit.value().type[Message::Missing] && no_obsolete) continue;
|
||||
writeMessage(mit.value());
|
||||
}
|
||||
ts << "</context>\n";
|
||||
@@ -197,3 +241,14 @@ bool TSFile::write(const PIString & path, const TSFile::Content & content, const
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void TSFile::Content::markAllMissing() {
|
||||
auto cit = contexts.makeIterator();
|
||||
while (cit.next()) {
|
||||
auto mit = cit.value().messages.makeIterator();
|
||||
while (mit.next()) {
|
||||
mit.value().type.setFlag(Message::Missing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,33 @@
|
||||
namespace TSFile {
|
||||
|
||||
struct Message {
|
||||
enum Type {
|
||||
Unfinished = 0x1,
|
||||
Missing = 0x2,
|
||||
};
|
||||
PIString source;
|
||||
PIString translation;
|
||||
PIString type;
|
||||
PIString comment;
|
||||
PIString filename;
|
||||
PIString line;
|
||||
bool obsolete = true;
|
||||
PIFlags<Type> type;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
PIMap<PIString, Message> messages;
|
||||
void confirm(const PIString & msg, const PIString & file, int line = -1);
|
||||
bool confirm(const PIString & msg, const PIString & file, int line = -1);
|
||||
};
|
||||
|
||||
using Content = PIMap<PIString, Context>;
|
||||
struct Content {
|
||||
void markAllMissing();
|
||||
PIString lang;
|
||||
PIMap<PIString, Context> contexts;
|
||||
};
|
||||
|
||||
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);
|
||||
bool write(const PIString & path, const Content & content, bool no_obsolete);
|
||||
|
||||
} // namespace TSFile
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "parser.h"
|
||||
#include "picli.h"
|
||||
#include "piiostream.h"
|
||||
#include "pitranslator.h"
|
||||
|
||||
using namespace PICoutManipulators;
|
||||
|
||||
@@ -56,7 +57,7 @@ int main(int argc, char * argv[]) {
|
||||
|
||||
PIVector<ParserSection> files = parse(cli.argumentValue("input"));
|
||||
if (files.isEmpty()) {
|
||||
piCout << "Error: resources description file is empty";
|
||||
piCerr << "Error: resources description file is empty"_tr("pip_rc");
|
||||
return 0;
|
||||
}
|
||||
if (cli.hasArgument("list")) {
|
||||
@@ -76,7 +77,7 @@ int main(int argc, char * argv[]) {
|
||||
if (outf.open(out_file, PIIODevice::ReadWrite)) {
|
||||
outf.clear();
|
||||
} else {
|
||||
piCout << "Error: can`t open out file" << out_file;
|
||||
piCerr << "Error: can`t open output file \"%1\""_tr("pip_rc").arg(out_file);
|
||||
return 1;
|
||||
}
|
||||
PIIOTextStream ts(&outf);
|
||||
@@ -86,7 +87,7 @@ int main(int argc, char * argv[]) {
|
||||
ts << "// \"" << _a << "\"\n";
|
||||
ts << "\n";
|
||||
if (!generate(init_name, outf, files)) {
|
||||
piCout << "Error: generate fail";
|
||||
piCerr << "Error: generate fail"_tr("pip_rc");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,17 @@ set_deploy_property(pip_tr
|
||||
COMPANY "${PIP_COMPANY}"
|
||||
INFO "Platform-Independent Primitives")
|
||||
make_rc(pip_tr _RC)
|
||||
add_executable(pip_tr "main.cpp" ${_RC})
|
||||
set(TS_LIST)
|
||||
if (NOT CROSSTOOLS)
|
||||
file(GLOB TS_LIST "${pip_ROOT_SRC}/lang/*.ts")
|
||||
endif()
|
||||
add_executable(pip_tr "main.cpp" "parser.h" "parser.cpp" ${_RC} ${TS_LIST})
|
||||
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()
|
||||
if (NOT CROSSTOOLS)
|
||||
add_custom_command(TARGET pip_tr POST_BUILD COMMAND "${CMAKE_COMMAND}" --build "${PIP_DLL_DIR}" --target pip_lang)
|
||||
endif()
|
||||
|
||||
@@ -17,14 +17,13 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "parser.h"
|
||||
#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"
|
||||
#include "piliterals_string.h"
|
||||
#include "pitranslator.h"
|
||||
#include "pitranslator_p.h"
|
||||
|
||||
using namespace PICoutManipulators;
|
||||
|
||||
@@ -42,12 +41,17 @@ 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;
|
||||
<< "\"pip_tr -P [-hHqnr] -l <lang> -o <output_file> <file1/dir1> [<file2/dir2>] [<file3/dir3>] [...]\"" << NewLine
|
||||
<< "\"pip_tr -C [-hHq] -o <output_file> <ts_file>\"" << NewLine;
|
||||
}
|
||||
|
||||
void usage() {
|
||||
header();
|
||||
piCout << Green << Bold << "Details:";
|
||||
piCout << Bold << "Mode";
|
||||
piCout << "-P --Parse " << Green << "- parse sources and create/update *.ts file";
|
||||
piCout << "-C --Compile " << Green << "- compile *.ts file";
|
||||
piCout << "";
|
||||
piCout << Bold << "Debug control";
|
||||
piCout << "-h " << Green << "- display this message and exit";
|
||||
piCout << "-H " << Green << "- display details help";
|
||||
@@ -56,10 +60,12 @@ void usage() {
|
||||
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 << "-o <output_file> " << Green
|
||||
<< "- output file for translation (QtLinguist *.ts file for parse mode or binary file for compile mode)";
|
||||
piCout << "";
|
||||
piCout << Bold << "Input control";
|
||||
piCout << "<file/dir> " << Green << "- add file or dir translation";
|
||||
piCout << "-r, --recursive " << Green << "- scan directories recursively";
|
||||
piCout << "<file/dir> " << Green << "- add source file/dir for parse mode or *.ts file for compile mode";
|
||||
}
|
||||
|
||||
void help() {
|
||||
@@ -67,105 +73,10 @@ void help() {
|
||||
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 << "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
mParse = 1,
|
||||
mCompile = 2,
|
||||
};
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
PICLI cli(argc, argv);
|
||||
@@ -177,48 +88,99 @@ int main(int argc, char * argv[]) {
|
||||
cli.addArgument("Help");
|
||||
cli.addArgument("quiet");
|
||||
cli.addArgument("no-obsolete");
|
||||
cli.addArgument("recursive");
|
||||
cli.addArgument("Parse");
|
||||
cli.addArgument("Compile");
|
||||
if (cli.hasArgument("Help")) {
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.argumentValue("language").isEmpty() ||
|
||||
cli.optionalArguments().isEmpty()) {
|
||||
int mode = 0;
|
||||
if (cli.hasArgument("Parse")) mode |= mParse;
|
||||
if (cli.hasArgument("Compile")) mode |= mCompile;
|
||||
if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.optionalArguments().isEmpty() ||
|
||||
(mode != mParse && mode != mCompile)) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
if (mode == mParse && cli.argumentValue("language").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;
|
||||
if (mode == mParse) {
|
||||
piCout << Cyan << Bold << "Reading ts file ...";
|
||||
auto content = TSFile::read(out_path);
|
||||
if (content.lang.isEmpty()) content.lang = cli.argumentValue("language");
|
||||
content.markAllMissing();
|
||||
piCout << Cyan << Bold << "Reading done";
|
||||
|
||||
bool recursive = cli.hasArgument("recursive");
|
||||
|
||||
PIStringList files;
|
||||
const static PIStringList ext({"h", "hpp", "cpp", "cxx"});
|
||||
for (const PIString & a: cli.optionalArguments()) {
|
||||
if (PIDir::isExists(a)) {
|
||||
auto dl = recursive ? PIDir(a).allEntries() : PIDir(a).entries();
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (PIFile::isExists(a)) files << a;
|
||||
}
|
||||
|
||||
piCout << Cyan << Bold << "Read" << files.size_s() << "files ...";
|
||||
PITimeMeasurer tm;
|
||||
PIDir out_dir(PIFile::FileInfo(out_path).dir());
|
||||
int count_all = 0, count_new = 0;
|
||||
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, count_all, count_new);
|
||||
}
|
||||
auto elapsed = tm.elapsed();
|
||||
piCout << Cyan << Bold
|
||||
<< "Reading done, found %1 strings (%2 new) in %3 ms"_a.arg(count_all).arg(count_new).arg((int)elapsed.toMilliseconds());
|
||||
|
||||
piCout << Cyan << Bold << "Writing ts file ...";
|
||||
if (!TSFile::write(out_path, content, cli.hasArgument("no-obsolete"))) return 1;
|
||||
piCout << Cyan << Bold << "Writing done";
|
||||
} else if (mode == mCompile) {
|
||||
piCout << Cyan << Bold << "Reading ts file ...";
|
||||
auto content = TSFile::read(cli.optionalArguments().front());
|
||||
if (content.lang.isEmpty()) content.lang = cli.argumentValue("language");
|
||||
piCout << Cyan << Bold << "Reading done";
|
||||
|
||||
int wcount = 0;
|
||||
PITranslatorPrivate::Translation t;
|
||||
t.lang = content.lang;
|
||||
auto cit = content.contexts.makeIterator();
|
||||
while (cit.next()) {
|
||||
auto * c = t.createContext(cit.key());
|
||||
auto mit = cit.value().messages.makeIterator();
|
||||
while (mit.next()) {
|
||||
const auto & m(mit.value());
|
||||
if (m.type[TSFile::Message::Unfinished] || m.type[TSFile::Message::Missing]) continue;
|
||||
c->add(m.source, m.translation);
|
||||
++wcount;
|
||||
// piCout << "tr" << m.source << "->" << m.translation;
|
||||
}
|
||||
}
|
||||
PIFile outf(out_path, PIIODevice::WriteOnly);
|
||||
if (!outf.isOpened()) {
|
||||
piCerr << "Can`t open file \"%1\"!"_tr("TSFile").arg(outf.path());
|
||||
return 1;
|
||||
}
|
||||
outf.clear();
|
||||
piCout << Cyan << Bold << "Writing bin file ...";
|
||||
PITranslatorPrivate::writeHeader(&outf);
|
||||
outf.write(t.save());
|
||||
piCout << Cyan << Bold << "Writing done (%1 strings)"_a.arg(wcount);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
139
utils/translator/parser.cpp
Normal file
139
utils/translator/parser.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "parser.h"
|
||||
|
||||
#include "piliterals_string.h"
|
||||
#include "pistringlist.h"
|
||||
|
||||
|
||||
PIString fromCode(const PIString & in) {
|
||||
return in.replacedAll("\\n", '\n')
|
||||
.replaceAll("\\r", '\r')
|
||||
.replaceAll("\\t", '\t')
|
||||
.replaceAll("\\\"", '"')
|
||||
.replaceAll("\\'", '\'')
|
||||
.replaceAll("\\\\", '\\');
|
||||
}
|
||||
|
||||
|
||||
void gatherStrings(TSFile::Content & content, const PIString & file, const PIString & file_loc, int & count_all, int & count_new) {
|
||||
static const PIStringList methods({"PITranslator::tr", "PITranslator::trNoOp", "piTr", "piTrNoOp"});
|
||||
static const PIStringList literals({"_tr", "_trNoOp"});
|
||||
PIString source, context;
|
||||
int pos = -1, ppos = 0, line = -1;
|
||||
int len = 0;
|
||||
auto isCLetter = [](const PIChar c) { return c.isAlpha() || c.isDigit() || c == '_'; };
|
||||
auto add = [&content, file_loc, &count_all, &count_new](const PIString & source, const PIString & context, int line) {
|
||||
if (source.isEmpty()) return;
|
||||
++count_all;
|
||||
if (content.contexts[context].confirm(fromCode(source), file_loc, line)) ++count_new;
|
||||
// piCout << "Context = \"" << context << "\", message = \"" << source << "\" (" << file_loc << ":" << line << ")";
|
||||
};
|
||||
// piCout << "file" << file_loc;
|
||||
for (const auto & kw: methods) {
|
||||
// piCout << "method" << kw;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos = ppos;
|
||||
add(source, context, line);
|
||||
}
|
||||
}
|
||||
for (const auto & kw: literals) {
|
||||
// piCout << "literal" << kw;
|
||||
pos = -1;
|
||||
for (;;) {
|
||||
source.clear();
|
||||
context.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, kw.size_s()) == kw) {
|
||||
// piCout << "line" << file.lineNumber(pos);
|
||||
pos += kw.size_s();
|
||||
if (pos < file.size_s() - 1) {
|
||||
auto pc = file[pos];
|
||||
if (isCLetter(pc) || pc == ':') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
pos = ppos;
|
||||
if (source.isEmpty()) continue;
|
||||
line = file.lineNumber(pos);
|
||||
add(source, context, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
utils/translator/parser.h
Normal file
9
utils/translator/parser.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef PITR_PARSER_H
|
||||
#define PITR_PARSER_H
|
||||
|
||||
#include "ts_file.h"
|
||||
|
||||
PIString fromCode(const PIString & in);
|
||||
void gatherStrings(TSFile::Content & content, const PIString & file, const PIString & file_loc, int & count_all, int & count_new);
|
||||
|
||||
#endif
|
||||
@@ -124,10 +124,12 @@ int main(int argc, char * argv[]) {
|
||||
|
||||
piCout << Cyan << Bold << "Reading ts file ...";
|
||||
auto content = TSFile::read(out_path);
|
||||
if (content.lang.isEmpty()) content.lang = cli.argumentValue("language");
|
||||
content.markAllMissing();
|
||||
piCout << Cyan << Bold << "Reading done";
|
||||
|
||||
piCout << Cyan << Bold << "Read" << files.size_s() << "files ...";
|
||||
auto & context(content[contextName]);
|
||||
auto & context(content.contexts[contextName]);
|
||||
PIDir out_dir(PIFile::FileInfo(out_path).dir());
|
||||
for (const auto & p: files) {
|
||||
PIString ext = PIFile::FileInfo(p).extension().toLowerCase().trim();
|
||||
@@ -142,7 +144,7 @@ int main(int argc, char * argv[]) {
|
||||
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;
|
||||
if (!TSFile::write(out_path, content, cli.hasArgument("no-obsolete"))) return 1;
|
||||
piCout << Cyan << Bold << "Writing done";
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user