From 413227a5c16a2c16e50442f228365ea3125c9a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 19 Mar 2023 16:04:42 +0100 Subject: [PATCH] Add `os_locale_str` to get user locale This function determines the preferred user locale setting. --- src/base/system.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++ src/base/system.h | 22 +++++++++++++++ src/test/os.cpp | 7 +++++ 3 files changed, 97 insertions(+) diff --git a/src/base/system.cpp b/src/base/system.cpp index 057176f8a..4f6142289 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -26,6 +26,7 @@ #if defined(CONF_FAMILY_UNIX) #include +#include #include #include #include @@ -51,6 +52,7 @@ #define _task_user_ #include +#include #include #include @@ -4266,6 +4268,72 @@ int os_version_str(char *version, int length) #endif } +void os_locale_str(char *locale, size_t length) +{ +#if defined(CONF_FAMILY_WINDOWS) + wchar_t wide_buffer[LOCALE_NAME_MAX_LENGTH]; + dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure"); + + // Assume maximum possible length for encoding as UTF-8. + char buffer[UTF8_BYTE_LENGTH * LOCALE_NAME_MAX_LENGTH + 1]; + dbg_assert(WideCharToMultiByte(CP_UTF8, 0, wide_buffer, -1, buffer, sizeof(buffer), NULL, NULL) > 0, "WideCharToMultiByte failure"); + + str_copy(locale, buffer, length); +#elif defined(CONF_PLATFORM_MACOS) + CFLocaleRef locale_ref = CFLocaleCopyCurrent(); + CFStringRef locale_identifier_ref = static_cast(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier)); + + // Count number of UTF16 codepoints, +1 for zero-termination. + // Assume maximum possible length for encoding as UTF-8. + CFIndex locale_identifier_size = (UTF8_BYTE_LENGTH * CFStringGetLength(locale_identifier_ref) + 1) * sizeof(char); + char *locale_identifier = (char *)malloc(locale_identifier_size); + dbg_assert(CFStringGetCString(locale_identifier_ref, locale_identifier, locale_identifier_size, kCFStringEncodingUTF8), "CFStringGetCString failure"); + + str_copy(locale, locale_identifier, length); + + free(locale_identifier); + CFRelease(locale_ref); +#else + static const char *ENV_VARIABLES[] = { + "LC_ALL", + "LC_MESSAGES", + "LANG", + }; + + locale[0] = '\0'; + for(const char *env_variable : ENV_VARIABLES) + { + const char *env_value = getenv(env_variable); + if(env_value) + { + str_copy(locale, env_value, length); + break; + } + } +#endif + + // Ensure RFC 3066 format: + // - use hyphens instead of underscores + // - truncate locale string after first non-standard letter + for(int i = 0; i < str_length(locale); ++i) + { + if(locale[i] == '_') + { + locale[i] = '-'; + } + else if(locale[i] != '-' && !(locale[i] >= 'a' && locale[i] <= 'z') && !(locale[i] >= 'A' && locale[i] <= 'Z') && !(locale[i] >= '0' && locale[i] <= '9')) + { + locale[i] = '\0'; + break; + } + } + + // Use default if we could not determine the locale, + // i.e. if only the C or POSIX locale is available. + if(locale[0] == '\0' || str_comp(locale, "C") == 0 || str_comp(locale, "POSIX") == 0) + str_copy(locale, "en-US", length); +} + #if defined(CONF_EXCEPTION_HANDLING) #if defined(CONF_FAMILY_WINDOWS) static HMODULE exception_handling_module = nullptr; diff --git a/src/base/system.h b/src/base/system.h index b6599f448..ee6331f99 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -2143,6 +2143,14 @@ char str_uppercase(char c); int str_isallnum(const char *str); unsigned str_quickhash(const char *str); +enum +{ + /** + * The maximum bytes necessary to encode one Unicode codepoint with UTF-8. + */ + UTF8_BYTE_LENGTH = 4, +}; + int str_utf8_to_skeleton(const char *str, int *buf, int buf_len); /* @@ -2586,6 +2594,20 @@ int secure_rand_below(int below); */ int os_version_str(char *version, int length); +/** + * Returns a string of the preferred locale of the user / operating system. + * The string conforms to [RFC 3066](https://www.ietf.org/rfc/rfc3066.txt) + * and only contains the characters `a`-`z`, `A`-`Z`, `0`-`9` and `-`. + * If the preferred locale could not be determined this function + * falls back to the locale `"en-US"`. + * + * @param locale Buffer to use for the output. + * @param length Length of the output buffer. + * + * @remark The destination buffer will be zero-terminated. + */ +void os_locale_str(char *locale, size_t length); + #if defined(CONF_EXCEPTION_HANDLING) void init_exception_handler(); void set_exception_handler_log_file(const char *log_file_path); diff --git a/src/test/os.cpp b/src/test/os.cpp index 4b83a9acc..21ee5177a 100644 --- a/src/test/os.cpp +++ b/src/test/os.cpp @@ -9,3 +9,10 @@ TEST(Os, VersionStr) EXPECT_FALSE(os_version_str(aVersion, sizeof(aVersion))); EXPECT_STRNE(aVersion, ""); } + +TEST(Os, LocaleStr) +{ + char aLocale[128]; + os_locale_str(aLocale, sizeof(aLocale)); + EXPECT_STRNE(aLocale, ""); +}