457 lines
16 KiB
C++
457 lines
16 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
an unofficial desktop messaging app, see https://telegram.org
|
|
|
|
Telegram Desktop 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, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
It 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.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014 John Preston, https://tdesktop.com
|
|
*/
|
|
#include "genlang.h"
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <QtCore/QtPlugin>
|
|
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
|
|
#endif
|
|
|
|
typedef unsigned int uint32;
|
|
|
|
QString layoutDirection;
|
|
typedef QMap<QString, QString> LangKeys;
|
|
LangKeys keys;
|
|
typedef QVector<QString> KeysOrder;
|
|
KeysOrder keysOrder;
|
|
|
|
bool skipWhitespaces(const char *&from, const char *end) {
|
|
while (from < end && (*from == ' ' || *from == '\n' || *from == '\t' || *from == '\r')) {
|
|
++from;
|
|
}
|
|
return (from < end);
|
|
}
|
|
|
|
bool skipComment(const char *&from, const char *end) {
|
|
if (from >= end) return false;
|
|
if (*from == '/') {
|
|
if (from + 1 >= end) return true;
|
|
if (*(from + 1) == '*') {
|
|
from += 2;
|
|
while (from + 1 < end && (*from != '*' || *(from + 1) != '/')) {
|
|
++from;
|
|
}
|
|
from += 2;
|
|
return (from < end);
|
|
} else if (*(from + 1) == '/') {
|
|
from += 2;
|
|
while (from < end && *from != '\n' && *from != '\r') {
|
|
++from;
|
|
}
|
|
++from;
|
|
return (from < end);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool skipJunk(const char *&from, const char *end) {
|
|
const char *start;
|
|
do {
|
|
start = from;
|
|
if (!skipWhitespaces(from, end)) return false;
|
|
if (!skipComment(from, end)) throw Exception("Unexpected end of comment!");
|
|
} while (start != from);
|
|
return true;
|
|
}
|
|
|
|
void readKeyValue(const char *&from, const char *end) {
|
|
if (!skipJunk(from, end)) return;
|
|
|
|
const char *nameStart = from;
|
|
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
|
|
++from;
|
|
}
|
|
|
|
QString varName = QString::fromUtf8(nameStart, int(from - nameStart));
|
|
|
|
if (!skipJunk(from, end)) throw Exception("Unexpected end of file!");
|
|
if (*from != ':') throw Exception(QString("':' expected after '%1'").arg(varName));
|
|
|
|
if (!skipJunk(++from, end)) throw Exception("Unexpected end of file!");
|
|
if (*from != '"') throw Exception(QString("Expected string after '%1:'").arg(varName));
|
|
|
|
QByteArray varValue;
|
|
const char *start = ++from;
|
|
while (from < end && *from != '"') {
|
|
if (*from == '\\') {
|
|
if (from + 1 >= end) throw Exception("Unexpected end of file!");
|
|
if (*(from + 1) == '"' || *(from + 1) == '\\') {
|
|
if (from > start) varValue.append(start, int(from - start));
|
|
start = ++from;
|
|
}
|
|
}
|
|
++from;
|
|
}
|
|
if (from >= end) throw Exception("Unexpected end of file!");
|
|
if (from > start) varValue.append(start, int(from - start));
|
|
|
|
if (!skipJunk(++from, end)) throw Exception("Unexpected end of file!");
|
|
if (*from != ';') throw Exception(QString("';' expected after '%1: \"value\"'").arg(varName));
|
|
|
|
skipJunk(++from, end);
|
|
|
|
if (varName == "direction") {
|
|
if (varValue == "LTR" || varValue == "RTL") {
|
|
layoutDirection = QString::fromUtf8(varValue);
|
|
} else {
|
|
throw Exception(QString("Unexpected value for 'direction' key: '%1'").arg(QString::fromUtf8(varValue)));
|
|
}
|
|
} else if (varName.midRef(0, 4) != "lng_") {
|
|
throw Exception(QString("Bad key '%1'").arg(varName));
|
|
} else if (keys.constFind(varName) != keys.cend()) {
|
|
throw Exception(QString("Key doubled '%1'").arg(varName));
|
|
} else {
|
|
keys.insert(varName, QString::fromUtf8(varValue));
|
|
keysOrder.push_back(varName);
|
|
}
|
|
}
|
|
|
|
QString escapeCpp(const QString &key, QString value, bool wideChar) {
|
|
if (value.isEmpty()) return "QString()";
|
|
value = value.replace('\\', "\\\\").replace('\n', "\\n").replace('\r', "").replace('"', "\\\"");
|
|
QString res;
|
|
res.reserve(value.size() * 10);
|
|
bool instr = false;
|
|
for (const QChar *ch = value.constData(), *e = value.constData() + value.size(); ch != e; ++ch) {
|
|
if (ch->unicode() < 32) {
|
|
throw Exception(QString("Bad value for key '%1'").arg(key));
|
|
} else if (ch->unicode() > 127) {
|
|
if (instr) {
|
|
res.append('"');
|
|
instr = false;
|
|
}
|
|
res.append(' ');
|
|
if (wideChar) {
|
|
res.append('L').append('"').append('\\').append('x').append(QString("%1").arg(ch->unicode(), 4, 16, QChar('0'))).append('"');
|
|
} else {
|
|
res.append('"');
|
|
QByteArray utf(QString(*ch).toUtf8());
|
|
for (const unsigned char *uch = (const unsigned char *)utf.constData(), *ue = (const unsigned char *)utf.constData() + utf.size(); uch != ue; ++uch) {
|
|
res.append('\\').append('x').append(QString("%1").arg(ushort(*uch), 2, 16, QChar('0')));
|
|
}
|
|
res.append('"');
|
|
}
|
|
} else {
|
|
if (!instr) {
|
|
res.append(' ');
|
|
if (wideChar) res.append('L');
|
|
res.append('"');
|
|
instr = true;
|
|
}
|
|
res.append(*ch);
|
|
}
|
|
}
|
|
if (instr) res.append('"');
|
|
return (wideChar ? "qsl(" : "QString::fromUtf8(") + res.mid(wideChar ? 2 : 1) + ")";
|
|
}
|
|
|
|
void writeCppKey(QTextStream &tcpp, const QString &key, const QString &val) {
|
|
QString wide = escapeCpp(key, val, true), utf = escapeCpp(key, val, false);
|
|
if (wide.indexOf(" L\"") < 0) {
|
|
tcpp << "\t\t\tset(" << key << ", " << wide << ");\n";
|
|
} else {
|
|
tcpp << "#ifdef Q_OS_WIN\n";
|
|
tcpp << "\t\t\tset(" << key << ", " << wide << ");\n";
|
|
tcpp << "#else\n";
|
|
tcpp << "\t\t\tset(" << key << ", " << utf << ");\n";
|
|
tcpp << "#endif\n";
|
|
}
|
|
}
|
|
|
|
bool genLang(const QString &lang_in, const QString &lang_out) {
|
|
QString lang_cpp = lang_out + ".cpp", lang_h = lang_out + ".h";
|
|
QFile f(lang_in);
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
cout << "Could not open styles input file '" << lang_in.toUtf8().constData() << "'!\n";
|
|
QCoreApplication::exit(1);
|
|
return false;
|
|
}
|
|
|
|
QByteArray blob = f.readAll();
|
|
const char *text = blob.constData(), *end = blob.constData() + blob.size();
|
|
f.close();
|
|
|
|
try {
|
|
while (text != end) {
|
|
readKeyValue(text, end);
|
|
}
|
|
|
|
QByteArray cppText, hText;
|
|
{
|
|
QTextStream tcpp(&cppText), th(&hText);
|
|
tcpp.setCodec("ISO 8859-1");
|
|
th.setCodec("ISO 8859-1");
|
|
th << "\
|
|
/*\n\
|
|
Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\
|
|
\n\
|
|
WARNING! All changes made in this file will be lost!\n\
|
|
\n\
|
|
This file is part of Telegram Desktop,\n\
|
|
an unofficial desktop messaging app, see https://telegram.org\n\
|
|
\n\
|
|
Telegram Desktop is free software: you can redistribute it and/or modify\n\
|
|
it under the terms of the GNU General Public License as published by\n\
|
|
the Free Software Foundation, either version 3 of the License, or\n\
|
|
(at your option) any later version.\n\
|
|
\n\
|
|
It is distributed in the hope that it will be useful,\n\
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
|
|
GNU General Public License for more details.\n\
|
|
\n\
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
|
|
Copyright (c) 2014 John Preston, https://tdesktop.com\n\
|
|
*/\n";
|
|
th << "#pragma once\n\n";
|
|
th << "enum LangKey {\n";
|
|
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
|
|
th << "\t" << keysOrder[i] << (i ? "" : " = 0") << ",\n";
|
|
}
|
|
th << "\n\tlng_keys_cnt\n";
|
|
th << "};\n\n";
|
|
th << "QString lang(LangKey key);\n";
|
|
th << "inline QString langDayOfMonth(const QDate &date) {\n";
|
|
th << "\tint32 month = date.month(), day = date.day();\n";
|
|
th << "\treturn (month > 0 && month <= 12) ? lang(lng_month_day).replace(qsl(\"{month}\"), lang(LangKey(lng_month1 + month - 1))).replace(qsl(\"{day}\"), QString::number(day)) : qsl(\"{err}\");\n";
|
|
th << "}\n\n";
|
|
th << "inline QString langDayOfWeek(const QDate &date) {\n";
|
|
th << "\tint32 day = date.dayOfWeek();\n";
|
|
th << "\treturn (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1 + day - 1)) : qsl(\"{err}\");\n";
|
|
th << "}\n\n";
|
|
th << "Qt::LayoutDirection langDir();\n\n";
|
|
th << "class LangLoader {\n";
|
|
th << "public:\n";
|
|
th << "\tconst QString &errors() const;\n";
|
|
th << "\tconst QString &warnings() const;\n\n";
|
|
th << "protected:\n";
|
|
th << "\tLangLoader() : _checked(false) {\n";
|
|
th << "\t\tmemset(_found, 0, sizeof(_found));\n";
|
|
th << "\t}\n\n";
|
|
th << "\tbool feedKeyValue(const QString &key, const QString &value);\n\n";
|
|
th << "\tvoid error(const QString &text) {\n";
|
|
th << "\t\t_err.push_back(text);\n";
|
|
th << "\t}\n";
|
|
th << "\tvoid warning(const QString &text) {\n";
|
|
th << "\t\t_warn.push_back(text);\n";
|
|
th << "\t}\n\n";
|
|
th << "private:\n";
|
|
th << "\tmutable QStringList _err, _warn;\n";
|
|
th << "\tmutable QString _errors, _warnings;\n";
|
|
th << "\tmutable bool _checked;\n";
|
|
th << "\tbool _found[lng_keys_cnt];\n\n";
|
|
th << "\tLangLoader(const LangLoader &);\n";
|
|
th << "\tLangLoader &operator=(const LangLoader &);\n";
|
|
th << "};\n";
|
|
|
|
tcpp << "\
|
|
/*\n\
|
|
Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\
|
|
\n\
|
|
WARNING! All changes made in this file will be lost!\n\
|
|
\n\
|
|
This file is part of Telegram Desktop,\n\
|
|
an unofficial desktop messaging app, see https://telegram.org\n\
|
|
\n\
|
|
Telegram Desktop is free software: you can redistribute it and/or modify\n\
|
|
it under the terms of the GNU General Public License as published by\n\
|
|
the Free Software Foundation, either version 3 of the License, or\n\
|
|
(at your option) any later version.\n\
|
|
\n\
|
|
It is distributed in the hope that it will be useful,\n\
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
|
|
GNU General Public License for more details.\n\
|
|
\n\
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
|
|
Copyright (c) 2014 John Preston, https://tdesktop.com\n\
|
|
*/\n";
|
|
tcpp << "#include \"stdafx.h\"\n#include \"lang.h\"\n\n";
|
|
tcpp << "namespace {\n";
|
|
tcpp << "\tQt::LayoutDirection _langDir = Qt::" << (layoutDirection == "LTR" ? "LeftToRight" : "RightToLeft") << ";\n";
|
|
tcpp << "\tconst char *_langKeyNames[lng_keys_cnt + 1] = {\n";
|
|
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
|
|
tcpp << "\t\t\"" << keysOrder[i] << "\",\n";
|
|
}
|
|
tcpp << "\t\t\"lng_keys_cnt\"\n";
|
|
tcpp << "\t};\n\n";
|
|
tcpp << "\tQString _langValues[lng_keys_cnt + 1];\n\n";
|
|
tcpp << "\tvoid set(LangKey key, const QString &val) {\n";
|
|
tcpp << "\t\t_langValues[key] = val;\n";
|
|
tcpp << "\t}\n\n";
|
|
tcpp << "\tclass LangInit {\n";
|
|
tcpp << "\tpublic:\n";
|
|
tcpp << "\t\tLangInit() {\n";
|
|
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
|
|
writeCppKey(tcpp, keysOrder[i], keys[keysOrder[i]]);
|
|
}
|
|
tcpp << "\t\t\tset(lng_keys_cnt, QString());\n";
|
|
tcpp << "\t\t}\n";
|
|
tcpp << "\t};\n\n";
|
|
tcpp << "\tLangInit _langInit;\n";
|
|
tcpp << "}\n\n";
|
|
|
|
tcpp << "QString lang(LangKey key) {\n";
|
|
tcpp << "\treturn _langValues[(key < 0 || key > lng_keys_cnt) ? lng_keys_cnt : key];\n";
|
|
tcpp << "}\n\n";
|
|
|
|
tcpp << "Qt::LayoutDirection langDir() {\n";
|
|
tcpp << "\treturn _langDir;\n";
|
|
tcpp << "}\n\n";
|
|
|
|
tcpp << "bool LangLoader::feedKeyValue(const QString &key, const QString &value) {\n";
|
|
tcpp << "\tif (key == qsl(\"direction\")) {\n";
|
|
tcpp << "\t\tif (value == qsl(\"LTR\")) {\n";
|
|
tcpp << "\t\t\t_langDir = Qt::LeftToRight;\n";
|
|
tcpp << "\t\t\treturn true;\n";
|
|
tcpp << "\t\t} else if (value == qsl(\"RTL\")) {\n";
|
|
tcpp << "\t\t\t_langDir = Qt::RightToLeft;\n";
|
|
tcpp << "\t\t\treturn true;\n";
|
|
tcpp << "\t\t} else {\n";
|
|
tcpp << "\t\t\t_err.push_back(qsl(\"Bad value for 'direction' key.\"));\n";
|
|
tcpp << "\t\t\treturn false;\n";
|
|
tcpp << "\t\t}\n";
|
|
tcpp << "\t}\n";
|
|
tcpp << "\tif (key.size() < 5 || key.midRef(0, 4) != qsl(\"lng_\")) {\n";
|
|
tcpp << "\t\t_err.push_back(qsl(\"Bad key name '%1'\").arg(key));\n";
|
|
tcpp << "\t\treturn false;\n";
|
|
tcpp << "\t}\n\n";
|
|
if (!keys.isEmpty()) {
|
|
QString tab("\t");
|
|
tcpp << "\tLangKey keyIndex = lng_keys_cnt;\n";
|
|
tcpp << "\tconst QChar *ch = key.constData(), *e = key.constData() + key.size();\n";
|
|
QString current("lng_");
|
|
int depth = current.size();
|
|
tcpp << "\tswitch ((ch + " << depth << ")->unicode()) {\n";
|
|
for (LangKeys::const_iterator i = keys.cbegin(), j = i + 1, e = keys.cend(); i != e; ++i) {
|
|
QString key = i.key();
|
|
while (key.midRef(0, depth) != current) {
|
|
tcpp << tab.repeated(depth - 3) << "}\n";
|
|
current.chop(1);
|
|
--depth;
|
|
tcpp << tab.repeated(depth - 3) << "break;\n";
|
|
}
|
|
do {
|
|
if (key == current) break;
|
|
|
|
QChar ich = i.key().at(current.size());
|
|
tcpp << tab.repeated(current.size() - 3) << "case '" << ich << "':\n";
|
|
if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) {
|
|
if (key == current + ich) {
|
|
tcpp << tab.repeated(depth - 3) << "\tif (ch + " << (depth + 1) << " == e) keyIndex = " << key << ";\n";
|
|
} else {
|
|
tcpp << tab.repeated(depth - 3) << "\tif (key.midRef(" << (depth + 1) << ") == qsl(\"" << i.key().mid(depth + 1) << "\")) keyIndex = " << key << ";\n";
|
|
}
|
|
tcpp << tab.repeated(depth - 3) << "break;\n";
|
|
break;
|
|
}
|
|
|
|
++depth;
|
|
current += ich;
|
|
|
|
if (key == current) {
|
|
tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " == e) {\n";
|
|
tcpp << tab.repeated(depth - 3) << "\tkeyIndex = " << key << ";\n";
|
|
tcpp << tab.repeated(depth - 3) << "}\n";
|
|
}
|
|
|
|
tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch ((ch + " << depth << ")->unicode()) {\n";
|
|
} while (true);
|
|
++j;
|
|
}
|
|
while (QString("lng_") != current) {
|
|
tcpp << tab.repeated(depth - 3) << "}\n";
|
|
current.chop(1);
|
|
--depth;
|
|
tcpp << tab.repeated(depth - 3) << "break;\n";
|
|
}
|
|
tcpp << "\t}\n\n";
|
|
tcpp << "\tif (keyIndex < lng_keys_cnt) {\n";
|
|
tcpp << "\t\t_found[keyIndex] = 1;\n";
|
|
tcpp << "\t\t_langValues[keyIndex] = value;\n";
|
|
tcpp << "\t\treturn true;\n";
|
|
tcpp << "\t}\n\n";
|
|
}
|
|
tcpp << "\t_err.push_back(qsl(\"Unknown key name '%1'\").arg(key));\n";
|
|
tcpp << "\treturn false;\n";
|
|
tcpp << "}\n\n";
|
|
|
|
tcpp << "const QString &LangLoader::errors() const {\n";
|
|
tcpp << "\tif (_errors.isEmpty() && !_err.isEmpty()) {\n";
|
|
tcpp << "\t\t_errors = _err.join('\\n');\n";
|
|
tcpp << "\t}\n";
|
|
tcpp << "\treturn _errors;\n";
|
|
tcpp << "}\n\n";
|
|
|
|
tcpp << "const QString &LangLoader::warnings() const {\n";
|
|
tcpp << "\tif (!_checked) {\n";
|
|
tcpp << "\t\tfor (int32 i = 0; i < lng_keys_cnt; ++i) {\n";
|
|
tcpp << "\t\t\tif (!_found[i]) {\n";
|
|
tcpp << "\t\t\t\t_warn.push_back(qsl(\"No value found for key '%1'\").arg(_langKeyNames[i]));\n";
|
|
tcpp << "\t\t\t}\n";
|
|
tcpp << "\t\t}\n";
|
|
tcpp << "\t\t_checked = true;\n";
|
|
tcpp << "\t}\n";
|
|
tcpp << "\tif (_warnings.isEmpty() && !_warn.isEmpty()) {\n";
|
|
tcpp << "\t\t_warnings = _warn.join('\\n');\n";
|
|
tcpp << "\t}\n";
|
|
tcpp << "\treturn _warnings;\n";
|
|
tcpp << "}\n";
|
|
}
|
|
|
|
QFile cpp(lang_cpp), h(lang_h);
|
|
bool write_cpp = true, write_h = true;
|
|
if (cpp.open(QIODevice::ReadOnly)) {
|
|
QByteArray wasCpp = cpp.readAll();
|
|
if (wasCpp.size() == cppText.size()) {
|
|
if (!memcmp(wasCpp.constData(), cppText.constData(), cppText.size())) {
|
|
write_cpp = false;
|
|
}
|
|
}
|
|
cpp.close();
|
|
}
|
|
if (write_cpp) {
|
|
cout << "lang.cpp updated, writing " << keysOrder.size() << " rows.\n";
|
|
if (!cpp.open(QIODevice::WriteOnly)) throw Exception("Could not open lang.cpp for writing!");
|
|
if (cpp.write(cppText) != cppText.size()) throw Exception("Could not open lang.cpp for writing!");
|
|
}
|
|
if (h.open(QIODevice::ReadOnly)) {
|
|
QByteArray wasH = h.readAll();
|
|
if (wasH.size() == hText.size()) {
|
|
if (!memcmp(wasH.constData(), hText.constData(), hText.size())) {
|
|
write_h = false;
|
|
}
|
|
}
|
|
h.close();
|
|
}
|
|
if (write_h) {
|
|
cout << "lang.h updated, writing " << keysOrder.size() << " rows.\n";
|
|
if (!h.open(QIODevice::WriteOnly)) throw Exception("Could not open lang.h for writing!");
|
|
if (h.write(hText) != hText.size()) throw Exception("Could not open lang.h for writing!");
|
|
}
|
|
} catch (exception &e) {
|
|
cout << e.what() << "\n";
|
|
QCoreApplication::exit(1);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|