#include "POTranslator.h" #include #include "FileSystem.h" struct POEntry { QString text; bool fuzzy; }; struct POTranslatorPrivate { QString filename; QHash mapping; QHash 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 newMapping; QHash 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(); } POTranslator::~POTranslator() { delete d; } 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; }