From 73680542b2d379276f279c59277fc9b73f018fc5 Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Tue, 17 Oct 2023 11:44:02 +0200 Subject: [PATCH 1/5] Get available locales hybrid mode --- .../Common/src/Interop/Interop.Locale.iOS.cs | 3 ++ .../System/Globalization/CultureData.Icu.cs | 17 +++++++- .../System.Globalization.Native/entrypoints.c | 1 + .../System.Globalization.Native/pal_locale.h | 2 + .../System.Globalization.Native/pal_locale.m | 43 +++++++++++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/Interop.Locale.iOS.cs b/src/libraries/Common/src/Interop/Interop.Locale.iOS.cs index 0f7b1763d5850..6725436b4f094 100644 --- a/src/libraries/Common/src/Interop/Interop.Locale.iOS.cs +++ b/src/libraries/Common/src/Interop/Interop.Locale.iOS.cs @@ -24,5 +24,8 @@ internal static partial class Globalization [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleTimeFormatNative", StringMarshalling = StringMarshalling.Utf8)] internal static partial string GetLocaleTimeFormatNative(string localeName, [MarshalAs(UnmanagedType.Bool)] bool shortFormat); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocalesNative", StringMarshalling = StringMarshalling.Utf16)] + internal static partial int GetLocalesNative([Out] char[]? value, int valueLength); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 0d4dad112249c..226cedcf648d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -420,7 +420,15 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) return Array.Empty(); } - int bufferLength = Interop.Globalization.GetLocales(null, 0); + int bufferLength; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + bufferLength = Interop.Globalization.GetLocalesNative(null, 0); + else + bufferLength = Interop.Globalization.GetLocales(null, 0); +#else + bufferLength = Interop.Globalization.GetLocales(null, 0); +#endif if (bufferLength <= 0) { return Array.Empty(); @@ -428,7 +436,14 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) char [] chars = new char[bufferLength]; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + bufferLength = Interop.Globalization.GetLocalesNative(chars, bufferLength); + else + bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); +#else bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); +#endif if (bufferLength <= 0) { return Array.Empty(); diff --git a/src/native/libs/System.Globalization.Native/entrypoints.c b/src/native/libs/System.Globalization.Native/entrypoints.c index 1cae8c378b828..1f345991505fa 100644 --- a/src/native/libs/System.Globalization.Native/entrypoints.c +++ b/src/native/libs/System.Globalization.Native/entrypoints.c @@ -72,6 +72,7 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative) DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative) DllImportEntry(GlobalizationNative_GetLocaleNameNative) + DllImportEntry(GlobalizationNative_GetLocalesNative) DllImportEntry(GlobalizationNative_GetLocaleTimeFormatNative) DllImportEntry(GlobalizationNative_GetTimeZoneDisplayNameNative) DllImportEntry(GlobalizationNative_IndexOfNative) diff --git a/src/native/libs/System.Globalization.Native/pal_locale.h b/src/native/libs/System.Globalization.Native/pal_locale.h index 7fe89f667f213..4a1fe0768e4fd 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.h +++ b/src/native/libs/System.Globalization.Native/pal_locale.h @@ -21,4 +21,6 @@ PALEXPORT int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeNam PALEXPORT const char* GlobalizationNative_GetLocaleNameNative(const char* localeName); PALEXPORT const char* GlobalizationNative_GetLocaleTimeFormatNative(const char* localeName, int shortFormat); + +PALEXPORT int32_t GlobalizationNative_GetLocalesNative(UChar* locales, int32_t length); #endif diff --git a/src/native/libs/System.Globalization.Native/pal_locale.m b/src/native/libs/System.Globalization.Native/pal_locale.m index d8ab7da1fbee0..da19c48317cf4 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.m +++ b/src/native/libs/System.Globalization.Native/pal_locale.m @@ -667,6 +667,49 @@ Returns time format information (in native format, it needs to be converted to . } } +// GlobalizationNative_GetLocalesNative gets all locale names and store it in the value buffer +// in case of success, it returns the count of the characters stored in value buffer +// in case of failure, it returns negative number. +// if the input value buffer is null, it returns the length needed to store the +// locale names list. +// if the value is not null, it fills the value with locale names separated by the length +// of each name. +int32_t GlobalizationNative_GetLocalesNative(UChar* value, int32_t length) +{ + @autoreleasepool + { + NSArray* availableLocaleIdentifiers = [NSLocale availableLocaleIdentifiers]; + int32_t index = 0; + int32_t totalLength = 0; + for (NSInteger i = 0; i < [availableLocaleIdentifiers count]; i++) + { + NSString *localeIdentifier = availableLocaleIdentifiers[i]; + int32_t localeNameLength = localeIdentifier.length; + totalLength += localeNameLength + 1; // add 1 for the name length + if (value != NULL) + { + if (totalLength > length) + return -3; + + value[index++] = (UChar) localeNameLength; + + for (int j = 0; j < localeNameLength; j++) + { + if ((UChar)[localeIdentifier characterAtIndex:j] == '_') + { + value[index++] = (UChar) '-'; + } + else + { + value[index++] = (UChar) [localeIdentifier characterAtIndex:j]; + } + } + } + } + return totalLength; + } +} + #endif #if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS) From e75ef703bacf0cc5f7d802551d18c04f443408c0 Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Tue, 17 Oct 2023 16:20:44 +0200 Subject: [PATCH 2/5] Fix the exception when value is empty --- .../System.Globalization.Native/pal_locale.m | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/src/native/libs/System.Globalization.Native/pal_locale.m b/src/native/libs/System.Globalization.Native/pal_locale.m index da19c48317cf4..7f50f4af417e2 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.m +++ b/src/native/libs/System.Globalization.Native/pal_locale.m @@ -97,7 +97,7 @@ static void GetParent(const char* localeID, char* parent, int32_t parentCapacity { @autoreleasepool { - const char* value; + NSString *value; NSString *locName = [NSString stringWithFormat:@"%s", localeName]; NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; @@ -112,35 +112,35 @@ static void GetParent(const char* localeID, char* parent, int32_t parentCapacity case LocaleString_LocalizedDisplayName: /// Display name (language + country usually) in English, eg "German (Germany)" (corresponds to LOCALE_SENGLISHDISPLAYNAME) case LocaleString_EnglishDisplayName: - value = [[gbLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier] UTF8String]; - break; + value = [gbLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier]; + break; /// Display name in native locale language, eg "Deutsch (Deutschland) (corresponds to LOCALE_SNATIVEDISPLAYNAME) case LocaleString_NativeDisplayName: - value = [[currentLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier] UTF8String]; + value = [currentLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier]; break; /// Language Display Name for a language, eg "German" in UI language (corresponds to LOCALE_SLOCALIZEDLANGUAGENAME) case LocaleString_LocalizedLanguageName: /// English name of language, eg "German" (corresponds to LOCALE_SENGLISHLANGUAGENAME) case LocaleString_EnglishLanguageName: - value = [[gbLocale localizedStringForLanguageCode:currentLocale.languageCode] UTF8String]; + value = [gbLocale localizedStringForLanguageCode:currentLocale.languageCode]; break; /// native name of language, eg "Deutsch" (corresponds to LOCALE_SNATIVELANGUAGENAME) case LocaleString_NativeLanguageName: - value = [[currentLocale localizedStringForLanguageCode:currentLocale.languageCode] UTF8String]; + value = [currentLocale localizedStringForLanguageCode:currentLocale.languageCode]; break; /// English name of country, eg "Germany" (corresponds to LOCALE_SENGLISHCOUNTRYNAME) case LocaleString_EnglishCountryName: - value = [[gbLocale localizedStringForCountryCode:currentLocale.countryCode] UTF8String]; + value = [gbLocale localizedStringForCountryCode:currentLocale.countryCode]; break; /// native name of country, eg "Deutschland" (corresponds to LOCALE_SNATIVECOUNTRYNAME) case LocaleString_NativeCountryName: - value = [[currentLocale localizedStringForCountryCode:currentLocale.countryCode] UTF8String]; + value = [currentLocale localizedStringForCountryCode:currentLocale.countryCode]; break; case LocaleString_ThousandSeparator: - value = [currentLocale.groupingSeparator UTF8String]; + value = currentLocale.groupingSeparator; break; case LocaleString_DecimalSeparator: - value = [currentLocale.decimalSeparator UTF8String]; + value = currentLocale.decimalSeparator; // or value = [[currentLocale objectForKey:NSLocaleDecimalSeparator] UTF8String]; break; case LocaleString_Digits: @@ -150,87 +150,84 @@ static void GetParent(const char* localeID, char* parent, int32_t parentCapacity [nf1 setLocale:currentLocale]; NSNumber *newNum = [nf1 numberFromString:digitsString]; - value = [[newNum stringValue] UTF8String]; + value = [newNum stringValue]; break; } case LocaleString_MonetarySymbol: - value = [currentLocale.currencySymbol UTF8String]; + value = currentLocale.currencySymbol; break; case LocaleString_Iso4217MonetarySymbol: // check if this is correct, check currencyISOCode - value = [currentLocale.currencySymbol UTF8String]; + value = currentLocale.currencySymbol; break; case LocaleString_CurrencyEnglishName: - value = [[gbLocale localizedStringForCurrencyCode:currentLocale.currencyCode] UTF8String]; + value = [gbLocale localizedStringForCurrencyCode:currentLocale.currencyCode]; break; case LocaleString_CurrencyNativeName: - value = [[currentLocale localizedStringForCurrencyCode:currentLocale.currencyCode] UTF8String]; + value = [currentLocale localizedStringForCurrencyCode:currentLocale.currencyCode]; break; case LocaleString_MonetaryDecimalSeparator: - value = [numberFormatter.currencyDecimalSeparator UTF8String]; + value = numberFormatter.currencyDecimalSeparator; break; case LocaleString_MonetaryThousandSeparator: - value = [numberFormatter.currencyGroupingSeparator UTF8String]; + value = numberFormatter.currencyGroupingSeparator; break; case LocaleString_AMDesignator: - value = [dateFormatter.AMSymbol UTF8String]; + value = dateFormatter.AMSymbol; break; case LocaleString_PMDesignator: - value = [dateFormatter.PMSymbol UTF8String]; + value = dateFormatter.PMSymbol; break; case LocaleString_PositiveSign: - value = [numberFormatter.plusSign UTF8String]; + value = numberFormatter.plusSign; break; case LocaleString_NegativeSign: - value = [numberFormatter.minusSign UTF8String]; + value = numberFormatter.minusSign; break; case LocaleString_Iso639LanguageTwoLetterName: - value = [[currentLocale objectForKey:NSLocaleLanguageCode] UTF8String]; + value = [currentLocale objectForKey:NSLocaleLanguageCode]; break; case LocaleString_Iso639LanguageThreeLetterName: { NSString *iso639_2 = [currentLocale objectForKey:NSLocaleLanguageCode]; - value = uloc_getISO3LanguageByLangCode([iso639_2 UTF8String]); - break; + return iso639_2 == nil ? strdup("") : strdup(uloc_getISO3LanguageByLangCode([iso639_2 UTF8String])); } case LocaleString_Iso3166CountryName: - value = [[currentLocale objectForKey:NSLocaleCountryCode] UTF8String]; + value = [currentLocale objectForKey:NSLocaleCountryCode]; break; case LocaleString_Iso3166CountryName2: { - const char *countryCode = strdup([[currentLocale objectForKey:NSLocaleCountryCode] UTF8String]); - value = uloc_getISO3CountryByCountryCode(countryCode); - break; + NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode]; + return countryCode == nil ? strdup("") : strdup(uloc_getISO3CountryByCountryCode([countryCode UTF8String])); } case LocaleString_NaNSymbol: - value = [numberFormatter.notANumberSymbol UTF8String]; + value = numberFormatter.notANumberSymbol; break; case LocaleString_PositiveInfinitySymbol: - value = [numberFormatter.positiveInfinitySymbol UTF8String]; + value = numberFormatter.positiveInfinitySymbol; break; case LocaleString_NegativeInfinitySymbol: - value = [numberFormatter.negativeInfinitySymbol UTF8String]; + value = numberFormatter.negativeInfinitySymbol; break; case LocaleString_PercentSymbol: - value = [numberFormatter.percentSymbol UTF8String]; + value = numberFormatter.percentSymbol; break; case LocaleString_PerMilleSymbol: - value = [numberFormatter.perMillSymbol UTF8String]; + value = numberFormatter.perMillSymbol; break; case LocaleString_ParentName: { char localeNameTemp[FULLNAME_CAPACITY]; const char* lName = [currentLocale.localeIdentifier UTF8String]; GetParent(lName, localeNameTemp, FULLNAME_CAPACITY); - value = strdup(localeNameTemp); - break; + return strdup(localeNameTemp); } default: - value = ""; + value = nil; break; } - return value ? strdup(value) : ""; + return value == nil ? strdup("") : strdup([value UTF8String]); } } From 280cd46dfdfab5349670d466fee05ea80e0fa57e Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Thu, 19 Oct 2023 11:16:13 +0200 Subject: [PATCH 3/5] Apply changes requested by review,fix currencyCode --- .../src/System/Globalization/CultureData.Icu.cs | 8 ++++---- src/native/libs/System.Globalization.Native/pal_locale.m | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 226cedcf648d8..72449d7542b22 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -422,9 +422,9 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) int bufferLength; #if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS - if (GlobalizationMode.Hybrid) + if (GlobalizationMode.Hybrid) bufferLength = Interop.Globalization.GetLocalesNative(null, 0); - else + else bufferLength = Interop.Globalization.GetLocales(null, 0); #else bufferLength = Interop.Globalization.GetLocales(null, 0); @@ -437,9 +437,9 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) char [] chars = new char[bufferLength]; #if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS - if (GlobalizationMode.Hybrid) + if (GlobalizationMode.Hybrid) bufferLength = Interop.Globalization.GetLocalesNative(chars, bufferLength); - else + else bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); #else bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); diff --git a/src/native/libs/System.Globalization.Native/pal_locale.m b/src/native/libs/System.Globalization.Native/pal_locale.m index 7f50f4af417e2..4789ac89691da 100644 --- a/src/native/libs/System.Globalization.Native/pal_locale.m +++ b/src/native/libs/System.Globalization.Native/pal_locale.m @@ -158,7 +158,7 @@ static void GetParent(const char* localeID, char* parent, int32_t parentCapacity break; case LocaleString_Iso4217MonetarySymbol: // check if this is correct, check currencyISOCode - value = currentLocale.currencySymbol; + value = currentLocale.currencyCode; break; case LocaleString_CurrencyEnglishName: value = [gbLocale localizedStringForCurrencyCode:currentLocale.currencyCode]; @@ -678,7 +678,12 @@ int32_t GlobalizationNative_GetLocalesNative(UChar* value, int32_t length) NSArray* availableLocaleIdentifiers = [NSLocale availableLocaleIdentifiers]; int32_t index = 0; int32_t totalLength = 0; - for (NSInteger i = 0; i < [availableLocaleIdentifiers count]; i++) + int32_t availableLength = (int32_t)[availableLocaleIdentifiers count]; + + if (availableLength <= 0) + return -1; // failed + + for (NSInteger i = 0; i < availableLength; i++) { NSString *localeIdentifier = availableLocaleIdentifiers[i]; int32_t localeNameLength = localeIdentifier.length; From 94ccf467a07c79f4bd4d2157ae46e8d82b6ce366 Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Thu, 19 Oct 2023 11:55:13 +0200 Subject: [PATCH 4/5] Add test case --- .../CultureInfo/CultureInfoGetCultures.cs | 25 +++++++++++++++++++ .../System.Globalization.IOS.Tests.csproj | 1 + 2 files changed, 26 insertions(+) create mode 100644 src/libraries/System.Globalization/tests/CultureInfo/CultureInfoGetCultures.cs diff --git a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoGetCultures.cs b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoGetCultures.cs new file mode 100644 index 0000000000000..5dcdfb54c1f83 --- /dev/null +++ b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoGetCultures.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Globalization.Tests +{ + public class CultureInfoGetCultures + { + [Fact] + public void GetSpecificCultures() + { + var specificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures); + Assert.True(specificCultures.Length > 0); + Assert.All(specificCultures, c => Assert.True(c.IsNeutralCulture == false)); + } + + [Fact] + public void GetAllCultures() + { + var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + Assert.True(allCultures.Length > 0); + } + } +} diff --git a/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj index eac50b0d2f3e0..b2845152ffde7 100644 --- a/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj +++ b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj @@ -5,6 +5,7 @@ true + From 5515a32779329c7cc1d078c3fa561579c0307c5b Mon Sep 17 00:00:00 2001 From: Meri Khamoyan Date: Thu, 19 Oct 2023 15:33:38 +0200 Subject: [PATCH 5/5] Add braces and fix order in csproj --- .../tests/Hybrid/System.Globalization.IOS.Tests.csproj | 2 +- .../src/System/Globalization/CultureData.Icu.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj index b2845152ffde7..bb38f00b88f9d 100644 --- a/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj +++ b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj @@ -5,8 +5,8 @@ true - + diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs index 72449d7542b22..12af791d392e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs @@ -423,9 +423,13 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) int bufferLength; #if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS if (GlobalizationMode.Hybrid) + { bufferLength = Interop.Globalization.GetLocalesNative(null, 0); + } else + { bufferLength = Interop.Globalization.GetLocales(null, 0); + } #else bufferLength = Interop.Globalization.GetLocales(null, 0); #endif @@ -438,9 +442,13 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types) #if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS if (GlobalizationMode.Hybrid) + { bufferLength = Interop.Globalization.GetLocalesNative(chars, bufferLength); + } else + { bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); + } #else bufferLength = Interop.Globalization.GetLocales(chars, bufferLength); #endif