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.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..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 @@ -6,6 +6,7 @@ + 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..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 @@ -420,7 +420,19 @@ 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 +440,18 @@ 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 cffad72a02372..84d2177d55884 100644 --- a/src/native/libs/System.Globalization.Native/entrypoints.c +++ b/src/native/libs/System.Globalization.Native/entrypoints.c @@ -69,6 +69,7 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative) DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative) DllImportEntry(GlobalizationNative_GetLocaleNameNative) + DllImportEntry(GlobalizationNative_GetLocalesNative) DllImportEntry(GlobalizationNative_GetLocaleTimeFormatNative) DllImportEntry(GlobalizationNative_IndexOfNative) DllImportEntry(GlobalizationNative_StartsWithNative) 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..4789ac89691da 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.currencyCode; 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]); } } @@ -667,6 +664,54 @@ 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; + 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; + 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)