Skip to content

Commit

Permalink
[iOS][non-icu] HybridGlobalization Get available locales (#93594)
Browse files Browse the repository at this point in the history
Implements `GlobalizationNative_GetLocalesNative` for iOS hybrid globalization in order to return all of the available locales on the device.

Fixes #93514

Contributes to #80689
  • Loading branch information
mkhamoyan authored Oct 19, 2023
1 parent 4dc17af commit bb3597a
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 38 deletions.
3 changes: 3 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Locale.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\CultureInfo\CultureInfoEnglishName.cs" />
<Compile Include="..\CultureInfo\CultureInfoGetCultures.cs" />
<Compile Include="..\CultureInfo\CultureInfoNumberFormat.cs" />
<Compile Include="..\CultureInfo\CultureInfoNames.cs" />
<Compile Include="..\CultureInfo\CultureInfoNativeName.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,15 +420,38 @@ private static CultureInfo[] IcuEnumCultures(CultureTypes types)
return Array.Empty<CultureInfo>();
}

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<CultureInfo>();
}

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<CultureInfo>();
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/System.Globalization.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/native/libs/System.Globalization.Native/pal_locale.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
119 changes: 82 additions & 37 deletions src/native/libs/System.Globalization.Native/pal_locale.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -112,35 +112,35 @@ static void GetParent(const char* localeID, char* parent, int32_t parentCapacity
case LocaleString_LocalizedDisplayName:
/// <summary>Display name (language + country usually) in English, eg "German (Germany)" (corresponds to LOCALE_SENGLISHDISPLAYNAME)</summary>
case LocaleString_EnglishDisplayName:
value = [[gbLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier] UTF8String];
break;
value = [gbLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier];
break;
/// <summary>Display name in native locale language, eg "Deutsch (Deutschland) (corresponds to LOCALE_SNATIVEDISPLAYNAME)</summary>
case LocaleString_NativeDisplayName:
value = [[currentLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier] UTF8String];
value = [currentLocale displayNameForKey:NSLocaleIdentifier value:currentLocale.localeIdentifier];
break;
/// <summary>Language Display Name for a language, eg "German" in UI language (corresponds to LOCALE_SLOCALIZEDLANGUAGENAME)</summary>
case LocaleString_LocalizedLanguageName:
/// <summary>English name of language, eg "German" (corresponds to LOCALE_SENGLISHLANGUAGENAME)</summary>
case LocaleString_EnglishLanguageName:
value = [[gbLocale localizedStringForLanguageCode:currentLocale.languageCode] UTF8String];
value = [gbLocale localizedStringForLanguageCode:currentLocale.languageCode];
break;
/// <summary>native name of language, eg "Deutsch" (corresponds to LOCALE_SNATIVELANGUAGENAME)</summary>
case LocaleString_NativeLanguageName:
value = [[currentLocale localizedStringForLanguageCode:currentLocale.languageCode] UTF8String];
value = [currentLocale localizedStringForLanguageCode:currentLocale.languageCode];
break;
/// <summary>English name of country, eg "Germany" (corresponds to LOCALE_SENGLISHCOUNTRYNAME)</summary>
case LocaleString_EnglishCountryName:
value = [[gbLocale localizedStringForCountryCode:currentLocale.countryCode] UTF8String];
value = [gbLocale localizedStringForCountryCode:currentLocale.countryCode];
break;
/// <summary>native name of country, eg "Deutschland" (corresponds to LOCALE_SNATIVECOUNTRYNAME)</summary>
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:
Expand All @@ -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]);
}
}

Expand Down Expand Up @@ -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<NSString*>* 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)
Expand Down

0 comments on commit bb3597a

Please sign in to comment.