Skip to content
Permalink
Browse files

Add server side translations capability (#9733)

* Add server side translations capability
  • Loading branch information
EvidenceBKidscode committed Apr 25, 2020
1 parent 914dbea commit cee3c5e73d7af2a876aa76275234ee76e7cb1bbc
@@ -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
@@ -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;
}

@@ -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;
@@ -339,12 +339,18 @@ class RemoteClient
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.
@@ -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);
@@ -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)
@@ -187,6 +187,9 @@ class ModApiEnvMod : public ModApiBase {
// 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);
@@ -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);
@@ -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);
}
}
}
@@ -334,7 +334,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
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 @@ class Server : public con::PeerHandler, public MapEventReceiver,
// 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;

@@ -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()
{
@@ -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
{
@@ -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,15 +752,15 @@ 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;
}
output.put(L'@');
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
}
@@ -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) {

0 comments on commit cee3c5e

Please sign in to comment.
You can’t perform that action at this time.