summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/launcher/translations/POTranslator.cpp
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:51:45 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:51:45 +0300
commitd3261e64152397db2dca4d691a990c6bc2a6f4dd (patch)
treefac2f7be638651181a72453d714f0f96675c2b8b /archived/projt-launcher/launcher/translations/POTranslator.cpp
parent31b9a8949ed0a288143e23bf739f2eb64fdc63be (diff)
downloadProject-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.tar.gz
Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.zip
NOISSUE add archived projects
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'archived/projt-launcher/launcher/translations/POTranslator.cpp')
-rw-r--r--archived/projt-launcher/launcher/translations/POTranslator.cpp364
1 files changed, 364 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/translations/POTranslator.cpp b/archived/projt-launcher/launcher/translations/POTranslator.cpp
new file mode 100644
index 0000000000..6e893097d2
--- /dev/null
+++ b/archived/projt-launcher/launcher/translations/POTranslator.cpp
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#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();
+}
+
+POTranslator::~POTranslator()
+{
+ delete d;
+}
+
+QString POTranslator::translate(const char* context,
+ const char* sourceText,
+ const char* disambiguation,
+ [[maybe_unused]] 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;
+}