mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2025-06-17 06:47:22 +08:00
318 lines
8.7 KiB
C++
318 lines
8.7 KiB
C++
#include "translation_service.hpp"
|
|
|
|
#include "fiber_pool.hpp"
|
|
#include "file_manager.hpp"
|
|
#include "http_client/http_client.hpp"
|
|
#include "pointers.hpp"
|
|
#include "thread_pool.hpp"
|
|
|
|
namespace big
|
|
{
|
|
translation_service::translation_service() :
|
|
m_url("https://raw.githubusercontent.com/YimMenu/Translations/master"),
|
|
m_fallback_url("https://cdn.jsdelivr.net/gh/YimMenu/Translations@master")
|
|
{
|
|
}
|
|
|
|
void translation_service::init()
|
|
{
|
|
m_translation_directory = std::make_unique<folder>(g_file_manager.get_project_folder("./translations").get_path());
|
|
|
|
bool loaded_remote_index = false;
|
|
for (size_t i = 0; i < 5 && !loaded_remote_index; i++)
|
|
{
|
|
if (i)
|
|
LOG(WARNING) << "Failed to download remote index, trying again... (" << i << ")";
|
|
loaded_remote_index = download_index();
|
|
}
|
|
|
|
if (load_local_index())
|
|
{
|
|
if (!loaded_remote_index)
|
|
{
|
|
LOG(WARNING) << "Failed to load remote index, attempting to use fallback.";
|
|
use_fallback_remote();
|
|
}
|
|
else if (m_local_index.version < m_remote_index.version)
|
|
{
|
|
LOG(INFO) << "Languages outdated, downloading new translations.";
|
|
|
|
update_language_packs();
|
|
m_local_index.version = m_remote_index.version;
|
|
}
|
|
|
|
load_translations();
|
|
|
|
if (loaded_remote_index)
|
|
try_set_default_language();
|
|
|
|
return;
|
|
}
|
|
|
|
if (!loaded_remote_index)
|
|
{
|
|
LOG(WARNING) << "Failed to load remote index, unable to load translations.";
|
|
return;
|
|
}
|
|
|
|
LOG(INFO) << "Downloading translations...";
|
|
|
|
m_local_index.fallback_default_language = m_remote_index.default_lang;
|
|
m_local_index.selected_language = m_remote_index.default_lang;
|
|
m_local_index.version = m_remote_index.version;
|
|
|
|
load_translations();
|
|
try_set_default_language();
|
|
}
|
|
|
|
std::string_view translation_service::get_translation(const std::string_view translation_key) const
|
|
{
|
|
return get_translation(rage::joaat(translation_key), translation_key);
|
|
}
|
|
|
|
|
|
std::string_view translation_service::get_translation(const rage::joaat_t translation_key, const std::string_view fallback) const
|
|
{
|
|
if (auto it = m_translations.find(translation_key); it != m_translations.end())
|
|
return it->second.c_str();
|
|
|
|
return fallback;
|
|
}
|
|
|
|
std::map<std::string, translation_entry>& translation_service::available_translations()
|
|
{
|
|
return m_remote_index.translations;
|
|
}
|
|
|
|
const std::string& translation_service::current_language_pack()
|
|
{
|
|
return m_local_index.selected_language;
|
|
}
|
|
|
|
void translation_service::select_language_pack(const std::string& pack_id)
|
|
{
|
|
g_thread_pool->push([this, &pack_id] {
|
|
m_local_index.selected_language = pack_id;
|
|
load_translations();
|
|
});
|
|
}
|
|
|
|
void translation_service::update_language_packs()
|
|
{
|
|
for (auto item : std::filesystem::directory_iterator(m_translation_directory->get_path()))
|
|
{
|
|
const auto path = item.path();
|
|
const auto stem = path.stem().string();
|
|
if (stem == "index" || item.path().extension() != ".json")
|
|
continue;
|
|
|
|
if (!download_language_pack(stem))
|
|
{
|
|
LOG(WARNING) << "Failed to update '" << stem << "' language pack";
|
|
}
|
|
}
|
|
}
|
|
|
|
void translation_service::update_n_reload_language_packs()
|
|
{
|
|
update_language_packs();
|
|
load_translations();
|
|
}
|
|
|
|
void translation_service::load_translations()
|
|
{
|
|
m_translations.clear();
|
|
|
|
// load default lang first to make sure there are fallback keys if another language pack doesn't have a certain key
|
|
auto j = load_translation(m_remote_index.default_lang);
|
|
for (auto& [key, value] : j.items())
|
|
{
|
|
m_translations.insert({rage::joaat(key), value.get<std::string>()});
|
|
}
|
|
|
|
// Don't load selected language if it's the same as default
|
|
if (m_local_index.selected_language != m_remote_index.default_lang)
|
|
{
|
|
auto j = load_translation(m_local_index.selected_language);
|
|
for (auto& [key, value] : j.items())
|
|
{
|
|
m_translations[rage::joaat(key)] = value;
|
|
}
|
|
}
|
|
|
|
save_local_index();
|
|
}
|
|
|
|
bool translation_service::does_language_exist(const std::string_view language)
|
|
{
|
|
auto file = m_translation_directory->get_file(std::format("./{}.json", language));
|
|
if (file.exists())
|
|
return true;
|
|
|
|
if (auto it = m_remote_index.translations.find(language.data()); it != m_remote_index.translations.end())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
nlohmann::json translation_service::load_translation(const std::string_view pack_id)
|
|
{
|
|
auto file = m_translation_directory->get_file(std::format("./{}.json", pack_id));
|
|
if (!file.exists())
|
|
{
|
|
LOG(INFO) << "Translations for '" << pack_id << "' does not exist, downloading from " << m_url;
|
|
if (!download_language_pack(pack_id))
|
|
{
|
|
LOG(WARNING) << "Failed to download language pack, can't recover...";
|
|
return {};
|
|
}
|
|
// make a copy available
|
|
m_local_index.fallback_languages[pack_id.data()] = m_remote_index.translations[pack_id.data()];
|
|
}
|
|
|
|
try
|
|
{
|
|
return nlohmann::json::parse(std::ifstream(file.get_path(), std::ios::binary));
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
LOG(WARNING) << "Failed to parse language pack. " << e.what();
|
|
|
|
if (auto it = m_remote_index.translations.find(pack_id.data()); it != m_remote_index.translations.end()) // ensure that local language files are not removed
|
|
std::filesystem::remove(file.get_path());
|
|
|
|
return {};
|
|
}
|
|
}
|
|
|
|
bool translation_service::download_language_pack(const std::string_view pack_id)
|
|
{
|
|
if (auto it = m_remote_index.translations.find(pack_id.data()); it != m_remote_index.translations.end())
|
|
{
|
|
const auto response = download_file("/" + it->second.file);
|
|
if (response.status_code == 200)
|
|
{
|
|
try
|
|
{
|
|
auto json = nlohmann::json::parse(response.text);
|
|
auto lang_file = m_translation_directory->get_file("./" + it->second.file);
|
|
|
|
auto out_file = std::ofstream(lang_file.get_path(), std::ios::binary | std::ios::trunc);
|
|
out_file << json.dump(4);
|
|
out_file.close();
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
LOG(WARNING) << "Failed to parse language pack. " << e.what();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool translation_service::download_index()
|
|
{
|
|
const auto response = download_file("/index.json");
|
|
if (response.status_code == 200)
|
|
{
|
|
try
|
|
{
|
|
m_remote_index = nlohmann::json::parse(response.text);
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
LOG(WARNING) << "Failed to load remote index. " << e.what();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool translation_service::load_local_index()
|
|
{
|
|
const auto local_index = m_translation_directory->get_file("./index.json");
|
|
if (local_index.exists())
|
|
{
|
|
try
|
|
{
|
|
const auto path = local_index.get_path();
|
|
m_local_index = nlohmann::json::parse(std::ifstream(path, std::ios::binary));
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
LOG(WARNING) << "Failed to load local index. " << e.what();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void translation_service::save_local_index()
|
|
{
|
|
nlohmann::json j = m_local_index;
|
|
|
|
const auto local_index = m_translation_directory->get_file("./index.json");
|
|
|
|
auto os = std::ofstream(local_index.get_path(), std::ios::binary | std::ios::trunc);
|
|
os << j.dump(4);
|
|
os.close();
|
|
}
|
|
|
|
void translation_service::use_fallback_remote()
|
|
{
|
|
m_remote_index.default_lang = m_local_index.fallback_default_language;
|
|
m_remote_index.translations = m_local_index.fallback_languages;
|
|
}
|
|
|
|
cpr::Response translation_service::download_file(const std::string& filename)
|
|
{
|
|
auto response = g_http_client.get(m_url + filename);
|
|
if (response.status_code != 200)
|
|
response = g_http_client.get(m_fallback_url + filename);
|
|
return response;
|
|
}
|
|
|
|
void translation_service::try_set_default_language()
|
|
{
|
|
if (!m_local_index.default_language_set)
|
|
{
|
|
g_fiber_pool->queue_job([this] {
|
|
std::string preferred_lang = "en_US";
|
|
auto game_lang = *g_pointers->m_gta.m_language;
|
|
|
|
switch (game_lang)
|
|
{
|
|
case 1: preferred_lang = "fr_FR"; break;
|
|
case 2: preferred_lang = "de_DE"; break;
|
|
case 3: preferred_lang = "it_IT"; break;
|
|
case 4:
|
|
case 11: preferred_lang = "es_ES"; break;
|
|
case 5: preferred_lang = "pt_BR"; break;
|
|
case 6: preferred_lang = "pl_PL"; break;
|
|
case 7: preferred_lang = "ru_RU"; break;
|
|
case 8: preferred_lang = "ko_KR"; break;
|
|
case 9: preferred_lang = "zh_TW"; break;
|
|
case 10: preferred_lang = "ja_JP"; break;
|
|
case 12: preferred_lang = "zh_CN"; break;
|
|
}
|
|
|
|
if (does_language_exist(preferred_lang))
|
|
{
|
|
m_local_index.selected_language = preferred_lang;
|
|
save_local_index();
|
|
}
|
|
|
|
m_local_index.default_language_set = true;
|
|
load_translations();
|
|
});
|
|
}
|
|
}
|
|
}
|