Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NOISSUE hotloading of translations and use of local PO files
The hotloading is still inefficient
- Loading branch information
Showing
5 changed files
with
650 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,373 @@ | ||
#include "POTranslator.h" | ||
|
||
#include <QDebug> | ||
#include "FileSystem.h" | ||
|
||
struct POEntry | ||
{ | ||
QString text; | ||
bool fuzzy; | ||
}; | ||
|
||
struct POTranslatorPrivate | ||
{ | ||
QString filename; | ||
QHash<QByteArray, POEntry> mapping; | ||
QHash<QByteArray, POEntry> mapping_disambiguatrion; | ||
bool loaded = false; | ||
|
||
void reload(); | ||
}; | ||
|
||
class ParserArray : public QByteArray | ||
{ | ||
public: | ||
ParserArray(const QByteArray &in) : QByteArray(in) | ||
{ | ||
} | ||
bool chomp(const char * data, int length) | ||
{ | ||
if(startsWith(data)) | ||
{ | ||
remove(0, length); | ||
return true; | ||
} | ||
return false; | ||
} | ||
bool chompString(QByteArray & appendHere) | ||
{ | ||
QByteArray msg; | ||
bool escape = false; | ||
if(size() < 2) | ||
{ | ||
qDebug() << "String fragment is too short"; | ||
return false; | ||
} | ||
if(!startsWith('"')) | ||
{ | ||
qDebug() << "String fragment does not start with \""; | ||
return false; | ||
} | ||
if(!endsWith('"')) | ||
{ | ||
qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1); | ||
return false; | ||
} | ||
for(int i = 1; i < size() - 1; i++) | ||
{ | ||
char c = operator[](i); | ||
if(escape) | ||
{ | ||
switch(c) | ||
{ | ||
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': | ||
case '7': | ||
{ | ||
int octal_start = i; | ||
while ((c = operator[](i)) >= '0' && c <= '7') | ||
{ | ||
i++; | ||
if (i == length() - 1) | ||
{ | ||
qDebug() << "Something went bad while parsing an octal escape string..."; | ||
return false; | ||
} | ||
} | ||
msg += mid(octal_start, i - octal_start).toUInt(0, 8); | ||
break; | ||
} | ||
case 'x': | ||
{ | ||
// chomp the 'x' | ||
i++; | ||
int hex_start = i; | ||
while (isxdigit(operator[](i))) | ||
{ | ||
i++; | ||
if (i == length() - 1) | ||
{ | ||
qDebug() << "Something went bad while parsing a hex escape string..."; | ||
return false; | ||
} | ||
} | ||
msg += mid(hex_start, i - hex_start).toUInt(0, 16); | ||
break; | ||
} | ||
default: | ||
{ | ||
qDebug() << "Invalid escape sequence character:" << c; | ||
return false; | ||
} | ||
} | ||
escape = false; | ||
} | ||
else if(c == '\\') | ||
{ | ||
escape = true; | ||
} | ||
else | ||
{ | ||
msg += c; | ||
} | ||
} | ||
if(escape) | ||
{ | ||
qDebug() << "Unterminated escape sequence..."; | ||
return false; | ||
} | ||
appendHere += msg; | ||
return true; | ||
} | ||
}; | ||
|
||
void POTranslatorPrivate::reload() | ||
{ | ||
QFile file(filename); | ||
if(!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) | ||
{ | ||
qDebug() << "Failed to open PO file:" << filename; | ||
return; | ||
} | ||
|
||
QByteArray context; | ||
QByteArray disambiguation; | ||
QByteArray id; | ||
QByteArray str; | ||
bool fuzzy = false; | ||
bool nextFuzzy = false; | ||
|
||
enum class Mode | ||
{ | ||
First, | ||
MessageContext, | ||
MessageId, | ||
MessageString | ||
} mode = Mode::First; | ||
|
||
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. | ||
if(!id.isEmpty()) | ||
{ | ||
auto normalKey = context + "|" + id; | ||
newMapping.insert(normalKey, {strStr, fuzzy}); | ||
if(!disambiguation.isEmpty()) | ||
{ | ||
auto disambiguationKey = context + "|" + id + "@" + disambiguation; | ||
newMapping_disambiguation.insert(disambiguationKey, {strStr, fuzzy}); | ||
} | ||
} | ||
context.clear(); | ||
disambiguation.clear(); | ||
id.clear(); | ||
str.clear(); | ||
fuzzy = nextFuzzy; | ||
nextFuzzy = false; | ||
}; | ||
while (!file.atEnd()) | ||
{ | ||
ParserArray line = file.readLine(); | ||
if(line.endsWith('\n')) | ||
{ | ||
line.resize(line.size() - 1); | ||
} | ||
if(line.endsWith('\r')) | ||
{ | ||
line.resize(line.size() - 1); | ||
} | ||
|
||
if(!line.size()) | ||
{ | ||
// NIL | ||
} | ||
else if(line[0] == '#') | ||
{ | ||
if(line.contains(", fuzzy")) | ||
{ | ||
nextFuzzy = true; | ||
} | ||
} | ||
else if(line.startsWith('"')) | ||
{ | ||
QByteArray temp; | ||
QByteArray *out = &temp; | ||
|
||
switch(mode) | ||
{ | ||
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; | ||
} | ||
if(!line.chompString(*out)) | ||
{ | ||
qDebug() << "Badly formatted string on line:" << lineNumber; | ||
return; | ||
} | ||
} | ||
else if(line.chomp("msgctxt ", 8)) | ||
{ | ||
switch(mode) | ||
{ | ||
case Mode::First: | ||
break; | ||
case Mode::MessageString: | ||
endEntry(); | ||
break; | ||
case Mode::MessageContext: | ||
case Mode::MessageId: | ||
qDebug() << "Unexpected msgctxt line:" << lineNumber; | ||
return; | ||
} | ||
if(line.chompString(context)) | ||
{ | ||
auto parts = context.split('|'); | ||
context = parts[0]; | ||
if(parts.size() > 1 && !parts[1].isEmpty()) | ||
{ | ||
disambiguation = parts[1]; | ||
} | ||
mode = Mode::MessageContext; | ||
} | ||
} | ||
else if (line.chomp("msgid ", 6)) | ||
{ | ||
switch(mode) | ||
{ | ||
case Mode::MessageContext: | ||
case Mode::First: | ||
break; | ||
case Mode::MessageString: | ||
endEntry(); | ||
break; | ||
case Mode::MessageId: | ||
qDebug() << "Unexpected msgid line:" << lineNumber; | ||
return; | ||
} | ||
if(line.chompString(id)) | ||
{ | ||
mode = Mode::MessageId; | ||
} | ||
} | ||
else if (line.chomp("msgstr ", 7)) | ||
{ | ||
switch(mode) | ||
{ | ||
case Mode::First: | ||
case Mode::MessageString: | ||
case Mode::MessageContext: | ||
qDebug() << "Unexpected msgstr line:" << lineNumber; | ||
return; | ||
case Mode::MessageId: | ||
break; | ||
} | ||
if(line.chompString(str)) | ||
{ | ||
mode = Mode::MessageString; | ||
} | ||
} | ||
else | ||
{ | ||
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(); | ||
} | ||
|
||
QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const | ||
{ | ||
if(disambiguation) | ||
{ | ||
auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation); | ||
auto iter = d->mapping_disambiguatrion.find(disambiguationKey); | ||
if(iter != d->mapping_disambiguatrion.end()) | ||
{ | ||
auto & entry = *iter; | ||
if(entry.text.isEmpty()) | ||
{ | ||
qDebug() << "Translation entry has no content:" << disambiguationKey; | ||
} | ||
if(entry.fuzzy) | ||
{ | ||
qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text; | ||
} | ||
return entry.text; | ||
} | ||
} | ||
auto key = QByteArray(context) + "|" + QByteArray(sourceText); | ||
auto iter = d->mapping.find(key); | ||
if(iter != d->mapping.end()) | ||
{ | ||
auto & entry = *iter; | ||
if(entry.text.isEmpty()) | ||
{ | ||
qDebug() << "Translation entry has no content:" << key; | ||
} | ||
if(entry.fuzzy) | ||
{ | ||
qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text; | ||
} | ||
return entry.text; | ||
} | ||
return QString(); | ||
} | ||
|
||
bool POTranslator::isEmpty() const | ||
{ | ||
return !d->loaded; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#pragma once | ||
|
||
#include <QTranslator> | ||
|
||
struct POTranslatorPrivate; | ||
|
||
class POTranslator : public QTranslator | ||
{ | ||
Q_OBJECT | ||
public: | ||
explicit POTranslator(const QString& filename, QObject * parent = nullptr); | ||
QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override; | ||
bool isEmpty() const override; | ||
private: | ||
POTranslatorPrivate * d; | ||
}; |
Oops, something went wrong.