2019-01-02 00:41:07 +00:00
|
|
|
#include "POTranslator.h"
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
#include "FileSystem.h"
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
struct POEntry {
|
2019-01-02 00:41:07 +00:00
|
|
|
QString text;
|
|
|
|
bool fuzzy;
|
|
|
|
};
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
struct POTranslatorPrivate {
|
2019-01-02 00:41:07 +00:00
|
|
|
QString filename;
|
|
|
|
QHash<QByteArray, POEntry> mapping;
|
|
|
|
QHash<QByteArray, POEntry> mapping_disambiguatrion;
|
|
|
|
bool loaded = false;
|
|
|
|
|
|
|
|
void reload();
|
|
|
|
};
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
class ParserArray : public QByteArray {
|
|
|
|
public:
|
|
|
|
ParserArray(const QByteArray& in) : QByteArray(in) {}
|
|
|
|
bool chomp(const char* data, int length)
|
2019-01-02 00:41:07 +00:00
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
if (startsWith(data)) {
|
2019-01-02 00:41:07 +00:00
|
|
|
remove(0, length);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
bool chompString(QByteArray& appendHere)
|
2019-01-02 00:41:07 +00:00
|
|
|
{
|
|
|
|
QByteArray msg;
|
|
|
|
bool escape = false;
|
2023-08-02 17:35:35 +01:00
|
|
|
if (size() < 2) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "String fragment is too short";
|
|
|
|
return false;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!startsWith('"')) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "String fragment does not start with \"";
|
|
|
|
return false;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!endsWith('"')) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1);
|
|
|
|
return false;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
for (int i = 1; i < size() - 1; i++) {
|
2019-01-02 00:41:07 +00:00
|
|
|
char c = operator[](i);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (escape) {
|
|
|
|
switch (c) {
|
2019-01-02 00:41:07 +00:00
|
|
|
case 'r':
|
|
|
|
msg += '\r';
|
|
|
|
break;
|
|
|
|
case 'n':
|
|
|
|
msg += '\n';
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
msg += '\t';
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
msg += '\v';
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
msg += '\a';
|
|
|
|
break;
|
|
|
|
case 'b':
|
|
|
|
msg += '\b';
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
msg += '\f';
|
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
msg += '"';
|
|
|
|
break;
|
|
|
|
case '\\':
|
|
|
|
msg.append('\\');
|
|
|
|
break;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
2023-08-02 17:35:35 +01:00
|
|
|
case '7': {
|
2019-01-02 00:41:07 +00:00
|
|
|
int octal_start = i;
|
2023-08-02 17:35:35 +01:00
|
|
|
while ((c = operator[](i)) >= '0' && c <= '7') {
|
2019-01-02 00:41:07 +00:00
|
|
|
i++;
|
2023-08-02 17:35:35 +01:00
|
|
|
if (i == length() - 1) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Something went bad while parsing an octal escape string...";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
msg += mid(octal_start, i - octal_start).toUInt(0, 8);
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
case 'x': {
|
2019-01-02 00:41:07 +00:00
|
|
|
// chomp the 'x'
|
|
|
|
i++;
|
|
|
|
int hex_start = i;
|
2023-08-02 17:35:35 +01:00
|
|
|
while (isxdigit(operator[](i))) {
|
2019-01-02 00:41:07 +00:00
|
|
|
i++;
|
2023-08-02 17:35:35 +01:00
|
|
|
if (i == length() - 1) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Something went bad while parsing a hex escape string...";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
msg += mid(hex_start, i - hex_start).toUInt(0, 16);
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
default: {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Invalid escape sequence character:" << c;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
escape = false;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (c == '\\') {
|
2019-01-02 00:41:07 +00:00
|
|
|
escape = true;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2019-01-02 00:41:07 +00:00
|
|
|
msg += c;
|
|
|
|
}
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (escape) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Unterminated escape sequence...";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
appendHere += msg;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void POTranslatorPrivate::reload()
|
|
|
|
{
|
|
|
|
QFile file(filename);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Failed to open PO file:" << filename;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray context;
|
|
|
|
QByteArray disambiguation;
|
|
|
|
QByteArray id;
|
|
|
|
QByteArray str;
|
|
|
|
bool fuzzy = false;
|
|
|
|
bool nextFuzzy = false;
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
enum class Mode { First, MessageContext, MessageId, MessageString } mode = Mode::First;
|
2019-01-02 00:41:07 +00:00
|
|
|
|
|
|
|
int lineNumber = 0;
|
|
|
|
QHash<QByteArray, POEntry> newMapping;
|
|
|
|
QHash<QByteArray, POEntry> newMapping_disambiguation;
|
|
|
|
auto endEntry = [&]() {
|
|
|
|
auto strStr = QString::fromUtf8(str);
|
|
|
|
// NOTE: PO header has empty id. We skip it.
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!id.isEmpty()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
auto normalKey = context + "|" + id;
|
2023-08-02 17:35:35 +01:00
|
|
|
newMapping.insert(normalKey, { strStr, fuzzy });
|
|
|
|
if (!disambiguation.isEmpty()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
auto disambiguationKey = context + "|" + id + "@" + disambiguation;
|
2023-08-02 17:35:35 +01:00
|
|
|
newMapping_disambiguation.insert(disambiguationKey, { strStr, fuzzy });
|
2019-01-02 00:41:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
context.clear();
|
|
|
|
disambiguation.clear();
|
|
|
|
id.clear();
|
|
|
|
str.clear();
|
|
|
|
fuzzy = nextFuzzy;
|
|
|
|
nextFuzzy = false;
|
|
|
|
};
|
2023-08-02 17:35:35 +01:00
|
|
|
while (!file.atEnd()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
ParserArray line = file.readLine();
|
2023-08-02 17:35:35 +01:00
|
|
|
if (line.endsWith('\n')) {
|
2019-01-02 00:41:07 +00:00
|
|
|
line.resize(line.size() - 1);
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (line.endsWith('\r')) {
|
2019-01-02 00:41:07 +00:00
|
|
|
line.resize(line.size() - 1);
|
|
|
|
}
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!line.size()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
// NIL
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (line[0] == '#') {
|
|
|
|
if (line.contains(", fuzzy")) {
|
2019-01-02 00:41:07 +00:00
|
|
|
nextFuzzy = true;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (line.startsWith('"')) {
|
2019-01-02 00:41:07 +00:00
|
|
|
QByteArray temp;
|
2023-08-02 17:35:35 +01:00
|
|
|
QByteArray* out = &temp;
|
2019-01-02 00:41:07 +00:00
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
switch (mode) {
|
2019-01-02 00:41:07 +00:00
|
|
|
case Mode::First:
|
|
|
|
qDebug() << "Unexpected escaped string during initial state... line:" << lineNumber;
|
|
|
|
return;
|
|
|
|
case Mode::MessageString:
|
|
|
|
out = &str;
|
|
|
|
break;
|
|
|
|
case Mode::MessageContext:
|
|
|
|
out = &context;
|
|
|
|
break;
|
|
|
|
case Mode::MessageId:
|
|
|
|
out = &id;
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!line.chompString(*out)) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Badly formatted string on line:" << lineNumber;
|
|
|
|
return;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (line.chomp("msgctxt ", 8)) {
|
|
|
|
switch (mode) {
|
2019-01-02 00:41:07 +00:00
|
|
|
case Mode::First:
|
|
|
|
break;
|
|
|
|
case Mode::MessageString:
|
|
|
|
endEntry();
|
|
|
|
break;
|
|
|
|
case Mode::MessageContext:
|
|
|
|
case Mode::MessageId:
|
|
|
|
qDebug() << "Unexpected msgctxt line:" << lineNumber;
|
|
|
|
return;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (line.chompString(context)) {
|
2019-01-02 00:41:07 +00:00
|
|
|
auto parts = context.split('|');
|
|
|
|
context = parts[0];
|
2023-08-02 17:35:35 +01:00
|
|
|
if (parts.size() > 1 && !parts[1].isEmpty()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
disambiguation = parts[1];
|
|
|
|
}
|
|
|
|
mode = Mode::MessageContext;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (line.chomp("msgid ", 6)) {
|
|
|
|
switch (mode) {
|
2019-01-02 00:41:07 +00:00
|
|
|
case Mode::MessageContext:
|
|
|
|
case Mode::First:
|
|
|
|
break;
|
|
|
|
case Mode::MessageString:
|
|
|
|
endEntry();
|
|
|
|
break;
|
|
|
|
case Mode::MessageId:
|
|
|
|
qDebug() << "Unexpected msgid line:" << lineNumber;
|
|
|
|
return;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (line.chompString(id)) {
|
2019-01-02 00:41:07 +00:00
|
|
|
mode = Mode::MessageId;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (line.chomp("msgstr ", 7)) {
|
|
|
|
switch (mode) {
|
2019-01-02 00:41:07 +00:00
|
|
|
case Mode::First:
|
|
|
|
case Mode::MessageString:
|
|
|
|
case Mode::MessageContext:
|
|
|
|
qDebug() << "Unexpected msgstr line:" << lineNumber;
|
|
|
|
return;
|
|
|
|
case Mode::MessageId:
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (line.chompString(str)) {
|
2019-01-02 00:41:07 +00:00
|
|
|
mode = Mode::MessageString;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "I did not understand line: " << lineNumber << ":" << QString::fromUtf8(line);
|
|
|
|
}
|
|
|
|
lineNumber++;
|
|
|
|
}
|
|
|
|
endEntry();
|
|
|
|
mapping = std::move(newMapping);
|
|
|
|
mapping_disambiguatrion = std::move(newMapping_disambiguation);
|
|
|
|
loaded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslator(parent)
|
|
|
|
{
|
|
|
|
d = new POTranslatorPrivate;
|
|
|
|
d->filename = filename;
|
|
|
|
d->reload();
|
|
|
|
}
|
|
|
|
|
2022-06-17 15:34:32 +01:00
|
|
|
POTranslator::~POTranslator()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
2019-01-02 00:41:07 +00:00
|
|
|
QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const
|
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
if (disambiguation) {
|
2019-01-02 00:41:07 +00:00
|
|
|
auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation);
|
|
|
|
auto iter = d->mapping_disambiguatrion.find(disambiguationKey);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (iter != d->mapping_disambiguatrion.end()) {
|
|
|
|
auto& entry = *iter;
|
|
|
|
if (entry.text.isEmpty()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Translation entry has no content:" << disambiguationKey;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (entry.fuzzy) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text;
|
|
|
|
}
|
|
|
|
return entry.text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto key = QByteArray(context) + "|" + QByteArray(sourceText);
|
|
|
|
auto iter = d->mapping.find(key);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (iter != d->mapping.end()) {
|
|
|
|
auto& entry = *iter;
|
|
|
|
if (entry.text.isEmpty()) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Translation entry has no content:" << key;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (entry.fuzzy) {
|
2019-01-02 00:41:07 +00:00
|
|
|
qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text;
|
|
|
|
}
|
|
|
|
return entry.text;
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool POTranslator::isEmpty() const
|
|
|
|
{
|
|
|
|
return !d->loaded;
|
|
|
|
}
|