Skip to content

Commit

Permalink
Initial changes for GetLocaleInfoString (#81470)
Browse files Browse the repository at this point in the history
Initial changes for globalization hybrid mode.
Implemented GetLocaleInfoStringName and GetLocaleNameNative for osx platforms.
  • Loading branch information
mkhamoyan authored Mar 28, 2023
1 parent 93e91df commit f561a05
Show file tree
Hide file tree
Showing 18 changed files with 320 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/workflow/trimming/feature-switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| EnableUnsafeBinaryFormatterSerialization | System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization | BinaryFormatter serialization support is trimmed when set to false |
| EventSourceSupport | System.Diagnostics.Tracing.EventSource.IsSupported | Any EventSource related code or logic is trimmed when set to false |
| InvariantGlobalization | System.Globalization.Invariant | All globalization specific code and data is trimmed when set to true |
| HybridGlobalization | System.Globalization.Hybrid | Loading ICU + native implementations |
| PredefinedCulturesOnly | System.Globalization.PredefinedCulturesOnly | Don't allow creating a culture for which the platform does not have data |
| UseSystemResourceKeys | System.Resources.UseSystemResourceKeys | Any localizable resources for system assemblies is trimmed when set to true |
| HttpActivityPropagationSupport | System.Net.Http.EnableActivityPropagation | Any dependency related to diagnostics support for System.Net.Http is trimmed when set to false |
Expand Down
1 change: 1 addition & 0 deletions eng/testing/tests.ioslike.targets
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
library test project. Eg. $(InvariantGlobalization) -->
<ItemGroup>
<_ApplePropertyNames Include="InvariantGlobalization" />
<_ApplePropertyNames Include="HybridGlobalization" />
<_ApplePropertyNames Include="AssemblyName" />
<_ApplePropertyNames Include="MonoEnableLLVM" />

Expand Down
16 changes: 16 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Locale.OSX.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Globalization
{
[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleNameNative", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial string GetLocaleNameNative(string localeName);

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLocaleInfoStringNative", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial string GetLocaleInfoStringNative(string localeName, uint localeStringData);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-osx</TargetFrameworks>
<TestRuntime>true</TestRuntime>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
<ItemGroup>
<Compile Include="HybridMode.cs" />
</ItemGroup>
</Project>
48 changes: 48 additions & 0 deletions src/libraries/System.Globalization/tests/Hybrid/HybridMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Xunit;

namespace System.Globalization.Tests
{
public class HybridModeTests
{
public static IEnumerable<object[]> EnglishName_TestData()
{
yield return new object[] { "en-US", "English (United States)" };
yield return new object[] { "fr-FR", "French (France)" };
}

public static IEnumerable<object[]> NativeName_TestData()
{
yield return new object[] { "en-US", "English (United States)" };
yield return new object[] { "fr-FR", "français (France)" };
yield return new object[] { "en-CA", "English (Canada)" };
}

[Theory]
[MemberData(nameof(EnglishName_TestData))]
public void TestEnglishName(string cultureName, string expected)
{
CultureInfo myTestCulture = new CultureInfo(cultureName);
Assert.Equal(expected, myTestCulture.EnglishName);
}

[Theory]
[MemberData(nameof(NativeName_TestData))]
public void TestNativeName(string cultureName, string expected)
{
CultureInfo myTestCulture = new CultureInfo(cultureName);
Assert.Equal(expected, myTestCulture.NativeName);
}

[Theory]
[InlineData("de-DE", "de")]
[InlineData("en-US", "en")]
public void TwoLetterISOLanguageName(string name, string expected)
{
Assert.Equal(expected, new CultureInfo(name).TwoLetterISOLanguageName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CompareOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Icu.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Nls.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureNotFoundException.cs" />
Expand Down Expand Up @@ -1278,6 +1279,9 @@
<Compile Include="$(CommonPath)Interop\Interop.Locale.cs">
<Link>Common\Interop\Interop.Locale.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Interop.Locale.OSX.cs" Condition="'$(IsOSXLike)' == 'true'">
<Link>Common\Interop\Interop.Locale.OSX.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Interop.Normalization.cs">
<Link>Common\Interop\Interop.Normalization.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;

namespace System.Globalization
{
internal sealed partial class CultureData
{
/// <summary>
/// This method uses the sRealName field (which is initialized by the constructor before this is called) to
/// initialize the rest of the state of CultureData based on the underlying OS globalization library.
/// </summary>
private bool InitNativeCultureDataCore()
{
Debug.Assert(_sRealName != null);
Debug.Assert(!GlobalizationMode.Invariant);
string realNameBuffer = _sRealName;

_sWindowsName = GetLocaleNameNative(realNameBuffer);
return true;
}

internal static unsafe string GetLocaleNameNative(string localeName)
{
return Interop.Globalization.GetLocaleNameNative(localeName);
}

private string GetLocaleInfoNative(LocaleStringData type)
{
Debug.Assert(!GlobalizationMode.Invariant);
Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfoNative] Expected _sWindowsName to be populated already");

return GetLocaleInfoNative(_sWindowsName, type);
}

// For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the
// "windows" name, which can be specific for downlevel (< windows 7) os's.
private static unsafe string GetLocaleInfoNative(string localeName, LocaleStringData type)
{
Debug.Assert(localeName != null, "[CultureData.GetLocaleInfoNative] Expected localeName to be not be null");

return Interop.Globalization.GetLocaleInfoStringNative(localeName, (uint)type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2304,7 +2304,11 @@ private string GetLocaleInfoCoreUserOverride(LocaleStringData type)
if (GlobalizationMode.Invariant)
return null!;

#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type);
#else
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type);
#endif
}

private string GetLocaleInfoCore(LocaleStringData type, string? uiCultureName = null)
Expand All @@ -2313,7 +2317,11 @@ private string GetLocaleInfoCore(LocaleStringData type, string? uiCultureName =
if (GlobalizationMode.Invariant)
return null!;

#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(type) : IcuGetLocaleInfo(type, uiCultureName);
#else
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type, uiCultureName);
#endif
}

private string GetLocaleInfoCore(string localeName, LocaleStringData type, string? uiCultureName = null)
Expand All @@ -2322,7 +2330,11 @@ private string GetLocaleInfoCore(string localeName, LocaleStringData type, strin
if (GlobalizationMode.Invariant)
return null!;

#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return GlobalizationMode.Hybrid ? GetLocaleInfoNative(localeName, type) : IcuGetLocaleInfo(localeName, type, uiCultureName);
#else
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(localeName, type) : IcuGetLocaleInfo(localeName, type, uiCultureName);
#endif
}

private int[] GetLocaleInfoCoreUserOverride(LocaleGroupingData type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ internal static partial class GlobalizationMode
private static partial class Settings
{
internal static bool Invariant { get; } = AppContextConfigHelper.GetBooleanConfig("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT");
#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
internal static bool Hybrid { get; } = AppContextConfigHelper.GetBooleanConfig("System.Globalization.Hybrid", "DOTNET_SYSTEM_GLOBALIZATION_HYBRID");
#endif
internal static bool PredefinedCulturesOnly { get; } = AppContextConfigHelper.GetBooleanConfig("System.Globalization.PredefinedCulturesOnly", "DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", GlobalizationMode.Invariant);
}

// Note: Invariant=true and Invariant=false are substituted at different levels in the ILLink.Substitutions file.
// This allows for the whole Settings nested class to be trimmed when Invariant=true, and allows for the Settings
// static cctor (on Unix) to be preserved when Invariant=false.
internal static bool Invariant => Settings.Invariant;
#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
internal static bool Hybrid => Settings.Hybrid;
#endif
internal static bool PredefinedCulturesOnly => Settings.PredefinedCulturesOnly;

private static bool TryGetAppLocalIcuSwitchValue([NotNullWhen(true)] out string? value) =>
Expand Down
1 change: 1 addition & 0 deletions src/mono/msbuild/apple/build/AppleBuild.targets
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
GenerateCMakeProject="$(GenerateCMakeProject)"
GenerateXcodeProject="$(GenerateXcodeProject)"
InvariantGlobalization="$(InvariantGlobalization)"
HybridGlobalization="$(HybridGlobalization)"
MainLibraryFileName="$(MainLibraryFileName)"
MonoRuntimeHeaders="$(_MonoHeaderPath)"
NativeMainSource="$(NativeMainSource)"
Expand Down
4 changes: 4 additions & 0 deletions src/native/libs/System.Globalization.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ static const Entry s_globalizationNative[] =
DllImportEntry(GlobalizationNative_ToAscii)
DllImportEntry(GlobalizationNative_ToUnicode)
DllImportEntry(GlobalizationNative_WindowsIdToIanaId)
#ifdef __APPLE__
DllImportEntry(GlobalizationNative_GetLocaleNameNative)
DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative)
#endif
};

EXTERN_C const void* GlobalizationResolveDllImport(const char* name);
Expand Down
4 changes: 4 additions & 0 deletions src/native/libs/System.Globalization.Native/pal_locale.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ PALEXPORT int32_t GlobalizationNative_GetLocales(UChar *value, int32_t valueLeng

PALEXPORT int32_t GlobalizationNative_GetLocaleName(const UChar* localeName, UChar* value, int32_t valueLength);

#ifdef __APPLE__
PALEXPORT const char* GlobalizationNative_GetLocaleNameNative(const char* localeName);
#endif

PALEXPORT int32_t GlobalizationNative_GetDefaultLocaleName(UChar* value, int32_t valueLength);

PALEXPORT int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName);
125 changes: 125 additions & 0 deletions src/native/libs/System.Globalization.Native/pal_locale.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

#include <stdlib.h>
#include "pal_locale_internal.h"
#include "pal_localeStringData.h"

#import <Foundation/Foundation.h>
#import <Foundation/NSFormatter.h>

char* DetectDefaultAppleLocaleName()
{
Expand All @@ -28,6 +30,129 @@
return strdup([localeName UTF8String]);
}

#if defined(TARGET_OSX) || defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS)

const char* GlobalizationNative_GetLocaleNameNative(const char* localeName)
{
NSString *locName = [NSString stringWithFormat:@"%s", localeName];
NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName];
const char* value = [currentLocale.localeIdentifier UTF8String];
return strdup(value);
}

const char* GlobalizationNative_GetLocaleInfoStringNative(const char* localeName, LocaleStringData localeStringData)
{
const char* value;
NSString *locName = [NSString stringWithFormat:@"%s", localeName];
NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.locale = currentLocale;
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:currentLocale];
NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];


switch (localeStringData)
{
///// <summary>localized name of locale, eg "German (Germany)" in UI language (corresponds to LOCALE_SLOCALIZEDDISPLAYNAME)</summary>
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;
/// <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];
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];
break;
/// <summary>native name of language, eg "Deutsch" (corresponds to LOCALE_SNATIVELANGUAGENAME)</summary>
case LocaleString_NativeLanguageName:
value = [[currentLocale localizedStringForLanguageCode:currentLocale.languageCode] UTF8String];
break;
/// <summary>English name of country, eg "Germany" (corresponds to LOCALE_SENGLISHCOUNTRYNAME)</summary>
case LocaleString_EnglishCountryName:
value = [[gbLocale localizedStringForCountryCode:currentLocale.countryCode] UTF8String];
break;
/// <summary>native name of country, eg "Deutschland" (corresponds to LOCALE_SNATIVECOUNTRYNAME)</summary>
case LocaleString_NativeCountryName:
value = [[currentLocale localizedStringForCountryCode:currentLocale.countryCode] UTF8String];
break;
case LocaleString_ThousandSeparator:
value = [currentLocale.groupingSeparator UTF8String];
break;
case LocaleString_DecimalSeparator:
value = [currentLocale.decimalSeparator UTF8String];
// or value = [[currentLocale objectForKey:NSLocaleDecimalSeparator] UTF8String];
break;
case LocaleString_MonetarySymbol:
value = [currentLocale.currencySymbol UTF8String];
break;
case LocaleString_Iso4217MonetarySymbol:
// check if this is correct, check currencyISOCode
value = [currentLocale.currencySymbol UTF8String];
break;
case LocaleString_CurrencyEnglishName:
value = [[gbLocale localizedStringForCurrencyCode:currentLocale.currencyCode] UTF8String];
break;
case LocaleString_CurrencyNativeName:
value = [[currentLocale localizedStringForCurrencyCode:currentLocale.currencyCode] UTF8String];
break;
case LocaleString_AMDesignator:
value = [dateFormatter.AMSymbol UTF8String];
break;
case LocaleString_PMDesignator:
value = [dateFormatter.PMSymbol UTF8String];
break;
case LocaleString_PositiveSign:
value = [numberFormatter.plusSign UTF8String];
break;
case LocaleString_NegativeSign:
value = [numberFormatter.minusSign UTF8String];
break;
case LocaleString_Iso639LanguageTwoLetterName:
// check if this is correct
value = [[currentLocale objectForKey:NSLocaleLanguageCode] UTF8String];
break;
case LocaleString_Iso3166CountryName:
value = [currentLocale.countryCode UTF8String];
break;
case LocaleString_NaNSymbol:
value = [numberFormatter.notANumberSymbol UTF8String];
break;
case LocaleString_PositiveInfinitySymbol:
value = [numberFormatter.positiveInfinitySymbol UTF8String];
break;
case LocaleString_NegativeInfinitySymbol:
value = [numberFormatter.negativeInfinitySymbol UTF8String];
break;
case LocaleString_PercentSymbol:
value = [numberFormatter.percentSymbol UTF8String];
break;
case LocaleString_PerMilleSymbol:
value = [numberFormatter.perMillSymbol UTF8String];
break;
// TODO find mapping for below cases
// https://github.com/dotnet/runtime/issues/83514
case LocaleString_Digits:
case LocaleString_MonetaryDecimalSeparator:
case LocaleString_MonetaryThousandSeparator:
case LocaleString_Iso639LanguageThreeLetterName:
case LocaleString_ParentName:
case LocaleString_Iso3166CountryName2:
default:
value = "";
break;
}

return strdup(value);
}
#endif

#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS)
const char* GlobalizationNative_GetICUDataPathFallback(void)
{
Expand Down
Loading

1 comment on commit f561a05

@Ayazcan3333
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.