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:
2024-11-03 14:39:42 +03:00
parent 9a928f6feb
commit b43158d3a8
14 changed files with 532 additions and 194 deletions

View File

@@ -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 <iostream>
#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<PIString> strings;
PIMap<uint, PIString> locations;
PIString mask(const PIString & in) {
static PIVector<PIPair<PIString, PIString>> map = {
{"&", "&amp;" },
{"<", "&lt;" },
{">", "&gt;" },
{"'", "&apos;"},
{"\"", "&quot;"},
};
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 = {
{"<", "&lt;" },
{">", "&gt;" },
{"'", "&apos;"},
{"\"", "&quot;"},
{"&", "&amp;" },
};
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<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[]) {
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<PIValueTree>(f.readAll()), PIString());
gatherStrings(context,
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 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 << " <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";
if (!TSFile::write(out_path, content, cli.argumentValue("language"), cli.hasArgument("no-obsolete"))) return 1;
piCout << Cyan << Bold << "Writing done";
return 0;
}