// This is the source code of AyuGram for Desktop. // // We do not and cannot prevent the use of our code, // but be respectful and credit the original author. // // Copyright @Radolyn, 2026 #include "ayu/ayu_lang.h" #include "qjsondocument.h" #include "core/application.h" #include "core/core_settings.h" #include "lang/lang_instance.h" #include "storage/localstorage.h" #include #include // hard-coded languages std::map langMapping = { {"pt-br", "pt"}, {"zh-hans-beta", "zh-hans"}, {"zh-hant-beta", "zh-hant"}, {"zh-hans-raw", "zh-hans"}, {"zh-hant-raw", "zh-hant"}, }; constexpr auto postfixes = { "zero", "one", "two", "few", "many", "other" }; AyuLanguage *AyuLanguage::instance = nullptr; AyuLanguage::AyuLanguage() = default; void AyuLanguage::init() { if (!instance) instance = new AyuLanguage; instance->loadCachedLanguage(); } AyuLanguage *AyuLanguage::currentInstance() { return instance; } QString AyuLanguage::getCacheDir() const { return cWorkingDir() + u"tdata/ayu/languages/"_q; } QString AyuLanguage::getCachePath(const QString &langId) const { return getCacheDir() + langId + u".json"_q; } void AyuLanguage::loadCachedLanguage() { const auto langPackId = Lang::GetInstance().id(); const auto langPackBaseId = Lang::GetInstance().baseId(); auto finalLangPackId = langMapping.contains(langPackId) ? langMapping[langPackId] : langPackId; if (finalLangPackId.isEmpty()) { finalLangPackId = langPackBaseId; } if (finalLangPackId.isEmpty()) { return; } const auto cachePath = getCachePath(finalLangPackId); QFile file(cachePath); if (!file.exists()) { const auto basePath = getCachePath(langPackBaseId); if (!QFile::exists(basePath)) { return; } file.setFileName(basePath); } if (file.open(QIODevice::ReadOnly)) { const auto data = file.readAll(); file.close(); QJsonParseError error{}; const auto doc = QJsonDocument::fromJson(data, &error); if (error.error == QJsonParseError::NoError) { LOG(("Loading cached AyuGram language: %1").arg(finalLangPackId)); applyLanguageJson(doc); } } } void AyuLanguage::saveCachedLanguage(const QByteArray &json, const QString &langId) { const auto cacheDir = getCacheDir(); QDir().mkpath(cacheDir); const auto cachePath = getCachePath(langId); QFile file(cachePath); if (file.open(QIODevice::WriteOnly)) { file.write(json); file.close(); LOG(("Cached AyuGram language: %1").arg(langId)); } } void AyuLanguage::fetchLanguage(const QString &id, const QString &baseId) { auto finalLangPackId = langMapping.contains(id) ? langMapping[id] : id; _currentLangId = finalLangPackId.isEmpty() ? baseId : finalLangPackId; if (Core::App().settings().proxy().isEnabled()) { const auto proxy = Core::App().settings().proxy().selected(); if (proxy.type == MTP::ProxyData::Type::Socks5 || proxy.type == MTP::ProxyData::Type::Http) { const auto networkProxy = ToNetworkProxy(ToDirectIpProxy(Core::App().settings().proxy().selected())); networkManager.setProxy(networkProxy); } } // using `jsdelivr` since China (...and maybe other?) users have some problems with GitHub // https://crowdin.com/project/ayugram/discussions/6 QUrl url; if (!finalLangPackId.isEmpty() && !baseId.isEmpty() && !needFallback) { url.setUrl(qsl("https://cdn.jsdelivr.net/gh/AyuGram/Languages@l10n_main/values/langs/%1/Shared.json").arg( finalLangPackId)); } else { url.setUrl(qsl("https://cdn.jsdelivr.net/gh/AyuGram/Languages@l10n_main/values/langs/%1/Shared.json").arg( needFallback ? baseId : finalLangPackId)); } _chkReply = networkManager.get(QNetworkRequest(url)); connect(_chkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(fetchError(QNetworkReply::NetworkError))); connect(_chkReply, SIGNAL(finished()), this, SLOT(fetchFinished())); } void AyuLanguage::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 not found! Fallback to main language: %1...").arg(langPackBaseId)); needFallback = true; _chkReply->disconnect(); fetchLanguage("", langPackBaseId); } else { const auto result = _chkReply->readAll().trimmed(); QJsonParseError error{}; const auto doc = QJsonDocument::fromJson(result, &error); if (error.error == QJsonParseError::NoError) { saveCachedLanguage(result, _currentLangId); applyLanguageJson(doc); } else { LOG(("Incorrect language JSON File.")); } _chkReply = nullptr; } } void AyuLanguage::fetchError(QNetworkReply::NetworkError e) { LOG(("Network error: %1").arg(e)); if (e == QNetworkReply::NetworkError::ContentNotFoundError) { const auto baseId = Lang::GetInstance().baseId(); const auto id = Lang::GetInstance().id(); if (!id.isEmpty() && !baseId.isEmpty() && !needFallback) { LOG(("AyuGram Language not found! Fallback to main language: %1...").arg(baseId)); needFallback = true; _chkReply->disconnect(); fetchLanguage("", baseId); } else { LOG(("AyuGram Language not found!")); _chkReply = nullptr; } } } void AyuLanguage::applyLanguageJson(QJsonDocument doc) { const auto json = doc.object(); for (const QString &brokenKey : json.keys()) { auto key = qsl("ayu_") + brokenKey; auto val = json.value(brokenKey).toString().replace(qsl("&"), qsl("&")); if (key.endsWith("_Android")) { continue; } for (const auto &postfix : postfixes) { if (key.endsWith(qsl("_") + postfix)) { key = key.replace(qsl("_") + postfix, qsl("#") + postfix); break; } } if (key.endsWith("_PC")) { key = key.replace("_PC", ""); } if (val.contains(qsl("%1$d")) && !val.contains(qsl("%2$d"))) { val = val.replace(qsl("%1$d"), qsl("{count}")); } else if (val.contains(qsl("%1$d")) && val.contains(qsl("%2$d"))) { val = val.replace(qsl("%1$d"), qsl("{count1}")).replace(qsl("%2$d"), qsl("{count2}")); } else if (val.contains(qsl("%1$s")) && !val.contains(qsl("%2$s"))) { val = val.replace(qsl("%1$s"), qsl("{item}")); } else if (val.contains(qsl("%1$s")) && val.contains(qsl("%2$s"))) { val = val.replace(qsl("%1$s"), qsl("{item1}")).replace(qsl("%2$s"), qsl("{item2}")); } Lang::GetInstance().resetValue(key.toUtf8()); Lang::GetInstance().applyValue(key.toUtf8(), val.toUtf8()); } Lang::GetInstance().updatePluralRules(); }