Skip to content

Commit

Permalink
NOISSUE hotloading of translations and use of local PO files
Browse files Browse the repository at this point in the history
The hotloading is still inefficient
  • Loading branch information
peterix committed Jan 2, 2019
1 parent 4cbd1a7 commit 4b7971f
Show file tree
Hide file tree
Showing 5 changed files with 650 additions and 51 deletions.
2 changes: 2 additions & 0 deletions api/logic/CMakeLists.txt
Expand Up @@ -387,6 +387,8 @@ add_unit_test(JavaVersion
set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h
translations/TranslationsModel.cpp
translations/POTranslator.h
translations/POTranslator.cpp
)

set(TOOLS_SOURCES
Expand Down
373 changes: 373 additions & 0 deletions api/logic/translations/POTranslator.cpp
@@ -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;
}
16 changes: 16 additions & 0 deletions api/logic/translations/POTranslator.h
@@ -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;
};

0 comments on commit 4b7971f

Please sign in to comment.