Start cloud langpack support.

Change the way langpacks are stored.
Support custom langpacks in the new storage.
This commit is contained in:
John Preston 2017-04-13 20:59:05 +03:00
parent 2334ba1fe1
commit 139d4e72b5
43 changed files with 1264 additions and 741 deletions

View File

@ -51,6 +51,10 @@ ApiWrap::ApiWrap()
Window::Theme::Background()->start(); Window::Theme::Background()->start();
} }
void ApiWrap::applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId) {
App::main()->feedUpdates(updates, sentMessageRandomId);
}
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) { void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]); auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
if (callback) { if (callback) {
@ -114,10 +118,6 @@ void ApiWrap::resolveMessageDatas() {
} }
} }
void ApiWrap::updatesReceived(const MTPUpdates &updates) {
App::main()->sentUpdatesReceived(updates);
}
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) { void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
switch (msgs.type()) { switch (msgs.type()) {
case mtpc_messages_messages: { case mtpc_messages_messages: {
@ -658,7 +658,7 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) {
if (auto channel = peer->asChannel()) { if (auto channel = peer->asChannel()) {
auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(true))).done([this, peer, user](const MTPUpdates &result) { auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(true))).done([this, peer, user](const MTPUpdates &result) {
App::main()->sentUpdatesReceived(result); applyUpdates(result);
_kickRequests.remove(KickRequest(peer, user)); _kickRequests.remove(KickRequest(peer, user));
if (auto channel = peer->asMegagroup()) { if (auto channel = peer->asMegagroup()) {
@ -704,7 +704,7 @@ void ApiWrap::unblockParticipant(PeerData *peer, UserData *user) {
if (auto channel = peer->asChannel()) { if (auto channel = peer->asChannel()) {
auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(false))).done([this, peer, user](const MTPUpdates &result) { auto requestId = request(MTPchannels_KickFromChannel(channel->inputChannel, user->inputUser, MTP_bool(false))).done([this, peer, user](const MTPUpdates &result) {
App::main()->sentUpdatesReceived(result); applyUpdates(result);
_kickRequests.remove(KickRequest(peer, user)); _kickRequests.remove(KickRequest(peer, user));
if (auto channel = peer->asMegagroup()) { if (auto channel = peer->asMegagroup()) {
@ -877,7 +877,7 @@ void ApiWrap::joinChannel(ChannelData *channel) {
} else if (!_channelAmInRequests.contains(channel)) { } else if (!_channelAmInRequests.contains(channel)) {
auto requestId = request(MTPchannels_JoinChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) { auto requestId = request(MTPchannels_JoinChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) {
_channelAmInRequests.remove(channel); _channelAmInRequests.remove(channel);
updatesReceived(result); applyUpdates(result);
}).fail([this, channel](const RPCError &error) { }).fail([this, channel](const RPCError &error) {
if (error.type() == qstr("CHANNELS_TOO_MUCH")) { if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(lang(lng_join_channel_error))); Ui::show(Box<InformBox>(lang(lng_join_channel_error)));
@ -895,7 +895,7 @@ void ApiWrap::leaveChannel(ChannelData *channel) {
} else if (!_channelAmInRequests.contains(channel)) { } else if (!_channelAmInRequests.contains(channel)) {
auto requestId = request(MTPchannels_LeaveChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) { auto requestId = request(MTPchannels_LeaveChannel(channel->inputChannel)).done([this, channel](const MTPUpdates &result) {
_channelAmInRequests.remove(channel); _channelAmInRequests.remove(channel);
updatesReceived(result); applyUpdates(result);
}).fail([this, channel](const RPCError &error) { }).fail([this, channel](const RPCError &error) {
_channelAmInRequests.remove(channel); _channelAmInRequests.remove(channel);
}).send(); }).send();

View File

@ -40,6 +40,8 @@ class ApiWrap : private MTP::Sender {
public: public:
ApiWrap(); ApiWrap();
void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>; using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>;
void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback); void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback);

View File

@ -89,12 +89,10 @@ void AboutBox::keyPressEvent(QKeyEvent *e) {
QString telegramFaqLink() { QString telegramFaqLink() {
auto result = qsl("https://telegram.org/faq"); auto result = qsl("https://telegram.org/faq");
if (cLang() > languageDefault && cLang() < languageCount) { auto language = Lang::Current().id();
const char *code = LanguageCodes[cLang()].c_str(); for (auto faqLanguage : { "de", "es", "it", "ko", "br" }) {
if (qstr("de") == code || qstr("es") == code || qstr("it") == code || qstr("ko") == code) { if (language.startsWith(QLatin1String(faqLanguage))) {
result += '/' + code; result.append('/').append(faqLanguage);
} else if (qstr("pt_BR") == code) {
result += qsl("/br");
} }
} }
return result; return result;

View File

@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "lang/lang_file_parser.h" #include "lang/lang_instance.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
void LanguageBox::prepare() { void LanguageBox::prepare() {
@ -35,75 +35,74 @@ void LanguageBox::prepare() {
setTitle(lang(lng_languages)); setTitle(lang(lng_languages));
auto haveTestLang = (cLang() == languageTest); request(MTPlangpack_GetLanguages()).done([this](const MTPVector<MTPLangPackLanguage> &result) {
auto currentId = Lang::Current().id();
_langGroup = std::make_shared<Ui::RadiobuttonGroup>(cLang()); auto currentFound = false;
auto y = st::boxOptionListPadding.top(); std::vector<QString> languageIds = { qsl("en") };
_langs.reserve(languageCount + (haveTestLang ? 1 : 0)); std::vector<QString> languageNames = { qsl("English") };
if (haveTestLang) { for (auto &language : result.v) {
_langs.emplace_back(this, _langGroup, languageTest, qsl("Custom Lang"), st::langsButton); t_assert(language.type() == mtpc_langPackLanguage);
_langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y); auto &data = language.c_langPackLanguage();
y += _langs.back()->heightNoMargins() + st::boxOptionListSkip; auto languageId = qs(data.vlang_code);
} auto languageName = qs(data.vname);
for (auto i = 0; i != languageCount; ++i) { if (languageId != qstr("en")) {
Lang::FileParser::Result result; languageIds.push_back(languageId);
if (i) { languageNames.push_back(languageName);
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { lng_language_name });
result = loader.found();
} else {
result.insert(lng_language_name, langOriginal(lng_language_name));
}
_langs.emplace_back(this, _langGroup, i, result.value(lng_language_name, LanguageCodes[i].c_str() + qsl(" language")), st::langsButton);
_langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y);
y += _langs.back()->heightNoMargins() + st::boxOptionListSkip;
}
_langGroup->setChangedCallback([this](int value) { languageChanged(value); });
auto optionsCount = languageCount + (haveTestLang ? 1 : 0);
setDimensions(st::langsWidth, st::boxOptionListPadding.top() + optionsCount * st::langsButton.height + (optionsCount - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
}
void LanguageBox::mousePressEvent(QMouseEvent *e) {
if ((e->modifiers() & Qt::CTRL) && (e->modifiers() & Qt::ALT) && (e->modifiers() & Qt::SHIFT)) {
for (int32 i = 1; i < languageCount; ++i) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), { kLangKeysCount });
if (!loader.errors().isEmpty()) {
Ui::show(Box<InformBox>(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" error :(\n\nError: ") + loader.errors()));
return;
} else if (!loader.warnings().isEmpty()) {
auto warn = loader.warnings();
if (warn.size() > 256) warn = warn.mid(0, 253) + qsl("...");
Ui::show(Box<InformBox>(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn));
return;
} }
} }
Ui::show(Box<InformBox>(qsl("Everything seems great in all %1 languages!").arg(languageCount - 1))); if (currentId == qstr("custom")) {
} languageIds.insert(languageIds.begin(), currentId);
languageNames.insert(languageNames.begin(), qsl("Custom LangPack"));
currentFound = true;
}
auto languageCount = languageIds.size();
_langGroup = std::make_shared<Ui::RadiobuttonGroup>(cLang());
auto y = st::boxOptionListPadding.top();
_langs.reserve(languageCount);
for (auto i = 0; i != languageCount; ++i) {
if (!currentFound && languageIds[i] == currentId) {
currentFound = true;
}
_langs.emplace_back(this, _langGroup, i, languageNames[i], st::langsButton);
auto button = _langs.back().data();
button->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y + st::langsButton.margin.top());
button->show();
y += button->heightNoMargins() + st::boxOptionListSkip;
}
_langGroup->setChangedCallback([this](int value) { languageChanged(value); });
setDimensions(st::langsWidth, st::boxOptionListPadding.top() + languageCount * st::langsButton.height + languageCount * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
}).fail([this](const RPCError &error) {
closeBox();
}).send();
setDimensions(st::langsWidth, st::langsWidth);
} }
void LanguageBox::languageChanged(int languageId) { void LanguageBox::languageChanged(int languageId) {
Expects(languageId == languageTest || (languageId >= 0 && languageId < base::array_size(LanguageCodes))); //Expects(languageId == languageTest || (languageId >= 0 && languageId < base::array_size(LanguageCodes)));
if (languageId == cLang()) { //if (languageId == cLang()) {
return; // return;
} //}
Lang::FileParser::Result result; //Lang::FileParser::Result result;
if (languageId > 0) { //if (languageId > 0) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[languageId].c_str() + qsl(".strings"), { lng_sure_save_language, lng_cancel, lng_box_ok }); // Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[languageId].c_str() + qsl(".strings"), { lng_sure_save_language, lng_cancel, lng_box_ok });
result = loader.found(); // result = loader.found();
} else if (languageId == languageTest) { //} else if (languageId == languageTest) {
Lang::FileParser loader(cLangFile(), { lng_sure_save_language, lng_cancel, lng_box_ok }); // Lang::FileParser loader(cLangFile(), { lng_sure_save_language, lng_cancel, lng_box_ok });
result = loader.found(); // result = loader.found();
} //}
auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)), //auto text = result.value(lng_sure_save_language, Lang::GetOriginalValue(lng_sure_save_language)),
save = result.value(lng_box_ok, langOriginal(lng_box_ok)), // save = result.value(lng_box_ok, Lang::GetOriginalValue(lng_box_ok)),
cancel = result.value(lng_cancel, langOriginal(lng_cancel)); // cancel = result.value(lng_cancel, Lang::GetOriginalValue(lng_cancel));
Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this, languageId] { //Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this, languageId] {
cSetLang(languageId); // cSetLang(languageId);
Local::writeSettings(); // Local::writeSettings();
App::restart(); // App::restart();
}), base::lambda_guarded(this, [this] { //}), base::lambda_guarded(this, [this] {
_langGroup->setValue(cLang()); // _langGroup->setValue(cLang());
})), KeepOtherLayers); //})), KeepOtherLayers);
} }

View File

@ -21,13 +21,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "mtproto/sender.h"
namespace Ui { namespace Ui {
class RadiobuttonGroup; class RadiobuttonGroup;
class Radiobutton; class Radiobutton;
} // namespace Ui } // namespace Ui
class LanguageBox : public BoxContent { class LanguageBox : public BoxContent, private MTP::Sender {
Q_OBJECT Q_OBJECT
public: public:
@ -37,8 +38,6 @@ public:
protected: protected:
void prepare() override; void prepare() override;
void mousePressEvent(QMouseEvent *e) override;
private: private:
void languageChanged(int languageId); void languageChanged(int languageId);

View File

@ -118,14 +118,6 @@ QRect computeSourceRect(const QImage &image) {
return result; return result;
} }
QString computeId(Id id) {
auto idAsParams = QStringList();
for (auto i = 0, size = id.size(); i != size; ++i) {
idAsParams.push_back("0x" + QString::number(id[i].unicode(), 16));
}
return "internal::ComputeId(" + idAsParams.join(", ") + ")";
}
} // namespace } // namespace
Generator::Generator(const Options &options) : project_(Project) Generator::Generator(const Options &options) : project_(Project)
@ -305,20 +297,11 @@ EmojiPtr Find(const QChar *start, const QChar *end, int *outLength) {\n\
return index ? &Items[index - 1] : nullptr;\n\ return index ? &Items[index - 1] : nullptr;\n\
}\n\ }\n\
\n\ \n\
inline QString ComputeId(gsl::span<ushort> utf16) {\n\
auto result = QString();\n\
result.reserve(utf16.size());\n\
for (auto ch : utf16) {\n\
result.append(QChar(ch));\n\
}\n\
return result;\n\
}\n\
\n\
void Init() {\n\ void Init() {\n\
auto id = IdData;\n\ auto id = IdData;\n\
Items.reserve(base::array_size(Data));\n\ Items.reserve(base::array_size(Data));\n\
for (auto &data : Data) {\n\ for (auto &data : Data) {\n\
Items.emplace_back(ComputeId(gsl::make_span(id, data.idSize)), data.column, data.row, data.postfixed, data.variated, data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\ Items.emplace_back(QString::fromRawData(id, data.idSize), data.column, data.row, data.postfixed, data.variated, data.original ? &Items[data.original - 1] : nullptr, One::CreationTag());\n\
id += data.idSize;\n\ id += data.idSize;\n\
}\n\ }\n\
}\n\ }\n\
@ -429,7 +412,7 @@ struct DataStruct {\n\
bool variated;\n\ bool variated;\n\
};\n\ };\n\
\n\ \n\
ushort IdData[] = {"; QChar IdData[] = {";
auto count = 0; auto count = 0;
auto fulllength = 0; auto fulllength = 0;
if (!enumerateWholeList([this, &count, &fulllength](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) { if (!enumerateWholeList([this, &count, &fulllength](Id id, int column, int row, bool isPostfixed, bool isVariated, bool isColored, int original) {

View File

@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <functional> #include <functional>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QSet> #include <QtCore/QSet>
#include <QtCore/QBuffer>
#include <QtGui/QImage> #include <QtGui/QImage>
#include <QtGui/QPainter> #include <QtGui/QPainter>
@ -109,7 +108,7 @@ QString stringToBinaryArray(const std::string &str) {
} // namespace } // namespace
Generator::Generator(const Langpack &langpack, const QString &destBasePath, const common::ProjectInfo &project) Generator::Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project)
: langpack_(langpack) : langpack_(langpack)
, basePath_(destBasePath) , basePath_(destBasePath)
, baseName_(QFileInfo(basePath_).baseName()) , baseName_(QFileInfo(basePath_).baseName())
@ -141,8 +140,6 @@ enum LangKey {\n";
};\n\ };\n\
\n\ \n\
QString lang(LangKey key);\n\ QString lang(LangKey key);\n\
\n\
QString langOriginal(LangKey key);\n\
\n"; \n";
for (auto &entry : langpack_.entries) { for (auto &entry : langpack_.entries) {
if (!entry.tags.empty()) { if (!entry.tags.empty()) {
@ -172,7 +169,7 @@ ushort GetTagIndex(QLatin1String tag);\n\
LangKey GetKeyIndex(QLatin1String key);\n\ LangKey GetKeyIndex(QLatin1String key);\n\
LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index);\n\ LangKey GetSubkeyIndex(LangKey key, ushort tag, ushort index);\n\
bool IsTagReplaced(LangKey key, ushort tag);\n\ bool IsTagReplaced(LangKey key, ushort tag);\n\
void FeedKeyValue(LangKey key, const QString &value);\n\ QString GetOriginalValue(LangKey key);\n\
\n"; \n";
return header_->finalize(); return header_->finalize();
@ -191,26 +188,47 @@ const char *KeyNames[kLangKeysCount] = {\n\
\n\ \n\
};\n\ };\n\
\n\ \n\
QString Values[kLangKeysCount], OriginalValues[kLangKeysCount];\n\ QChar DefaultData[] = {";
\n\ auto count = 0;
void set(LangKey key, const QString &val) {\n\ auto fulllength = 0;
Values[key] = val;\n\
}\n\
\n\
class Initializer {\n\
public:\n\
Initializer() {\n";
for (auto &entry : langpack_.entries) { for (auto &entry : langpack_.entries) {
source_->stream() << "\t\tset(" << getFullKey(entry) << ", QString::fromUtf8(" << stringToEncodedString(entry.value) << "));\n"; for (auto ch : entry.value) {
if (fulllength > 0) source_->stream() << ",";
if (!count++) {
source_->stream() << "\n";
} else {
if (count == 12) {
count = 0;
}
source_->stream() << " ";
}
source_->stream() << "0x" << QString::number(ch.unicode(), 16);
++fulllength;
}
} }
source_->stream() << "\ source_->stream() << " };\n\
}\n\
\n\ \n\
};\n\ int Offsets[] = {";
\n\ count = 0;
Initializer Instance;\n\ auto offset = 0;
\n"; auto writeOffset = [this, &count, &offset] {
if (offset > 0) source_->stream() << ",";
if (!count++) {
source_->stream() << "\n";
} else {
if (count == 12) {
count = 0;
}
source_->stream() << " ";
}
source_->stream() << offset;
};
for (auto &entry : langpack_.entries) {
writeOffset();
offset += entry.value.size();
}
writeOffset();
source_->stream() << " };\n";
source_->popNamespace().stream() << "\ source_->popNamespace().stream() << "\
\n\ \n\
const char *GetKeyName(LangKey key) {\n\ const char *GetKeyName(LangKey key) {\n\
@ -314,25 +332,13 @@ bool IsTagReplaced(LangKey key, ushort tag) {\n\
return false;\n\ return false;\n\
}\n\ }\n\
\n\ \n\
void FeedKeyValue(LangKey key, const QString &value) {\n\ QString GetOriginalValue(LangKey key) {\n\
Expects(key >= 0 && key < kLangKeysCount);\n\ Expects(key >= 0 && key < kLangKeysCount);\n\
if (OriginalValues[key].isEmpty()) {\n\ auto offset = Offsets[key];\n\
OriginalValues[key] = Values[key].isEmpty() ? qsl(\"{}\") : Values[key];\n\ return QString::fromRawData(DefaultData + offset, Offsets[key + 1] - offset);\n\
}\n\
Values[key] = value;\n\
}\n\ }\n\
\n"; \n";
source_->popNamespace().stream() << "\
\n\
QString lang(LangKey key) {\n\
return (key < 0 || key >= kLangKeysCount) ? QString() : Lang::Values[key];\n\
}\n\
\n\
QString langOriginal(LangKey key) {\n\
return (key < 0 || key >= kLangKeysCount || Lang::OriginalValues[key] == qsl(\"{}\")) ? QString() : (Lang::OriginalValues[key].isEmpty() ? Lang::Values[key] : Lang::OriginalValues[key]);\n\
}\n";
return source_->finalize(); return source_->finalize();
} }
@ -473,7 +479,7 @@ void Generator::writeSetSearch(const std::set<QString, std::greater<QString>> &s
return " << invalidResult << ";\n"; return " << invalidResult << ";\n";
} }
QString Generator::getFullKey(const Langpack::Entry &entry) { QString Generator::getFullKey(const LangPack::Entry &entry) {
if (entry.tags.empty()) { if (entry.tags.empty()) {
return entry.key; return entry.key;
} }

View File

@ -34,7 +34,7 @@ namespace lang {
class Generator { class Generator {
public: public:
Generator(const Langpack &langpack, const QString &destBasePath, const common::ProjectInfo &project); Generator(const LangPack &langpack, const QString &destBasePath, const common::ProjectInfo &project);
Generator(const Generator &other) = delete; Generator(const Generator &other) = delete;
Generator &operator=(const Generator &other) = delete; Generator &operator=(const Generator &other) = delete;
@ -42,13 +42,13 @@ public:
bool writeSource(); bool writeSource();
private: private:
QString getFullKey(const Langpack::Entry &entry); QString getFullKey(const LangPack::Entry &entry);
bool isTagPlural(const QString &key, const QString &tag) const; bool isTagPlural(const QString &key, const QString &tag) const;
template <typename ComputeResult> template <typename ComputeResult>
void writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult); void writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult);
const Langpack &langpack_; const LangPack &langpack_;
QString basePath_, baseName_; QString basePath_, baseName_;
const common::ProjectInfo &project_; const common::ProjectInfo &project_;
std::unique_ptr<common::CppFile> source_, header_; std::unique_ptr<common::CppFile> source_, header_;

View File

@ -128,7 +128,7 @@ common::LogStream ParsedFile::logErrorBadString() {
return logError(kErrorBadString); return logError(kErrorBadString);
} }
QString ParsedFile::extractTagsData(const QString &value, Langpack *to) { QString ParsedFile::extractTagsData(const QString &value, LangPack *to) {
auto tagStart = value.indexOf('{'); auto tagStart = value.indexOf('{');
if (tagStart < 0) { if (tagStart < 0) {
return value; return value;
@ -157,7 +157,7 @@ QString ParsedFile::extractTagsData(const QString &value, Langpack *to) {
return finalValue; return finalValue;
} }
QString ParsedFile::extractTagData(const QString &tagText, Langpack *to) { QString ParsedFile::extractTagData(const QString &tagText, LangPack *to) {
auto numericPart = tagText.indexOf(':'); auto numericPart = tagText.indexOf(':');
auto tag = (numericPart > 0) ? tagText.mid(0, numericPart) : tagText; auto tag = (numericPart > 0) ? tagText.mid(0, numericPart) : tagText;
if (!ValidateTag(tag)) { if (!ValidateTag(tag)) {
@ -190,7 +190,7 @@ QString ParsedFile::extractTagData(const QString &tagText, Langpack *to) {
} }
auto index = 0; auto index = 0;
for (auto &part : numericParts) { for (auto &part : numericParts) {
auto numericPartEntry = Langpack::Entry(); auto numericPartEntry = LangPack::Entry();
numericPartEntry.key = tag + QString::number(index++); numericPartEntry.key = tag + QString::number(index++);
if (part.indexOf('#') != part.lastIndexOf('#')) { if (part.indexOf('#') != part.lastIndexOf('#')) {
logErrorBadString() << "bad option for plural key part in tag: '" << tagText.toStdString() << "', too many '#'."; logErrorBadString() << "bad option for plural key part in tag: '" << tagText.toStdString() << "', too many '#'.";
@ -211,14 +211,14 @@ void ParsedFile::addEntity(const QString &key, const QString &value) {
return; return;
} }
} }
auto tagsData = Langpack(); auto tagsData = LangPack();
auto entry = Langpack::Entry(); auto entry = LangPack::Entry();
entry.key = key; entry.key = key;
entry.value = extractTagsData(value, &tagsData); entry.value = extractTagsData(value, &tagsData);
entry.tags = tagsData.tags; entry.tags = tagsData.tags;
result_.entries.push_back(entry); result_.entries.push_back(entry);
for (auto &pluralEntry : tagsData.entries) { for (auto &pluralEntry : tagsData.entries) {
auto taggedEntry = Langpack::Entry(); auto taggedEntry = LangPack::Entry();
taggedEntry.key = key + "__" + pluralEntry.key; taggedEntry.key = key + "__" + pluralEntry.key;
taggedEntry.value = pluralEntry.value; taggedEntry.value = pluralEntry.value;
result_.entries.push_back(taggedEntry); result_.entries.push_back(taggedEntry);

View File

@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace codegen { namespace codegen {
namespace lang { namespace lang {
struct Langpack { struct LangPack {
struct Tag { struct Tag {
QString tag; QString tag;
}; };
@ -53,7 +53,7 @@ public:
bool read(); bool read();
Langpack getResult() { LangPack getResult() {
return result_; return result_;
} }
@ -84,14 +84,14 @@ private:
BasicToken assertNextToken(BasicToken::Type type); BasicToken assertNextToken(BasicToken::Type type);
void addEntity(const QString &key, const QString &value); void addEntity(const QString &key, const QString &value);
QString extractTagsData(const QString &value, Langpack *to); QString extractTagsData(const QString &value, LangPack *to);
QString extractTagData(const QString &tag, Langpack *to); QString extractTagData(const QString &tag, LangPack *to);
QString filePath_; QString filePath_;
common::BasicTokenizedFile file_; common::BasicTokenizedFile file_;
Options options_; Options options_;
bool failed_ = false; bool failed_ = false;
Langpack result_; LangPack result_;
}; };

View File

@ -51,7 +51,7 @@ int Processor::launch() {
return 0; return 0;
} }
bool Processor::write(const Langpack &langpack) const { bool Processor::write(const LangPack &langpack) const {
bool forceReGenerate = false; bool forceReGenerate = false;
QDir dir(options_.outputPath); QDir dir(options_.outputPath);
if (!dir.mkpath(".")) { if (!dir.mkpath(".")) {

View File

@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace codegen { namespace codegen {
namespace lang { namespace lang {
class ParsedFile; class ParsedFile;
struct Langpack; struct LangPack;
// Walks through a file, parses it and generates the output. // Walks through a file, parses it and generates the output.
class Processor { class Processor {
@ -42,7 +42,7 @@ public:
~Processor(); ~Processor();
private: private:
bool write(const Langpack &langpack) const; bool write(const LangPack &langpack) const;
std::unique_ptr<ParsedFile> parser_; std::unique_ptr<ParsedFile> parser_;
const Options &options_; const Options &options_;

View File

@ -305,9 +305,6 @@ inline const QString &cTempDir() {
return res; return res;
} }
static const char *DefaultCountry = "US";
static const char *DefaultLanguage = "en";
enum { enum {
DialogsFirstLoad = 20, // first dialogs part size requested DialogsFirstLoad = 20, // first dialogs part size requested
DialogsPerPage = 500, // next dialogs part size DialogsPerPage = 500, // next dialogs part size

View File

@ -412,9 +412,6 @@ namespace Sandbox {
namespace internal { namespace internal {
struct Data { struct Data {
QString LangSystemISO;
int32 LangSystem = languageDefault;
QByteArray LastCrashDump; QByteArray LastCrashDump;
ProxyData PreLaunchProxy; ProxyData PreLaunchProxy;
}; };
@ -523,16 +520,6 @@ void start() {
base::TaskQueue::ProcessMainTasks(); base::TaskQueue::ProcessMainTasks();
}); });
SandboxData = std::make_unique<internal::Data>(); SandboxData = std::make_unique<internal::Data>();
SandboxData->LangSystemISO = psCurrentLanguage();
if (SandboxData->LangSystemISO.isEmpty()) SandboxData->LangSystemISO = qstr("en");
auto l = LangSystemISO().toLatin1();
for (auto i = 0; i < languageCount; ++i) {
if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) {
SandboxData->LangSystem = i;
break;
}
}
} }
bool started() { bool started() {
@ -548,8 +535,6 @@ uint64 UserTag() {
return SandboxUserTag; return SandboxUserTag;
} }
DefineReadOnlyVar(Sandbox, QString, LangSystemISO);
DefineReadOnlyVar(Sandbox, int32, LangSystem);
DefineVar(Sandbox, QByteArray, LastCrashDump); DefineVar(Sandbox, QByteArray, LastCrashDump);
DefineVar(Sandbox, ProxyData, PreLaunchProxy); DefineVar(Sandbox, ProxyData, PreLaunchProxy);

View File

@ -218,8 +218,6 @@ void finish();
uint64 UserTag(); uint64 UserTag();
DeclareReadOnlyVar(QString, LangSystemISO);
DeclareReadOnlyVar(int32, LangSystem);
DeclareVar(QByteArray, LastCrashDump); DeclareVar(QByteArray, LastCrashDump);
DeclareVar(ProxyData, PreLaunchProxy); DeclareVar(ProxyData, PreLaunchProxy);

View File

@ -47,12 +47,21 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "auth_session.h" #include "auth_session.h"
namespace Intro { namespace Intro {
namespace {
constexpr str_const kDefaultCountry = "US";
} // namespace
Widget::Widget(QWidget *parent) : TWidget(parent) Widget::Widget(QWidget *parent) : TWidget(parent)
, _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton), st::introSlideDuration) , _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton), st::introSlideDuration)
, _settings(this, object_ptr<Ui::RoundButton>(this, lang(lng_menu_settings), st::defaultBoxButton), st::introCoverDuration) , _settings(this, object_ptr<Ui::RoundButton>(this, lang(lng_menu_settings), st::defaultBoxButton), st::introCoverDuration)
, _next(this, QString(), st::introNextButton) { , _next(this, QString(), st::introNextButton) {
getData()->country = psCurrentCountry(); auto country = Platform::SystemCountry();
if (country.isEmpty()) {
country = str_const_toString(kDefaultCountry);
}
getData()->country = country;
_back->entity()->setClickedCallback([this] { historyMove(Direction::Back); }); _back->entity()->setClickedCallback([this] { historyMove(Direction::Back); });
_back->hideFast(); _back->hideFast();
@ -61,20 +70,20 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
_settings->entity()->setClickedCallback([] { App::wnd()->showSettings(); }); _settings->entity()->setClickedCallback([] { App::wnd()->showSettings(); });
if (cLang() == languageDefault) { //if (cLang() == languageDefault) {
auto systemLangId = Sandbox::LangSystem(); // auto systemLangId = Sandbox::LangSystem();
if (systemLangId != languageDefault) { // if (systemLangId != languageDefault) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), { lng_switch_to_this }); // Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[systemLangId].c_str() + qsl(".strings"), { lng_switch_to_this });
auto text = loader.found().value(lng_switch_to_this); // auto text = loader.found().value(lng_switch_to_this);
if (!text.isEmpty()) { // if (!text.isEmpty()) {
_changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, text), st::introCoverDuration); // _changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, text), st::introCoverDuration);
_changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); }); // _changeLanguage->entity()->setClickedCallback([this, systemLangId] { changeLanguage(systemLangId); });
} // }
} // }
} else { //} else {
_changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, langOriginal(lng_switch_to_this)), st::introCoverDuration); // _changeLanguage.create(this, object_ptr<Ui::LinkButton>(this, Lang::GetOriginalValue(lng_switch_to_this)), st::introCoverDuration);
_changeLanguage->entity()->setClickedCallback([this] { changeLanguage(languageDefault); }); // _changeLanguage->entity()->setClickedCallback([this] { changeLanguage(languageDefault); });
} //}
MTP::send(MTPhelp_GetNearestDc(), rpcDone(&Widget::gotNearestDC)); MTP::send(MTPhelp_GetNearestDc(), rpcDone(&Widget::gotNearestDC));

View File

@ -23,73 +23,35 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/parse_helper.h" #include "base/parse_helper.h"
namespace Lang { namespace Lang {
namespace {
constexpr auto kLangFileLimit = 1024 * 1024;
} // namespace
FileParser::FileParser(const QString &file, const std::set<LangKey> &request) FileParser::FileParser(const QString &file, const std::set<LangKey> &request)
: _filePath(file) : _content(base::parse::stripComments(ReadFile(file, file)))
, _request(request) , _request(request) {
, _readingAll(request.find(kLangKeysCount) != request.end()) { parse();
QFile f(_filePath); }
if (!f.open(QIODevice::ReadOnly)) {
error(qsl("Could not open input file!")); FileParser::FileParser(const QByteArray &content, base::lambda<void(QLatin1String key, const QByteArray &value)> callback)
: _content(base::parse::stripComments(content))
, _callback(std::move(callback)) {
parse();
}
void FileParser::parse() {
if (_content.isEmpty()) {
error(qsl("Got empty lang file content"));
return; return;
} }
if (f.size() > 1024 * 1024) {
error(qsl("Too big file: %1").arg(f.size()));
return;
}
QByteArray checkCodec = f.read(3);
if (checkCodec.size() < 3) {
error(qsl("Bad lang input file: %1").arg(file));
return;
}
f.seek(0);
QByteArray data; auto text = _content.constData(), end = text + _content.size();
int skip = 0; while (text != end) {
if ((checkCodec.at(0) == '\xFF' && checkCodec.at(1) == '\xFE') || (checkCodec.at(0) == '\xFE' && checkCodec.at(1) == '\xFF') || (checkCodec.at(1) == 0)) { if (!readKeyValue(text, end)) {
QTextStream stream(&f); break;
stream.setCodec("UTF-16");
QString string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
error(qsl("Could not read valid UTF-16 file: % 1").arg(file));
return;
} }
f.close();
data = string.toUtf8();
} else if (checkCodec.at(0) == 0) {
QByteArray tmp = "\xFE\xFF" + f.readAll(); // add fake UTF-16 BOM
f.close();
QTextStream stream(&tmp);
stream.setCodec("UTF-16");
QString string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
error(qsl("Could not read valid UTF-16 file: % 1").arg(file));
return;
}
data = string.toUtf8();
} else {
data = f.readAll();
if (checkCodec.at(0) == '\xEF' && checkCodec.at(1) == '\xBB' && checkCodec.at(2) == '\xBF') {
skip = 3; // skip UTF-8 BOM
}
}
data = base::parse::stripComments(data);
auto text = data.constData() + skip, end = text + data.size() - skip;
try {
while (text != end) {
if (!readKeyValue(text, end)) {
break;
}
}
} catch (std::exception &e) {
error(QString::fromUtf8(e.what()));
return;
} }
} }
@ -101,217 +63,152 @@ const QString &FileParser::errors() const {
} }
const QString &FileParser::warnings() const { const QString &FileParser::warnings() const {
if (!_checked) {
for (auto i = 0; i < kLangKeysCount; ++i) {
if (!_found[i]) {
_warningsList.push_back(qsl("No value found for key '%1'").arg(GetKeyName(LangKey(i))));
}
}
_checked = true;
}
if (_warnings.isEmpty() && !_warningsList.isEmpty()) { if (_warnings.isEmpty() && !_warningsList.isEmpty()) {
_warnings = _warningsList.join('\n'); _warnings = _warningsList.join('\n');
} }
return _warnings; return _warnings;
} }
void FileParser::foundKeyValue(LangKey key) {
if (key < kLangKeysCount) {
_found[key] = true;
}
}
bool FileParser::readKeyValue(const char *&from, const char *end) { bool FileParser::readKeyValue(const char *&from, const char *end) {
using base::parse::skipWhitespaces; using base::parse::skipWhitespaces;
if (!skipWhitespaces(from, end)) return false; if (!skipWhitespaces(from, end)) return false;
if (*from != '"') throw Exception(QString("Expected quote before key name!")); if (*from != '"') {
return error("Expected quote before key name!");
}
++from; ++from;
const char *nameStart = from; const char *nameStart = from;
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) { while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
++from; ++from;
} }
auto varName = QLatin1String(nameStart, from - nameStart); auto key = QLatin1String(nameStart, from - nameStart);
if (from == end || *from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(varName)); if (from == end || *from != '"') {
return error(qsl("Expected quote after key name '%1'!").arg(key));
}
++from; ++from;
if (!skipWhitespaces(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); if (!skipWhitespaces(from, end)) {
if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(varName)); return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); if (*from != '=') {
if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(varName)); return error(qsl("'=' expected in key '%1'!").arg(key));
}
auto varKey = GetKeyIndex(varName); if (!skipWhitespaces(++from, end)) {
bool feedingValue = _request.empty(); return error(qsl("Unexpected end of file in key '%1'!").arg(key));
if (feedingValue) { }
if (varKey == kLangKeysCount) { if (*from != '"') {
warning(QString("Unknown key '%1'!").arg(varName)); return error(qsl("Expected string after '=' in key '%1'!").arg(key));
}
} else if (!_readingAll && _request.find(varKey) == _request.end()) {
varKey = kLangKeysCount;
} }
bool readingValue = (varKey != kLangKeysCount);
QByteArray varValue; auto skipping = false;
QMap<ushort, bool> tagsUsed; auto keyIndex = kLangKeysCount;
if (!_callback) {
keyIndex = GetKeyIndex(key);
skipping = (_request.find(keyIndex) == _request.end());
}
auto value = QByteArray();
auto appendValue = [&value, skipping](auto&&... args) {
if (!skipping) {
value.append(std::forward<decltype(args)>(args)...);
}
};
const char *start = ++from; const char *start = ++from;
while (from < end && *from != '"') { while (from < end && *from != '"') {
if (*from == '\n') { if (*from == '\n') {
throw Exception(QString("Unexpected end of string in key '%1'!").arg(varName)); return error(qsl("Unexpected end of string in key '%1'!").arg(key));
} }
if (*from == '\\') { if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); if (from + 1 >= end) {
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') { return error(qsl("Unexpected end of file in key '%1'!").arg(key));
if (readingValue && from > start) varValue.append(start, from - start); }
if (*(from + 1) == '"' || *(from + 1) == '\\') {
if (from > start) appendValue(start, from - start);
start = ++from; start = ++from;
} else if (*(from + 1) == 'n') { } else if (*(from + 1) == 'n') {
if (readingValue) { if (from > start) appendValue(start, from - start);
if (from > start) varValue.append(start, int(from - start)); appendValue('\n');
varValue.append('\n');
}
start = (++from) + 1; start = (++from) + 1;
} }
} else if (readingValue && *from == '{') {
if (from > start) varValue.append(start, int(from - start));
const char *tagStart = ++from;
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
++from;
}
if (from == tagStart) {
readingValue = false;
warning(QString("Expected tag name in key '%1'!").arg(varName));
continue;
}
auto tagName = QLatin1String(tagStart, int(from - tagStart));
if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(varName));
auto index = GetTagIndex(tagName);
if (index == kTagsCount) {
readingValue = false;
warning(QString("Tag '%1' not found in key '%2', not using value.").arg(tagName).arg(varName));
continue;
}
if (!IsTagReplaced(varKey, index)) {
readingValue = false;
warning(QString("Unexpected tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
continue;
}
if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(tagName).arg(varName));
tagsUsed.insert(index, true);
QString tagReplacer(4, TextCommand);
tagReplacer[1] = TextCommandLangTag;
tagReplacer[2] = QChar(0x0020 + index);
varValue.append(tagReplacer.toUtf8());
if (*from == ':') {
start = ++from;
QByteArray subvarValue;
bool foundtag = false;
int countedIndex = 0;
while (from < end && *from != '"' && *from != '}') {
if (*from == '|') {
if (from > start) subvarValue.append(start, int(from - start));
if (countedIndex >= kTagsPluralVariants) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
auto subkey = GetSubkeyIndex(varKey, index, countedIndex++);
if (subkey == kLangKeysCount) {
readingValue = false;
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
break;
} else {
if (feedingValue) {
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName));
} else {
foundKeyValue(subkey);
}
}
subvarValue = QByteArray();
foundtag = false;
start = from + 1;
}
if (*from == '\n') {
throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') {
if (from > start) subvarValue.append(start, int(from - start));
start = ++from;
} else if (*(from + 1) == 'n') {
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append('\n');
start = (++from) + 1;
}
} else if (*from == '{') {
throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
} else if (*from == '#') {
if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
foundtag = true;
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append(tagReplacer.toUtf8());
start = from + 1;
}
++from;
}
if (!readingValue) continue;
if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (from > start) subvarValue.append(start, int(from - start));
if (countedIndex >= kTagsPluralVariants) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
auto subkey = GetSubkeyIndex(varKey, index, countedIndex++);
if (subkey == kLangKeysCount) {
readingValue = false;
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
break;
} else {
if (feedingValue) {
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName));
} else {
foundKeyValue(subkey);
}
}
}
start = from + 1;
} }
++from; ++from;
} }
if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); if (from >= end) {
if (readingValue && from > start) varValue.append(start, from - start); return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (from > start) {
appendValue(start, from - start);
}
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName)); if (!skipWhitespaces(++from, end)) {
if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(varName)); return error(qsl("Unexpected end of file in key '%1'!").arg(key));
}
if (*from != ';') {
return error(qsl("';' expected after \"value\" in key '%1'!").arg(key));
}
skipWhitespaces(++from, end); skipWhitespaces(++from, end);
if (readingValue) { if (_callback) {
if (feedingValue) { _callback(key, value);
if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(varName)); } else if (!skipping) {
} else { _result.insert(keyIndex, QString::fromUtf8(value));
foundKeyValue(varKey);
_result.insert(varKey, QString::fromUtf8(varValue));
}
} }
return true; return true;
} }
bool FileParser::feedKeyValue(LangKey key, const QString &value) { QByteArray FileParser::ReadFile(const QString &absolutePath, const QString &relativePath) {
if (key < kLangKeysCount) { QFile file(QFileInfo(relativePath).exists() ? relativePath : absolutePath);
_found[key] = 1; if (!file.open(QIODevice::ReadOnly)) {
FeedKeyValue(key, value); LOG(("Lang Error: Could not open file at '%1' ('%2')").arg(relativePath).arg(absolutePath));
return true; return QByteArray();
} }
return false; if (file.size() > kLangFileLimit) {
LOG(("Lang Error: File is too big: %1").arg(file.size()));
return QByteArray();
}
constexpr auto kCodecMagicSize = 3;
auto codecMagic = file.read(kCodecMagicSize);
if (codecMagic.size() < kCodecMagicSize) {
LOG(("Lang Error: Found bad file at '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
file.seek(0);
QByteArray data;
int skip = 0;
auto readUtf16Stream = [relativePath, absolutePath](auto &stream) {
stream.setCodec("UTF-16");
auto string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
LOG(("Lang Error: Could not read UTF-16 data from '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
if (string.isEmpty()) {
LOG(("Lang Error: Empty UTF-16 content in '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
return string.toUtf8();
};
if ((codecMagic.at(0) == '\xFF' && codecMagic.at(1) == '\xFE') || (codecMagic.at(0) == '\xFE' && codecMagic.at(1) == '\xFF') || (codecMagic.at(1) == 0)) {
return readUtf16Stream(QTextStream(&file));
} else if (codecMagic.at(0) == 0) {
auto utf16WithBOM = "\xFE\xFF" + file.readAll();
return readUtf16Stream(QTextStream(utf16WithBOM));
}
data = file.readAll();
if (codecMagic.at(0) == '\xEF' && codecMagic.at(1) == '\xBB' && codecMagic.at(2) == '\xBF') {
data = data.mid(3); // skip UTF-8 BOM
}
if (data.isEmpty()) {
LOG(("Lang Error: Empty UTF-8 content in '%1' ('%2')").arg(relativePath).arg(absolutePath));
return QByteArray();
}
return data;
} }
} // namespace Lang } // namespace Lang

View File

@ -28,7 +28,10 @@ class FileParser {
public: public:
using Result = QMap<LangKey, QString>; using Result = QMap<LangKey, QString>;
FileParser(const QString &file, const std::set<LangKey> &request = std::set<LangKey>()); FileParser(const QString &file, const std::set<LangKey> &request);
FileParser(const QByteArray &content, base::lambda<void(QLatin1String key, const QByteArray &value)> callback);
static QByteArray ReadFile(const QString &absolutePath, const QString &relativePath);
const QString &errors() const; const QString &errors() const;
const QString &warnings() const; const QString &warnings() const;
@ -38,11 +41,11 @@ public:
} }
private: private:
bool feedKeyValue(LangKey key, const QString &value); void parse();
void foundKeyValue(LangKey key);
void error(const QString &text) { bool error(const QString &text) {
_errorsList.push_back(text); _errorsList.push_back(text);
return false;
} }
void warning(const QString &text) { void warning(const QString &text) {
_warningsList.push_back(text); _warningsList.push_back(text);
@ -51,13 +54,11 @@ private:
mutable QStringList _errorsList, _warningsList; mutable QStringList _errorsList, _warningsList;
mutable QString _errors, _warnings; mutable QString _errors, _warnings;
mutable bool _checked = false;
std::array<bool, kLangKeysCount> _found = { { false } };
QString _filePath; const QByteArray _content;
std::set<LangKey> _request; const std::set<LangKey> _request;
const base::lambda<void(QLatin1String key, const QByteArray &value)> _callback;
bool _readingAll = false;
Result _result; Result _result;
}; };

View File

@ -0,0 +1,449 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram 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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "lang/lang_instance.h"
#include "messenger.h"
#include "lang/lang_file_parser.h"
#include "platform/platform_specific.h"
#include "storage/serialize_common.h"
#include "storage/localstorage.h"
namespace Lang {
namespace {
constexpr auto kDefaultLanguage = str_const("en");
constexpr auto kLangValuesLimit = 20000;
class ValueParser {
public:
ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value);
QString takeResult() {
Expects(!_failed);
return std::move(_result);
}
std::map<LangKey, QString> takePluralValues() {
Expects(!_failed);
return std::move(_plural);
}
bool parse();
private:
void appendToResult(const char *nextBegin);
void appendToPlural(const char *nextBegin);
bool feedPluralValue();
bool logError(const QString &text);
bool readTag();
const QByteArray &_key;
LangKey _keyIndex = kLangKeysCount;
QLatin1String _currentTag;
ushort _currentTagIndex = 0;
QString _currentTagReplacer;
QString _pluralValue;
int _pluralIndex = 0;
bool _pluralNumericFound = false;
bool _failed = true;
const char *_begin, *_ch, *_end;
QString _result;
std::map<LangKey, QString> _plural;
OrderedSet<ushort> _tagsUsed;
};
ValueParser::ValueParser(const QByteArray &key, LangKey keyIndex, const QByteArray &value)
: _key(key)
, _keyIndex(keyIndex)
, _begin(value.constData())
, _ch(_begin)
, _end(_begin + value.size()) {
}
void ValueParser::appendToResult(const char *nextBegin) {
if (_ch > _begin) _result.append(QString::fromUtf8(_begin, _ch - _begin));
_begin = nextBegin;
}
void ValueParser::appendToPlural(const char *nextBegin) {
if (_ch > _begin) _pluralValue.append(QString::fromUtf8(_begin, _ch - _begin));
_begin = nextBegin;
}
bool ValueParser::feedPluralValue() {
appendToPlural(_ch + 1);
if (_pluralIndex >= kTagsPluralVariants) {
return logError("Too many values inside counted tag");
}
auto pluralKeyIndex = GetSubkeyIndex(_keyIndex, _currentTagIndex, _pluralIndex);
if (pluralKeyIndex == kLangKeysCount) {
return logError("Unexpected counted tag");
} else {
_plural.emplace(pluralKeyIndex, _pluralValue);
}
++_pluralIndex;
_pluralValue = QString();
_pluralNumericFound = false;
return true;
};
bool ValueParser::logError(const QString &text) {
_failed = true;
auto loggedKey = (_currentTag.size() > 0) ? (_key + QString(':') + _currentTag) : QString(_key);
LOG(("Lang Error: %1 (key '%2')").arg(text).arg(loggedKey));
return false;
}
bool ValueParser::readTag() {
auto tagStart = _ch;
auto isTagChar = [](QChar ch) {
if (ch >= 'a' && ch <= 'z') {
return true;
} else if (ch >= 'A' && ch <= 'z') {
return true;
} else if (ch >= '0' && ch <= '9') {
return true;
}
return (ch == '_');
};
while (_ch != _end && isTagChar(*_ch)) {
++_ch;
}
if (_ch == tagStart) {
return logError("Expected tag name");
}
_currentTag = QLatin1String(tagStart, _ch - tagStart);
if (_ch == _end || (*_ch != '}' && *_ch != ':')) {
return logError("Expected '}' or ':' after tag name");
}
_currentTagIndex = GetTagIndex(_currentTag);
if (_currentTagIndex == kTagsCount) {
return logError("Unknown tag");
}
if (!IsTagReplaced(_keyIndex, _currentTagIndex)) {
return logError("Unexpected tag");
}
if (_tagsUsed.contains(_currentTagIndex)) {
return logError("Repeated tag");
}
_tagsUsed.insert(_currentTagIndex);
if (_currentTagReplacer.isEmpty()) {
_currentTagReplacer = QString(4, TextCommand);
_currentTagReplacer[1] = TextCommandLangTag;
}
_currentTagReplacer[2] = QChar(0x0020 + _currentTagIndex);
return true;
}
bool ValueParser::parse() {
_failed = false;
_result.reserve(_end - _begin);
for (; _ch != _end; ++_ch) {
if (*_ch == '{') {
appendToResult(_ch);
++_ch;
if (!readTag()) {
return false;
}
_result.append(_currentTagReplacer);
_begin = _ch + 1;
if (*_ch == ':') {
_pluralIndex = 0;
while (_ch != _end && *_ch != '}') {
if (*_ch == '|') {
if (!feedPluralValue()) {
return false;
}
} else if (*_ch == '\\') {
if (_ch + 1 >= _end) {
return logError("Unexpected end of file inside counted tag");
}
if (*(_ch + 1) == '{' || *(_ch + 1) == '#' || *(_ch + 1) == '}') {
appendToPlural(_ch + 1);
}
} else if (*_ch == '{') {
return logError("Unexpected tag inside counted tag");
} else if (*_ch == '#') {
if (_pluralNumericFound) {
return logError("Replacement '#' double used inside counted tag");
}
_pluralNumericFound = true;
appendToPlural(_ch + 1);
_pluralValue.append(_currentTagReplacer);
}
++_ch;
}
if (_ch == _end) {
return logError("Unexpected end of value inside counted tag");
}
if (!feedPluralValue()) {
return false;
}
}
_currentTag = QLatin1String();
}
}
appendToResult(_end);
return true;
}
} // namespace
void Instance::fillDefaults() {
_values.clear();
_values.reserve(kLangKeysCount);
for (auto i = 0; i != kLangKeysCount; ++i) {
_values.emplace_back(GetOriginalValue(LangKey(i)));
}
_id = str_const_toString(kDefaultLanguage);
_legacyId = kLegacyDefaultLanguage;
}
QString Instance::cloudLangCode() const {
if (isCustom() || id().isEmpty()) {
if (_systemLanguage.isEmpty()) {
_systemLanguage = Platform::SystemLanguage();
if (_systemLanguage.isEmpty()) {
_systemLanguage = str_const_toString(kDefaultLanguage);
}
}
return _systemLanguage;
}
return id();
}
QByteArray Instance::serialize() const {
auto size = Serialize::stringSize(_id);
size += sizeof(qint32); // version
size += Serialize::stringSize(_customFilePathAbsolute) + Serialize::stringSize(_customFilePathRelative);
size += Serialize::bytearraySize(_customFileContent);
size += sizeof(qint32); // _nonDefaultValues.size()
for (auto &nonDefault : _nonDefaultValues) {
size += Serialize::bytearraySize(nonDefault.first) + Serialize::bytearraySize(nonDefault.second);
}
auto result = QByteArray();
result.reserve(size);
{
QDataStream stream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1);
stream << _id << qint32(_version);
stream << _customFilePathAbsolute << _customFilePathRelative << _customFileContent;
stream << qint32(_nonDefaultValues.size());
for (auto &nonDefault : _nonDefaultValues) {
stream << nonDefault.first << nonDefault.second;
}
}
return result;
}
void Instance::fillFromSerialized(const QByteArray &data) {
QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_1);
QString id;
qint32 version = 0;
QString customFilePathAbsolute, customFilePathRelative;
QByteArray customFileContent;
qint32 nonDefaultValuesCount = 0;
stream >> id >> version;
stream >> customFilePathAbsolute >> customFilePathRelative >> customFileContent;
stream >> nonDefaultValuesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Lang Error: Could not read data from serialized langpack."));
return;
}
if (nonDefaultValuesCount > kLangValuesLimit) {
LOG(("Lang Error: Values count limit exceeded: %1").arg(nonDefaultValuesCount));
return;
}
if (!customFilePathAbsolute.isEmpty()) {
auto currentCustomFileContent = Lang::FileParser::ReadFile(customFilePathAbsolute, customFilePathRelative);
if (!currentCustomFileContent.isEmpty() && currentCustomFileContent != customFileContent) {
loadFromCustomContent(customFilePathAbsolute, customFilePathRelative, currentCustomFileContent);
Local::writeLangPack();
return;
}
}
std::vector<QByteArray> nonDefaultStrings;
nonDefaultStrings.reserve(2 * nonDefaultValuesCount);
for (auto i = 0; i != nonDefaultValuesCount; ++i) {
QByteArray key, value;
stream >> key >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("Lang Error: Could not read data from serialized langpack."));
return;
}
nonDefaultStrings.push_back(key);
nonDefaultStrings.push_back(value);
}
_id = id;
_version = version;
_customFilePathAbsolute = customFilePathAbsolute;
_customFilePathRelative = customFilePathRelative;
_customFileContent = customFileContent;
LOG(("Lang Info: Loaded cached, keys: %1").arg(nonDefaultValuesCount));
for (auto i = 0, count = nonDefaultValuesCount * 2; i != count; i += 2) {
applyValue(nonDefaultStrings[i], nonDefaultStrings[i + 1]);
}
}
void Instance::loadFromContent(const QByteArray &content) {
Lang::FileParser loader(content, [this](QLatin1String key, const QByteArray &value) {
applyValue(QByteArray(key.data(), key.size()), value);
});
if (!loader.errors().isEmpty()) {
LOG(("Lang load errors: %1").arg(loader.errors()));
} else if (!loader.warnings().isEmpty()) {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
}
void Instance::loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content) {
_id = qsl("custom");
_version = 0;
_customFilePathAbsolute = absolutePath;
_customFilePathRelative = relativePath;
_customFileContent = content;
loadFromContent(_customFileContent);
}
void Instance::fillFromCustomFile(const QString &filePath) {
auto absolutePath = QFileInfo(filePath).absoluteFilePath();
auto relativePath = QDir().relativeFilePath(filePath);
auto content = Lang::FileParser::ReadFile(absolutePath, relativePath);
if (!content.isEmpty()) {
loadFromCustomContent(absolutePath, relativePath, content);
}
}
void Instance::fillFromLegacy(int legacyId, const QString &legacyPath) {
if (legacyId == kLegacyDefaultLanguage) {
_legacyId = legacyId;
_id = str_const_toString(kLegacyLanguages[legacyId]);
} else if (legacyId == kLegacyCustomLanguage) {
auto absolutePath = QFileInfo(legacyPath).absoluteFilePath();
auto relativePath = QDir().relativeFilePath(absolutePath);
auto content = Lang::FileParser::ReadFile(absolutePath, relativePath);
if (!content.isEmpty()) {
loadFromCustomContent(absolutePath, relativePath, content);
}
} else if (legacyId > kLegacyDefaultLanguage && legacyId < base::array_size(kLegacyLanguages)) {
auto languageId = str_const_toString(kLegacyLanguages[_legacyId]);
auto resourcePath = qsl(":/langs/lang_") + languageId + qsl(".strings");
auto content = Lang::FileParser::ReadFile(resourcePath, resourcePath);
if (!content.isEmpty()) {
_legacyId = legacyId;
_id = languageId;
_version = 0;
loadFromContent(content);
}
}
}
void Instance::applyDifference(const MTPDlangPackDifference &difference) {
Expects(qs(difference.vlang_code) == _id);
Expects(difference.vfrom_version.v <= _version);
_version = difference.vversion.v;
for_const (auto &mtpString, difference.vstrings.v) {
switch (mtpString.type()) {
case mtpc_langPackString: {
auto &string = mtpString.c_langPackString();
applyValue(qba(string.vkey), qba(string.vvalue));
} break;
case mtpc_langPackStringPluralized: {
auto &string = mtpString.c_langPackStringPluralized();
auto key = qba(string.vkey);
applyValue(key + "#zero", string.has_zero_value() ? qba(string.vzero_value) : QByteArray());
applyValue(key + "#one", string.has_one_value() ? qba(string.vone_value) : QByteArray());
applyValue(key + "#two", string.has_two_value() ? qba(string.vtwo_value) : QByteArray());
applyValue(key + "#few", string.has_few_value() ? qba(string.vfew_value) : QByteArray());
applyValue(key + "#many", string.has_many_value() ? qba(string.vmany_value) : QByteArray());
applyValue(key + "#other", qba(string.vother_value));
} break;
case mtpc_langPackStringDeleted: {
auto &string = mtpString.c_langPackStringDeleted();
auto key = qba(string.vkey);
resetValue(key);
for (auto plural : { "#zero", "#one", "#two", "#few", "#many", "#other" }) {
resetValue(key + plural);
}
} break;
default: Unexpected("LangPack string type in applyUpdate().");
}
}
}
void Instance::resetValue(const QByteArray &key) {
_nonDefaultValues.erase(key);
auto keyIndex = GetKeyIndex(QLatin1String(key));
if (keyIndex != kLangKeysCount) {
_values[keyIndex] = GetOriginalValue(keyIndex);
}
}
void Instance::applyValue(const QByteArray &key, const QByteArray &value) {
_nonDefaultValues[key] = value;
auto pluralValues = std::map<ushort, QString>();
auto keyIndex = GetKeyIndex(QLatin1String(key));
if (keyIndex == kLangKeysCount) {
LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key)));
return;
}
ValueParser parser(key, keyIndex, value);
if (!parser.parse()) {
return;
}
_values[keyIndex] = parser.takeResult();
for (auto &plural : parser.takePluralValues()) {
_values[plural.first] = plural.second;
}
}
Instance &Current() {
return Messenger::Instance().langpack();
}
} // namespace Lang

View File

@ -0,0 +1,106 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram 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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "lang_auto.h"
namespace Lang {
constexpr auto kLegacyLanguageNone = -2;
constexpr auto kLegacyCustomLanguage = -1;
constexpr auto kLegacyDefaultLanguage = 0;
constexpr str_const kLegacyLanguages[] = {
"en",
"it",
"es",
"de",
"nl",
"pt_BR",
"ko",
};
class Instance {
public:
Instance() {
fillDefaults();
}
struct CreateFromIdTag {};
Instance(const QString &id, CreateFromIdTag) {
fillDefaults();
_id = id;
}
struct CreateFromCustomFileTag {};
Instance(const QString &filePath, CreateFromCustomFileTag) {
fillDefaults();
fillFromCustomFile(filePath);
}
Instance(const Instance &other) = delete;
Instance &operator=(const Instance &other) = delete;
Instance(Instance &&other) = default;
Instance &operator=(Instance &&other) = default;
QString id() const {
return _id;
}
bool isCustom() const {
return id() == qstr("custom");
}
int version() const {
return _version;
}
QString cloudLangCode() const;
QByteArray serialize() const;
void fillFromSerialized(const QByteArray &data);
void fillFromLegacy(int legacyId, const QString &legacyPath);
void applyDifference(const MTPDlangPackDifference &difference);
QString getValue(LangKey key) {
Expects(key >= 0 && key < kLangKeysCount);
Expects(_values.size() == kLangKeysCount);
return _values[key];
}
private:
void applyValue(const QByteArray &key, const QByteArray &value);
void resetValue(const QByteArray &key);
void fillDefaults();
void fillFromCustomFile(const QString &filePath);
void loadFromContent(const QByteArray &content);
void loadFromCustomContent(const QString &absolutePath, const QString &relativePath, const QByteArray &content);
QString _id;
int _legacyId = kLegacyLanguageNone;
QString _customFilePathAbsolute;
QString _customFilePathRelative;
QByteArray _customFileContent;
int _version = 0;
mutable QString _systemLanguage;
std::vector<QString> _values;
std::map<QByteArray, QByteArray> _nonDefaultValues;
};
Instance &Current();
} // namespace Lang

View File

@ -20,18 +20,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "lang_auto.h" #include "lang/lang_instance.h"
constexpr const str_const LanguageCodes[] = { inline QString lang(LangKey key) {
"en", return Lang::Current().getValue(key);
"it", }
"es",
"de",
"nl",
"pt_BR",
"ko",
};
constexpr const int languageTest = -1, languageDefault = 0, languageCount = base::array_size(LanguageCodes);
template <typename WithYear, typename WithoutYear> template <typename WithYear, typename WithoutYear>
inline QString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) { inline QString langDateMaybeWithYear(QDate date, WithYear withYear, WithoutYear withoutYear) {

View File

@ -5264,7 +5264,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
} break; } break;
case mtpc_updateConfig: { case mtpc_updateConfig: {
Messenger::Instance().mtp()->configLoadRequest(); Messenger::Instance().mtp()->requestConfig();
} break; } break;
case mtpc_updateUserPhone: { case mtpc_updateUserPhone: {
@ -5689,9 +5689,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
////// Cloud langpacks ////// Cloud langpacks
case mtpc_updateLangPack: { case mtpc_updateLangPack: {
auto &langpack = update.c_updateLangPack(); auto &langpack = update.c_updateLangPack();
Messenger::Instance().mtp()->applyLangPackDifference(langpack.vdifference);
} break; } break;
case mtpc_updateLangPackTooLong: { case mtpc_updateLangPackTooLong: {
Messenger::Instance().mtp()->requestLangPackDifference();
} break; } break;
} }

View File

@ -67,6 +67,7 @@ struct Messenger::Private {
Messenger::Messenger() : QObject() Messenger::Messenger() : QObject()
, _private(std::make_unique<Private>()) , _private(std::make_unique<Private>())
, _langpack(std::make_unique<Lang::Instance>())
, _audio(std::make_unique<Media::Audio::Instance>()) , _audio(std::make_unique<Media::Audio::Instance>())
, _logo(Window::LoadLogo()) , _logo(Window::LoadLogo())
, _logoNoMargin(Window::LoadLogoNoMargin()) { , _logoNoMargin(Window::LoadLogoNoMargin()) {
@ -96,7 +97,10 @@ Messenger::Messenger() : QObject()
cSetConfigScale(dbisOne); cSetConfigScale(dbisOne);
cSetRealScale(dbisOne); cSetRealScale(dbisOne);
} }
loadLanguage();
_translator = std::make_unique<Lang::Translator>();
QCoreApplication::instance()->installTranslator(_translator.get());
style::startManager(); style::startManager();
anim::startManager(); anim::startManager();
historyInit(); historyInit();
@ -202,12 +206,7 @@ QByteArray Messenger::serializeMtpAuthorization() const {
size += keysSize(keys) + keysSize(keysToDestroy); size += keysSize(keys) + keysSize(keysToDestroy);
result.reserve(size); result.reserve(size);
{ {
QBuffer buffer(&result); QDataStream stream(&result, QIODevice::WriteOnly);
if (!buffer.open(QIODevice::WriteOnly)) {
LOG(("MTP Error: could not open buffer to serialize mtp authorization."));
return result;
}
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1); stream.setVersion(QDataStream::Qt_5_1);
auto currentUserId = AuthSession::Exists() ? AuthSession::CurrentUserId() : 0; auto currentUserId = AuthSession::Exists() ? AuthSession::CurrentUserId() : 0;
@ -251,13 +250,7 @@ AuthSessionData *Messenger::getAuthSessionData() {
void Messenger::setMtpAuthorization(const QByteArray &serialized) { void Messenger::setMtpAuthorization(const QByteArray &serialized) {
Expects(!_mtproto); Expects(!_mtproto);
auto readonly = serialized; QDataStream stream(serialized);
QBuffer buffer(&readonly);
if (!buffer.open(QIODevice::ReadOnly)) {
LOG(("MTP Error: could not open serialized mtp authorization for reading."));
return;
}
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1); stream.setVersion(QDataStream::Qt_5_1);
auto userId = Serialize::read<qint32>(stream); auto userId = Serialize::read<qint32>(stream);
@ -375,34 +368,6 @@ void Messenger::destroyStaleAuthorizationKeys() {
} }
} }
void Messenger::loadLanguage() {
if (cLang() < languageTest) {
cSetLang(Sandbox::LangSystem());
}
if (cLang() == languageTest) {
if (QFileInfo(cLangFile()).exists()) {
Lang::FileParser loader(cLangFile());
cSetLangErrors(loader.errors());
if (!cLangErrors().isEmpty()) {
LOG(("Lang load errors: %1").arg(cLangErrors()));
} else if (!loader.warnings().isEmpty()) {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
} else {
cSetLang(languageDefault);
}
} else if (cLang() > languageDefault && cLang() < languageCount) {
Lang::FileParser loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings"));
if (!loader.errors().isEmpty()) {
LOG(("Lang load errors: %1").arg(loader.errors()));
} else if (!loader.warnings().isEmpty()) {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
}
_translator = std::make_unique<Lang::Translator>();
QCoreApplication::instance()->installTranslator(_translator.get());
}
void Messenger::startLocalStorage() { void Messenger::startLocalStorage() {
_dcOptions = std::make_unique<MTP::DcOptions>(); _dcOptions = std::make_unique<MTP::DcOptions>();
_dcOptions->constructFromBuiltIn(); _dcOptions->constructFromBuiltIn();
@ -417,7 +382,7 @@ void Messenger::startLocalStorage() {
}); });
subscribe(authSessionChanged(), [this] { subscribe(authSessionChanged(), [this] {
if (_mtproto) { if (_mtproto) {
_mtproto->configLoadRequest(); _mtproto->requestConfig();
} }
}); });
} }

View File

@ -48,6 +48,7 @@ class Instance;
} // namespace Media } // namespace Media
namespace Lang { namespace Lang {
class Instance;
class Translator; class Translator;
} // namespace Lang } // namespace Lang
@ -105,6 +106,9 @@ public:
AuthSession *authSession() { AuthSession *authSession() {
return _authSession.get(); return _authSession.get();
} }
Lang::Instance &langpack() {
return *_langpack;
}
void authSessionCreate(UserId userId); void authSessionCreate(UserId userId);
void authSessionDestroy(); void authSessionDestroy();
base::Observable<void> &authSessionChanged() { base::Observable<void> &authSessionChanged() {
@ -175,7 +179,6 @@ public slots:
private: private:
void destroyMtpKeys(MTP::AuthKeysList &&keys); void destroyMtpKeys(MTP::AuthKeysList &&keys);
void startLocalStorage(); void startLocalStorage();
void loadLanguage();
friend void App::quit(); friend void App::quit();
static void QuitAttempt(); static void QuitAttempt();
@ -193,6 +196,7 @@ private:
std::unique_ptr<MainWindow> _window; std::unique_ptr<MainWindow> _window;
FileUploader *_uploader = nullptr; FileUploader *_uploader = nullptr;
std::unique_ptr<Lang::Instance> _langpack;
std::unique_ptr<Lang::Translator> _translator; std::unique_ptr<Lang::Translator> _translator;
std::unique_ptr<MTP::DcOptions> _dcOptions; std::unique_ptr<MTP::DcOptions> _dcOptions;
std::unique_ptr<MTP::Instance> _mtproto; std::unique_ptr<MTP::Instance> _mtproto;

View File

@ -780,7 +780,7 @@ void ConnectionPrivate::tryToSend() {
MTPInitConnection<mtpRequest> initWrapper; MTPInitConnection<mtpRequest> initWrapper;
int32 initSize = 0, initSizeInInts = 0; int32 initSize = 0, initSizeInInts = 0;
if (needsLayer) { if (needsLayer) {
auto langCode = (cLang() == languageTest || cLang() == languageDefault) ? Sandbox::LangSystemISO() : str_const_toString(LanguageCodes[cLang()]); auto langCode = sessionData->langCode();
auto langPack = "tdesktop"; auto langPack = "tdesktop";
auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : cApiDeviceModel(); auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : cApiDeviceModel();
auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : cApiSystemVersion(); auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : cApiSystemVersion();
@ -1031,7 +1031,7 @@ void ConnectionPrivate::connectToServer(bool afterConfig) {
DEBUG_LOG(("MTP Info: DC %1 options for IPv4 over HTTP not found, waiting for config").arg(_shiftedDcId)); DEBUG_LOG(("MTP Info: DC %1 options for IPv4 over HTTP not found, waiting for config").arg(_shiftedDcId));
if (Global::TryIPv6() && noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 over HTTP not found, waiting for config").arg(_shiftedDcId)); if (Global::TryIPv6() && noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 over HTTP not found, waiting for config").arg(_shiftedDcId));
connect(_instance, SIGNAL(configLoaded()), this, SLOT(onConfigLoaded()), Qt::UniqueConnection); connect(_instance, SIGNAL(configLoaded()), this, SLOT(onConfigLoaded()), Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] { instance->configLoadRequest(); }); InvokeQueued(_instance, [instance = _instance] { instance->requestConfig(); });
return; return;
} }
@ -1231,7 +1231,7 @@ void ConnectionPrivate::finishAndDestroy() {
void ConnectionPrivate::requestCDNConfig() { void ConnectionPrivate::requestCDNConfig() {
connect(_instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection); connect(_instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] { instance->cdnConfigLoadRequest(); }); InvokeQueued(_instance, [instance = _instance] { instance->requestCDNConfig(); });
} }
void ConnectionPrivate::handleReceived() { void ConnectionPrivate::handleReceived() {

View File

@ -23,10 +23,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mtproto/dc_options.h" #include "mtproto/dc_options.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "auth_session.h" #include "auth_session.h"
#include "apiwrap.h"
#include "messenger.h" #include "messenger.h"
#include "mtproto/connection.h" #include "mtproto/connection.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
#include "mtproto/rsa_public_key.h" #include "mtproto/rsa_public_key.h"
#include "lang/lang_instance.h"
#include "base/timer.h"
namespace MTP { namespace MTP {
@ -46,8 +49,10 @@ public:
DcOptions *dcOptions(); DcOptions *dcOptions();
void configLoadRequest(); void requestConfig();
void cdnConfigLoadRequest(); void requestCDNConfig();
void requestLangPackDifference();
void applyLangPackDifference(const MTPLangPackDifference &difference);
void restart(); void restart();
void restart(ShiftedDcId shiftedDcId); void restart(ShiftedDcId shiftedDcId);
@ -124,6 +129,8 @@ private:
void checkDelayedRequests(); void checkDelayedRequests();
void switchLangPackId(const QString &id);
Instance *_instance = nullptr; Instance *_instance = nullptr;
DcOptions *_dcOptions = nullptr; DcOptions *_dcOptions = nullptr;
Instance::Mode _mode = Instance::Mode::Normal; Instance::Mode _mode = Instance::Mode::Normal;
@ -174,7 +181,9 @@ private:
base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> _stateChangedHandler; base::lambda<void(ShiftedDcId shiftedDcId, int32 state)> _stateChangedHandler;
base::lambda<void(ShiftedDcId shiftedDcId)> _sessionResetHandler; base::lambda<void(ShiftedDcId shiftedDcId)> _sessionResetHandler;
SingleTimer _checkDelayedTimer; base::Timer _checkDelayedTimer;
mtpRequestId _langPackRequestId = 0;
// Debug flag to find out how we end up crashing. // Debug flag to find out how we end up crashing.
bool MustNotCreateSessions = false; bool MustNotCreateSessions = false;
@ -232,13 +241,12 @@ void Instance::Private::start(Config &&config) {
_mainSession->start(); _mainSession->start();
} }
_checkDelayedTimer.setTimeoutHandler([this] { _checkDelayedTimer.setCallback([this] { checkDelayedRequests(); });
checkDelayedRequests();
});
t_assert((_mainDcId == Config::kNoneMainDc) == isKeysDestroyer()); t_assert((_mainDcId == Config::kNoneMainDc) == isKeysDestroyer());
if (!isKeysDestroyer()) { if (!isKeysDestroyer()) {
configLoadRequest(); requestConfig();
requestLangPackDifference();
} }
} }
@ -263,11 +271,11 @@ void Instance::Private::setMainDcId(DcId mainDcId) {
} }
DcId Instance::Private::mainDcId() const { DcId Instance::Private::mainDcId() const {
t_assert(_mainDcId != Config::kNoneMainDc); Expects(_mainDcId != Config::kNoneMainDc);
return _mainDcId; return _mainDcId;
} }
void Instance::Private::configLoadRequest() { void Instance::Private::requestConfig() {
if (_configLoader) { if (_configLoader) {
return; return;
} }
@ -279,7 +287,7 @@ void Instance::Private::configLoadRequest() {
_configLoader->load(); _configLoader->load();
} }
void Instance::Private::cdnConfigLoadRequest() { void Instance::Private::requestCDNConfig() {
if (_cdnConfigLoadRequestId || _mainDcId == Config::kNoneMainDc) { if (_cdnConfigLoadRequestId || _mainDcId == Config::kNoneMainDc) {
return; return;
} }
@ -295,6 +303,58 @@ void Instance::Private::cdnConfigLoadRequest() {
}).send(); }).send();
} }
void Instance::Private::requestLangPackDifference() {
auto &langpack = Lang::Current();
if (langpack.isCustom() || _langPackRequestId) {
return;
}
auto version = langpack.version();
if (version > 0) {
_langPackRequestId = request(MTPlangpack_GetDifference(MTP_int(version))).done([this](const MTPLangPackDifference &result) {
_langPackRequestId = 0;
applyLangPackDifference(result);
}).fail([this](const RPCError &error) {
_langPackRequestId = 0;
}).send();
} else {
_langPackRequestId = request(MTPlangpack_GetLangPack()).done([this](const MTPLangPackDifference &result) {
_langPackRequestId = 0;
applyLangPackDifference(result);
}).fail([this](const RPCError &error) {
_langPackRequestId = 0;
}).send();
}
}
void Instance::Private::applyLangPackDifference(const MTPLangPackDifference &difference) {
Expects(difference.type() == mtpc_langPackDifference);
auto &current = Lang::Current();
if (current.isCustom()) {
return;
}
auto &langpack = difference.c_langPackDifference();
switchLangPackId(qs(langpack.vlang_code));
if (current.version() < langpack.vfrom_version.v) {
requestLangPackDifference();
} else if (!langpack.vstrings.v.isEmpty()) {
current.applyDifference(langpack);
Local::writeLangPack();
} else {
LOG(("Lang Info: Up to date."));
}
}
void Instance::Private::switchLangPackId(const QString &id) {
auto &current = Lang::Current();
if (current.id() != id) {
current = Lang::Instance(id, Lang::Instance::CreateFromIdTag());
restart(maindc());
}
}
void Instance::Private::restart() { void Instance::Private::restart() {
for (auto &session : _sessions) { for (auto &session : _sessions) {
session.second->restart(); session.second->restart();
@ -639,7 +699,7 @@ void Instance::Private::checkDelayedRequests() {
} }
if (!_delayedRequests.empty()) { if (!_delayedRequests.empty()) {
_checkDelayedTimer.start(_delayedRequests.front().second - now); _checkDelayedTimer.callOnce(_delayedRequests.front().second - now);
} }
} }
@ -1261,12 +1321,24 @@ DcId Instance::mainDcId() const {
return _private->mainDcId(); return _private->mainDcId();
} }
void Instance::configLoadRequest() { QString Instance::cloudLangCode() const {
_private->configLoadRequest(); return Lang::Current().cloudLangCode();
} }
void Instance::cdnConfigLoadRequest() { void Instance::requestConfig() {
_private->cdnConfigLoadRequest(); _private->requestConfig();
}
void Instance::requestCDNConfig() {
_private->requestCDNConfig();
}
void Instance::requestLangPackDifference() {
_private->requestLangPackDifference();
}
void Instance::applyLangPackDifference(const MTPLangPackDifference &difference) {
_private->applyLangPackDifference(difference);
} }
void Instance::connectionFinished(internal::Connection *connection) { void Instance::connectionFinished(internal::Connection *connection) {

View File

@ -53,6 +53,7 @@ public:
void suggestMainDcId(DcId mainDcId); void suggestMainDcId(DcId mainDcId);
void setMainDcId(DcId mainDcId); void setMainDcId(DcId mainDcId);
DcId mainDcId() const; DcId mainDcId() const;
QString cloudLangCode() const;
void setKeyForWrite(DcId dcId, const AuthKeyPtr &key); void setKeyForWrite(DcId dcId, const AuthKeyPtr &key);
AuthKeysList getKeysForWrite() const; AuthKeysList getKeysForWrite() const;
@ -119,9 +120,10 @@ public:
bool isKeysDestroyer() const; bool isKeysDestroyer() const;
void scheduleKeyDestroy(ShiftedDcId shiftedDcId); void scheduleKeyDestroy(ShiftedDcId shiftedDcId);
void configLoadRequest(); void requestConfig();
void requestCDNConfig();
void cdnConfigLoadRequest(); void requestLangPackDifference();
void applyLangPackDifference(const MTPLangPackDifference &difference);
~Instance(); ~Instance();

View File

@ -76,6 +76,8 @@ Session::Session(gsl::not_null<Instance*> instance, ShiftedDcId shiftedDcId) : Q
connect(&timeouter, SIGNAL(timeout()), this, SLOT(checkRequestsByTimer())); connect(&timeouter, SIGNAL(timeout()), this, SLOT(checkRequestsByTimer()));
timeouter.start(1000); timeouter.start(1000);
data.setLangCode(instance->cloudLangCode());
connect(&sender, SIGNAL(timeout()), this, SLOT(needToResumeAndSend())); connect(&sender, SIGNAL(timeout()), this, SLOT(needToResumeAndSend()));
} }
@ -124,6 +126,7 @@ void Session::restart() {
DEBUG_LOG(("Session Error: can't restart a killed session")); DEBUG_LOG(("Session Error: can't restart a killed session"));
return; return;
} }
data.setLangCode(_instance->cloudLangCode());
emit needToRestart(); emit needToRestart();
} }

View File

@ -123,6 +123,15 @@ public:
_layerInited = was; _layerInited = was;
} }
QString langCode() const {
QReadLocker locker(&_lock);
return _langCode;
}
void setLangCode(const QString &code) {
QWriteLocker locker(&_lock);
_langCode = code;
}
void setSalt(uint64 salt) { void setSalt(uint64 salt) {
QWriteLocker locker(&_lock); QWriteLocker locker(&_lock);
_salt = salt; _salt = salt;
@ -259,6 +268,7 @@ private:
AuthKeyPtr _authKey; AuthKeyPtr _authKey;
bool _keyChecked = false; bool _keyChecked = false;
bool _layerInited = false; bool _layerInited = false;
QString _langCode;
mtpPreRequestMap _toSend; // map of request_id -> request, that is waiting to be sent mtpPreRequestMap _toSend; // map of request_id -> request, that is waiting to be sent
mtpRequestMap _haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers mtpRequestMap _haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers

View File

@ -278,16 +278,6 @@ void psActivateProcess(uint64 pid) {
// objc_activateProgram(); // objc_activateProgram();
} }
QString psCurrentCountry() {
QString country;// = objc_currentCountry();
return country.isEmpty() ? QString::fromLatin1(DefaultCountry) : country;
}
QString psCurrentLanguage() {
QString lng;// = objc_currentLang();
return lng.isEmpty() ? QString::fromLatin1(DefaultLanguage) : lng;
}
namespace { namespace {
QString getHomeDir() { QString getHomeDir() {
@ -402,6 +392,14 @@ bool TranslucentWindowsSupported(QPoint globalPosition) {
return false; return false;
} }
QString SystemCountry() {
return QString();
}
QString SystemLanguage() {
return QString();
}
namespace ThirdParty { namespace ThirdParty {
void start() { void start() {

View File

@ -63,8 +63,6 @@ void psClearInitLogs();
void psActivateProcess(uint64 pid = 0); void psActivateProcess(uint64 pid = 0);
QString psLocalServerPrefix(); QString psLocalServerPrefix();
QString psCurrentCountry();
QString psCurrentLanguage();
QString psAppDataPath(); QString psAppDataPath();
QString psDownloadPath(); QString psDownloadPath();
QString psCurrentExeDirectory(int argc, char *argv[]); QString psCurrentExeDirectory(int argc, char *argv[]);

View File

@ -322,16 +322,6 @@ void psActivateProcess(uint64 pid) {
} }
} }
QString psCurrentCountry() {
QString country = objc_currentCountry();
return country.isEmpty() ? QString::fromLatin1(DefaultCountry) : country;
}
QString psCurrentLanguage() {
QString lng = objc_currentLang();
return lng.isEmpty() ? QString::fromLatin1(DefaultLanguage) : lng;
}
QString psAppDataPath() { QString psAppDataPath() {
return objc_appDataPath(); return objc_appDataPath();
} }
@ -404,6 +394,15 @@ void StartTranslucentPaint(QPainter &p, QPaintEvent *e) {
#endif // OS_MAC_OLD #endif // OS_MAC_OLD
} }
QString SystemCountry() {
QString country = objc_currentCountry();
return country.isEmpty() ? QString::fromLatin1(DefaultCountry) : country;
}
QString SystemLanguage() {
return objc_currentLang();
}
} // namespace Platform } // namespace Platform
void psNewVersion() { void psNewVersion() {

View File

@ -64,8 +64,6 @@ void psClearInitLogs();
void psActivateProcess(uint64 pid = 0); void psActivateProcess(uint64 pid = 0);
QString psLocalServerPrefix(); QString psLocalServerPrefix();
QString psCurrentCountry();
QString psCurrentLanguage();
QString psAppDataPath(); QString psAppDataPath();
QString psDownloadPath(); QString psDownloadPath();
QString psCurrentExeDirectory(int argc, char *argv[]); QString psCurrentExeDirectory(int argc, char *argv[]);

View File

@ -20,14 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#ifdef Q_OS_MAC
#include "platform/mac/specific_mac.h"
#elif defined Q_OS_LINUX // Q_OS_MAC
#include "platform/linux/specific_linux.h"
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
#include "platform/win/specific_win.h"
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN
namespace Platform { namespace Platform {
void start(); void start();
@ -40,6 +32,9 @@ void InitOnTopPanel(QWidget *panel);
void DeInitOnTopPanel(QWidget *panel); void DeInitOnTopPanel(QWidget *panel);
void ReInitOnTopPanel(QWidget *panel); void ReInitOnTopPanel(QWidget *panel);
QString SystemLanguage();
QString SystemCountry();
namespace ThirdParty { namespace ThirdParty {
void start(); void start();
@ -47,3 +42,11 @@ void finish();
} // namespace ThirdParty } // namespace ThirdParty
} // namespace Platform } // namespace Platform
#ifdef Q_OS_MAC
#include "platform/mac/specific_mac.h"
#elif defined Q_OS_LINUX // Q_OS_MAC
#include "platform/linux/specific_linux.h"
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
#include "platform/win/specific_win.h"
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WIN

View File

@ -183,171 +183,6 @@ void psActivateProcess(uint64 pid) {
} }
} }
QString psCurrentCountry() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrCountry[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wstrCountry, chCount);
return len ? QString::fromStdWString(std::wstring(wstrCountry)) : QString::fromLatin1(DefaultCountry);
}
return QString::fromLatin1(DefaultCountry);
}
namespace {
QString langById(int lngId) {
int primary = lngId & 0xFF;
switch (primary) {
case 0x36: return qsl("af");
case 0x1C: return qsl("sq");
case 0x5E: return qsl("am");
case 0x01: return qsl("ar");
case 0x2B: return qsl("hy");
case 0x4D: return qsl("as");
case 0x2C: return qsl("az");
case 0x45: return qsl("bn");
case 0x6D: return qsl("ba");
case 0x2D: return qsl("eu");
case 0x23: return qsl("be");
case 0x1A:
if (lngId == LANG_CROATIAN) {
return qsl("hr");
} else if (lngId == LANG_BOSNIAN_NEUTRAL || lngId == LANG_BOSNIAN) {
return qsl("bs");
}
return qsl("sr");
break;
case 0x7E: return qsl("br");
case 0x02: return qsl("bg");
case 0x92: return qsl("ku");
case 0x03: return qsl("ca");
case 0x04: return qsl("zh");
case 0x83: return qsl("co");
case 0x05: return qsl("cs");
case 0x06: return qsl("da");
case 0x65: return qsl("dv");
case 0x13: return qsl("nl");
case 0x09: return qsl("en");
case 0x25: return qsl("et");
case 0x38: return qsl("fo");
case 0x0B: return qsl("fi");
case 0x0c: return qsl("fr");
case 0x62: return qsl("fy");
case 0x56: return qsl("gl");
case 0x37: return qsl("ka");
case 0x07: return qsl("de");
case 0x08: return qsl("el");
case 0x6F: return qsl("kl");
case 0x47: return qsl("gu");
case 0x68: return qsl("ha");
case 0x0D: return qsl("he");
case 0x39: return qsl("hi");
case 0x0E: return qsl("hu");
case 0x0F: return qsl("is");
case 0x70: return qsl("ig");
case 0x21: return qsl("id");
case 0x5D: return qsl("iu");
case 0x3C: return qsl("ga");
case 0x34: return qsl("xh");
case 0x35: return qsl("zu");
case 0x10: return qsl("it");
case 0x11: return qsl("ja");
case 0x4B: return qsl("kn");
case 0x3F: return qsl("kk");
case 0x53: return qsl("kh");
case 0x87: return qsl("rw");
case 0x12: return qsl("ko");
case 0x40: return qsl("ky");
case 0x54: return qsl("lo");
case 0x26: return qsl("lv");
case 0x27: return qsl("lt");
case 0x6E: return qsl("lb");
case 0x2F: return qsl("mk");
case 0x3E: return qsl("ms");
case 0x4C: return qsl("ml");
case 0x3A: return qsl("mt");
case 0x81: return qsl("mi");
case 0x4E: return qsl("mr");
case 0x50: return qsl("mn");
case 0x61: return qsl("ne");
case 0x14: return qsl("no");
case 0x82: return qsl("oc");
case 0x48: return qsl("or");
case 0x63: return qsl("ps");
case 0x29: return qsl("fa");
case 0x15: return qsl("pl");
case 0x16: return qsl("pt");
case 0x67: return qsl("ff");
case 0x46: return qsl("pa");
case 0x18: return qsl("ro");
case 0x17: return qsl("rm");
case 0x19: return qsl("ru");
case 0x3B: return qsl("se");
case 0x4F: return qsl("sa");
case 0x32: return qsl("tn");
case 0x59: return qsl("sd");
case 0x5B: return qsl("si");
case 0x1B: return qsl("sk");
case 0x24: return qsl("sl");
case 0x0A: return qsl("es");
case 0x41: return qsl("sw");
case 0x1D: return qsl("sv");
case 0x28: return qsl("tg");
case 0x49: return qsl("ta");
case 0x44: return qsl("tt");
case 0x4A: return qsl("te");
case 0x1E: return qsl("th");
case 0x51: return qsl("bo");
case 0x73: return qsl("ti");
case 0x1F: return qsl("tr");
case 0x42: return qsl("tk");
case 0x22: return qsl("uk");
case 0x20: return qsl("ur");
case 0x80: return qsl("ug");
case 0x43: return qsl("uz");
case 0x2A: return qsl("vi");
case 0x52: return qsl("cy");
case 0x88: return qsl("wo");
case 0x78: return qsl("ii");
case 0x6A: return qsl("yo");
}
return QString::fromLatin1(DefaultLanguage);
}
}
QString psCurrentLanguage() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, wstrLocale, chCount);
if (!len) return QString::fromLatin1(DefaultLanguage);
QString locale = QString::fromStdWString(std::wstring(wstrLocale));
QRegularExpressionMatch m = QRegularExpression("(^|[^a-z])([a-z]{2})-").match(locale);
if (m.hasMatch()) {
return m.captured(2);
}
}
chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, wstrLocale, chCount), lngId = 0;
if (len < 5) return QString::fromLatin1(DefaultLanguage);
for (int i = 0; i < 4; ++i) {
WCHAR ch = wstrLocale[i];
lngId *= 16;
if (ch >= WCHAR('0') && ch <= WCHAR('9')) {
lngId += (ch - WCHAR('0'));
} else if (ch >= WCHAR('A') && ch <= WCHAR('F')) {
lngId += (10 + ch - WCHAR('A'));
} else {
return QString::fromLatin1(DefaultLanguage);
}
}
return langById(lngId);
}
return QString::fromLatin1(DefaultLanguage);
}
QString psAppDataPath() { QString psAppDataPath() {
static const int maxFileLen = MAX_PATH * 10; static const int maxFileLen = MAX_PATH * 10;
WCHAR wstrPath[maxFileLen]; WCHAR wstrPath[maxFileLen];
@ -527,15 +362,177 @@ void finish() {
EventFilter::destroy(); EventFilter::destroy();
} }
namespace ThirdParty { QString SystemCountry() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, 0, 0);
void start() { if (chCount && chCount < 128) {
WCHAR wstrCountry[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, wstrCountry, chCount);
if (len) {
return QString::fromStdWString(std::wstring(wstrCountry));
}
}
return QString();
} }
void finish() { namespace {
QString GetLangCodeById(int lngId) {
int primary = lngId & 0xFF;
switch (primary) {
case 0x36: return qsl("af");
case 0x1C: return qsl("sq");
case 0x5E: return qsl("am");
case 0x01: return qsl("ar");
case 0x2B: return qsl("hy");
case 0x4D: return qsl("as");
case 0x2C: return qsl("az");
case 0x45: return qsl("bn");
case 0x6D: return qsl("ba");
case 0x2D: return qsl("eu");
case 0x23: return qsl("be");
case 0x1A:
if (lngId == LANG_CROATIAN) {
return qsl("hr");
} else if (lngId == LANG_BOSNIAN_NEUTRAL || lngId == LANG_BOSNIAN) {
return qsl("bs");
}
return qsl("sr");
break;
case 0x7E: return qsl("br");
case 0x02: return qsl("bg");
case 0x92: return qsl("ku");
case 0x03: return qsl("ca");
case 0x04: return qsl("zh");
case 0x83: return qsl("co");
case 0x05: return qsl("cs");
case 0x06: return qsl("da");
case 0x65: return qsl("dv");
case 0x13: return qsl("nl");
case 0x09: return qsl("en");
case 0x25: return qsl("et");
case 0x38: return qsl("fo");
case 0x0B: return qsl("fi");
case 0x0c: return qsl("fr");
case 0x62: return qsl("fy");
case 0x56: return qsl("gl");
case 0x37: return qsl("ka");
case 0x07: return qsl("de");
case 0x08: return qsl("el");
case 0x6F: return qsl("kl");
case 0x47: return qsl("gu");
case 0x68: return qsl("ha");
case 0x0D: return qsl("he");
case 0x39: return qsl("hi");
case 0x0E: return qsl("hu");
case 0x0F: return qsl("is");
case 0x70: return qsl("ig");
case 0x21: return qsl("id");
case 0x5D: return qsl("iu");
case 0x3C: return qsl("ga");
case 0x34: return qsl("xh");
case 0x35: return qsl("zu");
case 0x10: return qsl("it");
case 0x11: return qsl("ja");
case 0x4B: return qsl("kn");
case 0x3F: return qsl("kk");
case 0x53: return qsl("kh");
case 0x87: return qsl("rw");
case 0x12: return qsl("ko");
case 0x40: return qsl("ky");
case 0x54: return qsl("lo");
case 0x26: return qsl("lv");
case 0x27: return qsl("lt");
case 0x6E: return qsl("lb");
case 0x2F: return qsl("mk");
case 0x3E: return qsl("ms");
case 0x4C: return qsl("ml");
case 0x3A: return qsl("mt");
case 0x81: return qsl("mi");
case 0x4E: return qsl("mr");
case 0x50: return qsl("mn");
case 0x61: return qsl("ne");
case 0x14: return qsl("no");
case 0x82: return qsl("oc");
case 0x48: return qsl("or");
case 0x63: return qsl("ps");
case 0x29: return qsl("fa");
case 0x15: return qsl("pl");
case 0x16: return qsl("pt");
case 0x67: return qsl("ff");
case 0x46: return qsl("pa");
case 0x18: return qsl("ro");
case 0x17: return qsl("rm");
case 0x19: return qsl("ru");
case 0x3B: return qsl("se");
case 0x4F: return qsl("sa");
case 0x32: return qsl("tn");
case 0x59: return qsl("sd");
case 0x5B: return qsl("si");
case 0x1B: return qsl("sk");
case 0x24: return qsl("sl");
case 0x0A: return qsl("es");
case 0x41: return qsl("sw");
case 0x1D: return qsl("sv");
case 0x28: return qsl("tg");
case 0x49: return qsl("ta");
case 0x44: return qsl("tt");
case 0x4A: return qsl("te");
case 0x1E: return qsl("th");
case 0x51: return qsl("bo");
case 0x73: return qsl("ti");
case 0x1F: return qsl("tr");
case 0x42: return qsl("tk");
case 0x22: return qsl("uk");
case 0x20: return qsl("ur");
case 0x80: return qsl("ug");
case 0x43: return qsl("uz");
case 0x2A: return qsl("vi");
case 0x52: return qsl("cy");
case 0x88: return qsl("wo");
case 0x78: return qsl("ii");
case 0x6A: return qsl("yo");
}
return QString();
}
} // namespace
QString SystemLanguage() {
int chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, wstrLocale, chCount);
if (!len) {
return QString();
}
QString locale = QString::fromStdWString(std::wstring(wstrLocale));
QRegularExpressionMatch m = QRegularExpression("(^|[^a-z])([a-z]{2})-").match(locale);
if (m.hasMatch()) {
return m.captured(2);
}
}
chCount = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0, 0);
if (chCount && chCount < 128) {
WCHAR wstrLocale[128];
int len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, wstrLocale, chCount), lngId = 0;
if (len < 5) return QString();
for (int i = 0; i < 4; ++i) {
WCHAR ch = wstrLocale[i];
lngId *= 16;
if (ch >= WCHAR('0') && ch <= WCHAR('9')) {
lngId += (ch - WCHAR('0'));
} else if (ch >= WCHAR('A') && ch <= WCHAR('F')) {
lngId += (10 + ch - WCHAR('A'));
} else {
return QString();
}
}
return GetLangCodeById(lngId);
}
return QString();
} }
} // namespace ThirdParty
} // namespace Platform } // namespace Platform
namespace { namespace {

View File

@ -43,6 +43,15 @@ inline void DeInitOnTopPanel(QWidget *panel) {
inline void ReInitOnTopPanel(QWidget *panel) { inline void ReInitOnTopPanel(QWidget *panel) {
} }
namespace ThirdParty {
inline void start() {
}
inline void finish() {
}
} // namespace ThirdParty
} // namespace Platform } // namespace Platform
inline QString psServerPrefix() { inline QString psServerPrefix() {
@ -66,8 +75,6 @@ void psClearInitLogs();
void psActivateProcess(uint64 pid = 0); void psActivateProcess(uint64 pid = 0);
QString psLocalServerPrefix(); QString psLocalServerPrefix();
QString psCurrentCountry();
QString psCurrentLanguage();
QString psAppDataPath(); QString psAppDataPath();
QString psAppDataPathOld(); QString psAppDataPathOld();
QString psDownloadPath(); QString psDownloadPath();

View File

@ -42,8 +42,6 @@ QString gWorkingDir, gExeDir, gExeName;
QStringList gSendPaths; QStringList gSendPaths;
QString gStartUrl; QString gStartUrl;
QString gLangErrors;
QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog
bool gStartMinimized = false; bool gStartMinimized = false;

View File

@ -240,8 +240,6 @@ DeclareSetting(QString, LangFile);
DeclareSetting(QStringList, SendPaths); DeclareSetting(QStringList, SendPaths);
DeclareSetting(QString, StartUrl); DeclareSetting(QString, StartUrl);
DeclareSetting(QString, LangErrors);
DeclareSetting(bool, Retina); DeclareSetting(bool, Retina);
DeclareSetting(float64, RetinaFactor); DeclareSetting(float64, RetinaFactor);
DeclareSetting(int32, IntRetinaFactor); DeclareSetting(int32, IntRetinaFactor);

View File

@ -212,17 +212,16 @@ void GeneralWidget::chooseCustomLang() {
return; return;
} }
_testLanguage = QFileInfo(result.paths.front()).absoluteFilePath(); auto filePath = result.paths.front();
Lang::FileParser loader(_testLanguage, { lng_sure_save_language, lng_cancel, lng_box_ok }); Lang::FileParser loader(filePath, { lng_sure_save_language, lng_cancel, lng_box_ok });
if (loader.errors().isEmpty()) { if (loader.errors().isEmpty()) {
auto result = loader.found(); auto result = loader.found();
auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)), auto text = result.value(lng_sure_save_language, Lang::GetOriginalValue(lng_sure_save_language)),
save = result.value(lng_box_ok, langOriginal(lng_box_ok)), save = result.value(lng_box_ok, Lang::GetOriginalValue(lng_box_ok)),
cancel = result.value(lng_cancel, langOriginal(lng_cancel)); cancel = result.value(lng_cancel, Lang::GetOriginalValue(lng_cancel));
Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this] { Ui::show(Box<ConfirmBox>(text, save, cancel, base::lambda_guarded(this, [this, filePath] {
cSetLangFile(_testLanguage); Lang::Current() = Lang::Instance(filePath, Lang::Instance::CreateFromCustomFileTag());
cSetLang(languageTest); Local::writeLangPack();
Local::writeSettings();
onRestart(); onRestart();
}))); })));
} else { } else {

View File

@ -117,8 +117,6 @@ private:
object_ptr<Ui::WidgetSlideWrap<Ui::Checkbox>> _startMinimized = { nullptr }; object_ptr<Ui::WidgetSlideWrap<Ui::Checkbox>> _startMinimized = { nullptr };
object_ptr<Ui::Checkbox> _addInSendTo = { nullptr }; object_ptr<Ui::Checkbox> _addInSendTo = { nullptr };
QString _testLanguage;
}; };
} // namespace Settings } // namespace Settings

View File

@ -538,8 +538,8 @@ enum {
dbiNotifyView = 0x1c, dbiNotifyView = 0x1c,
dbiSendToMenu = 0x1d, dbiSendToMenu = 0x1d,
dbiCompressPastedImage = 0x1e, dbiCompressPastedImage = 0x1e,
dbiLang = 0x1f, dbiLangOld = 0x1f,
dbiLangFile = 0x20, dbiLangFileOld = 0x20,
dbiTileBackground = 0x21, dbiTileBackground = 0x21,
dbiAutoLock = 0x22, dbiAutoLock = 0x22,
dbiDialogLastPath = 0x23, dbiDialogLastPath = 0x23,
@ -568,13 +568,14 @@ enum {
dbiNativeNotifications = 0x44, dbiNativeNotifications = 0x44,
dbiNotificationsCount = 0x45, dbiNotificationsCount = 0x45,
dbiNotificationsCorner = 0x46, dbiNotificationsCorner = 0x46,
dbiTheme = 0x47, dbiThemeKey = 0x47,
dbiDialogsWidthRatio = 0x48, dbiDialogsWidthRatio = 0x48,
dbiUseExternalVideoPlayer = 0x49, dbiUseExternalVideoPlayer = 0x49,
dbiDcOptions = 0x4a, dbiDcOptions = 0x4a,
dbiMtpAuthorization = 0x4b, dbiMtpAuthorization = 0x4b,
dbiLastSeenWarningSeenOld = 0x4c, dbiLastSeenWarningSeenOld = 0x4c,
dbiAuthSessionData = 0x4d, dbiAuthSessionData = 0x4d,
dbiLangPackKey = 0x4e,
dbiEncryptedWithSalt = 333, dbiEncryptedWithSalt = 333,
dbiEncrypted = 444, dbiEncrypted = 444,
@ -625,6 +626,7 @@ FileKey _recentHashtagsAndBotsKey = 0;
bool _recentHashtagsAndBotsWereRead = false; bool _recentHashtagsAndBotsWereRead = false;
FileKey _savedPeersKey = 0; FileKey _savedPeersKey = 0;
FileKey _langPackKey = 0;
typedef QMap<StorageKey, FileDesc> StorageMap; typedef QMap<StorageKey, FileDesc> StorageMap;
StorageMap _imagesMap, _stickerImagesMap, _audiosMap; StorageMap _imagesMap, _stickerImagesMap, _audiosMap;
@ -850,11 +852,17 @@ void _readReportSpamStatuses() {
} }
struct ReadSettingsContext { struct ReadSettingsContext {
int legacyLanguageId = Lang::kLegacyLanguageNone;
QString legacyLanguageFile;
MTP::DcOptions dcOptions; MTP::DcOptions dcOptions;
}; };
void applyReadContext(ReadSettingsContext &&context) { void applyReadContext(ReadSettingsContext &&context) {
Messenger::Instance().dcOptions()->addFromOther(std::move(context.dcOptions)); Messenger::Instance().dcOptions()->addFromOther(std::move(context.dcOptions));
if (context.legacyLanguageId != Lang::kLegacyLanguageNone) {
Lang::Current().fillFromLegacy(context.legacyLanguageId, context.legacyLanguageFile);
writeLangPack();
}
} }
bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) { bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) {
@ -1142,7 +1150,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
}; };
} break; } break;
case dbiTheme: { case dbiThemeKey: {
quint64 themeKey = 0; quint64 themeKey = 0;
stream >> themeKey; stream >> themeKey;
if (!_checkStreamStatus(stream)) return false; if (!_checkStreamStatus(stream)) return false;
@ -1150,6 +1158,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
_themeKey = themeKey; _themeKey = themeKey;
} break; } break;
case dbiLangPackKey: {
quint64 langPackKey = 0;
stream >> langPackKey;
if (!_checkStreamStatus(stream)) return false;
_langPackKey = langPackKey;
} break;
case dbiTryIPv6: { case dbiTryIPv6: {
qint32 v; qint32 v;
stream >> v; stream >> v;
@ -1205,22 +1221,20 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
cSetRealScale(s); cSetRealScale(s);
} break; } break;
case dbiLang: { case dbiLangOld: {
qint32 v; qint32 v;
stream >> v; stream >> v;
if (!_checkStreamStatus(stream)) return false; if (!_checkStreamStatus(stream)) return false;
if (v == languageTest || (v >= 0 && v < languageCount)) { context.legacyLanguageId = v;
cSetLang(v);
}
} break; } break;
case dbiLangFile: { case dbiLangFileOld: {
QString v; QString v;
stream >> v; stream >> v;
if (!_checkStreamStatus(stream)) return false; if (!_checkStreamStatus(stream)) return false;
cSetLangFile(v); context.legacyLanguageFile = v;
} break; } break;
case dbiWindowPosition: { case dbiWindowPosition: {
@ -2212,9 +2226,10 @@ void finish() {
} }
void readTheme(); void readTheme();
void readLangPack();
void start() { void start() {
t_assert(_manager == 0); Expects(!_manager);
_manager = new internal::Manager(); _manager = new internal::Manager();
_localLoader = new TaskQueue(0, FileLoaderQueueStopTimeout); _localLoader = new TaskQueue(0, FileLoaderQueueStopTimeout);
@ -2269,6 +2284,7 @@ void start() {
_settingsSalt = salt; _settingsSalt = salt;
readTheme(); readTheme();
readLangPack();
applyReadContext(std::move(context)); applyReadContext(std::move(context));
} }
@ -2301,7 +2317,10 @@ void writeSettings() {
size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password); size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password);
} }
if (_themeKey) { if (_themeKey) {
size += sizeof(quint32) + 2 * sizeof(quint64); size += sizeof(quint32) + sizeof(quint64);
}
if (_langPackKey) {
size += sizeof(quint32) + sizeof(quint64);
} }
size += sizeof(quint32) + sizeof(qint32) * 7; size += sizeof(quint32) + sizeof(qint32) * 7;
@ -2318,9 +2337,7 @@ void writeSettings() {
data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate()); data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate());
data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck()); data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck());
data.stream << quint32(dbiScale) << qint32(cConfigScale()); data.stream << quint32(dbiScale) << qint32(cConfigScale());
data.stream << quint32(dbiLang) << qint32(cLang());
data.stream << quint32(dbiDcOptions) << dcOptionsSerialized; data.stream << quint32(dbiDcOptions) << dcOptionsSerialized;
data.stream << quint32(dbiLangFile) << cLangFile();
data.stream << quint32(dbiConnectionType) << qint32(Global::ConnectionType()); data.stream << quint32(dbiConnectionType) << qint32(Global::ConnectionType());
if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) { if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) {
@ -2329,7 +2346,10 @@ void writeSettings() {
} }
data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6()); data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6());
if (_themeKey) { if (_themeKey) {
data.stream << quint32(dbiTheme) << quint64(_themeKey); data.stream << quint32(dbiThemeKey) << quint64(_themeKey);
}
if (_langPackKey) {
data.stream << quint32(dbiLangPackKey) << quint64(_langPackKey);
} }
TWindowPos pos(cWindowPos()); TWindowPos pos(cWindowPos());
@ -3808,7 +3828,7 @@ void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const
_themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString(); _themePaletteAbsolutePath = Window::Theme::IsPaletteTestingPath(pathAbsolute) ? pathAbsolute : QString();
if (!_themeKey) { if (!_themeKey) {
_themeKey = genKey(); _themeKey = genKey(FileOption::Safe);
writeSettings(); writeSettings();
} }
@ -3839,6 +3859,32 @@ bool hasTheme() {
return (_themeKey != 0); return (_themeKey != 0);
} }
void readLangPack() {
FileReadDescriptor langpack;
if (!_langPackKey || !readEncryptedFile(langpack, _langPackKey, FileOption::Safe, SettingsKey)) {
return;
}
auto data = QByteArray();
langpack.stream >> data;
if (langpack.stream.status() == QDataStream::Ok) {
Lang::Current().fillFromSerialized(data);
}
}
void writeLangPack() {
auto langpack = Lang::Current().serialize();
if (!_langPackKey) {
_langPackKey = genKey(FileOption::Safe);
writeSettings();
}
EncryptedDescriptor data(Serialize::bytearraySize(langpack));
data.stream << langpack;
FileWriteDescriptor file(_langPackKey, FileOption::Safe);
file.writeEncrypted(data, SettingsKey);
}
QString themePaletteAbsolutePath() { QString themePaletteAbsolutePath() {
return _themePaletteAbsolutePath; return _themePaletteAbsolutePath;
} }

View File

@ -163,6 +163,8 @@ bool hasTheme();
QString themePaletteAbsolutePath(); QString themePaletteAbsolutePath();
bool copyThemeColorsToPalette(const QString &file); bool copyThemeColorsToPalette(const QString &file);
void writeLangPack();
void writeRecentHashtagsAndBots(); void writeRecentHashtagsAndBots();
void readRecentHashtagsAndBots(); void readRecentHashtagsAndBots();

View File

@ -179,6 +179,8 @@
<(src_loc)/intro/introstart.h <(src_loc)/intro/introstart.h
<(src_loc)/lang/lang_file_parser.cpp <(src_loc)/lang/lang_file_parser.cpp
<(src_loc)/lang/lang_file_parser.h <(src_loc)/lang/lang_file_parser.h
<(src_loc)/lang/lang_instance.cpp
<(src_loc)/lang/lang_instance.h
<(src_loc)/lang/lang_keys.cpp <(src_loc)/lang/lang_keys.cpp
<(src_loc)/lang/lang_keys.h <(src_loc)/lang/lang_keys.h
<(src_loc)/lang/lang_tag.cpp <(src_loc)/lang/lang_tag.cpp