summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoler_j <soler_j@etna-alternance.net>2025-05-04 01:26:36 +0200
committersoler_j <soler_j@etna-alternance.net>2025-05-04 01:26:36 +0200
commite82d081f0fc630fe9244ff9d461f91dd1d4dc63b (patch)
tree09c49fec114326a982648ccb4ce961f092a44b19
parent23cba7cd6b32b7c5db98aa5a3f9206d4bce07902 (diff)
parent502ae25637f103a063c145509c051d1ce6061e4f (diff)
Merge branch '1-more-actions-to-handle' of github.com:ketsuna-org/bot-creator-api into 1-more-actions-to-handle
-rw-r--r--.gitignore1
-rw-r--r--.vscode/settings.json2
-rw-r--r--Dockerfile2
-rw-r--r--bot/include/http_webhook_server.hpp29
-rw-r--r--bot/include/utils.hpp28
-rw-r--r--bot/src/actions/delete.cpp144
-rw-r--r--bot/src/handle_actions.cpp4
-rw-r--r--bot/src/http_webhook_server.cpp140
-rw-r--r--bot/src/main.cpp4
-rw-r--r--bot/src/utils.cpp39
10 files changed, 280 insertions, 113 deletions
diff --git a/.gitignore b/.gitignore
index e123953..0deb2c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@ devenv.local.nix
.pre-commit-config.yaml
build
discord-bot
+.cache/*
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 42e44f8..798eedd 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,5 @@
{
- "cmake.sourceDirectory": "/home/exa/perso/bot-creator-api/bot",
+ "cmake.sourceDirectory": "/Users/jeremy/Documents/bot-creator-api/bot",
"files.associations": {
"map": "cpp",
"__bit_reference": "cpp",
diff --git a/Dockerfile b/Dockerfile
index 0d6f66a..3ad17b9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -41,7 +41,7 @@ RUN mkdir build && cd build && \
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/usr/local && \
- make -j$(nproc)
+ make -j2
# Étape finale d'exécution
FROM ubuntu:24.04
diff --git a/bot/include/http_webhook_server.hpp b/bot/include/http_webhook_server.hpp
index a42829f..8ba6841 100644
--- a/bot/include/http_webhook_server.hpp
+++ b/bot/include/http_webhook_server.hpp
@@ -1,12 +1,18 @@
#pragma once
-#include <sys/epoll.h>
-#include <netinet/in.h>
#include <functional>
#include <string>
#include <unordered_map>
#include <system_error>
#include <sstream>
+#include <string>
+#include <netinet/in.h>
+
+#ifdef __linux__
+ #include <sys/epoll.h>
+#elif defined(__APPLE__)
+ #include <sys/event.h>
+#endif
class HttpWebhookServer {
public:
@@ -27,7 +33,8 @@ public:
HttpWebhookServer(uint16_t port, Handler handler);
~HttpWebhookServer();
-
+ void setupEpoll();
+ void handleEvents();
void start();
void stop();
@@ -39,17 +46,29 @@ private:
};
void setupSocket();
- void setupEpoll();
- void handleEvent(struct epoll_event* event);
+ void setupEventLoop();
+ void handleEvent(int fd, uint32_t events);
void handleClient(int fd);
void closeClient(int fd);
+ void registerFdForRead(int fd);
+ void modifyFdToWrite(int fd);
+ void sendToClient(int fd);
void parseHttpRequest(ClientContext& ctx, HttpRequest& req);
void buildHttpResponse(const HttpResponse& res, std::string& output);
int server_fd = -1;
+ int event_fd = -1;
int epoll_fd = -1;
bool running = false;
uint16_t port;
Handler request_handler;
std::unordered_map<int, ClientContext> clients;
+
+#ifdef __linux__
+ static constexpr int MAX_EVENTS = 64;
+ struct epoll_event events[MAX_EVENTS];
+#elif defined(__APPLE__)
+ static constexpr int MAX_EVENTS = 64;
+ struct kevent events[MAX_EVENTS];
+#endif
};
diff --git a/bot/include/utils.hpp b/bot/include/utils.hpp
index 820fd01..7008dec 100644
--- a/bot/include/utils.hpp
+++ b/bot/include/utils.hpp
@@ -1,7 +1,5 @@
// utils.hpp
#pragma once
-#ifndef UTILS_HPP
-#define UTILS_HPP
#include <dpp/dpp.h>
#include <dpp/nlohmann/json.hpp>
@@ -11,6 +9,12 @@
#include <sstream>
#include <algorithm>
using namespace dpp;
+
+enum class Lang {
+ en,
+ fr,
+};
+
namespace app
{
@@ -81,7 +85,25 @@ namespace app
*/
std::string string_from_json(const nlohmann::json &j);
+ /**
+ * @brief Gets the available locale for a given locale string
+ *
+ * @param locale The locale string to check
+ * @return std::string The available locale or "en" if not found
+ */
+ Lang get_available_locale(std::string locale);
+
+ /**
+ * @brienf translate a string from a locale, optionnal parameters and a default value
+ * @param str The string to translate to found in an Array of translations
+ * @param locale The locale to use for translation
+ * @param array_translations The array of translations
+ * @param args The optional parameters to replace in the string
+ * @return std::string The translated string
+ */
+ std::string translate(const std::string &str, const std::string &locale, const std::unordered_map<Lang, std::unordered_map<std::string, std::string>> &array_translations, const std::unordered_map<std::string, std::string> &args = {});
+
+
} // namespace dpp
-#endif // UTILS_HPP
// utils.hpp
diff --git a/bot/src/actions/delete.cpp b/bot/src/actions/delete.cpp
index 58835cf..da74bbf 100644
--- a/bot/src/actions/delete.cpp
+++ b/bot/src/actions/delete.cpp
@@ -1,107 +1,127 @@
-#include <dpp/dpp.h>
+#include "../../include/utils.hpp"
-dpp::task<bool> delete_action(const dpp::slashcommand_t &event, const nlohmann::json &action, const std::unordered_map<std::string, std::string> &key_values, dpp::user &user_ptr, dpp::cluster *cluster)
-{
- dpp::guild guild_ptr = event.command.get_guild();
- // let's retrieve the member.
- dpp::guild_member member_ptr = guild_ptr.members.find(user_ptr.id)->second;
- dpp::guild_member bot_member_ptr = guild_ptr.members.find(cluster->me.id)->second;
- std::unordered_map<std::string, std::string> error_messages = {
+
+const std::unordered_map<Lang, std::unordered_map<std::string, std::string>> error_messages_map = {
+ {Lang::en, {
+ {"error_no_messages", "No message to delete."},
{"error", "You need to wait a bit before deleting messages."},
{"error_amount", "The amount of messages to delete must be between 1 and 100."},
- {"error_perm_channel", "You do not have permission to delete messages in this channel."}};
+ {"error_perm_channel", "You do not have permission to delete messages in this channel."}
+ }},
+ {Lang::fr, {
+ {"error_no_messages", "Aucun message à supprimer."},
+ {"error", "Vous devez attendre un peu avant de supprimer des messages."},
+ {"error_amount", "Le nombre de messages à supprimer doit être compris entre 1 et 100."},
+ {"error_perm_channel", "Vous n'avez pas la permission de supprimer des messages dans ce canal."}
+ }}
+};
- if (action.contains("error_amount"))
- {
- error_messages["error_amount"] = action["error_amount"].get<std::string>();
- }
+dpp::task<bool> delete_action(const dpp::slashcommand_t &event, const nlohmann::json &action,
+ const std::unordered_map<std::string, std::string> &key_values,
+ dpp::user &user_ptr, dpp::cluster *cluster)
+{
+ // setup locale for gettext
+ std::string locale = event.command.locale;
+ const dpp::channel *channel_ptr = &event.command.get_channel();
+ const auto &guild_ptr = event.command.get_guild();
- if (action.contains("error_perm_channel"))
- {
- error_messages["error_perm_channel"] = action["error_perm_channel"].get<std::string>();
- }
+ const auto member_it = guild_ptr.members.find(user_ptr.id);
+ const auto *member_ptr = (member_it != guild_ptr.members.end()) ? &member_it->second : nullptr;
+
+ const auto bot_member_it = guild_ptr.members.find(cluster->me.id);
+ const auto *bot_member_ptr = (bot_member_it != guild_ptr.members.end()) ? &bot_member_it->second : nullptr;
- if (action.contains("error"))
+ const std::unordered_map<std::string, std::string> error_messages = [&action,&locale]()
{
- error_messages["error"] = action["error"].get<std::string>();
- }
- // let's retrieve the current channel
- const dpp::channel *channel_ptr = &event.command.get_channel();
- auto user_as_perms = channel_ptr->get_user_permissions(member_ptr).has(dpp::p_manage_messages);
- auto bot_as_perms = channel_ptr->get_user_permissions(bot_member_ptr).has(dpp::p_manage_messages);
- if (!user_as_perms)
+ std::unordered_map<std::string, std::string> defaults = {
+ {"error_no_messages", app::translate("error_no_messages", locale, error_messages_map)},
+ {"error", app::translate("error", locale, error_messages_map)},
+ {"error_amount", app::translate("error_amount", locale, error_messages_map)},
+ {"error_perm_channel", app::translate("error_perm_channel", locale, error_messages_map)},
+ };
+ if (action.contains("error_amount"))
+ defaults["error_amount"] = action["error_amount"];
+ if (action.contains("error_perm_channel"))
+ defaults["error_perm_channel"] = action["error_perm_channel"];
+ if (action.contains("error"))
+ defaults["error"] = action["error"];
+ return defaults;
+ }();
+
+ if (!member_ptr || !bot_member_ptr)
{
- event.edit_response(error_messages["error_perm_channel"]);
+ event.edit_response(error_messages.at("error_perm_channel"));
co_return false;
}
- if (!bot_as_perms)
+
+ const bool has_permissions = channel_ptr->get_user_permissions(*member_ptr).has(dpp::p_manage_messages) && channel_ptr->get_user_permissions(*bot_member_ptr).has(dpp::p_manage_messages);
+ if (!has_permissions)
{
- event.edit_response(error_messages["error_perm_channel"]);
+ event.edit_response(error_messages.at("error_perm_channel"));
co_return false;
}
+
int amount = 0;
if (action.contains("depend_on"))
{
- std::string depend_on = action["depend_on"];
- auto it = key_values.find(depend_on);
- if (it != key_values.end())
+ const std::string &depend_on = action["depend_on"];
+ if (const auto it = key_values.find(depend_on); it != key_values.end())
{
- std::string depend_on_value = it->second;
-
- // let's convert the depend_on_value to an int
- amount = std::stoi(depend_on_value);
- if (amount < 0 || amount > 100)
+ try
+ {
+ amount = std::stoi(it->second);
+ if (amount < 1 || amount > 100)
+ {
+ event.edit_response(error_messages.at("error_amount"));
+ co_return false;
+ }
+ }
+ catch (const std::exception &e)
{
- event.edit_response(error_messages["error_amount"]);
+ event.edit_response(error_messages.at("error"));
co_return false;
}
}
}
+
if (amount > 0)
{
- dpp::confirmation_callback_t callback = co_await cluster->co_messages_get(channel_ptr->id, 0, 0, 0, amount);
+ const time_t two_weeks_ago = dpp::utility::time_f() - 1209600;
+
+ auto callback = co_await cluster->co_messages_get(channel_ptr->id, 0, 0, 0, amount);
if (callback.is_error())
{
- printf("Error: %s\n", callback.get_error().message.c_str());
- event.edit_response(error_messages["error"]);
+ event.edit_response(error_messages.at("error"));
co_return false;
}
- auto messages = callback.get<dpp::message_map>();
+
+ const auto &messages = callback.get<dpp::message_map>();
if (messages.empty())
{
- event.edit_response("No messages to delete.");
+ event.edit_response(error_messages.at("error_no_messages"));
co_return false;
}
+
std::vector<dpp::snowflake> msg_ids;
+ msg_ids.reserve(messages.size());
- for (const auto &msg : messages)
+ for (const auto &[id, msg] : messages)
{
- // let's check if the message is older than 2 weeks
- if (msg.second.get_creation_time() < dpp::utility::time_f() - 1209600)
+ if (msg.get_creation_time() >= two_weeks_ago)
{
- printf("Message is older than 2 weeks\n");
- continue;
- }
- else
- {
- msg_ids.push_back(msg.second.id);
+ msg_ids.emplace_back(id);
}
}
if (!msg_ids.empty())
{
- dpp::confirmation_callback_t result;
- if (msg_ids.size() == 1)
- {
- result = co_await cluster->co_message_delete(msg_ids[0], channel_ptr->id);
- }
- else
- {
- result = co_await cluster->co_message_delete_bulk(msg_ids, channel_ptr->id);
- }
- if (result.is_error())
+ const auto delete_result = msg_ids.size() == 1
+ ? co_await cluster->co_message_delete(msg_ids.front(), channel_ptr->id)
+ : co_await cluster->co_message_delete_bulk(msg_ids, channel_ptr->id);
+
+ if (delete_result.is_error())
{
- event.edit_response(error_messages["error"]);
+ event.edit_response(error_messages.at("error"));
co_return false;
}
}
diff --git a/bot/src/handle_actions.cpp b/bot/src/handle_actions.cpp
index e469ec7..0b3f2ff 100644
--- a/bot/src/handle_actions.cpp
+++ b/bot/src/handle_actions.cpp
@@ -1,8 +1,6 @@
-#include <dpp/dpp.h>
#include "../include/actions/delete.hpp"
dpp::task<bool> handle_actions(const dpp::slashcommand_t &event, const nlohmann::json &actions, const std::unordered_map<std::string, std::string> &key_values)
{
-
dpp::cluster *cluster = event.owner;
dpp::user user_ptr = event.command.get_issuing_user();
dpp::async thinking = event.co_thinking(false);
@@ -36,4 +34,6 @@ dpp::task<bool> handle_actions(const dpp::slashcommand_t &event, const nlohmann:
}
}
}
+ co_await thinking;
+ co_return true;
}
diff --git a/bot/src/http_webhook_server.cpp b/bot/src/http_webhook_server.cpp
index a6daa63..fd01a1f 100644
--- a/bot/src/http_webhook_server.cpp
+++ b/bot/src/http_webhook_server.cpp
@@ -3,6 +3,15 @@
#include <unistd.h>
#include <cstring>
#include <algorithm>
+#include <sstream>
+#include <stdexcept>
+#include <system_error>
+
+#ifdef __linux__
+#include <sys/epoll.h>
+#elif defined(__APPLE__)
+#include <sys/event.h>
+#endif
HttpWebhookServer::HttpWebhookServer(uint16_t port, Handler handler)
: port(port), request_handler(handler) {
@@ -17,12 +26,16 @@ HttpWebhookServer::~HttpWebhookServer() {
}
void HttpWebhookServer::setupSocket() {
- server_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
- if (server_fd == -1) throw std::system_error(errno, std::generic_category());
+ server_fd = ::socket(AF_INET, SOCK_STREAM, 0);
+ if (server_fd == -1)
+ throw std::system_error(errno, std::generic_category());
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ if (::fcntl(server_fd, F_SETFL, O_NONBLOCK) == -1)
+ throw std::system_error(errno, std::generic_category());
+
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
@@ -36,6 +49,7 @@ void HttpWebhookServer::setupSocket() {
}
void HttpWebhookServer::setupEpoll() {
+#ifdef __linux__
epoll_fd = ::epoll_create1(0);
if (epoll_fd == -1)
throw std::system_error(errno, std::generic_category());
@@ -46,33 +60,96 @@ void HttpWebhookServer::setupEpoll() {
if (::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1)
throw std::system_error(errno, std::generic_category());
+
+#elif defined(__APPLE__)
+ epoll_fd = ::kqueue();
+ if (epoll_fd == -1)
+ throw std::system_error(errno, std::generic_category());
+
+ struct kevent ev_set;
+ EV_SET(&ev_set, server_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr);
+ if (kevent(epoll_fd, &ev_set, 1, nullptr, 0, nullptr) == -1)
+ throw std::system_error(errno, std::generic_category());
+#endif
+}
+
+void HttpWebhookServer::registerFdForRead(int fd) {
+#ifdef __linux__
+ epoll_event event{};
+ event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
+ event.data.fd = fd;
+ ::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
+#elif defined(__APPLE__)
+ struct kevent ev_set;
+ EV_SET(&ev_set, fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr);
+ kevent(epoll_fd, &ev_set, 1, nullptr, 0, nullptr);
+#endif
+}
+
+void HttpWebhookServer::modifyFdToWrite(int fd) {
+#ifdef __linux__
+ epoll_event event{};
+ event.events = EPOLLOUT | EPOLLET | EPOLLRDHUP;
+ event.data.fd = fd;
+ ::epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event);
+#elif defined(__APPLE__)
+ struct kevent ev_set[2];
+ EV_SET(&ev_set[0], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr);
+ EV_SET(&ev_set[1], fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, nullptr);
+ kevent(epoll_fd, ev_set, 2, nullptr, 0, nullptr);
+#endif
}
void HttpWebhookServer::start() {
running = true;
+
+#ifdef __linux__
epoll_event events[64];
+#elif defined(__APPLE__)
+ struct kevent events[64];
+#endif
while (running) {
+#ifdef __linux__
int nfds = ::epoll_wait(epoll_fd, events, 64, -1);
+#elif defined(__APPLE__)
+ int nfds = ::kevent(epoll_fd, nullptr, 0, events, 64, nullptr);
+#endif
if (nfds == -1 && errno != EINTR)
throw std::system_error(errno, std::generic_category());
for (int i = 0; i < nfds; ++i) {
- if (events[i].data.fd == server_fd) {
+#ifdef __linux__
+ int fd = events[i].data.fd;
+ bool isWritable = events[i].events & EPOLLOUT;
+#elif defined(__APPLE__)
+ int fd = static_cast<int>(events[i].ident);
+ bool isWritable = events[i].filter == EVFILT_WRITE;
+#endif
+
+ if (fd == server_fd) {
while (true) {
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
+
+#ifdef __linux__
int client_fd = ::accept4(server_fd, (sockaddr*)&client_addr, &client_len, SOCK_NONBLOCK);
+#elif defined(__APPLE__)
+ int client_fd = ::accept(server_fd, (sockaddr*)&client_addr, &client_len);
+ if (client_fd != -1) {
+ int flags = fcntl(client_fd, F_GETFL, 0);
+ fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
+ }
+#endif
if (client_fd == -1) break;
- epoll_event event{};
- event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
- event.data.fd = client_fd;
- ::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
clients[client_fd] = ClientContext{};
+ registerFdForRead(client_fd);
}
+ } else if (isWritable) {
+ sendToClient(fd);
} else {
- handleClient(events[i].data.fd);
+ handleClient(fd);
}
}
}
@@ -83,11 +160,35 @@ void HttpWebhookServer::stop() {
}
void HttpWebhookServer::closeClient(int fd) {
+#ifdef __linux__
::epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
+#elif defined(__APPLE__)
+ struct kevent ev_del[2];
+ EV_SET(&ev_del[0], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr);
+ EV_SET(&ev_del[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr);
+ kevent(epoll_fd, ev_del, 2, nullptr, 0, nullptr);
+#endif
::close(fd);
clients.erase(fd);
}
+void HttpWebhookServer::sendToClient(int fd) {
+ if (!clients.count(fd)) return;
+
+ auto& ctx = clients[fd];
+ if (!ctx.output_buffer.empty()) {
+ ssize_t sent = ::send(fd,
+ ctx.output_buffer.data() + ctx.bytes_written,
+ ctx.output_buffer.size() - ctx.bytes_written,
+ MSG_DONTWAIT);
+
+ if (sent > 0) ctx.bytes_written += sent;
+ if (ctx.bytes_written == ctx.output_buffer.size()) {
+ closeClient(fd);
+ }
+ }
+}
+
void HttpWebhookServer::handleClient(int fd) {
char buffer[4096];
ssize_t count = ::recv(fd, buffer, sizeof(buffer), MSG_DONTWAIT);
@@ -103,31 +204,12 @@ void HttpWebhookServer::handleClient(int fd) {
HttpResponse res = request_handler(req);
buildHttpResponse(res, ctx.output_buffer);
- epoll_event event{};
- event.events = EPOLLOUT | EPOLLET | EPOLLRDHUP;
- event.data.fd = fd;
- ::epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event);
+ modifyFdToWrite(fd);
}
- }
- else if (count == 0 || (count == -1 && errno != EAGAIN)) {
+ } else if (count == 0 || (count == -1 && errno != EAGAIN)) {
closeClient(fd);
return;
}
-
- if (!clients.count(fd)) return; // Client déjà fermé plus haut
-
- auto& ctx = clients[fd];
- if (!ctx.output_buffer.empty()) {
- ssize_t sent = ::send(fd,
- ctx.output_buffer.data() + ctx.bytes_written,
- ctx.output_buffer.size() - ctx.bytes_written,
- MSG_DONTWAIT);
-
- if (sent > 0) ctx.bytes_written += sent;
- if (ctx.bytes_written == ctx.output_buffer.size()) {
- closeClient(fd);
- }
- }
}
void HttpWebhookServer::parseHttpRequest(ClientContext& ctx, HttpRequest& req) {
diff --git a/bot/src/main.cpp b/bot/src/main.cpp
index 42e84bd..1edac34 100644
--- a/bot/src/main.cpp
+++ b/bot/src/main.cpp
@@ -1,5 +1,3 @@
-#include <dpp/dpp.h>
-#include <string>
#include "../include/utils.hpp"
#include "../include/http_webhook_server.hpp"
#include "../include/handle_actions.hpp"
@@ -20,7 +18,7 @@ dpp::activity_type activity_type_from_string(const std::string& type) {
} else if (type == "competing") {
return dpp::activity_type::at_competing;
} else {
- throw std::invalid_argument("Invalid activity type");
+ return dpp::activity_type::at_game; // Default to "playing" if the type is unknown
}
}
diff --git a/bot/src/utils.cpp b/bot/src/utils.cpp
index 349f584..a2ce928 100644
--- a/bot/src/utils.cpp
+++ b/bot/src/utils.cpp
@@ -1,10 +1,4 @@
-#include <dpp/dpp.h>
-#include <dpp/nlohmann/json.hpp>
-#include <map>
-#include <string>
-#include <regex>
-#include <sstream>
-#include <algorithm>
+#include "../include/utils.hpp"
using namespace dpp;
namespace app
@@ -212,4 +206,35 @@ namespace app
}
return str;
}
+
+ Lang get_available_locale(std::string locale)
+ {
+ std::transform(locale.begin(), locale.end(), locale.begin(), ::tolower);
+ if (locale == "fr" || locale == "fr-fr")
+ return Lang::fr;
+ else if (locale == "en" || locale == "en-us")
+ return Lang::en;
+ else
+ return Lang::en; // Default to English if no match is found
+ }
+
+ std::string translate(const std::string &str, const std::string &locale, const std::unordered_map<Lang, std::unordered_map<std::string, std::string>> &array_translations, const std::unordered_map<std::string, std::string> &args)
+ {
+ Lang lang = get_available_locale(locale);
+ auto it = array_translations.find(lang);
+ if (it != array_translations.end())
+ {
+ auto it2 = it->second.find(str);
+ if (it2 != it->second.end())
+ {
+ std::string translation = it2->second;
+ for (const auto &arg : args)
+ {
+ translation = update_string(translation, args);
+ }
+ return translation;
+ }
+ }
+ return str; // Return the original string if no translation is found
+ }
}