Files
hyblocker--OpenVR-SpaceCali…/src/overlay/localisation.cpp
T
2026-06-22 20:34:48 +02:00

312 lines
11 KiB
C++

#include "localisation.h"
#include "configuration.h"
#include "log.h"
#include "platform.h"
#include "util.h"
#include <errno.h>
BEGIN_EXTERNAL_HEADERS
#include <filesystem>
#include <glaze/glaze.hpp>
END_EXTERNAL_HEADERS
namespace spacecal {
// enum -> string mapping
#define LOCALE_LIST \
X(System, "system") \
X(English_UK, "en_GB") \
X(English_US, "en_US") \
X(French, "fr") \
X(Italian, "it") \
X(German, "de") \
X(Dutch, "nl") \
X(Spanish_Spain, "es_ES") \
X(Spanish_LatinAmerica, "es_US") \
X(Danish, "da") \
X(Swedish, "sv") \
X(Finnish, "fi") \
X(Norwegian, "no") \
X(Bulgarian, "bg") \
X(Polish, "pl") \
X(Czech, "cs") \
X(Greek, "el") \
X(Hungarian, "hu") \
X(Portuguese_Portugal, "pt_PT") \
X(Portuguese_Brazil, "pt_BR") \
X(Romanian, "ro") \
X(Russian, "ru") \
X(Turkish, "tr") \
X(Ukrainian, "uk") \
X(Chinese_Simplified, "zh_HANS") \
X(Chinese_Traditional, "zh_HANT") \
X(Japanese, "ja") \
X(Korean, "ko") \
X(Thai, "th") \
X(Vietnamese, "vi")
LocalisationManager* LocalisationManager::m_instance = nullptr;
void LocalisationManager::init() {
if (m_instance != nullptr) {
LOG_FATAL("Tried creating LocalisationManager more than once! Breaking singleton. Aborting...");
return;
}
m_instance = this;
m_nativeLanguageNames.clear();
// config will be valid by this point, so we can read it
m_selectedLocale = getLocaleFromRegionString(ConfigurationManager::getInstance()->getConfiguration()->ui_locale);
if (m_selectedLocale == Locale::System) {
m_selectedLocale = estimateSystemLocale();
}
loadLocalisationStrings(m_selectedLocale);
// build native tongue locale name list
LOG_INFO("Building native locale name table...");
#define X(enum_val, str_val) loadNativeNameForLocale(Locale::enum_val, str_val);
LOCALE_LIST
#undef X
m_nativeLanguageNames[Locale::System] = m_nativeLanguageNames[estimateSystemLocale()];
}
bool LocalisationManager::setLocale(const Locale locale) {
if (locale == Locale::System) {
m_selectedLocale = estimateSystemLocale();
} else {
m_selectedLocale = locale;
}
return loadLocalisationStrings(m_selectedLocale);
}
std::string LocalisationManager::getString(const std::string& input) const {
if (m_localisedStrings.contains(input)) {
return m_localisedStrings.at(input);
}
LOG_WARN("String \"{0}\" was not defined in a localisation file. Falling back to input key.", input.c_str());
return input;
}
bool LocalisationManager::loadLocalisationStrings(const Locale locale) {
// To load locale strings we first:
// - Unload all existing locales
// - Load the en_GB locale
// - If the locale is not en_GB, load the desired locale, replacing all entries with the new data. This enables english fallback strings
m_localisedStrings.clear();
Locale targetLocale = locale;
if (locale == Locale::System) {
targetLocale = estimateSystemLocale();
}
bool result = loadLocaleFromFile(Locale::English_UK);
if (locale != Locale::English_UK) {
result |= loadLocaleFromFile(locale);
}
return result;
}
bool LocalisationManager::loadLocaleFromFile(const Locale locale)
{
auto rootDir = util::getSpaceCalibratorLangsDir();
std::string langPath = (rootDir / (getLocaleAsRegionString(locale) + ".json")).string();
errno = 0;
FILE* localeFile = fopen(langPath.c_str(), "rb");
if (!localeFile) {
switch (errno) {
case ENOENT:
LOG_WARNING("The locale file \"{0}\" does not exist on disk.", langPath);
return false;
case EACCES:
LOG_WARNING("The locale file \"{0}\" could not be loaded due to misconfigured permissions.", langPath);
return false;
case EMFILE:
case ENFILE:
LOG_WARNING("The locale file \"{0}\" could not be loaded as too many files are open on the system.", langPath);
return false;
case EFBIG:
LOG_WARNING("The locale file \"{0}\" could not be loaded as it was too large.", langPath);
return false;
default:
LOG_WARNING("The locale file \"{0}\" could not be loaded due to an unknown error ({1}).", langPath, errno);
return false;
}
}
if (localeFile) {
// Read to std::string
fseek(localeFile, 0, SEEK_END);
size_t localeFileSize = ftell(localeFile);
std::string buffer;
buffer.resize(localeFileSize);
rewind(localeFile);
fread(&buffer[0], 1, localeFileSize, localeFile);
fclose(localeFile);
localeFile = nullptr;
glz::generic json{};
glz::error_ctx jsonError = glz::read_jsonc(json, buffer);
if (jsonError.ec != glz::error_code::none) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". {1}.", langPath, jsonError.custom_error_message);
return false;
}
if (json.empty()) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". Expected JSON object, got empty file.", langPath);
return false;
}
if (!json.is_object()) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". Expected JSON object, got another type.", langPath);
return false;
}
// JSON is in expected format, awesome!
// Therefore, we should iterate through it and begin loading the key value pairs into memory
glz::generic::object_t jsonObject = json.as<glz::generic::object_t>();
for (std::pair<const std::string, glz::generic> elem : jsonObject) {
if (elem.first.empty()) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". Expected JSON key-value pair of type string, got invalid key.", langPath);
continue;
}
if (!elem.second.is_string()) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". Expected JSON key-value pair of type string, got another value type.", langPath);
continue;
}
m_localisedStrings[elem.first] = elem.second.get_string();
}
LOG_INFO("Loaded locale file \"{0}\"...", langPath);
return true;
}
else {
LOG_WARNING("The locale file \"{0}\" could not be found on the disk.", langPath);
return false;
}
return false;
}
void LocalisationManager::loadNativeNameForLocale(const Locale locale, const std::string& regionCode) {
if (locale == Locale::System || locale == Locale::Count) {
return;
}
const auto rootDir = util::getSpaceCalibratorLangsDir();
std::string langPath = (rootDir / (regionCode + ".json")).string();
std::string expectedKey = "languages_" + regionCode;
// fallback to the loaded locale string
m_nativeLanguageNames[locale] = getString(expectedKey);
errno = 0;
FILE* file = fopen(langPath.c_str(), "rb");
if (!file) {
switch (errno) {
case ENOENT:
LOG_WARNING("The locale file \"{0}\" does not exist on disk.", langPath);
return;
case EACCES:
LOG_WARNING("The locale file \"{0}\" could not be loaded due to misconfigured permissions.", langPath);
return;
case EMFILE:
case ENFILE:
LOG_WARNING("The locale file \"{0}\" could not be loaded as too many files are open on the system.", langPath);
return;
case EFBIG:
LOG_WARNING("The locale file \"{0}\" could not be loaded as it was too large.", langPath);
return;
default:
LOG_WARNING("The locale file \"{0}\" could not be loaded due to an unknown error ({1}).", langPath, errno);
return;
}
}
fseek(file, 0, SEEK_END);
size_t localeFileSize = ftell(file);
std::string buffer;
buffer.resize(localeFileSize);
rewind(file);
fread(&buffer[0], 1, localeFileSize, file);
fclose(file);
glz::generic json{};
glz::error_ctx jsonError = glz::read_jsonc(json, buffer);
if (jsonError.ec != glz::error_code::none) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". {1}.", langPath, jsonError.custom_error_message);
return;
}
if (json.empty()) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". Expected JSON object, got empty file.", langPath);
return;
}
if (!json.is_object()) {
LOG_WARNING("Failed to parse JSON for locale file \"{0}\". Expected JSON object, got another type.", langPath);
return;
}
glz::generic::object_t jsonObject = json.as<glz::generic::object_t>();
if (jsonObject.contains(expectedKey)) {
const glz::generic& value = jsonObject.at(expectedKey);
if (value.is_string()) {
m_nativeLanguageNames[locale] = value.get_string();
return;
}
}
LOG_WARNING("Key \"{0}\" missing or invalid type in \"{1}\".", expectedKey, langPath);
}
std::string LocalisationManager::getNativeTongueLocaleName(const Locale locale) const {
auto it = m_nativeLanguageNames.find(locale);
if (it != m_nativeLanguageNames.end()) {
return it->second;
}
if (locale == Locale::Count) {
#if _DEBUG
return "cc_CC";
#else
return "English (UK)";
#endif
}
#if _DEBUG
return "xx_XX";
#else
return "English (UK)";
#endif
}
std::string LocalisationManager::getLocaleAsRegionString(const Locale locale) const
{
switch (locale) {
#define X(enum_val, str_val) case Locale::enum_val: return str_val;
LOCALE_LIST
#undef X
case Locale::Count:
#if _DEBUG
return "cc_CC";
#else
return "en_GB";
#endif
}
#if _DEBUG
return "xx_XX";
#else
return "en_GB";
#endif
}
const Locale LocalisationManager::getLocaleFromRegionString(const std::string& szLocale) const {
#define X(enum_val, str_val) if (szLocale == str_val) return Locale::enum_val;
LOCALE_LIST
#undef X
return Locale::System;
}
}