-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Initial changes for globalization hybrid mode #81470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
f8e72d8
a82743d
954ed0f
1362d45
cb23688
af32324
83f4e7b
4ac3e6b
60e090a
26ce9a9
a10f09d
dc347f5
09d599e
bacc227
e844a40
f7f730a
46c9e68
1b7aa37
789613b
0b9f32d
1aee1e0
b382101
1556d17
5ab240a
5028045
ad4c98b
3eb5cae
27424bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// 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; | ||
using System; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Globalization | ||
{ | ||
[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "NativeGetLocaleName", StringMarshalling = StringMarshalling.Utf16)] | ||
internal static unsafe partial string NativeGetLocaleName(string localeName, int valueLength); | ||
|
||
[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "NativeGetLocaleInfoString", StringMarshalling = StringMarshalling.Utf8)] | ||
internal static unsafe partial string NativeGetLocaleInfoString(string localeName, uint localeStringData, int valueLength, string? uiLocaleName = null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework> | ||
<TestRuntime>true</TestRuntime> | ||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<InvariantGlobalization>false</InvariantGlobalization> | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly> | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<HybridGlobalization>true</HybridGlobalization> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="HybridMode.cs" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Buffers; | ||
using System.Reflection; | ||
using System.Buffers.Binary; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using Xunit; | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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); | ||
} | ||
|
||
[Theory] | ||
[InlineData("de-DE", "Deutsch (Deutschland)", "de (DE)")] | ||
public void TestDisplayName(string cultureName, string nativeName, string englishDisplayName) | ||
{ | ||
CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Assert.Equal(nativeName, ci.NativeName); | ||
Assert.Equal(englishDisplayName, ci.DisplayName); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2303,7 +2303,11 @@ private string GetLocaleInfoCoreUserOverride(LocaleStringData type) | |
// This is never reached but helps illinker statically remove dependencies | ||
if (GlobalizationMode.Invariant) | ||
return null!; | ||
|
||
#if TARGET_IOS | ||
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
: GlobalizationMode.Hybrid ? NativeGetLocaleInfo(type) | ||
: IcuGetLocaleInfo(type); | ||
#endif | ||
return ShouldUseUserOverrideNlsData ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type); | ||
} | ||
|
||
|
@@ -2312,16 +2316,24 @@ private string GetLocaleInfoCore(LocaleStringData type, string? uiCultureName = | |
// This is never reached but helps illinker statically remove dependencies | ||
if (GlobalizationMode.Invariant) | ||
return null!; | ||
|
||
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type, uiCultureName); | ||
#if TARGET_IOS | ||
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
: GlobalizationMode.Hybrid ? NativeGetLocaleInfo(type, uiCultureName) | ||
: IcuGetLocaleInfo(type, uiCultureName); | ||
#endif | ||
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(type) : IcuGetLocaleInfo(type, uiCultureName); | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private string GetLocaleInfoCore(string localeName, LocaleStringData type, string? uiCultureName = null) | ||
{ | ||
// This is never reached but helps illinker statically remove dependencies | ||
if (GlobalizationMode.Invariant) | ||
return null!; | ||
|
||
#if TARGET_IOS | ||
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(localeName, type) | ||
: GlobalizationMode.Hybrid ? NativeGetLocaleInfo(localeName, type, uiCultureName) | ||
: IcuGetLocaleInfo(localeName, type, uiCultureName); | ||
#endif | ||
return GlobalizationMode.UseNls ? NlsGetLocaleInfo(localeName, type) : IcuGetLocaleInfo(localeName, type, uiCultureName); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// 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 System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace System.Globalization | ||
{ | ||
internal sealed partial class CultureData | ||
{ | ||
// Native constants, check if we need this for native | ||
private const int Native_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value | ||
private const int Native_ULOC_FULLNAME_CAPACITY = 157; // max size of locale name | ||
|
||
/// <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() | ||
{ | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Debug.Assert(_sRealName != null); | ||
Debug.Assert(!GlobalizationMode.Invariant); | ||
string realNameBuffer = _sRealName; | ||
|
||
// Get the locale name | ||
NativeGetLocaleName(realNameBuffer, out _sWindowsName); | ||
return true; | ||
} | ||
|
||
internal static unsafe bool NativeGetLocaleName(string localeName, out string? windowsName) | ||
{ | ||
windowsName = Interop.Globalization.NativeGetLocaleName(localeName, Native_ULOC_FULLNAME_CAPACITY); | ||
return true; | ||
} | ||
|
||
private string NativeGetLocaleInfo(LocaleStringData type, string? uiCultureName = null) | ||
{ | ||
Debug.Assert(!GlobalizationMode.Invariant); | ||
mdh1418 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Debug.Assert(!GlobalizationMode.UseNls); | ||
Debug.Assert(_sWindowsName != null, "[CultureData.NativeGetLocaleInfo] Expected _sWindowsName to be populated already"); | ||
|
||
return NativeGetLocaleInfo(_sWindowsName, type, uiCultureName); | ||
} | ||
|
||
// 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 NativeGetLocaleInfo(string localeName, LocaleStringData type, string? uiCultureName = null) | ||
{ | ||
Debug.Assert(!GlobalizationMode.UseNls); | ||
Debug.Assert(localeName != null, "[CultureData.NativeGetLocaleInfo] Expected localeName to be not be null"); | ||
|
||
string result = Interop.Globalization.NativeGetLocaleInfoString(localeName, (uint)type, Native_ULOC_KEYWORD_AND_VALUES_CAPACITY, uiCultureName); | ||
return result; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,13 +13,15 @@ internal static partial class GlobalizationMode | |||||||||
private static partial class Settings | ||||||||||
{ | ||||||||||
internal static bool Invariant { get; } = AppContextConfigHelper.GetBooleanConfig("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); | ||||||||||
internal static bool Hybrid { get; } = AppContextConfigHelper.GetBooleanConfig("System.Globalization.Hybrid", "DOTNET_SYSTEM_GLOBALIZATION_HYBRID"); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be under iOS ifdef - since all uses are under iOS ifdef as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, plus wasm and wasi as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now I will put under ioslike platforms. For wasm we have seperate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If runtime/src/libraries/System.Runtime/tests/TrimmingTests/System.Runtime.TrimmingTests.proj Lines 16 to 19 in 9b38f2a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once more functionalities will be implemented this will be used and we can have tests in TrimmingTests for hybrid mode. |
||||||||||
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; | ||||||||||
internal static bool Hybrid => Settings.Hybrid; | ||||||||||
internal static bool PredefinedCulturesOnly => Settings.PredefinedCulturesOnly; | ||||||||||
|
||||||||||
private static bool TryGetAppLocalIcuSwitchValue([NotNullWhen(true)] out string? value) => | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ PALEXPORT int32_t GlobalizationNative_GetLocales(UChar *value, int32_t valueLeng | |
|
||
PALEXPORT int32_t GlobalizationNative_GetLocaleName(const UChar* localeName, UChar* value, int32_t valueLength); | ||
|
||
PALEXPORT const char* NativeGetLocaleName(const char* localeName, int32_t valueLength); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should still have https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/interop-guidelines.md#unix-shims: all exports should have a prefix that corresponds to the Libraries' name, e.g. "SystemNative_" or "CryptoNative_" |
||
|
||
PALEXPORT int32_t GlobalizationNative_GetDefaultLocaleName(UChar* value, int32_t valueLength); | ||
|
||
PALEXPORT int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName); |
Uh oh!
There was an error while loading. Please reload this page.