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:
2024-11-05 13:49:00 +03:00
parent 73ed51e3d4
commit 57f8c1313e
52 changed files with 1571 additions and 480 deletions

View File

@@ -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);
}
}
}

View File

@@ -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