mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2024-12-22 12:07:46 +08:00
feat: Chat translator (#2931)
This commit is contained in:
parent
9ad4885a8f
commit
0fa7c580c1
125
docs/chat translator/setup_LibreTranslate_with_docker.md
Normal file
125
docs/chat translator/setup_LibreTranslate_with_docker.md
Normal file
@ -0,0 +1,125 @@
|
||||
# Setup LibreTranslate Translation on Local Computer using Docker
|
||||
|
||||
Yimmenu's chat translation feature relies on LibreTranslate. This guide will help you setup LibreTranslate translation service on your computer.
|
||||
|
||||
## Table of Contents
|
||||
- [Quick Start](#quick-start)
|
||||
- [1. Install Docker](#1-install-docker)
|
||||
- [2. Run Docker Desktop](#2-run-docker-desktop)
|
||||
- [3. Download LibreTranslate Setup Script](#3-download-libretranslate-setup-script)
|
||||
- [4. Run Setup Script](#4-run-setup-script)
|
||||
- [Advanced](#advanced)
|
||||
- [How to Add Startup Arguments](#how-to-add-startup-arguments)
|
||||
- [Common Arguments](#common-arguments)
|
||||
- [1. Specify Languages to Download](#1-specify-languages-to-download)
|
||||
- [2. Specify IP Address Binding](#2-specify-ip-address-binding)
|
||||
- [3. Specify Listening Port](#3-specify-listening-port)
|
||||
- [Removal](#removal)
|
||||
- [Uninstall Docker](#uninstall-docker)
|
||||
- [Remove LibreTranslate from Docker](#remove-libretranslate-from-docker)
|
||||
- [Using Docker Desktop](#using-docker-desktop)
|
||||
- [Using Docker Command](#using-docker-command)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install Docker
|
||||
|
||||
Windows: [Install Docker on Windows](https://docs.docker.com/desktop/install/windows-install/) \
|
||||
Linux: [Install Docker on Linux](https://docs.docker.com/desktop/install/linux-install/) \
|
||||
Mac: [Install Docker on Mac](https://docs.docker.com/desktop/install/mac-install/)
|
||||
|
||||
For Windows users, after running the Docker Desktop installer, simply click OK, and the installer will automatically complete all steps. If you haven't enabled WSL in control panel, restart Windows as prompted after installation to use Docker.
|
||||
|
||||
![docker_setup](https://github.com/sch-lda/YimMenu/assets/54973190/96b42f4e-dedc-4ba8-96af-496490325f0a)
|
||||
![docker_setup_restart](https://github.com/sch-lda/YimMenu/assets/54973190/728842f0-b364-4ad6-ab24-302967fbc4db)
|
||||
|
||||
### 2. Run Docker Desktop
|
||||
|
||||
You must run Docker Desktop and complete the initialization configuration before proceeding.
|
||||
|
||||
Open Docker Desktop from the desktop shortcut or start menu \
|
||||
Click `Accept` to agree to Docker Desktop's Terms of Service \
|
||||
Click `Continue without signing in` and then `skip survey`
|
||||
|
||||
You can proceed to the next step when you see the green "Engine Running" indicator at the bottom left of the Docker Desktop main window.
|
||||
|
||||
![docker_running](https://github.com/sch-lda/YimMenu/assets/54973190/eb7f7e7e-2e05-431d-9bfa-5f7048cff588)
|
||||
|
||||
### 3. Download LibreTranslate Setup Script
|
||||
|
||||
Windows: [Download run.bat](https://raw.githubusercontent.com/LibreTranslate/LibreTranslate/main/run.bat) \
|
||||
Linux/Mac: [Download run.sh](https://raw.githubusercontent.com/LibreTranslate/LibreTranslate/main/run.sh)
|
||||
|
||||
### 4. Run Setup Script
|
||||
|
||||
Running the script will automatically download LibreTranslate images and all supported languages. This process may take a few minutes.
|
||||
|
||||
When the console outputs `Running on http://*:5000`, it means LibreTranslate has successfully run on port 5000. You can now turn on the chat translation switch in Yimmenu and configure the target language.
|
||||
|
||||
To stop LibreTranslate, press Ctrl+C in the command prompt window.
|
||||
|
||||
After completing the initial installation, to start LibreTranslate, you only need to open Docker Desktop and then run the LibreTranslate setup script.
|
||||
|
||||
## Advanced
|
||||
|
||||
### How to Add Startup Arguments
|
||||
|
||||
Open Command Prompt or Linux Shell and use the `cd` command to navigate to the directory where `run.bat` or `run.sh` is located. For example, if your `run.bat` file is in `E:\git-repo\LibreTranslate\run.bat`, you need to use the command `cd /d "E:\git-repo\LibreTranslate"`.
|
||||
|
||||
Run with additional arguments:
|
||||
|
||||
Linux/macOS: `./run.sh [args]` \
|
||||
Windows: `run.bat [args]`
|
||||
|
||||
LibreTranslate supports various parameters, see [here](https://github.com/LibreTranslate/LibreTranslate?tab=readme-ov-file#arguments) for details.
|
||||
|
||||
### Common Arguments
|
||||
|
||||
This document only lists the most likely parameters to be used as the backend service for Yimmenu chat translation.
|
||||
|
||||
#### 1. Specify Languages to Download
|
||||
|
||||
If you don't want the translation models to take up too much space, you can add parameters to specify which models to load. See the full language support list [here](https://www.argosopentech.com/argospm/index/).
|
||||
|
||||
For example, if you only need Chinese-English translation, you can use the command `run.bat --load-only zh,en --update-models`.
|
||||
|
||||
#### 2. Specify IP Address Binding
|
||||
|
||||
If you don't want the LibreTranslate translation service to be exposed to networks outside your local computer, you can use `--host [ip]` to modify the IP address bound by LibreTranslate. When set to 127.0.0.1, other computers on the LAN will be unable to access LibreTranslate service on your computer.
|
||||
|
||||
For example, `run.bat --host 127.0.0.1`.
|
||||
|
||||
#### 3. Specify Listening Port
|
||||
|
||||
If other programs need to use port 5000, you can use the `--port [port]` parameter to customize the listening port of LibreTranslate.
|
||||
|
||||
For example, `run.bat --port 8080`.
|
||||
|
||||
**If you change the listening port of LibreTranslate, please fill in the same port in the URL setting of Yimmenu chat translation settings.**
|
||||
|
||||
## Removal
|
||||
|
||||
### Uninstall Docker
|
||||
|
||||
If your Docker is only used to run LibreTranslate, you can uninstall Docker directly from the control panel or Windows settings after stopping LibreTranslate, and LibreTranslate will be completely removed as well.
|
||||
|
||||
### Remove LibreTranslate from Docker
|
||||
|
||||
If you wish to uninstall LibreTranslate while keeping other containers, please follow this guide.
|
||||
|
||||
#### Using Docker Desktop
|
||||
|
||||
You can easily remove all images and volumes of LibreTranslate using the GUI of Docker Desktop. Please ensure that LibreTranslate is not running.
|
||||
|
||||
![docker_uninstall_volumes](https://github.com/sch-lda/YimMenu/assets/54973190/bb1201dc-1fb9-4208-bda7-2dc61ac59355)
|
||||
![docker_uninstall_images](https://github.com/sch-lda/YimMenu/assets/54973190/0ca02c98-a008-49db-9cf1-f36dec88c9fc)
|
||||
|
||||
#### Using Docker Command
|
||||
|
||||
If you are using Docker Engine for Linux/Mac, you can also remove LibreTranslate using docker command:
|
||||
|
||||
```bash
|
||||
docker image rm libretranslate/libretranslate
|
||||
docker volume rm lt-db
|
||||
docker volume rm lt-local
|
||||
```
|
@ -125,6 +125,7 @@ namespace big
|
||||
looped::session_randomize_ceo_colors();
|
||||
looped::session_auto_kick_host();
|
||||
looped::session_block_jobs();
|
||||
looped::session_chat_translator();
|
||||
|
||||
if (g_script_connection_service)
|
||||
g_script_connection_service->on_tick();
|
||||
|
@ -35,6 +35,7 @@ namespace big
|
||||
static void session_block_jobs();
|
||||
static void session_randomize_ceo_colors();
|
||||
static void session_auto_kick_host();
|
||||
static void session_chat_translator();
|
||||
|
||||
static void system_self_globals();
|
||||
static void system_update_pointers();
|
||||
|
40
src/backend/looped/session/chat_translator.cpp
Normal file
40
src/backend/looped/session/chat_translator.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "backend/looped/looped.hpp"
|
||||
#include "util/chat.hpp"
|
||||
#include "services/api/api_service.hpp"
|
||||
#include "thread_pool.hpp"
|
||||
|
||||
namespace big
|
||||
{
|
||||
inline std::atomic_bool translate_lock{false};
|
||||
|
||||
void looped::session_chat_translator()
|
||||
{
|
||||
if (!translate_queue.empty() and !translate_lock and g.session.chat_translator.enabled)
|
||||
{
|
||||
if (translate_queue.size() >= 3)
|
||||
{
|
||||
LOG(WARNING) << "[Chat Translator]Message queue is too large, cleaning it. Try enabling spam timer.";
|
||||
translate_queue.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
auto& first_message = translate_queue.front();
|
||||
translate_lock = true;
|
||||
g_thread_pool->push([first_message] {
|
||||
std::string translate_result;
|
||||
std::string sender = "[T]" + first_message.sender;
|
||||
translate_result = g_api_service->get_translation(first_message.content, g.session.chat_translator.target_language);
|
||||
|
||||
translate_lock = false;
|
||||
if (translate_result != "")
|
||||
{
|
||||
if (g.session.chat_translator.draw_result)
|
||||
chat::draw_chat(translate_result, sender, false);
|
||||
if (g.session.chat_translator.print_result)
|
||||
LOG(INFO) << "[" << first_message.sender << "]" << first_message.content << " --> " << translate_result;
|
||||
}
|
||||
});
|
||||
translate_queue.pop();
|
||||
}
|
||||
}
|
||||
}
|
@ -451,7 +451,19 @@ namespace big
|
||||
|
||||
bool fast_join = false;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(session, log_chat_messages, log_text_messages, decloak_players, force_session_host, force_script_host, player_magnet_enabled, player_magnet_count, is_team, join_in_sctv_slots, kick_chat_spammers, kick_host_when_forcing_host, explosion_karma, damage_karma, disable_traffic, disable_peds, force_thunder, block_ceo_money, randomize_ceo_colors, block_jobs, block_muggers, block_ceo_raids, send_to_apartment_idx, send_to_warehouse_idx, chat_commands, chat_command_default_access_level, show_cheating_message, anonymous_bounty, lock_session, fast_join, unhide_players_from_player_list, allow_friends_into_locked_session, trust_friends, use_spam_timer, spam_timer, spam_length)
|
||||
struct chat_translator
|
||||
{
|
||||
bool enabled = false;
|
||||
bool print_result = false;
|
||||
bool draw_result = true;
|
||||
bool bypass_same_language = true;
|
||||
std::string target_language = "en";
|
||||
std::string endpoint = "http://localhost:5000/translate";
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(chat_translator, enabled, print_result, draw_result, bypass_same_language, target_language, endpoint);
|
||||
} chat_translator{};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(session, log_chat_messages, log_text_messages, decloak_players, force_session_host, force_script_host, player_magnet_enabled, player_magnet_count, is_team, join_in_sctv_slots, kick_chat_spammers, kick_host_when_forcing_host, explosion_karma, damage_karma, disable_traffic, disable_peds, force_thunder, block_ceo_money, randomize_ceo_colors, block_jobs, block_muggers, block_ceo_raids, send_to_apartment_idx, send_to_warehouse_idx, chat_commands, chat_command_default_access_level, show_cheating_message, anonymous_bounty, lock_session, fast_join, unhide_players_from_player_list, allow_friends_into_locked_session, trust_friends, use_spam_timer, spam_timer, spam_length, chat_translator)
|
||||
} session{};
|
||||
|
||||
struct settings
|
||||
|
@ -137,6 +137,11 @@ namespace big
|
||||
{
|
||||
if (g.session.log_chat_messages)
|
||||
chat::log_chat(message, player, SpamReason::NOT_A_SPAMMER, is_team);
|
||||
if (g.session.chat_translator.enabled)
|
||||
{
|
||||
chat_message new_message{player->get_name(), message};
|
||||
translate_queue.push(new_message);
|
||||
}
|
||||
|
||||
if (g.session.chat_commands && message[0] == g.session.chat_command_prefix)
|
||||
command::process(std::string(message + 1), std::make_shared<chat_command_context>(player));
|
||||
|
@ -18,6 +18,44 @@ namespace big
|
||||
g_api_service = nullptr;
|
||||
}
|
||||
|
||||
std::string api_service::get_translation(std::string message, std::string target_language)
|
||||
{
|
||||
std::string url = g.session.chat_translator.endpoint;
|
||||
const auto response = g_http_client.post(url,
|
||||
{{"Content-Type", "application/json"}}, std::format(R"({{"q":"{}", "source":"auto", "target": "{}"}})", message, target_language));
|
||||
if (response.status_code == 200)
|
||||
{
|
||||
try
|
||||
{
|
||||
nlohmann::json obj = nlohmann::json::parse(response.text);
|
||||
std::string source_language = obj["detectedLanguage"]["language"];
|
||||
std::string result = obj["translatedText"];
|
||||
|
||||
if (source_language == g.session.chat_translator.target_language && g.session.chat_translator.bypass_same_language)
|
||||
return "";
|
||||
return result;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
LOG(WARNING) << "[Chat Translator]Error when parse JSON data: " << e.what();
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
else if (response.status_code == 0)
|
||||
{
|
||||
g.session.chat_translator.enabled = false;
|
||||
g_notification_service.push_error("TRANSLATOR_TOGGLE"_T.data(), "TRANSLATOR_FAILED_TO_CONNECT"_T.data());
|
||||
LOG(WARNING) << "[Chat Translator]Unable to connect to LibreTranslate server. Follow the guide in Yimmenu Wiki to setup LibreTranslate server on your computer.";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(WARNING) << "[Chat Translator]Error when sending request. Status code: " << response.status_code << " Response: " << response.text;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool api_service::get_rid_from_username(std::string_view username, uint64_t& result)
|
||||
{
|
||||
const auto response = g_http_client.post("https://scui.rockstargames.com/api/friend/accountsearch", {{"Authorization", AUTHORIZATION_TICKET}, {"X-Requested-With", "XMLHttpRequest"}}, {std::format("searchNickname={}", username)});
|
||||
|
@ -12,6 +12,9 @@ namespace big
|
||||
api_service();
|
||||
~api_service();
|
||||
|
||||
// Makes an API call to a LibreTranslate endpoint.
|
||||
std::string get_translation(std::string message, std::string target_language);
|
||||
|
||||
// Returns true if an valid profile matching his username has been found
|
||||
bool get_rid_from_username(std::string_view username, uint64_t& result);
|
||||
|
||||
|
@ -182,7 +182,7 @@ namespace big::chat
|
||||
log.close();
|
||||
}
|
||||
|
||||
inline void draw_chat(const char* msg, const char* player_name, bool is_team)
|
||||
inline void render_chat(const char* msg, const char* player_name, bool is_team)
|
||||
{
|
||||
int scaleform = GRAPHICS::REQUEST_SCALEFORM_MOVIE("MULTIPLAYER_CHAT");
|
||||
|
||||
@ -212,6 +212,16 @@ namespace big::chat
|
||||
HUD::CLOSE_MP_TEXT_CHAT();
|
||||
}
|
||||
|
||||
inline void draw_chat(const std::string& message, const std::string& sender, bool is_team)
|
||||
{
|
||||
if (rage::tlsContext::get()->m_is_script_thread_active)
|
||||
render_chat(message.c_str(), sender.c_str(), is_team);
|
||||
else
|
||||
g_fiber_pool->queue_job([message, sender, is_team] {
|
||||
render_chat(message.c_str(), sender.c_str(), is_team);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool is_on_same_team(CNetGamePlayer* player)
|
||||
{
|
||||
auto target_id = player->m_player_id;
|
||||
@ -275,11 +285,17 @@ namespace big::chat
|
||||
}
|
||||
|
||||
if (draw)
|
||||
if (rage::tlsContext::get()->m_is_script_thread_active)
|
||||
draw_chat(message.c_str(), g_player_service->get_self()->get_name(), is_team);
|
||||
else
|
||||
g_fiber_pool->queue_job([message, target, is_team] {
|
||||
draw_chat(message.c_str(), g_player_service->get_self()->get_name(), is_team);
|
||||
});
|
||||
draw_chat(message, g_player_service->get_self()->get_name(), is_team);
|
||||
}
|
||||
}
|
||||
|
||||
namespace big
|
||||
{
|
||||
struct chat_message
|
||||
{
|
||||
std::string sender;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
inline std::queue<chat_message> translate_queue;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
#include "core/data/apartment_names.hpp"
|
||||
#include "core/data/apartment_names.hpp"
|
||||
#include "core/data/command_access_levels.hpp"
|
||||
#include "core/data/region_codes.hpp"
|
||||
#include "core/data/warehouse_names.hpp"
|
||||
@ -26,6 +26,12 @@ namespace big
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct target_language_type
|
||||
{
|
||||
const char* type;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
void render_rid_joiner()
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
@ -120,6 +126,7 @@ namespace big
|
||||
|
||||
bool_command whitelist_friends("trustfriends", "TRUST_FRIENDS", "TRUST_FRIENDS_DESC", g.session.trust_friends);
|
||||
bool_command whitelist_session("trustsession", "TRUST_SESSION", "TRUST_SESSION_DESC", g.session.trust_session);
|
||||
bool_command chat_translate("translatechat", "TRANSLATOR_TOGGLE", "TRANSLATOR_TOGGLE_DESC", g.session.chat_translator.enabled);
|
||||
|
||||
void render_misc()
|
||||
{
|
||||
@ -228,6 +235,34 @@ namespace big
|
||||
}
|
||||
}
|
||||
|
||||
components::command_checkbox<"translatechat">();
|
||||
if (g.session.chat_translator.enabled)
|
||||
{
|
||||
|
||||
ImGui::Checkbox("TRANSLATOR_HIDE_SAME_LANGUAGE"_T.data(), &g.session.chat_translator.bypass_same_language);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("TRANSLATOR_HIDE_SAME_LANGUAGE_DESC"_T.data());
|
||||
|
||||
components::small_text("TRANSLATOR_OUTPUT"_T.data());
|
||||
ImGui::Checkbox("TRANSLATOR_SHOW_ON_CHAT"_T.data(), &g.session.chat_translator.draw_result);
|
||||
ImGui::Checkbox("TRANSLATOR_PRINT_TO_CONSOLE"_T.data(), &g.session.chat_translator.print_result);
|
||||
|
||||
static const auto target_language = std::to_array<target_language_type>({{"sq", "Albanian"}, {"ar", "Arabic"}, {"az", "Azerbaijani"}, {"bn", "Bengali"}, {"bg", "Bulgarian"}, {"ca", "Catalan"}, {"zh", "Chinese"}, {"zt", "Chinese(traditional)"}, {"cs", "Czech"}, {"da", "Danish"}, {"nl", "Dutch"}, {"en", "English"}, {"eo", "Esperanto"}, {"et", "Estonian"}, {"fi", "Finnish"}, {"fr", "French"}, {"de", "German"}, {"el", "Greek"}, {"he", "Hebrew"}, {"hi", "Hindi"}, {"hu", "Hungarian"}, {"id", "Indonesian"}, {"ga", "Irish"}, {"it", "Italian"}, {"ja", "Japanese"}, {"ko", "Korean"}, {"lv", "Latvian"}, {"lt", "Lithuanian"}, {"ms", "Malay"}, {"nb", "Norwegian"}, {"fa", "Persian"}, {"pl", "Polish"}, {"pt", "Portuguese"}, {"ro", "Romanian"}, {"ru", "Russian"}, {"sr", "Serbian"}, {"sk", "Slovak"}, {"sl", "Slovenian"}, {"es", "Spanish"}, {"sv", "Swedish"}, {"tl", "Tagalog"}, {"th", "Thai"}, {"tr", "Turkish"}, {"uk", "Ukrainian"}, {"ur", "Urdu"}, {"vi", "Vietnamese"}});
|
||||
|
||||
components::input_text_with_hint("TRANSLATOR_ENDPOINT"_T.data(), "http://localhost:5000/translate", g.session.chat_translator.endpoint);
|
||||
|
||||
if (ImGui::BeginCombo("TRANSLATOR_TARGET_LANGUAGE"_T.data(), g.session.chat_translator.target_language.c_str()))
|
||||
{
|
||||
for (const auto& [type, name] : target_language)
|
||||
{
|
||||
components::selectable(name, false, [&type] {
|
||||
g.session.chat_translator.target_language = type;
|
||||
});
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user