diff --git a/gnucash/CMakeLists.txt b/gnucash/CMakeLists.txt index 1b52dd1f5f..32dec4a198 100644 --- a/gnucash/CMakeLists.txt +++ b/gnucash/CMakeLists.txt @@ -34,6 +34,7 @@ endif() set(gnucash_noinst_HEADERS gnucash-commands.hpp gnucash-core-app.hpp + gnucash-locale-platform.h ) set (gnucash_SOURCES @@ -45,7 +46,9 @@ set (gnucash_SOURCES ) if (MINGW) - list(APPEND gnucash_SOURCES "gnucash-windows-locale.c") + list(APPEND gnucash_SOURCES "gnucash-locale-windows.c") +elseif(MAC_INTEGRATION) + list(APPEND gnucash_SOURCES "gnucash-locale-macos.mm") endif() add_executable (gnucash @@ -71,7 +74,10 @@ set(gnucash_cli_SOURCES ) if (MINGW) - list(APPEND gnucash_cli_SOURCES "gnucash-windows-locale.c") + list(APPEND gnucash_cli_SOURCES "gnucash-locale-locale.c") +endif() +if (MAC_INTEGRATION) + list(APPEND gnucash_cli_SOURCES "gnucash-locale-macos.mm") endif() add_executable (gnucash-cli @@ -266,7 +272,7 @@ gnc_add_scheme_targets(price-quotes set_local_dist(gnucash_DIST_local CMakeLists.txt environment.in generate-gnc-script gnucash.cpp gnucash-commands.cpp gnucash-cli.cpp gnucash-core-app.cpp - gnucash-windows-locale.c gnucash.rc.in gnucash-valgrind.in + gnucash-locale-macos.mm gnucash-locale-windows.c gnucash.rc.in gnucash-valgrind.in gnucash-gresources.xml ${gresource_files} price-quotes.scm ${gnucash_noinst_HEADERS} ${gnucash_EXTRA_DIST}) diff --git a/gnucash/gnucash-core-app.cpp b/gnucash/gnucash-core-app.cpp index c589caa597..04aec814d1 100644 --- a/gnucash/gnucash-core-app.cpp +++ b/gnucash/gnucash-core-app.cpp @@ -25,7 +25,6 @@ #include #include #ifdef __MINGW32__ -extern "C" void set_win32_thread_locale(char**); #include #include #endif @@ -46,6 +45,7 @@ extern "C" { #include #include #include +#include "gnucash-locale-platform.h" } #include @@ -63,10 +63,6 @@ static QofLogModule log_module = GNC_MOD_GUI; #include #include -#ifdef MAC_INTEGRATION -# include -#endif - /* GNC_VCS is defined whenever we're building from an svn/svk/git/bzr tree */ #ifdef GNC_VCS static int is_development_version = TRUE; @@ -90,187 +86,6 @@ gnc_print_unstable_message(void) << bl::format (bl::translate ("To find the last stable version, please refer to {1}")) % PACKAGE_URL << "\n"; } -#ifdef MAC_INTEGRATION -static void -mac_set_currency_locale(NSLocale *locale, NSString *locale_str) -{ - /* If the currency doesn't match the base locale, we need to find a locale that does match, because setlocale won't know what to do with just a currency identifier. */ - NSLocale *cur_locale = [ [NSLocale alloc] initWithLocaleIdentifier: locale_str]; - if (![ [locale objectForKey: NSLocaleCurrencyCode] isEqualToString: - [cur_locale objectForKey: NSLocaleCurrencyCode] ]) - { - NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; - NSEnumerator *locale_iter = [all_locales objectEnumerator]; - NSString *this_locale; - NSString *currency = [locale objectForKey: NSLocaleCurrencyCode]; - NSString *money_locale = nil; - while ((this_locale = (NSString*)[locale_iter nextObject])) - { - NSLocale *templocale = [ [NSLocale alloc] - initWithLocaleIdentifier: this_locale]; - if ([ [templocale objectForKey: NSLocaleCurrencyCode] - isEqualToString: currency]) - { - money_locale = this_locale; - [templocale release]; - break; - } - [templocale release]; - } - if (money_locale) - setlocale(LC_MONETARY, [money_locale UTF8String]); - } - [cur_locale release]; -} -/* The locale that we got from AppKit isn't a supported POSIX one, so we need to - * find something close. First see if we can find another locale for the - * country; failing that, try the language. Ultimately fall back on en_US. - */ -static NSString* -mac_find_close_country(NSString *locale_str, NSString *country_str, - NSString *lang_str) -{ - NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; - NSEnumerator *locale_iter = [all_locales objectEnumerator]; - NSString *this_locale, *new_locale = nil; - PWARN("Apple Locale is set to a value %s not supported" - " by the C runtime", [locale_str UTF8String]); - while ((this_locale = [(NSString*)[locale_iter nextObject] stringByAppendingString: @".UTF-8"])) - if ([ [ [NSLocale componentsFromLocaleIdentifier: this_locale] - objectForKey: NSLocaleCountryCode] - isEqualToString: country_str] && - setlocale (LC_ALL, [this_locale UTF8String])) - { - new_locale = this_locale; - break; - } - if (!new_locale) - while ((this_locale = (NSString*)[locale_iter nextObject])) - if ([ [ [NSLocale componentsFromLocaleIdentifier: this_locale] - objectForKey: NSLocaleLanguageCode] - isEqualToString: lang_str] && - setlocale (LC_ALL, [this_locale UTF8String])) - { - new_locale = this_locale; - break; - } - if (new_locale) - locale_str = new_locale; - else - { - locale_str = @"en_US.UTF-8"; - setlocale(LC_ALL, [locale_str UTF8String]); - } - PWARN("Using %s instead.", [locale_str UTF8String]); - return locale_str; -} - -/* Language subgroups (e.g., US English) are reported in the form "ll-SS" - * (e.g. again, "en-US"), not what gettext wants. We convert those to - * old-style locales, which is easy for most cases. There are two where it - * isn't, though: Simplified Chinese (zh-Hans) and traditional Chinese - * (zh-Hant), which are normally assigned the locales zh_CN and zh_TW, - * respectively. Those are handled specially. - */ -static NSString* -mac_convert_complex_language(NSString* this_lang) -{ - NSArray *elements = [this_lang componentsSeparatedByString: @"-"]; - if ([elements count] == 1) - return this_lang; - if ([ [elements objectAtIndex: 0] isEqualToString: @"zh"]) { - if ([ [elements objectAtIndex: 1] isEqualToString: @"Hans"]) - this_lang = @"zh_CN"; - else - this_lang = @"zh_TW"; - } - else - this_lang = [elements componentsJoinedByString: @"_"]; - return this_lang; -} - -static void -mac_set_languages(NSArray* languages, NSString *lang_str) -{ - /* Process the language list. */ - - const gchar *langs = NULL; - NSEnumerator *lang_iter = [languages objectEnumerator]; - NSArray *new_languages = [NSArray array]; - NSString *this_lang = NULL; - NSRange not_found = {NSNotFound, 0}; - while ((this_lang = [lang_iter nextObject])) { - this_lang = [this_lang stringByTrimmingCharactersInSet: - [NSCharacterSet characterSetWithCharactersInString: @"\""] ]; - this_lang = mac_convert_complex_language(this_lang); - new_languages = [new_languages arrayByAddingObject: this_lang]; -/* If it's an English language, add the "C" locale after it so that - * any messages can default to it */ - if (!NSEqualRanges([this_lang rangeOfString: @"en"], not_found)) - new_languages = [new_languages arrayByAddingObject: @"C"]; - if (![new_languages containsObject: lang_str]) { - NSArray *temp_array = [NSArray arrayWithObject: lang_str]; - new_languages = [temp_array arrayByAddingObjectsFromArray: new_languages]; - } - langs = [ [new_languages componentsJoinedByString:@":"] UTF8String]; - } - if (langs && strlen(langs) > 0) - { - PWARN("Language list: %s", langs); - g_setenv("LANGUAGE", langs, TRUE); - } -} - -static void -set_mac_locale() -{ - NSAutoreleasePool *pool = [ [NSAutoreleasePool alloc] init]; - NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; - NSLocale *locale = [NSLocale currentLocale]; - NSString *lang_str, *country_str, *locale_str; - NSArray *languages = [ [defs arrayForKey: @"AppleLanguages"] retain]; - @try - { - lang_str = [locale objectForKey: NSLocaleLanguageCode]; - country_str = [locale objectForKey: NSLocaleCountryCode]; - locale_str = [ [ [lang_str stringByAppendingString: @"_"] - stringByAppendingString: country_str] - stringByAppendingString: @".UTF-8"]; - } - @catch (NSException *err) - { - PWARN("Locale detection raised error %s: %s. " - "Check that your locale settings in " - "System Preferences>Languages & Text are set correctly.", - [ [err name] UTF8String], [ [err reason] UTF8String]); - locale_str = @"_"; - } -/* If we didn't get a valid current locale, the string will be just "_" */ - if ([locale_str isEqualToString: @"_"]) - locale_str = @"en_US.UTF-8"; - - lang_str = mac_convert_complex_language(lang_str); - if (!setlocale(LC_ALL, [locale_str UTF8String])) - locale_str = mac_find_close_country(locale_str, country_str, lang_str); - if (g_getenv("LANG") == NULL) - g_setenv("LANG", [locale_str UTF8String], TRUE); - mac_set_currency_locale(locale, locale_str); -/* Now call gnc_localeconv() to force creation of the app locale - * before another call to setlocale messes it up. */ - gnc_localeconv (); - /* Process the languages, including the one from the Apple locale. */ - if (g_getenv("LANGUAGE") == NULL) - { - if ([languages count] > 0) - mac_set_languages(languages, lang_str); - else - g_setenv("LANGUAGE", [lang_str UTF8String], TRUE); - } - [languages release]; - [pool drain]; -} -#endif /* MAC_INTEGRATION */ - static gboolean try_load_config_array(const gchar *fns[]) { @@ -415,10 +230,8 @@ Gnucash::CoreApp::CoreApp () * The user may have configured a different language via * the environment file. */ - #ifdef MAC_INTEGRATION - set_mac_locale(); - #elif defined __MINGW32__ - set_win32_thread_locale(&sys_locale); + #if defined MAC_INTEGRATION || defined __MINGW32__ + sys_locale = set_platform_locale(); #endif gnc_environment_setup(); #if ! defined MAC_INTEGRATION && ! defined __MINGW32__/* setlocale already done */ diff --git a/gnucash/gnucash-locale-macos.mm b/gnucash/gnucash-locale-macos.mm new file mode 100644 index 0000000000..dc33b596c0 --- /dev/null +++ b/gnucash/gnucash-locale-macos.mm @@ -0,0 +1,222 @@ +/* + * gnucash-mac-locale.mm -- Macos specific locale handling + * + * Copyright (C) 2020 John Ralls + * Copyright (C) 2021 Geert Janssens + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ +#include + +#include +#include +#include +#include + +extern "C" { +#include +#include "gnucash-locale-platform.h" +} + +/* This static indicates the debugging module that this .o belongs to. */ +static QofLogModule log_module = GNC_MOD_GUI; + +static void +mac_set_currency_locale(NSLocale *locale, NSString *locale_str) +{ + /* If the currency doesn't match the base locale, we need to find a locale that does match, because setlocale won't know what to do with just a currency identifier. */ + NSLocale *cur_locale = [ [NSLocale alloc] initWithLocaleIdentifier: locale_str]; + if (![ [locale objectForKey: NSLocaleCurrencyCode] isEqualToString: + [cur_locale objectForKey: NSLocaleCurrencyCode] ]) + { + NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; + NSEnumerator *locale_iter = [all_locales objectEnumerator]; + NSString *this_locale; + NSString *currency = [locale objectForKey: NSLocaleCurrencyCode]; + NSString *money_locale = nil; + while ((this_locale = (NSString*)[locale_iter nextObject])) + { + NSLocale *templocale = [ [NSLocale alloc] + initWithLocaleIdentifier: this_locale]; + if ([ [templocale objectForKey: NSLocaleCurrencyCode] + isEqualToString: currency]) + { + money_locale = this_locale; + [templocale release]; + break; + } + [templocale release]; + } + if (money_locale) + setlocale(LC_MONETARY, [money_locale UTF8String]); + } + [cur_locale release]; +} +/* The locale that we got from AppKit isn't a supported POSIX one, so we need to + * find something close. First see if we can find another locale for the + * country; failing that, try the language. Ultimately fall back on en_US. + */ +static NSString* +mac_find_close_country(NSString *locale_str, NSString *country_str, + NSString *lang_str) +{ + NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; + NSEnumerator *locale_iter = [all_locales objectEnumerator]; + NSString *this_locale, *new_locale = nil; + PWARN("Apple Locale is set to a value %s not supported" + " by the C runtime", [locale_str UTF8String]); + while ((this_locale = [(NSString*)[locale_iter nextObject] stringByAppendingString: @".UTF-8"])) + if ([ [ [NSLocale componentsFromLocaleIdentifier: this_locale] + objectForKey: NSLocaleCountryCode] + isEqualToString: country_str] && + setlocale (LC_ALL, [this_locale UTF8String])) + { + new_locale = this_locale; + break; + } + if (!new_locale) + while ((this_locale = (NSString*)[locale_iter nextObject])) + if ([ [ [NSLocale componentsFromLocaleIdentifier: this_locale] + objectForKey: NSLocaleLanguageCode] + isEqualToString: lang_str] && + setlocale (LC_ALL, [this_locale UTF8String])) + { + new_locale = this_locale; + break; + } + if (new_locale) + locale_str = new_locale; + else + { + locale_str = @"en_US.UTF-8"; + setlocale(LC_ALL, [locale_str UTF8String]); + } + PWARN("Using %s instead.", [locale_str UTF8String]); + return locale_str; +} + +/* Language subgroups (e.g., US English) are reported in the form "ll-SS" + * (e.g. again, "en-US"), not what gettext wants. We convert those to + * old-style locales, which is easy for most cases. There are two where it + * isn't, though: Simplified Chinese (zh-Hans) and traditional Chinese + * (zh-Hant), which are normally assigned the locales zh_CN and zh_TW, + * respectively. Those are handled specially. + */ +static NSString* +mac_convert_complex_language(NSString* this_lang) +{ + NSArray *elements = [this_lang componentsSeparatedByString: @"-"]; + if ([elements count] == 1) + return this_lang; + if ([ [elements objectAtIndex: 0] isEqualToString: @"zh"]) { + if ([ [elements objectAtIndex: 1] isEqualToString: @"Hans"]) + this_lang = @"zh_CN"; + else + this_lang = @"zh_TW"; + } + else + this_lang = [elements componentsJoinedByString: @"_"]; + return this_lang; +} + +static void +mac_set_languages(NSArray* languages, NSString *lang_str) +{ + /* Process the language list. */ + + const char *langs = NULL; + NSEnumerator *lang_iter = [languages objectEnumerator]; + NSArray *new_languages = [NSArray array]; + NSString *this_lang = NULL; + NSRange not_found = {NSNotFound, 0}; + while ((this_lang = [lang_iter nextObject])) { + this_lang = [this_lang stringByTrimmingCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString: @"\""] ]; + this_lang = mac_convert_complex_language(this_lang); + new_languages = [new_languages arrayByAddingObject: this_lang]; +/* If it's an English language, add the "C" locale after it so that + * any messages can default to it */ + if (!NSEqualRanges([this_lang rangeOfString: @"en"], not_found)) + new_languages = [new_languages arrayByAddingObject: @"C"]; + if (![new_languages containsObject: lang_str]) { + NSArray *temp_array = [NSArray arrayWithObject: lang_str]; + new_languages = [temp_array arrayByAddingObjectsFromArray: new_languages]; + } + langs = [ [new_languages componentsJoinedByString:@":"] UTF8String]; + } + if (langs && strlen(langs) > 0) + { + PWARN("Language list: %s", langs); + g_setenv("LANGUAGE", langs, TRUE); + } +} + +char * +set_platform_locale() +{ + NSAutoreleasePool *pool = [ [NSAutoreleasePool alloc] init]; + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + NSLocale *locale = [NSLocale currentLocale]; + NSString *lang_str, *country_str, *locale_str; + NSArray *languages = [ [defs arrayForKey: @"AppleLanguages"] retain]; + char *gnc_locale = NULL; + @try + { + lang_str = [locale objectForKey: NSLocaleLanguageCode]; + country_str = [locale objectForKey: NSLocaleCountryCode]; + locale_str = [ [ [lang_str stringByAppendingString: @"_"] + stringByAppendingString: country_str] + stringByAppendingString: @".UTF-8"]; + } + @catch (NSException *err) + { + PWARN("Locale detection raised error %s: %s. " + "Check that your locale settings in " + "System Preferences>Languages & Text are set correctly.", + [ [err name] UTF8String], [ [err reason] UTF8String]); + locale_str = @"_"; + } +/* If we didn't get a valid current locale, the string will be just "_" */ + if ([locale_str isEqualToString: @"_"]) + locale_str = @"en_US.UTF-8"; + + lang_str = mac_convert_complex_language(lang_str); + if (!setlocale(LC_ALL, [locale_str UTF8String])) + locale_str = mac_find_close_country(locale_str, country_str, lang_str); +/* Cache the final locale string to be returned to the calling program */ + gnc_locale = g_strdup ([locale_str UTF8String]); + + if (g_getenv("LANG") == NULL) + g_setenv("LANG", [locale_str UTF8String], TRUE); + mac_set_currency_locale(locale, locale_str); + +/* Now call gnc_localeconv() to force creation of the app locale + * before another call to setlocale messes it up. */ + gnc_localeconv (); + /* Process the languages, including the one from the Apple locale. */ + if (g_getenv("LANGUAGE") == NULL) + { + if ([languages count] > 0) + mac_set_languages(languages, lang_str); + else + g_setenv("LANGUAGE", [lang_str UTF8String], TRUE); + } + [languages release]; + [pool drain]; + return gnc_locale; +} diff --git a/gnucash/gnucash-locale-platform.h b/gnucash/gnucash-locale-platform.h new file mode 100644 index 0000000000..1eab7f8daa --- /dev/null +++ b/gnucash/gnucash-locale-platform.h @@ -0,0 +1,30 @@ +/* + * gnucash-locale-platform.h -- Common header for platform specific locale handling + * + * Copyright (C) 2021 Geert Janssens + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ + + +#ifndef GNUCASH_LOCALE_PLATFORM_H +#define GNUCASH_LOCALE_PLATFORM_H + +char *set_platform_locale(); + +#endif diff --git a/gnucash/gnucash-windows-locale.c b/gnucash/gnucash-locale-windows.c similarity index 61% rename from gnucash/gnucash-windows-locale.c rename to gnucash/gnucash-locale-windows.c index 95709a9aa8..119bfd3acc 100644 --- a/gnucash/gnucash-windows-locale.c +++ b/gnucash/gnucash-locale-windows.c @@ -1,7 +1,8 @@ /* - * gnucash-core-app.cpp -- Basic application object for gnucash binaries + * gnucash-locale-windows.c -- Windows specific locale handling * * Copyright (C) 2020 John Ralls + * Copyright (C) 2021 Geert Janssens * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -24,17 +25,15 @@ #include #include #include - -//sacrificial prototype -void set_win32_thread_locale(char **sys_locale); +#include "gnucash-locale-platform.h" /* If one of the Unix locale variables LC_ALL, LC_MESSAGES, or LANG is * set in the environment check to see if it's a valid locale and if * it is set both the Windows and POSIX locales to that. If not * retrieve the Windows locale and set POSIX to match. */ -void -set_win32_thread_locale(char **sys_locale) +char *void +set_platform_locale() { WCHAR lpLocaleName[LOCALE_NAME_MAX_LENGTH]; char *locale = NULL; @@ -43,31 +42,30 @@ set_win32_thread_locale(char **sys_locale) ((locale = getenv ("LC_MESSAGES")) != NULL && locale[0] != '\0') || ((locale = getenv ("LANG")) != NULL && locale[0] != '\0')) { - gunichar2* wlocale = NULL; - int len = 0; - len = strchr(locale, '.') - locale; - locale[2] = '-'; - wlocale = g_utf8_to_utf16 (locale, len, NULL, NULL, NULL); - if (IsValidLocaleName(wlocale)) - { - LCID lcid = LocaleNameToLCID(wlocale, LOCALE_ALLOW_NEUTRAL_NAMES); - SetThreadLocale(lcid); - locale[2] = '_'; - setlocale (LC_ALL, locale); - *sys_locale = g_strdup (locale); - g_free(wlocale); - return; - } - g_free(locale); - g_free(wlocale); + gunichar2* wlocale = NULL; + int len = 0; + len = strchr(locale, '.') - locale; + locale[2] = '-'; + wlocale = g_utf8_to_utf16 (locale, len, NULL, NULL, NULL); + if (IsValidLocaleName(wlocale)) + { + LCID lcid = LocaleNameToLCID(wlocale, LOCALE_ALLOW_NEUTRAL_NAMES); + SetThreadLocale(lcid); + locale[2] = '_'; + setlocale (LC_ALL, locale); + g_free(wlocale); + return g_strdup (locale); + } + g_free(locale); + g_free(wlocale); } if (GetUserDefaultLocaleName(lpLocaleName, LOCALE_NAME_MAX_LENGTH)) { - *sys_locale = g_utf16_to_utf8((gunichar2*)lpLocaleName, - LOCALE_NAME_MAX_LENGTH, - NULL, NULL, NULL); - (*sys_locale)[2] = '_'; - setlocale (LC_ALL, *sys_locale); - return; + locale = g_utf16_to_utf8((gunichar2*)lpLocaleName, + LOCALE_NAME_MAX_LENGTH, + NULL, NULL, NULL); + (locale)[2] = '_'; + setlocale (LC_ALL, locale); + return locale; } } diff --git a/po/POTFILES.in b/po/POTFILES.in index 09c6ccc9bc..4f2cf84e1d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -216,7 +216,7 @@ gnucash/gnucash-cli.cpp gnucash/gnucash-commands.cpp gnucash/gnucash-core-app.cpp gnucash/gnucash.cpp -gnucash/gnucash-windows-locale.c +gnucash/gnucash-locale-windows.c gnucash/gschemas/org.gnucash.dialogs.business.gschema.xml.in gnucash/gschemas/org.gnucash.dialogs.checkprinting.gschema.xml.in gnucash/gschemas/org.gnucash.dialogs.commodities.gschema.xml.in