#pragma region Copyright (c) 2014-2016 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 "../core/Console.hpp" #include "../core/Memory.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../object/ObjectManager.h" #include "LanguagePack.h" extern "C" { #include "../config.h" #include "../drawing/drawing.h" #include "../object.h" #include "../openrct2.h" #include "../util/util.h" #include "localisation.h" enum { RCT2_LANGUAGE_ID_ENGLISH_UK, RCT2_LANGUAGE_ID_ENGLISH_US, RCT2_LANGUAGE_ID_FRENCH, RCT2_LANGUAGE_ID_GERMAN, RCT2_LANGUAGE_ID_SPANISH, RCT2_LANGUAGE_ID_ITALIAN, RCT2_LANGUAGE_ID_DUTCH, RCT2_LANGUAGE_ID_SWEDISH, RCT2_LANGUAGE_ID_8, RCT2_LANGUAGE_ID_KOREAN, RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED, RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL, RCT2_LANGUAGE_ID_12, RCT2_LANGUAGE_ID_PORTUGUESE, RCT2_LANGUAGE_ID_END = 255 }; static TTFFontSetDescriptor TTFFontMSGothic = {{ { "msgothic.ttc", "MS PGothic", 9, 1, 0, 15, nullptr }, { "msgothic.ttc", "MS PGothic", 12, 1, 0, 17, nullptr }, { "msgothic.ttc", "MS PGothic", 12, 1, 0, 17, nullptr }, { "msgothic.ttc", "MS PGothic", 13, 1, 0, 20, nullptr }, }}; static TTFFontSetDescriptor TTFFontMingLiu = {{ { "msjh.ttc", "JhengHei", 9, -1, -3, 6, nullptr }, { "mingliu.ttc", "MingLiU", 11, 1, 1, 12, nullptr }, { "mingliu.ttc", "MingLiU", 12, 1, 0, 12, nullptr }, { "mingliu.ttc", "MingLiU", 13, 1, 0, 20, nullptr }, }}; static TTFFontSetDescriptor TTFFontSimSun = {{ { "msyh.ttc", "YaHei", 9, -1, -3, 6, nullptr }, { "simsun.ttc", "SimSun", 11, 1, -1, 14, nullptr }, { "simsun.ttc", "SimSun", 12, 1, -2, 14, nullptr }, { "simsun.ttc", "SimSun", 13, 1, 0, 20, nullptr }, }}; static TTFFontSetDescriptor TTFFontGulim = {{ { "gulim.ttc", "Gulim", 11, 1, 0, 15, nullptr }, { "gulim.ttc", "Gulim", 12, 1, 0, 17, nullptr }, { "gulim.ttc", "Gulim", 12, 1, 0, 17, nullptr }, { "gulim.ttc", "Gulim", 13, 1, 0, 20, nullptr }, }}; static TTFFontSetDescriptor TTFFontArial = {{ { "arial.ttf", "Arial", 8, 0, -1, 6, nullptr }, { "arial.ttf", "Arial", 10, 0, -1, 12, nullptr }, { "arial.ttf", "Arial", 11, 0, -1, 12, nullptr }, { "arial.ttf", "Arial", 12, 0, -1, 20, nullptr }, }}; const language_descriptor LanguagesDescriptors[LANGUAGE_COUNT] = { { "", "", "", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_UNDEFINED { "en-GB", "English (UK)", "English (UK)", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_ENGLISH_UK { "en-US", "English (US)", "English (US)", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_US }, // LANGUAGE_ENGLISH_US { "de-DE", "German", "Deutsch", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_GERMAN }, // LANGUAGE_GERMAN { "nl-NL", "Dutch", "Nederlands", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_DUTCH }, // LANGUAGE_DUTCH { "fr-FR", "French", "Fran\xC3\xA7" "ais", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_FRENCH }, // LANGUAGE_FRENCH { "hu-HU", "Hungarian", "Magyar", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_HUNGARIAN { "pl-PL", "Polish", "Polski", &TTFFontArial, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_POLISH { "es-ES", "Spanish", "Espa\xC3\xB1ol", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_SPANISH }, // LANGUAGE_SPANISH { "sv-SE", "Swedish", "Svenska", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_SWEDISH }, // LANGUAGE_SWEDISH { "it-IT", "Italian", "Italiano", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ITALIAN }, // LANGUAGE_ITALIAN { "pt-BR", "Portuguese (BR)", "Portugu\xC3\xAAs (BR)", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_PORTUGUESE }, // LANGUAGE_PORTUGUESE_BR { "zh-TW", "Chinese (Traditional)", "Chinese (Traditional)", &TTFFontMingLiu, RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL }, // LANGUAGE_CHINESE_TRADITIONAL { "zh-CN", "Chinese (Simplified)", "Chinese (Simplified)", &TTFFontSimSun, RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED }, // LANGUAGE_CHINESE_SIMPLIFIED { "fi-FI", "Finnish", "Suomi", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_FINNISH { "ko-KR", "Korean", "Korean", &TTFFontGulim, RCT2_LANGUAGE_ID_KOREAN }, // LANGUAGE_KOREAN { "ru-RU", "Russian", "Russian", &TTFFontArial, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_RUSSIAN { "cs-CZ", "Czech", "Czech", &TTFFontArial, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_CZECH { "ja-JP", "Japanese", "Japanese", &TTFFontMSGothic, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_JAPANESE { "nb-NO", "Norwegian", "Norsk", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_UK, }, // LANGUAGE_NORWEGIAN }; int gCurrentLanguage = LANGUAGE_UNDEFINED; bool gUseTrueTypeFont = false; static ILanguagePack * _languageFallback = nullptr; static ILanguagePack * _languageCurrent = nullptr; static const utf8 BlackUpArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xB2, (utf8)0x00 }; static const utf8 BlackDownArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xBC, (utf8)0x00 }; static const utf8 BlackLeftArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x97, (utf8)0x80, (utf8)0x00 }; static const utf8 BlackRightArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xB6, (utf8)0x00 }; static const utf8 CheckBoxMarkString[] = { (utf8)0xE2, (utf8)0x9C, (utf8)0x93, (utf8)0x00 }; void utf8_remove_format_codes(utf8 *text, bool allowcolours) { utf8 *dstCh = text; utf8 *ch = text; int codepoint; while ((codepoint = utf8_get_next(ch, (const utf8 * *)&ch)) != 0) { if (!utf8_is_format_code(codepoint) || (allowcolours && utf8_is_colour_code(codepoint))) { dstCh = utf8_write_codepoint(dstCh, codepoint); } } *dstCh = 0; } const char * language_get_string(rct_string_id id) { const char * result = nullptr; 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; } utf8 * GetLanguagePath(utf8 * buffer, size_t bufferSize, uint32 languageId) { const char * locale = LanguagesDescriptors[languageId].locale; platform_get_openrct_data_path(buffer, sizeof(bufferSize)); Path::Append(buffer, sizeof(bufferSize), "language"); Path::Append(buffer, sizeof(bufferSize), locale); String::Append(buffer, sizeof(bufferSize), ".txt"); return buffer; } bool language_open(int id) { char filename[MAX_PATH]; language_close_all(); if (id == LANGUAGE_UNDEFINED) { 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; if (LanguagesDescriptors[id].font == FONT_OPENRCT2_SPRITE) { ttf_dispose(); gUseTrueTypeFont = false; gCurrentTTFFontSet = nullptr; } else { if (!String::IsNullOrEmpty(gConfigFonts.file_name)) { static TTFFontSetDescriptor TTFFontCustom = {{ { gConfigFonts.file_name, gConfigFonts.font_name, gConfigFonts.size_tiny, gConfigFonts.x_offset, gConfigFonts.y_offset, gConfigFonts.height_tiny, nullptr }, { gConfigFonts.file_name, gConfigFonts.font_name, gConfigFonts.size_small, gConfigFonts.x_offset, gConfigFonts.y_offset, gConfigFonts.height_small, nullptr }, { gConfigFonts.file_name, gConfigFonts.font_name, gConfigFonts.size_medium, gConfigFonts.x_offset, gConfigFonts.y_offset, gConfigFonts.height_medium, nullptr }, { gConfigFonts.file_name, gConfigFonts.font_name, gConfigFonts.size_big, gConfigFonts.x_offset, gConfigFonts.y_offset, gConfigFonts.height_big, nullptr }, }}; ttf_dispose(); gUseTrueTypeFont = true; gCurrentTTFFontSet = &TTFFontCustom; bool fontInitialised = ttf_initialise(); if (!fontInitialised) { log_warning("Unable to initialise configured TrueType font -- falling back to Language default."); } else { // Objects and their localized strings need to be refreshed GetObjectManager()->ResetObjects(); return true; } } ttf_dispose(); gUseTrueTypeFont = true; gCurrentTTFFontSet = LanguagesDescriptors[id].font; bool fontInitialised = ttf_initialise(); if (!fontInitialised) { // Have we tried Arial yet? if (gCurrentTTFFontSet != &TTFFontArial) { Console::WriteLine("Unable to initialise prefered TrueType font -- falling back to Arial."); gCurrentTTFFontSet = &TTFFontArial; fontInitialised = ttf_initialise(); if (!fontInitialised) { // Fall back to sprite font. Console::WriteLine("Falling back to sprite font."); gUseTrueTypeFont = false; gCurrentTTFFontSet = nullptr; return false; } } } } // Objects and their localized strings need to be refreshed GetObjectManager()->ResetObjects(); return true; } return 0; } void language_close_all() { SafeDelete(_languageFallback); SafeDelete(_languageCurrent); gCurrentLanguage = LANGUAGE_UNDEFINED; } constexpr rct_string_id STEX_BASE_STRING_ID = 3447; constexpr rct_string_id NONSTEX_BASE_STRING_ID = 3463; constexpr uint16 MAX_OBJECT_CACHED_STRINGS = 2048; /* rct2: 0x0098DA16 */ static uint16 ObjectTypeStringTableCount[] = { 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 }; static utf8 *_cachedObjectStrings[MAX_OBJECT_CACHED_STRINGS] = { nullptr }; static wchar_t convert_specific_language_character_to_unicode(int languageId, wchar_t codepoint) { switch (languageId) { case RCT2_LANGUAGE_ID_KOREAN: return codepoint; case RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL: return encoding_convert_big5_to_unicode(codepoint); case RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED: return encoding_convert_gb2312_to_unicode(codepoint); default: return codepoint; } } static utf8 * convert_multibyte_charset(const char * src, int languageId) { constexpr char CODEPOINT_DOUBLEBYTE = (char)0xFF; size_t reservedLength = (String::LengthOf(src) * 4) + 1; utf8 * buffer = Memory::Allocate(reservedLength); utf8 * dst = buffer; for (const char * ch = src; *ch != 0;) { if (*ch == CODEPOINT_DOUBLEBYTE) { ch++; char a = *ch++; char b = *ch++; uint16 codepoint = (uint16)((a << 8) | b); codepoint = convert_specific_language_character_to_unicode(languageId, codepoint); dst = String::WriteCodepoint(dst, codepoint); } else { dst = String::WriteCodepoint(dst, *ch++); } } *dst++ = 0; size_t actualLength = (size_t)(dst - buffer); buffer = Memory::Reallocate(buffer, actualLength); return buffer; } static bool rct2_language_is_multibyte_charset(int languageId) { switch (languageId) { case RCT2_LANGUAGE_ID_KOREAN: case RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL: case RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED: case RCT2_LANGUAGE_ID_8: return true; default: return false; } } utf8 *rct2_language_string_to_utf8(const char *src, int languageId) { if (rct2_language_is_multibyte_charset(languageId)) { return convert_multibyte_charset(src, languageId); } else { return win1252_to_utf8_alloc(src); } } 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); return outStringIds[0] != STR_NONE || outStringIds[1] != STR_NONE || outStringIds[2] != STR_NONE; } static bool _availableObjectStringIdsInitialised = false; static std::stack _availableObjectStringIds; rct_string_id language_allocate_object_string(const utf8 * 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; } void language_free_object_string(rct_string_id stringId) { if (stringId != 0) { if (_languageCurrent != nullptr) { _languageCurrent->SetString(stringId, nullptr); } _availableObjectStringIds.push(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); } }