NSUserDefaults: Retrieve native UI languages from Windows and Android System APIs (#426)

* Create NSString Win32Additions Category

* NSUserDefaults: Fetch Windows UI language information

* NSUserDefaults: Add winnls include

* Android Native UI Language

* NSUserDefaults: Replace incorrect separator on Windows

* NSProcessInfo: BCP-47 Note

* GSConfig: Bump MinGW WINVER to Vista

* NSString+Win32Additions: Do not add array to arp twice

* NSUserDefaults: Increase default length
This commit is contained in:
Hugo Melder 2024-08-07 16:26:16 +02:00 committed by GitHub
parent 726777bf6e
commit 81b3c721bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 229 additions and 4 deletions

View file

@ -304,7 +304,7 @@ typedef struct {
# if defined(__MINGW__)
# include <w32api.h>
# define GS_WINVER Windows2000
# define GS_WINVER WindowsVista
# elif defined(_MSC_VER)
# include <WinSDKVer.h>
# define GS_WINVER _WIN32_WINNT_WIN10

View file

@ -1605,7 +1605,45 @@ GSInitializeProcessAndroid(JNIEnv *env, jobject context)
}
}
// get current time zone
char *localeList = NULL;
#if __ANDROID_API__ >= 24
// get locales ordered by user preference
jclass localeListCls = (*env)->FindClass(env, "android/os/LocaleList");
jmethodID localeListGetDefaultMethod = (*env)->GetStaticMethodID(env, localeListCls, "getDefault", "()Landroid/os/LocaleList;");
jobject localeListObj = (*env)->CallStaticObjectMethod(env, localeListCls, localeListGetDefaultMethod);
if (localeCls) {
// Retrieve string representation of the locale list
jmethodID localeListToLanguageTagsMethod = (*env)->GetMethodID(env, localeListCls, "toLanguageTags", "()Ljava/lang/String;");
jstring localeListJava = (*env)->CallObjectMethod(env, localeListObj, localeListToLanguageTagsMethod);
if (localeListJava) {
const char *localeListOrig = (*env)->GetStringUTFChars(env, localeIdJava, NULL);
// Some devices return with it enclosed in []'s so check if both exists before
// removing to ensure it is formatted correctly
if (localeListOrig[0] == '[' && localeListOrig[strlen(localeListOrig) - 1] == ']') {
localeList = strdup(localeListOrig + 1);
localeList[strlen(localeList) - 1] = '\0';
} else {
localeList = strdup(localeListOrig);
}
// NOTE: This is an IETF BCP 47 language tag and may not correspond exactly tocorrespond ll-CC format
// e.g. gsw-u-sd-chzh is a valid BCP 47 language tag, but uses an ISO 639-3 subtag to classify the language.
// There is no easy fix to this, as we use ISO 639-2 subtags internally.
for (int i = 0; localeList[i]; i++) {
if (localeList[i] == '-') {
localeList[i] = '_';
}
}
(*env)->ReleaseStringUTFChars(env, localeListJava, localeListOrig);
}
}
#endif
jclass timezoneCls = (*env)->FindClass(env, "java/util/TimeZone");
jmethodID timezoneDefaultMethod = (*env)->GetStaticMethodID(env, timezoneCls, "getDefault", "()Ljava/util/TimeZone;");
jmethodID timezoneIdMethod = (*env)->GetMethodID(env, timezoneCls, "getID", "()Ljava/lang/String;");
@ -1613,20 +1651,28 @@ GSInitializeProcessAndroid(JNIEnv *env, jobject context)
jstring timezoneIdJava = (*env)->CallObjectMethod(env, timezoneObj, timezoneIdMethod);
const char *timezoneId = (*env)->GetStringUTFChars(env, timezoneIdJava, NULL);
char *localeListValue = "";
if (localeList) {
localeListValue = localeList;
}
// initialize process with these options
char *argv[] = {
arg0,
"-Locale", localeId,
"-Local Time Zone", (char *)timezoneId,
"-GSAndroidLocaleList", localeListValue,
"-GSLogSyslog", "YES" // use syslog (available via logcat) instead of stdout/stderr (not available on Android)
};
GSInitializeProcessAndroidWithArgs(env, context, sizeof(argv)/sizeof(char *), argv, NULL);
free(arg0);
free(localeId);
free(localeList);
(*env)->ReleaseStringUTFChars(env, packageCodePathJava, packageCodePath);
(*env)->ReleaseStringUTFChars(env, packageNameJava, packageName);
(*env)->ReleaseStringUTFChars(env, localeIdJava, localeId);
(*env)->ReleaseStringUTFChars(env, localeIdJava, localeIdOrig);
(*env)->ReleaseStringUTFChars(env, timezoneIdJava, timezoneId);
}

View file

@ -56,6 +56,10 @@
#import "GNUstepBase/NSString+GNUstepBase.h"
#if defined(_WIN32)
#import "win32/NSString+Win32Additions.h"
#include <winnls.h>
/* Fake interface to avoid compiler warnings
*/
@interface NSUserDefaultsWin32 : NSUserDefaults
@ -355,6 +359,94 @@ systemLanguages()
{
NSMutableArray *names = [NSMutableArray arrayWithCapacity: 10];
#ifdef WIN32
NSEnumerator *enumerator;
NSArray *languages;
NSString *locale;
BOOL ret;
unsigned long numberOfLanguages = 0;
unsigned long length = 7;
unsigned long factor = sizeof(wchar_t);
wchar_t *buffer = malloc(length * factor);
if (!buffer)
{
return names;
}
/* Returns a wchar_t list of languages in the form ll-CC, where ll is the
* two-letter language code, and CC is the two-letter country code.
*/
ret = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages,
buffer, &length);
if (!ret)
{
length = 0;
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages,
NULL, &length)) {
wchar_t *oldBuffer = buffer;
buffer = realloc(buffer, length * factor);
if (!buffer)
{
free(oldBuffer);
return names;
}
ret = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, buffer, &length);
if (!ret)
{
free(buffer);
return names;
}
}
}
languages = [NSString arrayFromWCharList:buffer length:length];
enumerator = [languages objectEnumerator];
free(buffer);
while (nil != (locale = [enumerator nextObject]))
{
/* Replace "-" Separator with "_" */
locale = [locale stringByReplacingOccurrencesOfString:@"-" withString:@"_"];
[names addObjectsFromArray: GSLanguagesFromLocale(locale)];
}
#elif defined(__ANDROID__)
// When running on Android, the process must be correctly initialized
// with GSInitializeProcessAndroid (See NSProcessInfo).
//
// If the minimum API level is 24 or higher, the user-prefered locales
// are retrieved from the Android system and passed as GSAndroidLocaleList
// process argument
NSArray *args = [[NSProcessInfo processInfo] arguments];
NSEnumerator *enumerator = [args objectEnumerator];
NSString *key = nil;
NSString *localeList = nil;
[enumerator nextObject]; // Skip process name.
while (nil != (key = [enumerator nextObject]))
{
if ([key isEqualToString:@"-GSAndroidLocaleList"])
{
localeList = [enumerator nextObject];
break;
}
}
// The locale list is a comma-separated list of locales of form ll-CC
if (localeList != nil)
{
NSString *locale;
NSArray *locales = [localeList componentsSeparatedByString: @","];
enumerator = [locales objectEnumerator];
while (nil != (locale = [enumerator nextObject]))
{
[names addObjectsFromArray: GSLanguagesFromLocale(locale)];
}
}
#else
// Add the languages listed in the LANGUAGE environment variable
// (a non-POSIX GNU extension)
{
@ -371,7 +463,7 @@ systemLanguages()
}
}
}
// If LANGUAGES did not yield any languages, try LC_MESSAGES
if ([names count] == 0)
@ -383,6 +475,7 @@ systemLanguages()
[names addObjectsFromArray: GSLanguagesFromLocale(locale)];
}
}
#endif
return names;
}

View file

@ -37,6 +37,7 @@ win32_OBJC_FILES =\
NSMessagePortNameServer.m \
NSStream.m \
NSUserDefaults.m \
NSString+Win32Additions.m\
-include Makefile.preamble

View file

@ -0,0 +1,40 @@
/** Category for converting Windows Strings
Copyright (C) 1998 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Created: July 2024
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110 USA.
*/
#import <Foundation/NSString.h>
#include <wchar.h>
/**
* Converts a wchar_t list to an array of strings.
* The list is NULL-delimited and terminated by two NULL (wchar_t) characters.
*
* The encoding is Unicode (UTF-16LE).
*/
@interface NSString (Win32Additions)
+ (GS_GENERIC_CLASS(NSArray, NSString *) *) arrayFromWCharList: (wchar_t *)list
length: (unsigned long)length;
@end

View file

@ -0,0 +1,45 @@
/** Category for converting Windows Strings
Copyright (C) 1998 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Created: July 2024
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110 USA.
*/
#import "NSString+Win32Additions.h"
@implementation NSString (Win32Additions)
+ (GS_GENERIC_CLASS(NSArray, NSString *) *) arrayFromWCharList: (wchar_t *)list
length: (unsigned long)length
{
NSString *string;
GS_GENERIC_CLASS(NSArray, NSString *) * array;
string = [[NSString alloc] initWithBytes: list
length: (length - 2) * sizeof(wchar_t)
encoding: NSUTF16LittleEndianStringEncoding];
array = [string componentsSeparatedByString: @"\0"];
RELEASE(string);
return array;
}
@end