Skip to content

Commit

Permalink
Multilingual motd
Browse files Browse the repository at this point in the history
  • Loading branch information
CupnPlateGames committed Mar 23, 2024
1 parent 175db25 commit a6f49cc
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 11 deletions.
16 changes: 16 additions & 0 deletions doc/hosting/AutohostConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ Each player slot can be customized, starting from 0. The first slot will be defi

The `motd` is displayed in the chat box when a player joins the game. It is optional and will be truncated if it exceeds 256 characters.

It can be a single text entry or an object defining multiple messages in various languages. The default language will be `en` (English) and it must be provided. The default language can be changed by providing a `default` entry.

```
"motd": "Hello world"
```
or
```
"motd": {
"default": "de",
"de": "Hallo Welt",
"en": "Hello world",
"fr": "Bonjour le monde",
"ru": "привет мир"
}
```

## Sample file

```
Expand Down
3 changes: 3 additions & 0 deletions lib/framework/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
// Make xgettext recognize the context
#define NP_(Context, String) gettext_noop(String)

#define MAX_LOCALE_CODE_LENGTH (5)
#define DEFAULT_LOCALE "en"

WZ_DECL_PURE const char *getLanguage();
WZ_DECL_PURE const char *getLanguageName();
WZ_DECL_NONNULL(1) bool setLanguage(const char *name);
Expand Down
182 changes: 182 additions & 0 deletions lib/framework/wzi18nstring.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* This file is part of Warzone 2100.
* Copyright (C) 2024 Warzone 2100 Project
*
* Warzone 2100 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Warzone 2100 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Warzone 2100; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "wzi18nstring.h"

#include "i18n.h"
#include "wzglobal.h"

WzI18nString::WzI18nString()
{
_defaultLocale = DEFAULT_LOCALE;
_strings[_defaultLocale] = WzString();
}


WzI18nString::WzI18nString(WzString string)
{
_defaultLocale = DEFAULT_LOCALE;
_strings[_defaultLocale] = string;
}

WzI18nString::WzI18nString(const WzI18nString& other)
{
_defaultLocale = other._defaultLocale;
for (const auto &locale : other.listLocales())
{
_strings[std::string(locale)] = WzString(other.getLocaleString(locale));
}
}

WzI18nString::WzI18nString(WzConfig &ini, const WzString &key, const int maxChars)
{
if (!ini.contains(key))
{
reset();
return;
}
json_variant value = ini.value(key);
if (value.jsonValue().is_string())
{
WzString string = value.toWzString();
if (string.length() > maxChars)
{
string.truncate(maxChars);
debug(LOG_WARNING, "%s: value for %s was truncated to fit into %d characters.", ini.fileName().toUtf8().c_str(), key.toUtf8().c_str(), maxChars);
}
_defaultLocale = DEFAULT_LOCALE;
_strings[_defaultLocale] = string;
return;
}
if (ini.beginGroup(key))
{
for (auto locale : getLocales())
{
std::string localeCode = std::string(locale.code);
WzString string = ini.string(WzString::fromUtf8(localeCode));
if (string.length() > maxChars)
{
string.truncate(maxChars);
debug(LOG_WARNING, "%s: value for %s (%s) was truncated to fit into %d characters.", ini.fileName().toUtf8().c_str(), key.toUtf8().c_str(), localeCode.c_str(), maxChars);
}
if (!string.isEmpty())
{
_strings[localeCode] = string;
}
}
_defaultLocale = DEFAULT_LOCALE;
WzString altDefault = ini.string(WzString::fromUtf8("default"));
if (!altDefault.isEmpty())
{
_defaultLocale = altDefault.toUtf8();
}
if (!this->contains(_defaultLocale))
{
debug(LOG_ERROR, "%s: no entry for %s in default language %s.", ini.fileName().toUtf8().c_str(), key.toUtf8().c_str(), _defaultLocale.c_str());
reset();
}
ini.endGroup();
}
}

bool WzI18nString::contains(std::string localeCode) const
{
return _strings.find(localeCode) != _strings.end();
}

bool WzI18nString::isEmpty() const
{
return this->getDefaultString().isEmpty();
}

const std::string& WzI18nString::getDefaultLocaleCode() const
{
return _defaultLocale;
}

const WzString& WzI18nString::getDefaultString() const
{
return _strings.find(_defaultLocale)->second;
}

const WzString& WzI18nString::getLocaleString() const
{
return this->getLocaleString(getLanguage());
}

const WzString& WzI18nString::getLocaleString(const char* localeCode) const
{
return this->getLocaleString(std::string(localeCode));
}

const WzString& WzI18nString::getLocaleString(std::string localeCode) const
{
auto it = _strings.find(localeCode);
if (it != _strings.end())
{
return it->second;
}
return _strings.find(_defaultLocale)->second;
}

std::vector<std::string> WzI18nString::listLocales() const
{
std::vector<std::string> locales;
for (auto it = _strings.begin(); it != _strings.end(); ++it)
{
locales.push_back(it->first);
}
return locales;
}

void WzI18nString::reset()
{
_defaultLocale = DEFAULT_LOCALE;
_strings.clear();
_strings[_defaultLocale] = WzString();
}

void WzI18nString::reset(std::string &defaultLocale)
{
_strings.clear();
_defaultLocale = defaultLocale;
_strings[_defaultLocale] = WzString();
}

void WzI18nString::reset(std::string &defaultLocale, WzString &text)
{
_strings.clear();
_defaultLocale = defaultLocale;
_strings[_defaultLocale] = text;
}

void WzI18nString::setLocaleString(const char* localeCode, WzString text)
{
_strings[localeCode] = text;
}

WzI18nString& WzI18nString::operator=(const WzI18nString& other)
{
_defaultLocale = other._defaultLocale;
for (const auto &locale : other.listLocales())
{
_strings[std::string(locale)] = WzString(other.getLocaleString(locale));
}
return *this;
}
61 changes: 61 additions & 0 deletions lib/framework/wzi18nstring.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* This file is part of Warzone 2100.
* Copyright (C) 2024 Warzone 2100 Project
*
* Warzone 2100 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Warzone 2100 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Warzone 2100; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#ifndef _LIB_FRAMEWORK_WZI18NSTRING_H
#define _LIB_FRAMEWORK_WZI18NSTRING_H

#include <string>
#include <map>
#include <vector>
#include "lib/framework/wzstring.h"
#include "lib/framework/wzconfig.h"

/**
* User provided multilingual string.
*/
class WzI18nString {
public:
WzI18nString();
WzI18nString(WzString string);
WzI18nString(std::string defaultLocale, std::map<std::string, WzString> strings);
WzI18nString(WzConfig &ini, const WzString &key, const int maxChars);
WzI18nString(const WzI18nString& other);

bool contains(std::string localeCode) const;
bool isEmpty() const;
const std::string& getDefaultLocaleCode() const;
const WzString& getDefaultString() const;
const WzString& getLocaleString() const;
/** Get the string in the given locale or the default one if not set. */
const WzString& getLocaleString(const char* localeCode) const;
const WzString& getLocaleString(std::string localeCode) const;
std::vector<std::string> listLocales() const;
public:
void reset();
void reset(std::string &defaultlocale);
void reset(std::string &defaultLocale, WzString &text);
void setLocaleString(const char* localeCode, WzString text);
public:
WzI18nString& operator=(const WzI18nString& other);
private:
std::string _defaultLocale;
std::map<std::string, WzString> _strings;
};

#endif // _LIB_FRAMEWORK_WZI18NSTRING_H
1 change: 1 addition & 0 deletions lib/netplay/netplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5804,6 +5804,7 @@ const char *messageTypeToString(unsigned messageType_)
case NET_PING: return "NET_PING";
case NET_PLAYER_STATS: return "NET_PLAYER_STATS";
case NET_TEXTMSG: return "NET_TEXTMSG";
case NET_I18NTEXTMSG: return "NET_I18NTEXTMSG";
case NET_PLAYERRESPONDING: return "NET_PLAYERRESPONDING";
case NET_OPTIONS: return "NET_OPTIONS";
case NET_KICK: return "NET_KICK";
Expand Down
1 change: 1 addition & 0 deletions lib/netplay/netplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ enum MESSAGE_TYPES
NET_PING, ///< ping players.
NET_PLAYER_STATS, ///< player stats
NET_TEXTMSG, ///< A simple text message between machines.
NET_I18NTEXTMSG, ///< A multilingual text message between machines.
NET_PLAYERRESPONDING, ///< computer that sent this is now playing warzone!
NET_OPTIONS, ///< welcome a player to a game.
NET_KICK, ///< kick a player .
Expand Down
31 changes: 20 additions & 11 deletions src/multiint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
#include "lib/framework/wzapp.h"
#include "lib/framework/wzconfig.h"
#include "lib/framework/wzpaths.h"
#include "lib/framework/wzi18nstring.h"

#include <locale.h>
#include <time.h>

#include "lib/framework/frameresource.h"
Expand Down Expand Up @@ -284,7 +286,7 @@ static struct
} locked;
static bool spectatorHost = false;
static uint16_t defaultOpenSpectatorSlots = 0;
static WzString motd = WzString();
static WzI18nString motd = WzI18nString();

struct AIDATA
{
Expand Down Expand Up @@ -6041,16 +6043,11 @@ static void loadMapMessageOfTheDay(WzConfig& ini)
{
if (ini.contains("motd"))
{
motd = ini.value("motd").toWzString();
if (motd.length() > MAX_CONSOLE_STRING_LENGTH)
{
motd.truncate(MAX_CONSOLE_STRING_LENGTH);
debug(LOG_WARNING, "MOTD was truncated to fit into %d characters.", MAX_CONSOLE_STRING_LENGTH);
}
motd = WzI18nString(ini, "motd", MAX_CONSOLE_STRING_LENGTH);
}
else
{
motd = WzString("");
motd = WzI18nString("");
}
}

Expand Down Expand Up @@ -7358,6 +7355,17 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running)
}
break;

case NET_I18NTEXTMSG:
if (ingame.localOptionsReceived)
{
NetworkI18nTextMessage message;
if (message.receive(queue))
{
displayRoomMessage(buildMessage(message.sender, message.text.getLocaleString(getLanguage()).toUtf8().c_str()));
}
}
break;

case NET_VOTE:
if (NetPlay.isHost && ingame.localOptionsReceived)
{
Expand Down Expand Up @@ -7684,9 +7692,10 @@ void WzMultiplayerOptionsTitleUI::screenSizeDidChange(unsigned int oldWidth, uns
static void printHostHelpMessagesToConsole()
{
char buf[512] = { '\0' };
if (!motd.isEmpty())
WzString localeMotd = motd.getLocaleString(getLanguage());
if (!localeMotd.isEmpty())
{
ssprintf(buf, "%s", motd.toUtf8().c_str());
ssprintf(buf, "%s", localeMotd.toUtf8().c_str());
displayRoomNotifyMessage(buf);
}
if (challengeActive)
Expand Down Expand Up @@ -8645,7 +8654,7 @@ void sendRoomMotdToSingleReceiver(uint32_t receiver)
return;
}
ASSERT_OR_RETURN(, isHumanPlayer(receiver), "Invalid receiver: %" PRIu32 "", receiver);
NetworkTextMessage message(NOTIFY_MESSAGE, motd.toUtf8().c_str());
NetworkI18nTextMessage message(NOTIFY_MESSAGE, &motd);
message.enqueue(NETnetQueue(receiver));
}

Expand Down
Loading

0 comments on commit a6f49cc

Please sign in to comment.