Files

269 lines
6.7 KiB
C++

/*
Copyright 2025 flyinghead
This file is part of Flycast.
Flycast 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.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "i18n.h"
#include "resources.h"
#include "cfg/option.h"
#include "tinygettext/tinygettext.hpp"
#include "tinygettext/file_system.hpp"
#include "tinygettext/log.hpp"
#include "log/Log.h"
#include "emulator.h"
#ifdef _WIN32
#include <windows.h>
#include <nowide/stackstring.hpp>
#endif
#include <vector>
#include <sstream>
#include <algorithm>
#include <unordered_set>
#include <mutex>
using namespace tinygettext;
namespace i18n
{
static bool inited;
class ResourceFileSystem : public FileSystem
{
std::vector<std::string> open_directory(const std::string& pathname) override {
return resource::listDirectory(pathname);
}
std::unique_ptr<std::istream> open_file(const std::string& filename) override
{
size_t size;
std::unique_ptr<u8[]> data = resource::load(filename, size);
if (data == nullptr)
return nullptr;
std::string str((const char *)&data[0], (const char *)&data[size]);
return std::make_unique<std::istringstream>(str);
}
};
static Dictionary *dictionary;
static Dictionary& getDictionary()
{
if (dictionary == nullptr)
reloadLanguage();
return *dictionary;
}
static std::string getUnixLocaleVariant(const std::string& locale)
{
size_t at = locale.find('@');
if (at == locale.npos)
return {};
size_t end = locale.find_first_of("_-.@", ++at);
if (end == locale.npos)
return locale.substr(at);
else
return locale.substr(at, end - at);
}
void parseLocale(const std::string& locale, std::string& language, std::string& country, std::string& variant)
{
size_t sep = locale.find_first_of("_-.@");
language = locale.substr(0, sep);
country.clear();
variant.clear();
if (sep == locale.npos)
return;
if (locale[sep] == '.') {
variant = getUnixLocaleVariant(locale);
return;
}
size_t sep2 = locale.find_first_of("_-.@", ++sep);
if (sep2 == locale.npos) {
country = locale.substr(sep);
return;
}
country = locale.substr(sep, sep2 - sep);
if (locale[sep2] == '.') {
variant = getUnixLocaleVariant(locale);
return;
}
bool lastIsVariant = locale[sep2] == '@';
sep = ++sep2;
sep2 = locale.find_first_of("_-.@", sep);
if (sep2 == locale.npos)
variant = locale.substr(sep);
else
variant = locale.substr(sep, sep2 - sep);
if (!lastIsVariant)
std::swap(country, variant);
}
void init()
{
if (inited)
return;
inited = true;
#ifndef LIBRETRO
try {
std::locale::global(std::locale(""));
} catch (const std::runtime_error& e) {
INFO_LOG(BOOT, "Error setting c++ locale: %s", e.what());
setlocale(LC_ALL, "");
}
#endif
Log::set_log_info_callback([](const std::string& msg)
{
#if !defined(NDEBUG) || defined(DEBUGFAST)
static std::mutex mutex;
static std::unordered_set<std::string> msgFilter;
if (msg.empty())
return;
{
std::lock_guard<std::mutex> _(mutex);
if (msgFilter.count(msg) != 0)
return;
msgFilter.insert(msg);
}
INFO_LOG(COMMON, "%s", msg.substr(0, msg.length() - 1).c_str());
#endif
});
Log::set_log_warning_callback([](const std::string& msg) {
if (!msg.empty())
WARN_LOG(COMMON, "%s", msg.substr(0, msg.length() - 1).c_str());
});
Log::set_log_error_callback([](const std::string& msg) {
if (!msg.empty())
ERROR_LOG(COMMON, "%s", msg.substr(0, msg.length() - 1).c_str());
});
}
void reloadLanguage()
{
init();
std::string locale = getCurrentLocale();
std::string language, country, variant;
parseLocale(locale, language, country, variant);
static std::unique_ptr<DictionaryManager> dictMgr;
if (!dictMgr)
{
dictMgr = std::make_unique<DictionaryManager>(std::make_unique<ResourceFileSystem>());
dictMgr->add_directory("i18n");
}
dictMgr->set_language(Language::from_spec(language, country, variant));
dictionary = &dictMgr->get_dictionary();
EventManager::event(Event::LocaleChange);
}
static const std::string& translate(const std::string& msg) {
return getDictionary().translate(msg);
}
std::string Ts(const std::string& msg) {
return translate(msg);
}
const char *T(const char *msg)
{
if (msg == nullptr)
return nullptr;
std::string smsg(msg);
const std::string& tr = translate(smsg);
if (&tr == &smsg)
return msg;
else
return tr.c_str();
}
std::string translateCtx_s(const std::string& context, const std::string& msg) {
return getDictionary().translate_ctxt(context, msg);
}
const char *translateCtx(const std::string& context, const char *msg)
{
if (msg == nullptr)
return nullptr;
std::string smsg(msg);
const std::string& tr = getDictionary().translate_ctxt(context, smsg);
if (&tr == &smsg)
return msg;
else
return tr.c_str();
}
std::string translatePlural_s(const std::string& msg, const std::string& msgPlural, int num) {
return getDictionary().translate_plural(msg, msgPlural, num);
}
const char *translatePlural(const char *msg, const char *msgPlural, int num) {
if (msg == nullptr)
return nullptr;
std::string smsg(msg);
std::string smsgPlural(msgPlural);
const std::string& tr = getDictionary().translate_plural(smsg, smsgPlural, num);
if (&tr == &smsg)
return msg;
else if (&tr == &smsgPlural)
return msgPlural;
else
return tr.c_str();
}
#if !defined(__ANDROID__) && !defined(__SWITCH__) && !defined(LIBRETRO) && !defined(TARGET_MAC)
std::string getSystemLocale()
{
#if defined(_WIN32)
ULONG numLanguages = 0;
ULONG bufferSize = 0;
if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, nullptr, &bufferSize) || bufferSize == 0)
{
ERROR_LOG(COMMON, "GetUserPreferredUILanguages(size) failed: %lx", GetLastError());
return "en";
}
std::vector<wchar_t> buffer(bufferSize);
if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, buffer.data(), &bufferSize))
{
ERROR_LOG(COMMON, "GetUserPreferredUILanguages(data) failed: %lx", GetLastError());
return "en";
}
nowide::stackstring name;
if (name.convert(buffer.data()))
return name.get();
ERROR_LOG(COMMON, "UTF-16 to UTF-8 conversion failed");
return "en";
#else
const char* locale = setlocale(LC_MESSAGES, nullptr);
return locale ? locale : "en";
#endif
}
#endif
#ifndef LIBRETRO
std::string getCurrentLocale()
{
std::string locale = config::UILanguage;
if (!locale.empty())
return locale;
return getSystemLocale();
}
#endif
}