Host: Add message translation functions

This commit is contained in:
Stenzek 2023-06-19 20:26:38 +10:00 committed by Connor McLaughlin
parent ff02d41992
commit f7bc05c735
4 changed files with 171 additions and 2 deletions

View File

@ -658,6 +658,18 @@ void Host::VSyncOnCPUThread()
GSRunner::PumpPlatformMessages();
}
s32 Host::Internal::GetTranslatedStringImpl(
const std::string_view& context, const std::string_view& msg, char* tbuf, size_t tbuf_space)
{
if (msg.size() > tbuf_space)
return -1;
else if (msg.empty())
return 0;
std::memcpy(tbuf, msg.data(), msg.size());
return static_cast<s32>(msg.size());
}
//////////////////////////////////////////////////////////////////////////
// Platform specific code
//////////////////////////////////////////////////////////////////////////

View File

@ -1179,6 +1179,23 @@ void Host::SetFullscreen(bool enabled)
g_emu_thread->setFullscreen(enabled, true);
}
s32 Host::Internal::GetTranslatedStringImpl(
const std::string_view& context, const std::string_view& msg, char* tbuf, size_t tbuf_space)
{
// This is really awful. Thankfully we're caching the results...
const std::string temp_context(context);
const std::string temp_msg(msg);
const QString translated_msg = qApp->translate(temp_context.c_str(), temp_msg.c_str());
const QByteArray translated_utf8 = translated_msg.toUtf8();
const size_t translated_size = translated_utf8.size();
if (translated_size > tbuf_space)
return -1;
else if (translated_size > 0)
std::memcpy(tbuf, translated_utf8.constData(), translated_size);
return static_cast<s32>(translated_size);
}
alignas(16) static SysMtgsThread s_mtgs_thread;
SysMtgsThread& GetMTGS()

View File

@ -26,13 +26,122 @@
#include "common/Assertions.h"
#include "common/CrashHandler.h"
#include "common/FileSystem.h"
#include "common/HeterogeneousContainers.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include <cstdarg>
#include <shared_mutex>
static std::mutex s_settings_mutex;
static LayeredSettingsInterface s_layered_settings_interface;
namespace Host
{
static std::pair<const char*, u32> LookupTranslationString(
const std::string_view& context, const std::string_view& msg);
static std::mutex s_settings_mutex;
static LayeredSettingsInterface s_layered_settings_interface;
static constexpr u32 TRANSLATION_STRING_CACHE_SIZE = 4 * 1024 * 1024;
using TranslationStringMap = UnorderedStringMap<std::pair<u32, u32>>;
using TranslationStringContextMap = UnorderedStringMap<TranslationStringMap>;
static std::shared_mutex s_translation_string_mutex;
static TranslationStringContextMap s_translation_string_map;
static std::vector<char> s_translation_string_cache;
static u32 s_translation_string_cache_pos;
} // namespace Host
std::pair<const char*, u32> Host::LookupTranslationString(const std::string_view& context, const std::string_view& msg)
{
// TODO: TranslatableString, compile-time hashing.
TranslationStringContextMap::iterator ctx_it;
TranslationStringMap::iterator msg_it;
std::pair<const char*, u32> ret;
s32 len;
// Shouldn't happen, but just in case someone tries to translate an empty string.
if (unlikely(msg.empty()))
{
ret.first = &s_translation_string_cache[0];
ret.second = 0;
return ret;
}
s_translation_string_mutex.lock_shared();
ctx_it = UnorderedStringMapFind(s_translation_string_map, context);
if (unlikely(ctx_it == s_translation_string_map.end()))
goto add_string;
msg_it = UnorderedStringMapFind(ctx_it->second, msg);
if (unlikely(msg_it == ctx_it->second.end()))
goto add_string;
ret.first = &s_translation_string_cache[msg_it->second.first];
ret.second = msg_it->second.second;
s_translation_string_mutex.unlock_shared();
return ret;
add_string:
s_translation_string_mutex.unlock_shared();
s_translation_string_mutex.lock();
if (unlikely(s_translation_string_cache.empty()))
{
// First element is always an empty string.
s_translation_string_cache.resize(TRANSLATION_STRING_CACHE_SIZE);
s_translation_string_cache[0] = '\0';
s_translation_string_cache_pos = 0;
}
if ((len = Internal::GetTranslatedStringImpl(context, msg,
&s_translation_string_cache[s_translation_string_cache_pos],
TRANSLATION_STRING_CACHE_SIZE - 1 - s_translation_string_cache_pos)) < 0)
{
Console.Error("WARNING: Clearing translation string cache, it might need to be larger.");
s_translation_string_cache_pos = 0;
if ((len = Internal::GetTranslatedStringImpl(context, msg,
&s_translation_string_cache[s_translation_string_cache_pos],
TRANSLATION_STRING_CACHE_SIZE - 1 - s_translation_string_cache_pos)) < 0)
{
pxFailRel("Failed to get translated string after clearing cache.");
len = 0;
}
}
// New context?
if (ctx_it == s_translation_string_map.end())
ctx_it = s_translation_string_map.emplace(context, TranslationStringMap()).first;
// Impl doesn't null terminate, we need that for C strings.
// TODO: do we want to consider aligning the buffer?
const u32 insert_pos = s_translation_string_cache_pos;
s_translation_string_cache[insert_pos + static_cast<u32>(len)] = 0;
ctx_it->second.emplace(msg, std::pair<u32, u32>(insert_pos, static_cast<u32>(len)));
s_translation_string_cache_pos = insert_pos + static_cast<u32>(len) + 1;
ret.first = &s_translation_string_cache[insert_pos];
ret.second = static_cast<u32>(len);
s_translation_string_mutex.unlock();
return ret;
}
const char* Host::TranslateToCString(const std::string_view& context, const std::string_view& msg)
{
return LookupTranslationString(context, msg).first;
}
std::string_view Host::TranslateToStringView(const std::string_view& context, const std::string_view& msg)
{
const auto mp = LookupTranslationString(context, msg);
return std::string_view(mp.first, mp.second);
}
std::string Host::TranslateToString(const std::string_view& context, const std::string_view& msg)
{
return std::string(TranslateToStringView(context, msg));
}
void Host::ReportFormattedErrorAsync(const std::string_view& title, const char* format, ...)
{

View File

@ -17,6 +17,8 @@
#include "common/Pcsx2Defs.h"
#include "fmt/format.h"
#include <ctime>
#include <functional>
#include <mutex>
@ -48,6 +50,24 @@ namespace Host
/// Returns the modified time of a resource.
std::optional<std::time_t> GetResourceFileTimestamp(const char* filename);
/// Returns a localized version of the specified string within the specified context.
/// The pointer is guaranteed to be valid until the next language change.
const char* TranslateToCString(const std::string_view& context, const std::string_view& msg);
/// Returns a localized version of the specified string within the specified context.
/// The view is guaranteed to be valid until the next language change.
/// NOTE: When passing this to fmt, positional arguments should be used in the base string, as
/// not all locales follow the same word ordering.
std::string_view TranslateToStringView(const std::string_view& context, const std::string_view& msg);
/// Returns a localized version of the specified string within the specified context.
std::string TranslateToString(const std::string_view& context, const std::string_view& msg);
/// Returns a localized version of the specified string, after formatting.
template<typename... T>
std::string TranslateAndFmt(const std::string_view& context, const std::string_view& format, T&&... args);
/// Adds OSD messages, duration is in seconds.
void AddOSDMessage(std::string message, float duration = 2.0f);
void AddKeyedOSDMessage(std::string key, std::string message, float duration = 2.0f);
@ -155,5 +175,16 @@ namespace Host
/// Sets the input profile settings layer. Called by VMManager when the game changes.
void SetInputSettingsLayer(SettingsInterface* sif);
/// Implementation to retrieve a translated string.
s32 GetTranslatedStringImpl(const std::string_view& context, const std::string_view& msg, char* tbuf, size_t tbuf_space);
} // namespace Internal
} // namespace Host
// Helper macros for retrieving translated strings.
#define TRANSLATE(context, msg) Host::TranslateToCString(context, msg)
#define TRANSLATE_SV(context, msg) Host::TranslateToStringView(context, msg)
#define TRANSLATE_STR(context, msg) Host::TranslateToString(context, msg)
// Does not translate the string at runtime, but allows the UI to in its own way.
#define TRANSLATE_NOOP(context, msg) msg