Add server side translations capability (#9733)
authorEvidenceB Kidscode <49488517+EvidenceBKidscode@users.noreply.github.com>
Sat, 25 Apr 2020 05:20:00 +0000 (07:20 +0200)
committerGitHub <noreply@github.com>
Sat, 25 Apr 2020 05:20:00 +0000 (07:20 +0200)
* Add server side translations capability

14 files changed:
doc/lua_api.txt
src/client/client.cpp
src/client/game.cpp
src/clientiface.h
src/network/serverpackethandler.cpp
src/script/lua_api/l_env.cpp
src/script/lua_api/l_env.h
src/script/lua_api/l_server.cpp
src/server.cpp
src/server.h
src/translation.cpp
src/translation.h
src/util/string.cpp
src/util/string.h

index 77f06682faa6cbf1b2f916cf6717d9d33cc073fd..3ca32649a553a16845e075ae52ed8e718cfc1390 100644 (file)
@@ -3176,8 +3176,22 @@ Strings that need to be translated can contain several escapes, preceded by `@`.
   `minetest.translate`, but is in translation files.
 * `@n` acts as a literal newline as well.
 
+Server side translations
+------------------------
+
+On some specific cases, server translation could be useful. For example, filter
+a list on labels and send results to client. A method is supplied to achieve
+that:
+
+`minetest.get_translated_string(lang_code, string)`: Translates `string` using
+translations for `lang_code` language. It gives the same result as if the string
+was translated by the client.
 
+The `lang_code` to use for a given player can be retrieved from
+the table returned by `minetest.get_player_information(name)`.
 
+IMPORTANT: This functionality should only be used for sorting, filtering or similar purposes.
+You do not need to use this to get translated strings to show up on the client.
 
 Perlin noise
 ============
@@ -4153,6 +4167,7 @@ Utilities
           connection_uptime = 200,   -- seconds since client connected
           protocol_version = 32,     -- protocol version used by client
           formspec_version = 2,      -- supported formspec version
+          lang_code = "fr"           -- Language code used for translation
           -- following information is available on debug build only!!!
           -- DO NOT USE IN MODS
           --ser_vers = 26,             -- serialization version used by client
index 8ee0869cd8204d06913aabcab722525244a32da0..941fc203dea603fefab8a62e701df4aa1d8cdb17 100644 (file)
@@ -736,7 +736,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
        if (!name.empty()) {
                TRACESTREAM(<< "Client: Loading translation: "
                                << "\"" << filename << "\"" << std::endl);
-               g_translations->loadTranslation(data);
+               g_client_translations->loadTranslation(data);
                return true;
        }
 
index 3429cc57bb78b1345eb2b08f02eab45e87930997..610522dc2ffba57143a07c07777bfabc17adea2b 100644 (file)
@@ -1055,7 +1055,7 @@ bool Game::startup(bool *kill,
        m_invert_mouse = g_settings->getBool("invert_mouse");
        m_first_loop_after_window_activation = true;
 
-       g_translations->clear();
+       g_client_translations->clear();
 
        if (!init(map_dir, address, port, gamespec))
                return false;
index bf95df4a8a126d079fbca266db2cf17dfd869159..83fa6fe99c3ae973fdf64c29b6c3a10c97cd41d6 100644 (file)
@@ -339,12 +339,18 @@ public:
        u8 getMinor() const { return m_version_minor; }
        u8 getPatch() const { return m_version_patch; }
        const std::string &getFull() const { return m_full_version; }
+       
+       void setLangCode(const std::string &code) { m_lang_code = code; }
+       const std::string &getLangCode() const { return m_lang_code; }
 private:
        // Version is stored in here after INIT before INIT2
        u8 m_pending_serialization_version = SER_FMT_VER_INVALID;
 
        /* current state of client */
        ClientState m_state = CS_Created;
+       
+       // Client sent language code
+       std::string m_lang_code;
 
        /*
                Blocks that have been sent to client.
index c685500cebea1e0c6842be1d4dbd99a7d4a33eb9..5136eb0ec58b66441d402616e6fab949a4b5bef4 100644 (file)
@@ -311,6 +311,9 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
 
        RemoteClient *client = getClient(peer_id, CS_InitDone);
 
+       // Keep client language for server translations
+       client->setLangCode(lang);
+
        // Send active objects
        {
                PlayerSAO *sao = getPlayerSAO(peer_id);
index 831464d3bf77c459c2188e669da1c1f1bd1691d1..3fb58b8c8c9c44a2b7ea8fd4f1e51b8050e65d69 100644 (file)
@@ -40,6 +40,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "remoteplayer.h"
 #include "server/luaentity_sao.h"
 #include "server/player_sao.h"
+#include "util/string.h"
+#include "translation.h"
 #ifndef SERVER
 #include "client/client.h"
 #endif
@@ -1302,6 +1304,19 @@ int ModApiEnvMod::l_forceload_free_block(lua_State *L)
        return 0;
 }
 
+// get_translated_string(lang_code, string)
+int ModApiEnvMod::l_get_translated_string(lua_State * L)
+{
+       GET_ENV_PTR;
+       std::string lang_code = luaL_checkstring(L, 1);
+       std::string string = luaL_checkstring(L, 2);
+       getServer(L)->loadTranslationLanguage(lang_code);
+       string = wide_to_utf8(translate_string(utf8_to_wide(string),
+                       &(*g_server_translations)[lang_code]));
+       lua_pushstring(L, string.c_str());
+       return 1;
+}
+
 void ModApiEnvMod::Initialize(lua_State *L, int top)
 {
        API_FCT(set_node);
@@ -1349,6 +1364,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top)
        API_FCT(transforming_liquid_add);
        API_FCT(forceload_block);
        API_FCT(forceload_free_block);
+       API_FCT(get_translated_string);
 }
 
 void ModApiEnvMod::InitializeClient(lua_State *L, int top)
index ac2f8b5883b86a9c59329b5281d1e8574ba65500..9050b43061a09f3bfb6be9ca30d10dac9c4e9ca6 100644 (file)
@@ -187,6 +187,9 @@ private:
        // stops forceloading a position
        static int l_forceload_free_block(lua_State *L);
 
+       // Get a string translated server side
+       static int l_get_translated_string(lua_State * L);
+
 public:
        static void Initialize(lua_State *L, int top);
        static void InitializeClient(lua_State *L, int top);
index 00e849cdf9b614bd4efc1c76dfed4be0a47862ac..7137484e8bc93f645320521aca0c8d9852da6d83 100644 (file)
@@ -163,6 +163,7 @@ int ModApiServer::l_get_player_information(lua_State *L)
        u16 prot_vers;
        u8 ser_vers,major,minor,patch;
        std::string vers_string;
+       std::string lang_code;
 
 #define ERET(code)                                                             \
        if (!(code)) {                                                             \
@@ -182,7 +183,7 @@ int ModApiServer::l_get_player_information(lua_State *L)
                &avg_jitter))
 
        ERET(getServer(L)->getClientInfo(player->getPeerId(), &state, &uptime, &ser_vers,
-               &prot_vers, &major, &minor, &patch, &vers_string))
+               &prot_vers, &major, &minor, &patch, &vers_string, &lang_code))
 
        lua_newtable(L);
        int table = lua_gettop(L);
@@ -237,6 +238,10 @@ int ModApiServer::l_get_player_information(lua_State *L)
        lua_pushnumber(L, player->formspec_version);
        lua_settable(L, table);
 
+       lua_pushstring(L, "lang_code");
+       lua_pushstring(L, lang_code.c_str());
+       lua_settable(L, table);
+
 #ifndef NDEBUG
        lua_pushstring(L,"serialization_version");
        lua_pushnumber(L, ser_vers);
index c32aa53062a75e42f6a2bb147f4559117bfc93f9..af6d3e40d5ddf795a4acf9e55e404132f671047d 100644 (file)
@@ -64,6 +64,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "chat_interface.h"
 #include "remoteplayer.h"
 #include "server/player_sao.h"
+#include "translation.h"
 
 class ClientNotFoundException : public BaseException
 {
@@ -1266,7 +1267,8 @@ bool Server::getClientInfo(
                u8*          major,
                u8*          minor,
                u8*          patch,
-               std::string* vers_string
+               std::string* vers_string,
+               std::string* lang_code
        )
 {
        *state = m_clients.getClientState(peer_id);
@@ -1286,6 +1288,7 @@ bool Server::getClientInfo(
        *minor = client->getMinor();
        *patch = client->getPatch();
        *vers_string = client->getFull();
+       *lang_code = client->getLangCode();
 
        m_clients.unlock();
 
@@ -3937,3 +3940,20 @@ void Server::broadcastModChannelMessage(const std::string &channel,
                m_script->on_modchannel_message(channel, sender, message);
        }
 }
+
+void Server::loadTranslationLanguage(const std::string &lang_code)
+{
+       if (g_server_translations->count(lang_code))
+               return; // Already loaded
+
+       std::string suffix = "." + lang_code + ".tr";
+       for (const auto &i : m_media) {
+               if (str_ends_with(i.first, suffix)) {
+                       std::ifstream t(i.second.path);
+                       std::string data((std::istreambuf_iterator<char>(t)),
+                       std::istreambuf_iterator<char>());
+
+                       (*g_server_translations)[lang_code].loadTranslation(data);
+               }
+       }
+}
index eecc2c0f02e938e7b9a742308824d2b845514508..b995aba287a91c80f0d8d8168795ad2dcee185e7 100644 (file)
@@ -334,7 +334,7 @@ public:
        bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval);
        bool getClientInfo(session_t peer_id, ClientState *state, u32 *uptime,
                        u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch,
-                       std::string* vers_string);
+                       std::string* vers_string, std::string* lang_code);
 
        void printToConsoleOnly(const std::string &text);
 
@@ -358,6 +358,9 @@ public:
        // Send block to specific player only
        bool SendBlock(session_t peer_id, const v3s16 &blockpos);
 
+       // Load translations for a language
+       void loadTranslationLanguage(const std::string &lang_code);
+
        // Bind address
        Address m_bind_addr;
 
index d17467ce75846112ef82038dda155c9b9ef2790c..8bbaee0a36acc9e9e0aef0746e2cae575a96192f 100644 (file)
@@ -20,9 +20,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "translation.h"
 #include "log.h"
 #include "util/string.h"
+#include <unordered_map>
 
-static Translations main_translations;
-Translations *g_translations = &main_translations;
+
+#ifndef SERVER
+// Client translations
+Translations client_translations;
+Translations *g_client_translations = &client_translations;
+#endif
+
+// Per language server translations
+std::unordered_map<std::string,Translations> server_translations;
+std::unordered_map<std::string,Translations> *g_server_translations = &server_translations;
 
 Translations::~Translations()
 {
index 18fc6c38fed5f874d2c68d967a1e60cc9f4f0b74..71423b15ee1accfc614e44949352859c6819022c 100644 (file)
@@ -23,7 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <string>
 
 class Translations;
-extern Translations *g_translations;
+extern std::unordered_map<std::string, Translations> *g_server_translations;
+#ifndef SERVER
+extern Translations *g_client_translations;
+#endif
 
 class Translations
 {
index 2ee3ec73569d8271bcb61023db732217b01f509b..6e1db798c2750d1bc593e8ff6321487fd082b03f 100644 (file)
@@ -693,10 +693,12 @@ void str_replace(std::string &str, char from, char to)
  * before filling it again.
  */
 
-void translate_all(const std::wstring &s, size_t &i, std::wstring &res);
+void translate_all(const std::wstring &s, size_t &i,
+               Translations *translations, std::wstring &res);
 
-void translate_string(const std::wstring &s, const std::wstring &textdomain,
-               size_t &i, std::wstring &res) {
+void translate_string(const std::wstring &s, Translations *translations,
+               const std::wstring &textdomain, size_t &i, std::wstring &res)
+{
        std::wostringstream output;
        std::vector<std::wstring> args;
        int arg_number = 1;
@@ -750,7 +752,7 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
                        if (arg_number >= 10) {
                                errorstream << "Ignoring too many arguments to translation" << std::endl;
                                std::wstring arg;
-                               translate_all(s, i, arg);
+                               translate_all(s, i, translations, arg);
                                args.push_back(arg);
                                continue;
                        }
@@ -758,7 +760,7 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
                        output << arg_number;
                        ++arg_number;
                        std::wstring arg;
-                       translate_all(s, i, arg);
+                       translate_all(s, i, translations, arg);
                        args.push_back(arg);
                } else {
                        // This is an escape sequence *inside* the template string to translate itself.
@@ -767,8 +769,13 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
                }
        }
 
+       std::wstring toutput;
        // Translate the template.
-       std::wstring toutput = g_translations->getTranslation(textdomain, output.str());
+       if (translations != nullptr)
+               toutput = translations->getTranslation(
+                               textdomain, output.str());
+       else
+               toutput = output.str();
 
        // Put back the arguments in the translated template.
        std::wostringstream result;
@@ -802,7 +809,9 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
        res = result.str();
 }
 
-void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
+void translate_all(const std::wstring &s, size_t &i,
+               Translations *translations, std::wstring &res)
+{
        std::wostringstream output;
        while (i < s.length()) {
                // Not an escape sequence: just add the character.
@@ -851,7 +860,7 @@ void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
                        if (parts.size() > 1)
                                textdomain = parts[1];
                        std::wstring translated;
-                       translate_string(s, textdomain, i, translated);
+                       translate_string(s, translations, textdomain, i, translated);
                        output << translated;
                } else {
                        // Another escape sequence, such as colors. Preserve it.
@@ -862,9 +871,21 @@ void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
        res = output.str();
 }
 
-std::wstring translate_string(const std::wstring &s) {
+// Translate string server side
+std::wstring translate_string(const std::wstring &s, Translations *translations)
+{
        size_t i = 0;
        std::wstring res;
-       translate_all(s, i, res);
+       translate_all(s, i, translations, res);
        return res;
 }
+
+// Translate string client side
+std::wstring translate_string(const std::wstring &s)
+{
+#ifdef SERVER
+       return translate_string(s, nullptr);
+#else
+       return translate_string(s, g_client_translations);
+#endif
+}
index 0d2a6bdb2cbcf6e70008da71ad0380bc5c53a2fd..185fb55e2401e994dbb8c05c37ebdf047dd4bc79 100644 (file)
@@ -31,6 +31,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <cctype>
 #include <unordered_map>
 
+class Translations;
+
 #define STRINGIFY(x) #x
 #define TOSTRING(x) STRINGIFY(x)
 
@@ -650,6 +652,8 @@ std::vector<std::basic_string<T> > split(const std::basic_string<T> &s, T delim)
        return tokens;
 }
 
+std::wstring translate_string(const std::wstring &s, Translations *translations);
+
 std::wstring translate_string(const std::wstring &s);
 
 inline std::wstring unescape_translate(const std::wstring &s) {