diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7aded057a..2e2680971 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -100,6 +100,8 @@ PRIVATE ayu/ayu_state.h ayu/ayu_settings.cpp ayu/ayu_settings.h + ayu/ayu_lang.cpp + ayu/ayu_lang.h ayu/boxes/confirmation_box.cpp ayu/boxes/confirmation_box.h ayu/boxes/edit_deleted_mark.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 570b7b3ed..a03d82adb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3842,3 +3842,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mac_hold_to_quit" = "Hold {text} to Quit"; // Keys finished + + +// AyuGram keys generated + +"ayu_AyuPreferences" = "AyuGram Preferences"; +"ayu_GhostEssentialsHeader" = "Ghost essentials"; +"ayu_SendReadPackets" = "Send read status"; +"ayu_SendOnlinePackets" = "Send online status"; +"ayu_SendUploadProgress" = "Send typing & upload status"; +"ayu_SendOfflinePacketAfterOnline" = "Immediate offline after online"; +"ayu_MarkReadAfterSend" = "Send read status after reply"; +"ayu_UseScheduledMessages" = "Schedule messages"; +"ayu_SpyEssentialsHeader" = "Spy essentials"; +"ayu_KeepDeletedMessages" = "Keep deleted messages"; +"ayu_KeepMessagesHistory" = "Keep edits history"; +"ayu_QoLTogglesHeader" = "QoL toggles"; +"ayu_RealForwardTime" = "Show real forward time"; +"ayu_ShowFromChannel" = "Show «channel» label"; +"ayu_KeepAliveService" = "Keep Alive Service"; +"ayu_EnableAds" = "Enable ads"; +"ayu_CustomizationHeader" = "Customization"; +"ayu_DeletedMarkText" = "Deleted mark"; +"ayu_ShowGhostToggleInDrawer" = "Show ghost mode toggle"; +"ayu_CleanDatabase" = "Clean database"; +"ayu_CleanDatabaseNotification" = "AyuGram database cleaned"; +"ayu_EnableGhostMode" = "Enable Ghost"; +"ayu_DisableGhostMode" = "Disable Ghost"; +"ayu_GhostModeEnabled" = "Ghost mode turned on"; +"ayu_GhostModeDisabled" = "Ghost mode turned off"; +"ayu_EditsHistoryTitle" = "Edits history"; +"ayu_EditsHistoryMenuText" = "History"; +"ayu_ReadUntilMenuText" = "Read until"; +"ayu_LikelyOfflineStatus" = "offline ?"; +"ayu_SettingsWatermark" = "AyuGram developed and maintained by Radolyn Labs"; diff --git a/Telegram/SourceFiles/ayu/ayu_lang.cpp b/Telegram/SourceFiles/ayu/ayu_lang.cpp new file mode 100644 index 000000000..cb83310c7 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ayu_lang.cpp @@ -0,0 +1,119 @@ +#include "ayu_lang.h" + +#include "qjsondocument.h" +#include "lang/lang_instance.h" +#include "core/application.h" +#include "core/core_settings.h" + +CustomLangPack *CustomLangPack::instance = nullptr; + +CustomLangPack::CustomLangPack() = default; + +void CustomLangPack::initInstance() { + if (!instance) + instance = new CustomLangPack; +} + +CustomLangPack *CustomLangPack::currentInstance() { + return instance; +} + +void CustomLangPack::fetchCustomLangPack(const QString& langPackId, const QString& langPackBaseId) { + LOG(("Current Language pack ID: %1, Base ID: %2").arg(langPackId, langPackBaseId)); + + const auto proxy = Core::App().settings().proxy().isEnabled() ? Core::App().settings().proxy().selected() : MTP::ProxyData(); + if (proxy.type == MTP::ProxyData::Type::Socks5 || proxy.type == MTP::ProxyData::Type::Http) { + QNetworkProxy LocaleProxy = MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)); + networkManager.setProxy(LocaleProxy); + } + + QUrl url; + if (!langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) { + url.setUrl(qsl("https://raw.githubusercontent.com/AyuGram/localization/translations/desktop/%1.json").arg(langPackId)); + } else { + url.setUrl(qsl("https://raw.githubusercontent.com/AyuGram/localization/translations/desktop/%1.json").arg(needFallback ? langPackBaseId : langPackId)); + } + _chkReply = networkManager.get(QNetworkRequest(url)); + connect(_chkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(fetchError(QNetworkReply::NetworkError))); + connect(_chkReply, SIGNAL(finished()), this, SLOT(fetchFinished())); + LOG(("Fetching %1 lang pack...").arg(needFallback ? (langPackBaseId.isEmpty() ? langPackId : langPackBaseId) : langPackId)); +} + +void CustomLangPack::fetchFinished() { + if (!_chkReply) return; + + QString langPackBaseId = Lang::GetInstance().baseId(); + QString langPackId = Lang::GetInstance().id(); + auto statusCode = _chkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (statusCode == 404 && !langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) { + LOG(("AyuGram Language pack not found! Fallback to main language: %1...").arg(langPackBaseId)); + needFallback = true; + _chkReply->disconnect(); + fetchCustomLangPack("", langPackBaseId); + } + else { + QByteArray result = _chkReply->readAll().trimmed(); + QJsonParseError error{}; + QJsonDocument str = QJsonDocument::fromJson(result, &error); + if (error.error == QJsonParseError::NoError) { + parseLangFile(str); + } else { + LOG(("Incorrect JSON File. Fallback to default language: English...")); + loadDefaultLangFile(); + } + + _chkReply = nullptr; + } +} + +void CustomLangPack::fetchError(QNetworkReply::NetworkError e) { + LOG(("Network error: %1").arg(e)); + + if (e == QNetworkReply::NetworkError::ContentNotFoundError) { + QString langPackBaseId = Lang::GetInstance().baseId(); + QString langPackId = Lang::GetInstance().id(); + + if (!langPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) { + LOG(("AyuGram Language pack not found! Fallback to main language: %1...").arg(langPackBaseId)); + needFallback = true; + _chkReply->disconnect(); + fetchCustomLangPack("", langPackBaseId); + } else { + LOG(("AyuGram Language pack not found! Fallback to default language: English...")); + loadDefaultLangFile(); + _chkReply = nullptr; + } + } +} + +void CustomLangPack::loadDefaultLangFile() { + QFile file(":/localization/en.json"); + if (file.open(QIODevice::ReadOnly)) { + QJsonDocument str = QJsonDocument::fromJson(file.readAll()); + QJsonObject json = str.object(); + for (const QString& key : json.keys()) { + Lang::GetInstance().applyValue(key.toUtf8(), json.value(key).toString().toUtf8()); + } + Lang::GetInstance().updatePluralRules(); + file.close(); + } +} + +void CustomLangPack::parseLangFile(QJsonDocument str) { + QJsonObject json = str.object(); + for (const QString& brokenKey : json.keys()) { + auto key = qsl("ayu_") + brokenKey; + auto val = json.value(brokenKey).toString().toUtf8(); + + Lang::GetInstance().resetValue(key.toUtf8()); + Lang::GetInstance().applyValue(key.toUtf8(), val); + if (key.contains("#other")) { + Lang::GetInstance().resetValue(key.toUtf8().replace("#other", "#few")); + Lang::GetInstance().resetValue(key.toUtf8().replace("#other", "#few")); + Lang::GetInstance().applyValue(key.toUtf8().replace("#other", "#few"), val); + Lang::GetInstance().applyValue(key.toUtf8().replace("#other", "#many"), val); + } + } + Lang::GetInstance().updatePluralRules(); +} \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/ayu_lang.h b/Telegram/SourceFiles/ayu/ayu_lang.h new file mode 100644 index 000000000..fe5af4880 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ayu_lang.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +class CustomLangPack : public QObject { +Q_OBJECT + Q_DISABLE_COPY(CustomLangPack) + +public: + static CustomLangPack *currentInstance(); + static void initInstance(); + static CustomLangPack *instance; + + void fetchCustomLangPack(const QString& langPackId, const QString& langPackBaseId); + void loadDefaultLangFile(); + void parseLangFile(QJsonDocument str); + +public Q_SLOTS: + void fetchFinished(); + void fetchError(QNetworkReply::NetworkError e); + +private: + CustomLangPack(); + ~CustomLangPack() = default; + + QNetworkAccessManager networkManager; + QNetworkReply *_chkReply = nullptr; + bool needFallback = false; +}; \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/context_menu/context_menu.cpp b/Telegram/SourceFiles/ayu/context_menu/context_menu.cpp index 2d14656de..dc43dd1ea 100644 --- a/Telegram/SourceFiles/ayu/context_menu/context_menu.cpp +++ b/Telegram/SourceFiles/ayu/context_menu/context_menu.cpp @@ -1,5 +1,6 @@ #include "context_menu.h" #include "history/history_inner_widget.h" +#include "lang_auto.h" #include "ui/widgets/popup_menu.h" #include "base/unixtime.h" #include "styles/style_chat.h" @@ -36,7 +37,7 @@ namespace AyuUi { void AyuPopupMenu::addReadUntilAction(HistoryItem *item) const { const auto history = item->history(); - _ayuSubMenu->addAction(QString("Read until"), [=]() { + _ayuSubMenu->addAction(tr::ayu_ReadUntilMenuText(tr::now), [=]() { AyuState::setAllowSendReadPacket(true); history->session().data().histories().readInboxOnNewMessage(item); }, &st::menuIconShowInChat); diff --git a/Telegram/SourceFiles/ayu/settings/settings_ayu.cpp b/Telegram/SourceFiles/ayu/settings/settings_ayu.cpp index 3cae4e768..370ec46e7 100644 --- a/Telegram/SourceFiles/ayu/settings/settings_ayu.cpp +++ b/Telegram/SourceFiles/ayu/settings/settings_ayu.cpp @@ -3,6 +3,7 @@ #include "ayu/ayu_settings.h" #include "ayu/settings/settings_ayu.h" +#include "lang_auto.h" #include "mainwindow.h" #include "settings/settings_common.h" #include "ui/wrap/vertical_layout.h" @@ -24,7 +25,7 @@ namespace Settings { rpl::producer Ayu::title() { - return rpl::single(QString("AyuGram Settings")); + return tr::ayu_AyuPreferences(); } Ayu::Ayu( @@ -38,11 +39,12 @@ namespace Settings { not_null controller) { auto settings = &AyuSettings::getInstance(); - AddSubsectionTitle(container, rpl::single(QString("General"))); + AddSkip(container); + AddSubsectionTitle(container, tr::ayu_GhostEssentialsHeader()); AddButton( container, - rpl::single(QString("Send read packets")), + tr::ayu_SendReadPackets(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->sendReadPackets) @@ -56,7 +58,7 @@ namespace Settings { AddButton( container, - rpl::single(QString("Send online packets")), + tr::ayu_SendOnlinePackets(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->sendOnlinePackets) @@ -70,21 +72,7 @@ namespace Settings { AddButton( container, - rpl::single(QString("Send offline packet after online")), - st::settingsButtonNoIcon - )->toggleOn( - rpl::single(settings->sendOfflinePacketAfterOnline) - )->toggledValue( - ) | rpl::filter([=](bool enabled) { - return (enabled != settings->sendOfflinePacketAfterOnline); - }) | rpl::start_with_next([=](bool enabled) { - settings->set_sendOfflinePacketAfterOnline(enabled); - Local::writeSettings(); - }, container->lifetime()); - - AddButton( - container, - rpl::single(QString("Send typing & upload progress")), + tr::ayu_SendUploadProgress(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->sendUploadProgress) @@ -98,7 +86,21 @@ namespace Settings { AddButton( container, - rpl::single(QString("Use scheduled messages to keep offline")), + tr::ayu_SendOfflinePacketAfterOnline(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->sendOfflinePacketAfterOnline) + )->toggledValue( + ) | rpl::filter([=](bool enabled) { + return (enabled != settings->sendOfflinePacketAfterOnline); + }) | rpl::start_with_next([=](bool enabled) { + settings->set_sendOfflinePacketAfterOnline(enabled); + Local::writeSettings(); + }, container->lifetime()); + + AddButton( + container, + tr::ayu_UseScheduledMessages(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->useScheduledMessages) @@ -110,9 +112,14 @@ namespace Settings { Local::writeSettings(); }, container->lifetime()); + AddDivider(container); + AddSkip(container); + + AddSubsectionTitle(container, tr::ayu_SpyEssentialsHeader()); + AddButton( container, - rpl::single(QString("Keep deleted messages")), + tr::ayu_KeepDeletedMessages(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->keepDeletedMessages) @@ -126,7 +133,7 @@ namespace Settings { AddButton( container, - rpl::single(QString("Keep messages' history")), + tr::ayu_KeepMessagesHistory(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->keepMessagesHistory) @@ -138,11 +145,16 @@ namespace Settings { Local::writeSettings(); }, container->lifetime()); + AddDivider(container); + AddSkip(container); + + AddSubsectionTitle(container, tr::ayu_CustomizationHeader()); + auto currentDeletedMark = lifetime().make_state>(); auto btn = AddButtonWithLabel( container, - rpl::single(QString("Deleted Mark")), + tr::ayu_DeletedMarkText(), currentDeletedMark->changes(), st::settingsButtonNoIcon ); @@ -160,7 +172,7 @@ namespace Settings { auto btn2 = AddButtonWithLabel( container, - rpl::single(QString("Edited Mark")), + rpl::single(QString("Edited mark")), currentEditedMark->changes(), st::settingsButtonNoIcon ); @@ -174,7 +186,7 @@ namespace Settings { }); *currentEditedMark = settings->editedMark; - AddDividerText(container, rpl::single(QString("AyuGram developed and maintained by Radolyn Labs"))); + AddDividerText(container, tr::ayu_SettingsWatermark()); } void Ayu::setupContent(not_null controller) { diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h index 1ca396c9f..0dff008e3 100644 --- a/Telegram/SourceFiles/lang/lang_instance.h +++ b/Telegram/SourceFiles/lang/lang_instance.h @@ -105,12 +105,14 @@ public: || (_base && _base->isNonDefaultPlural(key)); } + void resetValue(const QByteArray &key); + void applyValue(const QByteArray &key, const QByteArray &value); + void updatePluralRules(); + private: void setBaseId(const QString &baseId, const QString &pluralId); void applyDifferenceToMe(const MTPDlangPackDifference &difference); - void applyValue(const QByteArray &key, const QByteArray &value); - void resetValue(const QByteArray &key); void reset(const Language &language); void fillFromCustomContent( const QString &absolutePath, @@ -122,7 +124,6 @@ private: const QString &absolutePath, const QString &relativePath, const QByteArray &content); - void updatePluralRules(); void updateChoosingStickerReplacement(); Instance *_derived = nullptr; diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index a24c49bf6..42e13e589 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -395,7 +395,7 @@ void SetupSections( Calls::Id(), { &st::settingsIconCalls, kIconGreen }); addSection( - rpl::single(QString("AyuGram Settings")), + tr::ayu_AyuPreferences(), Ayu::Id(), { &st::settingsPremiumIconStar, kIconPurple }); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 771744d65..ec78b4169 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "ayu/ayu_settings.h" +#include "ayu/ayu_lang.h" #ifndef Q_OS_WIN #include @@ -1131,6 +1132,14 @@ void readLangPack() { if (langpack.stream.status() == QDataStream::Ok) { Lang::GetInstance().fillFromSerialized(data, langpack.version); } + QString langPackBaseId = Lang::GetInstance().baseId(); + QString langPackId = Lang::GetInstance().id(); + if (langPackId.isEmpty()) { + LOG(("Lang ID not found! Re-use old language pack...")); + return; + } + CustomLangPack::initInstance(); + CustomLangPack::currentInstance()->fetchCustomLangPack(langPackId, langPackBaseId); } void writeLangPack() { diff --git a/ayu-scripts/.gitignore b/ayu-scripts/.gitignore new file mode 100644 index 000000000..5ceb3864c --- /dev/null +++ b/ayu-scripts/.gitignore @@ -0,0 +1 @@ +venv diff --git a/ayu-scripts/desktop-specific.json b/ayu-scripts/desktop-specific.json new file mode 100644 index 000000000..55ef5234c --- /dev/null +++ b/ayu-scripts/desktop-specific.json @@ -0,0 +1,3 @@ +{ + "ayu_SettingsWatermark": "AyuGram developed and maintained by Radolyn Labs" +} \ No newline at end of file diff --git a/ayu-scripts/requirements.txt b/ayu-scripts/requirements.txt new file mode 100644 index 000000000..b50fb414e --- /dev/null +++ b/ayu-scripts/requirements.txt @@ -0,0 +1,9 @@ +beautifulsoup4==4.12.2 +bs4==0.0.1 +certifi==2023.5.7 +charset-normalizer==3.1.0 +idna==3.4 +lxml==4.9.2 +requests==2.31.0 +soupsieve==2.4.1 +urllib3==2.0.2 diff --git a/ayu-scripts/sync-translations.py b/ayu-scripts/sync-translations.py new file mode 100644 index 000000000..c17d47ed5 --- /dev/null +++ b/ayu-scripts/sync-translations.py @@ -0,0 +1,45 @@ +import json +import os.path +import sys + +import bs4 +import requests + +if os.path.exists('desktop-specific.json'): + os.chdir('../') + +req = requests.get( + 'https://raw.githubusercontent.com/AyuGram/AyuGram4A/rewrite/TMessagesProj/src/main/res/values/ayu.xml' +) + +tree = bs4.BeautifulSoup(req.text, 'xml') + +strings = {} +for string in tree.find_all('string'): + t = string.attrs['name'] + strings[f'ayu_{t}'] = string.text + +with open('./ayu-scripts/desktop-specific.json') as f: + data = json.load(f) + + strings.update(data) + +req = requests.get( + 'https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/langs/lang.strings' +) + +data = req.text + +data += ''' + +// AyuGram keys generated + +''' + +for k, v in strings.items(): + data += f'"{k}" = "{v}";\n' + +with open(os.path.realpath('./Telegram/Resources/langs/lang.strings'), 'w', encoding='utf-8') as f: + f.write(data) + +print('Done.')