From 945ee04f622cc96bdee88d107da29802b6d3d722 Mon Sep 17 00:00:00 2001 From: obligaron Date: Sun, 28 Nov 2021 09:41:34 +0100 Subject: [PATCH] Add OptionEntry Language --- Source/init.cpp | 5 +- Source/options.cpp | 208 ++++++++++++++++++++++++++------------ Source/options.h | 28 ++++- Source/utils/language.cpp | 4 +- 4 files changed, 172 insertions(+), 73 deletions(-) diff --git a/Source/init.cpp b/Source/init.cpp index b0331e2e3..6f8c7dae7 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -127,9 +127,10 @@ std::vector GetMPQSearchPaths() void init_language_archives(const std::vector &paths) { - if (strcasecmp("en", sgOptions.Language.szCode) != 0 || strlen(sgOptions.Language.szCode) != 2) { + string_view code = *sgOptions.Language.code; + if (code != "en") { char langMpqName[10] = {}; - CopyUtf8(langMpqName, sgOptions.Language.szCode, sizeof(langMpqName)); + CopyUtf8(langMpqName, code.data(), sizeof(langMpqName)); strncat(langMpqName, ".mpq", sizeof(langMpqName) - strlen(langMpqName) - 1); lang_mpq = LoadMPQ(paths, langMpqName); diff --git a/Source/options.cpp b/Source/options.cpp index 279031ab5..8e8bd0860 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -229,6 +229,12 @@ void OptionShowFPSChanged() frameflag = false; } +void OptionLanguageCodeChanged() +{ + LanguageInitialize(); + init_language_archives(); +} + } // namespace void SetIniValue(const char *sectionName, const char *keyName, const char *value, int len) @@ -316,70 +322,6 @@ void LoadOptions() sgOptions.Controller.bRearTouch = GetIniBool("Controller", "Enable Rear Touchpad", true); #endif -#ifdef __ANDROID__ - JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv(); - jobject activity = (jobject)SDL_AndroidGetActivity(); - jclass clazz(env->GetObjectClass(activity)); - jmethodID method_id = env->GetMethodID(clazz, "getLocale", "()Ljava/lang/String;"); - jstring jLocale = (jstring)env->CallObjectMethod(activity, method_id); - const char *cLocale = env->GetStringUTFChars(jLocale, nullptr); - std::string locale = cLocale; - env->ReleaseStringUTFChars(jLocale, cLocale); - env->DeleteLocalRef(jLocale); - env->DeleteLocalRef(activity); - env->DeleteLocalRef(clazz); -#elif defined(__vita__) - int32_t language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english - const char *vita_locales[] = { - "ja_JP", - "en_US", - "fr_FR", - "es_ES", - "de_DE", - "it_IT", - "nl_NL", - "pt_PT", - "ru_RU", - "ko_KR", - "zh_TW", - "zh_CN", - "fi_FI", - "sv_SE", - "da_DK", - "no_NO", - "pl_PL", - "pt_BR", - "en_GB", - "tr_TR", - }; - SceAppUtilInitParam initParam; - SceAppUtilBootParam bootParam; - memset(&initParam, 0, sizeof(SceAppUtilInitParam)); - memset(&bootParam, 0, sizeof(SceAppUtilBootParam)); - sceAppUtilInit(&initParam, &bootParam); - sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_LANG, &language); - if (language < 0 || language > SCE_SYSTEM_PARAM_LANG_TURKISH) - language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english - std::string locale = std::string(vita_locales[language]); - sceAppUtilShutdown(); -#elif defined(__3DS__) - std::string locale = n3ds::GetLocale(); -#else - std::string locale = std::locale("").name().substr(0, 5); -#endif - - locale = locale.substr(0, 5); - LogVerbose("Prefered locale: {}", locale); - if (!HasTranslation(locale)) { - locale = locale.substr(0, 2); - if (!HasTranslation(locale)) { - locale = "en"; - } - } - LogVerbose("Best match locale: {}", locale); - - GetIniValue("Language", "Code", sgOptions.Language.szCode, sizeof(sgOptions.Language.szCode), locale.c_str()); - keymapper.Load(); if (demo::IsRunning()) @@ -445,8 +387,6 @@ void SaveOptions() SetIniValue("Controller", "Enable Rear Touchpad", sgOptions.Controller.bRearTouch); #endif - SetIniValue("Language", "Code", sgOptions.Language.szCode); - keymapper.Save(); SaveIni(); @@ -734,13 +674,147 @@ std::vector ChatOptions::GetEntries() return {}; } +OptionEntryLanguageCode::OptionEntryLanguageCode() + : OptionEntryListBase("Code", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, "Language", "Define what language to use in game") +{ +} +void OptionEntryLanguageCode::LoadFromIni(string_view category) +{ +#ifdef __ANDROID__ + JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv(); + jobject activity = (jobject)SDL_AndroidGetActivity(); + jclass clazz(env->GetObjectClass(activity)); + jmethodID method_id = env->GetMethodID(clazz, "getLocale", "()Ljava/lang/String;"); + jstring jLocale = (jstring)env->CallObjectMethod(activity, method_id); + const char *cLocale = env->GetStringUTFChars(jLocale, nullptr); + std::string locale = cLocale; + env->ReleaseStringUTFChars(jLocale, cLocale); + env->DeleteLocalRef(jLocale); + env->DeleteLocalRef(activity); + env->DeleteLocalRef(clazz); +#elif defined(__vita__) + int32_t language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english + const char *vita_locales[] = { + "ja_JP", + "en_US", + "fr_FR", + "es_ES", + "de_DE", + "it_IT", + "nl_NL", + "pt_PT", + "ru_RU", + "ko_KR", + "zh_TW", + "zh_CN", + "fi_FI", + "sv_SE", + "da_DK", + "no_NO", + "pl_PL", + "pt_BR", + "en_GB", + "tr_TR", + }; + SceAppUtilInitParam initParam; + SceAppUtilBootParam bootParam; + memset(&initParam, 0, sizeof(SceAppUtilInitParam)); + memset(&bootParam, 0, sizeof(SceAppUtilBootParam)); + sceAppUtilInit(&initParam, &bootParam); + sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_LANG, &language); + if (language < 0 || language > SCE_SYSTEM_PARAM_LANG_TURKISH) + language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english + std::string locale = std::string(vita_locales[language]); + sceAppUtilShutdown(); +#elif defined(__3DS__) + std::string locale = n3ds::GetLocale(); +#else + std::string locale = std::locale("").name().substr(0, 5); +#endif + + locale = locale.substr(0, 5); + LogVerbose("Preferred locale: {}", locale); + if (!HasTranslation(locale)) { + locale = locale.substr(0, 2); + if (!HasTranslation(locale)) { + locale = "en"; + } + } + LogVerbose("Best match locale: {}", locale); + + GetIniValue(category.data(), key.data(), szCode, sizeof(szCode), locale.c_str()); +} +void OptionEntryLanguageCode::SaveToIni(string_view category) const +{ + SetIniValue(category.data(), key.data(), szCode); +} + +void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const +{ + if (!languages.empty()) + return; + + // Add well-known supported languages + languages.push_back({ "bg", "Bulgarian" }); + languages.push_back({ "cs", "Czech" }); + languages.push_back({ "da", "Danish" }); + languages.push_back({ "de", "German" }); + languages.push_back({ "en", "English" }); + languages.push_back({ "es", "Spanish" }); + languages.push_back({ "fr", "French" }); + languages.push_back({ "ja", "Japanese" }); + languages.push_back({ "hr", "Croatian" }); + languages.push_back({ "it", "Italian" }); + languages.push_back({ "ko_KR", "Korean" }); + languages.push_back({ "pl", "Polish" }); + languages.push_back({ "pt_BR", "Portuguese (Brazil)" }); + languages.push_back({ "ro_RO", "Romansh" }); + languages.push_back({ "ru", "Russian" }); + languages.push_back({ "sv", "Swedish" }); + languages.push_back({ "uk", "Ukrainian" }); + languages.push_back({ "zh_CN", "Simplified Chinese" }); + languages.push_back({ "zh_TW", "Traditional Chinese" }); + + // Ensures that the ini specified language is present in languages list even if unkown (for example if someone starts to translate a new language) + if (std::find_if(languages.begin(), languages.end(), [this](const auto &x) { return x.first == this->szCode; }) == languages.end()) { + languages.push_back({ szCode, szCode }); + } +} + +size_t OptionEntryLanguageCode::GetListSize() const +{ + CheckLanguagesAreInitialized(); + return languages.size(); +} +string_view OptionEntryLanguageCode::GetListDescription(size_t index) const +{ + CheckLanguagesAreInitialized(); + return languages[index].second; +} +size_t OptionEntryLanguageCode::GetActiveListIndex() const +{ + CheckLanguagesAreInitialized(); + auto found = std::find_if(languages.begin(), languages.end(), [this](const auto &x) { return x.first == this->szCode; }); + if (found == languages.end()) + return 0; + return std::distance(languages.begin(), found); +} +void OptionEntryLanguageCode::SetActiveListIndex(size_t index) +{ + strcpy(szCode, languages[index].first.c_str()); + NotifyValueChanged(); +} + LanguageOptions::LanguageOptions() : OptionCategoryBase("Language", N_("Language"), N_("Language Settings")) { + code.SetValueChangedCallback(OptionLanguageCodeChanged); } std::vector LanguageOptions::GetEntries() { - return {}; + return { + &code, + }; } } // namespace devilution diff --git a/Source/options.h b/Source/options.h index 14060bce4..dce6fc052 100644 --- a/Source/options.h +++ b/Source/options.h @@ -186,6 +186,31 @@ public: } }; +class OptionEntryLanguageCode : public OptionEntryListBase { +public: + OptionEntryLanguageCode(); + + void LoadFromIni(string_view category) override; + void SaveToIni(string_view category) const override; + + [[nodiscard]] virtual size_t GetListSize() const override; + [[nodiscard]] virtual string_view GetListDescription(size_t index) const override; + [[nodiscard]] virtual size_t GetActiveListIndex() const override; + virtual void SetActiveListIndex(size_t index) override; + + string_view operator*() const + { + return szCode; + } + +private: + /** @brief Language code (IETF) for text. */ + char szCode[6]; + mutable std::vector> languages; + + void CheckLanguagesAreInitialized() const; +}; + struct OptionCategoryBase { OptionCategoryBase(string_view key, string_view name, string_view description); @@ -391,8 +416,7 @@ struct LanguageOptions : OptionCategoryBase { LanguageOptions(); std::vector GetEntries() override; - /** @brief Language code (IETF) for text. */ - char szCode[6]; + OptionEntryLanguageCode code; }; struct Options { diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index 28e018f61..7688c9d1d 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -283,7 +283,7 @@ bool HasTranslation(const std::string &locale) bool IsSmallFontTall() { - string_view code(sgOptions.Language.szCode, 2); + string_view code = (*sgOptions.Language.code).substr(0, 2); return code == "zh" || code == "ja" || code == "ko"; } @@ -291,7 +291,7 @@ void LanguageInitialize() { translation = { {}, {} }; - const std::string lang = sgOptions.Language.szCode; + const std::string lang(*sgOptions.Language.code); SDL_RWops *rw; // Translations normally come in ".gmo" files.