From b31934c7c5788cb3df9e9f4878bcacca67a1a67c Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 29 Apr 2025 14:33:52 -0700 Subject: [PATCH] Fix culture creation with undetermined lang tag --- .../src/System/Globalization/CultureData.Icu.cs | 15 +++++++++++++-- .../CultureInfo/CultureInfoCtor.cs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 2 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 a95226e089686e..4b976bd92456a1 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 @@ -19,6 +19,7 @@ internal sealed partial class CultureData /// /// The locale name that ICU returns. /// The extension part in the original culture name. + /// The original input culture name. /// The index of the collation in the name. /// /// BCP 47 specifications allow for extensions in the locale name, following the format language-script-region-extensions-collation. However, @@ -29,7 +30,7 @@ internal sealed partial class CultureData /// between names with extensions and those without. For example, we may have a name like en-US and en-US-u-xx. Although .NET doesn't support the extension xx, /// we still include it in the name to distinguish it from the name without the extension. /// - private static string NormalizeCultureName(string name, ReadOnlySpan extension, out int collationStart) + private static string NormalizeCultureName(string name, ReadOnlySpan extension, string originalName, out int collationStart) { Debug.Assert(name is not null); Debug.Assert(name.Length <= ICU_ULOC_FULLNAME_CAPACITY); @@ -39,6 +40,16 @@ private static string NormalizeCultureName(string name, ReadOnlySpan exten Span buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; int bufferIndex = 0; + // Using `Undetermined` language (e.g. `und-US`), the returned name from ICU getName is `_US`. We return back the original language `und` + if (name.Length > 1 && (name[0] == '-' || name[0] == '_') && originalName.StartsWith("und", StringComparison.OrdinalIgnoreCase)) + { + buffer[bufferIndex++] = 'u'; + buffer[bufferIndex++] = 'n'; + buffer[bufferIndex++] = 'd'; + + changed = true; + } + for (int i = 0; i < name.Length && bufferIndex < ICU_ULOC_FULLNAME_CAPACITY; i++) { char c = name[i]; @@ -141,7 +152,7 @@ private bool InitIcuCultureDataCore() Debug.Assert(_sWindowsName != null); - _sRealName = NormalizeCultureName(_sWindowsName, indexOfExtensions > 0 ? _sRealName.AsSpan(indexOfExtensions) : ReadOnlySpan.Empty, out int collationStart); + _sRealName = NormalizeCultureName(_sWindowsName, indexOfExtensions > 0 ? _sRealName.AsSpan(indexOfExtensions) : ReadOnlySpan.Empty, _sRealName, out int collationStart); _iLanguage = LCID; if (_iLanguage == 0) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs index e5fd8c7fd0a123..8fa5ba6f53a153 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoCtor.cs @@ -388,6 +388,18 @@ public static IEnumerable Ctor_String_TestData() } } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))] + [InlineData("und-us", "und-US", "und-US")] + [InlineData("und-us_tradnl", "und-US", "und-US_tradnl")] + [InlineData("und-es-u-co-phoneb", "und-ES", "und-ES_phoneb")] + [InlineData("und-es-t-something", "und-ES", "und-ES")] + public void CtorUndeterminedLanguageTag(string cultureName, string expectedCultureName, string expectedSortName) + { + CultureInfo culture = new CultureInfo(cultureName); + Assert.Equal(expectedCultureName, culture.Name); + Assert.Equal(expectedSortName, culture.CompareInfo.Name); + } + [Theory] [MemberData(nameof(Ctor_String_TestData))] public void Ctor_String(string name, string[] expectedNames)