Browse Source

Handle multiple language preferences on windows

This removes the fallback functionality to non-regional translation files as I was unsure how best to handle that.
pull/1524/head
ephphatha 4 years ago committed by Anders Jenbo
parent
commit
5fa9213adc
  1. 72
      Source/options.cpp

72
Source/options.cpp

@ -881,6 +881,15 @@ OptionEntryLanguageCode::OptionEntryLanguageCode()
} }
void OptionEntryLanguageCode::LoadFromIni(string_view category) void OptionEntryLanguageCode::LoadFromIni(string_view category)
{ {
if (GetIniValue(category.data(), key.data(), szCode, sizeof(szCode))) {
if (HasTranslation(szCode)) {
// User preferred language is available
return;
}
}
// Might be a first run or the user has attempted to load a translation that doesn't exist via manual ini edit. Try find a best fit from the platform locale information.
std::vector<std::string> locales {};
#ifdef __ANDROID__ #ifdef __ANDROID__
JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv(); JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity(); jobject activity = (jobject)SDL_AndroidGetActivity();
@ -888,7 +897,7 @@ void OptionEntryLanguageCode::LoadFromIni(string_view category)
jmethodID method_id = env->GetMethodID(clazz, "getLocale", "()Ljava/lang/String;"); jmethodID method_id = env->GetMethodID(clazz, "getLocale", "()Ljava/lang/String;");
jstring jLocale = (jstring)env->CallObjectMethod(activity, method_id); jstring jLocale = (jstring)env->CallObjectMethod(activity, method_id);
const char *cLocale = env->GetStringUTFChars(jLocale, nullptr); const char *cLocale = env->GetStringUTFChars(jLocale, nullptr);
std::string locale = cLocale; locales.emplace_back(cLocale);
env->ReleaseStringUTFChars(jLocale, cLocale); env->ReleaseStringUTFChars(jLocale, cLocale);
env->DeleteLocalRef(jLocale); env->DeleteLocalRef(jLocale);
env->DeleteLocalRef(activity); env->DeleteLocalRef(activity);
@ -925,56 +934,71 @@ void OptionEntryLanguageCode::LoadFromIni(string_view category)
sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_LANG, &language); sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_LANG, &language);
if (language < 0 || language > SCE_SYSTEM_PARAM_LANG_TURKISH) if (language < 0 || language > SCE_SYSTEM_PARAM_LANG_TURKISH)
language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english
std::string locale = std::string(vita_locales[language]); locales.emplace_back(vita_locales[language]);
sceAppUtilShutdown(); sceAppUtilShutdown();
#elif defined(__3DS__) #elif defined(__3DS__)
std::string locale = n3ds::GetLocale(); locales.push_back(n3ds::GetLocale());
#elif defined(_WIN32) #elif defined(_WIN32)
std::string locale;
#if WINVER >= 0x0600 #if WINVER >= 0x0600
WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH]; auto wideCharToUtf8 = [](PWSTR wideString) {
if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
// The user default locale could be loaded, we need to convert from WIN32's default of UTF16 to UTF8
char utf8Buffer[12] {}; char utf8Buffer[12] {};
// We only handle 5 character locales (lang2-region2), so don't bother reading past that. This does leave the resulting string unterminated but the buffer was zero initialised anyway. // We only handle 5 character locales (lang2-region2), so don't bother reading past that. This does leave the resulting string unterminated but the buffer was zero initialised anyway.
WideCharToMultiByte(CP_UTF8, 0, localeBuffer, 5, utf8Buffer, 12, nullptr, nullptr); WideCharToMultiByte(CP_UTF8, 0, wideString, 5, utf8Buffer, sizeof(utf8Buffer), nullptr, nullptr);
// GetUserDefaultLocaleName could return an ISO 639-2/T string (three letter language code) or even an arbitrary custom locale, however we only handle 639-1 (two letter language code) locale names when checking the fallback language. // GetUserDefaultLocaleName could return an ISO 639-2/T string (three letter language code) or even an arbitrary custom locale, however we only handle 639-1 (two letter language code) locale names when checking the fallback language.
if (utf8Buffer[2] == '-') { // if a region is included in the locale do a simple transformation to the expected POSIX style.
// if a region is included in the locale do a simple transformation to the expected POSIX style. std::replace(utf8Buffer, utf8Buffer + sizeof(utf8Buffer), '-', '_');
utf8Buffer[2] = '_'; return std::move(std::string(utf8Buffer));
} };
WCHAR localeBuffer[LOCALE_NAME_MAX_LENGTH];
locale.append(utf8Buffer); if (GetUserDefaultLocaleName(localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
// Found a user default locale convert from WIN32's default of UTF16 to UTF8 and add to the list
locales.push_back(wideCharToUtf8(localeBuffer));
}
ULONG numberOfLanguages;
ULONG bufferSize = LOCALE_NAME_MAX_LENGTH;
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, localeBuffer, &bufferSize);
PWSTR bufferOffset = localeBuffer;
for (unsigned i = 0; i < numberOfLanguages; i++) {
// Found one (or more) user preferred UI language(s), add these to the list to check as well
locales.push_back(wideCharToUtf8(bufferOffset));
// GetUserPreferredUILanguages returns a null separated list of strings, need to increment past the null terminating the current string.
bufferOffset += lstrlenW(localeBuffer) + 1;
} }
#else #else
// Fallback method for older versions of windows, this is deprecated since Vista // Fallback method for older versions of windows, this is deprecated since Vista
char localeBuffer[LOCALE_NAME_MAX_LENGTH]; char localeBuffer[LOCALE_NAME_MAX_LENGTH];
// Deliberately not using the unicode versions here as the information retrieved should be represented in ASCII/single byte UTF8 codepoints. // Deliberately not using the unicode versions here as the information retrieved should be represented in ASCII/single byte UTF8 codepoints.
if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) { if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
locale.append(localeBuffer); std::string locale { localeBuffer };
if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) { if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, localeBuffer, LOCALE_NAME_MAX_LENGTH) != 0) {
locale.append("_"); locale.append("_");
locale.append(localeBuffer); locale.append(localeBuffer);
} }
locales.push_back(std::move(locale));
} }
#endif #endif
#else #else
std::string locale = std::locale("").name().substr(0, 5); locales.emplace_back(std::locale("").name().substr(0, 5));
#endif #endif
locale = locale.substr(0, 5); LogVerbose("Found {} user preferred locales", locales);
LogVerbose("Preferred locale: {}", locale);
if (!HasTranslation(locale)) { for (const auto &locale : locales) {
locale = locale.substr(0, 2); LogVerbose("Trying to load translation: {}", locale);
if (!HasTranslation(locale)) { if (HasTranslation(locale)) {
locale = "en"; LogVerbose("Best match locale: {}", locale);
CopyUtf8(szCode, locale, sizeof(szCode));
return;
} }
} }
LogVerbose("Best match locale: {}", locale);
GetIniValue(category.data(), key.data(), szCode, sizeof(szCode), locale.c_str()); LogVerbose("No suitable translation found");
strcpy(szCode, "en");
} }
void OptionEntryLanguageCode::SaveToIni(string_view category) const void OptionEntryLanguageCode::SaveToIni(string_view category) const
{ {

Loading…
Cancel
Save