diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index f711ada36f..e35b840746 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -64,7 +64,7 @@ #include "interface/Viewport.h" #include "Intro.h" #include "localisation/Date.h" -#include "localisation/Language.h" +#include "localisation/LocalisationService.h" #include "network/DiscordService.h" #include "network/http.h" #include "network/network.h" @@ -74,6 +74,7 @@ using namespace OpenRCT2; using namespace OpenRCT2::Audio; +using namespace OpenRCT2::Localisation; using namespace OpenRCT2::Ui; namespace OpenRCT2 @@ -87,6 +88,7 @@ namespace OpenRCT2 std::shared_ptr const _uiContext; // Services + std::shared_ptr _localisationService; IObjectRepository * _objectRepository = nullptr; IObjectManager * _objectManager = nullptr; ITrackDesignRepository * _trackDesignRepository = nullptr; @@ -131,7 +133,6 @@ namespace OpenRCT2 { window_close_all(); http_dispose(); - language_close_all(); object_manager_unload_all_objects(); gfx_object_check_all_images_freed(); gfx_unload_g2(); @@ -169,6 +170,11 @@ namespace OpenRCT2 return _env; } + std::shared_ptr GetLocalisationService() override + { + return _localisationService; + } + IObjectManager * GetObjectManager() override { return _objectManager; @@ -350,19 +356,27 @@ namespace OpenRCT2 _discordService = new DiscordService(); #endif - if (!language_open(gConfigGeneral.language)) + try { - log_error("Failed to open configured language..."); - if (!language_open(LANGUAGE_ENGLISH_UK)) + _localisationService->OpenLanguage(gConfigGeneral.language, *_objectManager); + } + catch (const std::exception& e) + { + log_error("Failed to open configured language: %s", e.what()); + try { - log_fatal("Failed to open fallback language..."); + _localisationService->OpenLanguage(LANGUAGE_ENGLISH_UK, *_objectManager); + } + catch (const std::exception&) + { + log_fatal("Failed to open fallback language: %s", e.what()); return false; } } if (platform_process_is_elevated()) { - std::string elevationWarning = language_get_string(STR_ADMIN_NOT_RECOMMENDED); + std::string elevationWarning = _localisationService->GetString(STR_ADMIN_NOT_RECOMMENDED); if (gOpenRCT2Headless) { Console::Error::WriteLine(elevationWarning.c_str()); diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 2a4460e46e..ddf6b43bfd 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -77,6 +77,11 @@ namespace OpenRCT2 interface IAudioContext; } + namespace Localisation + { + class LocalisationService; + } + namespace Ui { interface IUiContext; @@ -92,6 +97,7 @@ namespace OpenRCT2 virtual std::shared_ptr GetAudioContext() abstract; virtual std::shared_ptr GetUiContext() abstract; virtual std::shared_ptr GetPlatformEnvironment() abstract; + virtual std::shared_ptr GetLocalisationService() abstract; virtual IObjectManager * GetObjectManager() abstract; virtual IObjectRepository * GetObjectRepository() abstract; virtual ITrackDesignRepository * GetTrackDesignRepository() abstract; diff --git a/src/openrct2/localisation/Language.cpp b/src/openrct2/localisation/Language.cpp index 792a5cba2a..f9d9e28006 100644 --- a/src/openrct2/localisation/Language.cpp +++ b/src/openrct2/localisation/Language.cpp @@ -22,10 +22,10 @@ #include "../interface/Fonts.h" #include "../interface/FontFamilies.h" #include "../object/ObjectManager.h" -#include "LanguagePack.h" - #include "../platform/platform.h" +#include "LanguagePack.h" #include "Localisation.h" +#include "LocalisationService.h" // clang-format off const language_descriptor LanguagesDescriptors[LANGUAGE_COUNT] = @@ -55,12 +55,6 @@ const language_descriptor LanguagesDescriptors[LANGUAGE_COUNT] = }; // clang-format on -sint32 gCurrentLanguage = LANGUAGE_UNDEFINED; -bool gUseTrueTypeFont = false; - -static ILanguagePack * _languageFallback = nullptr; -static ILanguagePack * _languageCurrent = nullptr; - // clang-format off const utf8 BlackUpArrowString[] = { (utf8)(uint8)0xC2, (utf8)(uint8)0x8E, (utf8)(uint8)0xE2, (utf8)(uint8)0x96, (utf8)(uint8)0xB2, (utf8)(uint8)0x00 }; const utf8 BlackDownArrowString[] = { (utf8)(uint8)0xC2, (utf8)(uint8)0x8E, (utf8)(uint8)0xE2, (utf8)(uint8)0x96, (utf8)(uint8)0xBC, (utf8)(uint8)0x00 }; @@ -100,130 +94,53 @@ uint8 language_get_id_from_locale(const char * locale) const char * language_get_string(rct_string_id id) { - const char * result = nullptr; - if (id == STR_EMPTY) - { - result = ""; - } - else if (id != STR_NONE) - { - if (_languageCurrent != nullptr) - { - result = _languageCurrent->GetString(id); - } - if (result == nullptr && _languageFallback != nullptr) - { - result = _languageFallback->GetString(id); - } - if (result == nullptr) - { - result = "(undefined string)"; - } - } - return result; -} - -static utf8 * GetLanguagePath(utf8 * buffer, size_t bufferSize, uint32 languageId) -{ - const char * locale = LanguagesDescriptors[languageId].locale; - - platform_get_openrct_data_path(buffer, bufferSize); - Path::Append(buffer, bufferSize, "language"); - Path::Append(buffer, bufferSize, locale); - String::Append(buffer, bufferSize, ".txt"); - return buffer; + const auto& localisationService = OpenRCT2::GetContext()->GetLocalisationService(); + return localisationService->GetString(id); } bool language_open(sint32 id) { - char filename[MAX_PATH]; - - language_close_all(); - if (id == LANGUAGE_UNDEFINED) + auto context = OpenRCT2::GetContext(); + const auto& localisationService = context->GetLocalisationService(); + auto objectManager = context->GetObjectManager(); + try + { + localisationService->OpenLanguage(id, *objectManager); + return true; + } + catch (const std::exception&) { return false; } - - if (id != LANGUAGE_ENGLISH_UK) - { - GetLanguagePath(filename, sizeof(filename), LANGUAGE_ENGLISH_UK); - _languageFallback = LanguagePackFactory::FromFile(LANGUAGE_ENGLISH_UK, filename); - } - - GetLanguagePath(filename, sizeof(filename), id); - _languageCurrent = LanguagePackFactory::FromFile(id, filename); - if (_languageCurrent != nullptr) - { - gCurrentLanguage = id; - TryLoadFonts(); - - // Objects and their localised strings need to be refreshed - auto context = OpenRCT2::GetContext(); - context->GetObjectManager()->ResetObjects(); - return true; - } - - return false; } -void language_close_all() -{ - SafeDelete(_languageFallback); - SafeDelete(_languageCurrent); - gCurrentLanguage = LANGUAGE_UNDEFINED; -} - -constexpr rct_string_id NONSTEX_BASE_STRING_ID = 3463; -constexpr uint16 MAX_OBJECT_CACHED_STRINGS = 2048; - bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_string_id *outStringIds) { - outStringIds[0] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 0); - outStringIds[1] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 1); - outStringIds[2] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 2); + const auto& localisationService = OpenRCT2::GetContext()->GetLocalisationService(); + auto result = localisationService->GetLocalisedScenarioStrings(scenarioFilename); + outStringIds[0] = std::get<0>(result); + outStringIds[1] = std::get<1>(result); + outStringIds[2] = std::get<2>(result); return outStringIds[0] != STR_NONE || outStringIds[1] != STR_NONE || outStringIds[2] != STR_NONE; } -static bool _availableObjectStringIdsInitialised = false; -static std::stack _availableObjectStringIds; - void language_free_object_string(rct_string_id stringId) { - if (stringId != 0) - { - if (_languageCurrent != nullptr) - { - _languageCurrent->RemoveString(stringId); - } - _availableObjectStringIds.push(stringId); - } + const auto& localisationService = OpenRCT2::GetContext()->GetLocalisationService(); + localisationService->FreeObjectString(stringId); } rct_string_id language_get_object_override_string_id(const char * identifier, uint8 index) { - if (_languageCurrent == nullptr) - { - return STR_NONE; - } - return _languageCurrent->GetObjectOverrideStringId(identifier, index); + const auto& localisationService = OpenRCT2::GetContext()->GetLocalisationService(); + return localisationService->GetObjectOverrideStringId(identifier, index); } rct_string_id language_allocate_object_string(const std::string &target) { - if (!_availableObjectStringIdsInitialised) - { - _availableObjectStringIdsInitialised = true; - for (rct_string_id stringId = NONSTEX_BASE_STRING_ID + MAX_OBJECT_CACHED_STRINGS; stringId >= NONSTEX_BASE_STRING_ID; stringId--) - { - _availableObjectStringIds.push(stringId); - } - } - - rct_string_id stringId = _availableObjectStringIds.top(); - _availableObjectStringIds.pop(); - _languageCurrent->SetString(stringId, target); - return stringId; + const auto& localisationService = OpenRCT2::GetContext()->GetLocalisationService(); + return localisationService->AllocateObjectString(target); } diff --git a/src/openrct2/localisation/Language.h b/src/openrct2/localisation/Language.h index 8725d8454c..7ad4a2493e 100644 --- a/src/openrct2/localisation/Language.h +++ b/src/openrct2/localisation/Language.h @@ -98,7 +98,6 @@ extern const utf8 CheckBoxMarkString[]; uint8 language_get_id_from_locale(const char * locale); const char *language_get_string(rct_string_id id); bool language_open(sint32 id); -void language_close_all(); uint32 utf8_get_next(const utf8 *char_ptr, const utf8 **nextchar_ptr); utf8 *utf8_write_codepoint(utf8 *dst, uint32 codepoint); diff --git a/src/openrct2/localisation/LocalisationService.cpp b/src/openrct2/localisation/LocalisationService.cpp new file mode 100644 index 0000000000..56adc5ac11 --- /dev/null +++ b/src/openrct2/localisation/LocalisationService.cpp @@ -0,0 +1,159 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 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 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#include +#include "../Context.h" +#include "../core/Path.hpp" +#include "../interface/Fonts.h" +#include "../object/ObjectManager.h" +#include "../PlatformEnvironment.h" +#include "Language.h" +#include "LanguagePack.h" +#include "LocalisationService.h" +#include "StringIds.h" + +using namespace OpenRCT2; +using namespace OpenRCT2::Localisation; + +static constexpr rct_string_id NONSTEX_BASE_STRING_ID = 3463; +static constexpr uint16 MAX_OBJECT_CACHED_STRINGS = 2048; + +LocalisationService::LocalisationService(const std::shared_ptr& env) + : _env(env) +{ + for (rct_string_id stringId = NONSTEX_BASE_STRING_ID + MAX_OBJECT_CACHED_STRINGS; stringId >= NONSTEX_BASE_STRING_ID; stringId--) + { + _availableObjectStringIds.push(stringId); + } +} + +const char * LocalisationService::GetString(rct_string_id id) const +{ + const char * result = nullptr; + if (id == STR_EMPTY) + { + result = ""; + } + else if (id != STR_NONE) + { + if (_languageCurrent != nullptr) + { + result = _languageCurrent->GetString(id); + } + if (result == nullptr && _languageFallback != nullptr) + { + result = _languageFallback->GetString(id); + } + if (result == nullptr) + { + result = "(undefined string)"; + } + } + return result; +} + +std::string LocalisationService::GetLanguagePath(uint32 languageId) const +{ + auto locale = std::string(LanguagesDescriptors[languageId].locale); + auto languageDirectory = _env->GetDirectoryPath(DIRBASE::OPENRCT2, DIRID::LANGUAGE); + auto languagePath = Path::Combine(languageDirectory, locale + "txt"); + return languagePath; +} + +void LocalisationService::OpenLanguage(sint32 id, IObjectManager& objectManager) +{ + CloseLanguages(); + if (id == LANGUAGE_UNDEFINED) + { + throw std::invalid_argument("id was undefined"); + } + + std::string filename; + if (id != LANGUAGE_ENGLISH_UK) + { + filename = GetLanguagePath(LANGUAGE_ENGLISH_UK); + _languageFallback = std::unique_ptr(LanguagePackFactory::FromFile(LANGUAGE_ENGLISH_UK, filename.c_str())); + } + + filename = GetLanguagePath(id); + _languageCurrent = std::unique_ptr(LanguagePackFactory::FromFile(id, filename.c_str())); + if (_languageCurrent != nullptr) + { + _currentLanguage = id; + TryLoadFonts(); + + // Objects and their localised strings need to be refreshed + objectManager.ResetObjects(); + } + + throw std::runtime_error("Unable to open language " + std::to_string(id)); +} + +void LocalisationService::CloseLanguages() +{ + _languageFallback = nullptr; + _languageCurrent = nullptr; + _currentLanguage = LANGUAGE_UNDEFINED; +} + +std::tuple LocalisationService::GetLocalisedScenarioStrings(const std::string& scenarioFilename) const +{ + auto result0 = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename.c_str(), 0); + auto result1 = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename.c_str(), 1); + auto result2 = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename.c_str(), 2); + return std::make_tuple(result0, result1, result2); +} + +rct_string_id LocalisationService::GetObjectOverrideStringId(const char * identifier, uint8 index) const +{ + if (_languageCurrent == nullptr) + { + return STR_NONE; + } + return _languageCurrent->GetObjectOverrideStringId(identifier, index); +} + +rct_string_id LocalisationService::AllocateObjectString(const std::string& target) +{ + auto stringId = _availableObjectStringIds.top(); + _availableObjectStringIds.pop(); + _languageCurrent->SetString(stringId, target); + return stringId; +} + +void LocalisationService::FreeObjectString(rct_string_id stringId) +{ + if (stringId != STR_EMPTY) + { + if (_languageCurrent != nullptr) + { + _languageCurrent->RemoveString(stringId); + } + _availableObjectStringIds.push(stringId); + } +} + +sint32 LocalisationService_GetCurrentLanguage() +{ + const auto& localisationService = GetContext()->GetLocalisationService(); + return localisationService->GetCurrentLanguage(); +} + +bool LocalisationService_UseTrueTypeFont() +{ + const auto& localisationService = GetContext()->GetLocalisationService(); + return localisationService->UseTrueTypeFont(); +} diff --git a/src/openrct2/localisation/LocalisationService.h b/src/openrct2/localisation/LocalisationService.h new file mode 100644 index 0000000000..23beeb6a02 --- /dev/null +++ b/src/openrct2/localisation/LocalisationService.h @@ -0,0 +1,63 @@ +#pragma region Copyright (c) 2018 OpenRCT2 Developers +/***************************************************************************** +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 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 3 of the License, or +* (at your option) any later version. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include +#include "../common.h" + +interface ILanguagePack; +interface IObjectManager; + +namespace OpenRCT2 +{ + interface IPlatformEnvironment; +} + +namespace OpenRCT2::Localisation +{ + class LocalisationService + { + private: + const std::shared_ptr _env; + sint32 _currentLanguage{}; + bool _useTrueTypeFont{}; + std::unique_ptr _languageFallback; + std::unique_ptr _languageCurrent; + std::stack _availableObjectStringIds; + + public: + sint32 GetCurrentLanguage() const { return _currentLanguage; } + sint32 UseTrueTypeFont() const { return _useTrueTypeFont; } + + LocalisationService(const std::shared_ptr& env); + + const char * GetString(rct_string_id id) const; + std::tuple GetLocalisedScenarioStrings(const std::string& scenarioFilename) const; + rct_string_id GetObjectOverrideStringId(const char * identifier, uint8 index) const; + std::string GetLanguagePath(uint32 languageId) const; + + void OpenLanguage(sint32 id, IObjectManager& objectManager); + void CloseLanguages(); + rct_string_id AllocateObjectString(const std::string& target); + void FreeObjectString(rct_string_id stringId); + }; +} + +// Legacy getters +sint32 LocalisationService_GetCurrentLanguage(); +bool LocalisationService_UseTrueTypeFont();