Skip to content

Commit

Permalink
Fix Culture Native and Display Names (dotnet#53468)
Browse files Browse the repository at this point in the history
* Fix Culture Native and Display Names

* fix mono break

* Address the feedback

* Address more feedback

* Use ThreadCultureChange in the tests

* Exclude the test on  mobile platfoms
  • Loading branch information
tarekgh authored Jun 3, 2021
1 parent 598929f commit c3fc263
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 109 deletions.
2 changes: 1 addition & 1 deletion src/libraries/Common/src/Interop/Interop.Locale.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal static partial class Globalization

[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoString")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern unsafe bool GetLocaleInfoString(string localeName, uint localeStringData, char* value, int valueLength);
internal static extern unsafe bool GetLocaleInfoString(string localeName, uint localeStringData, char* value, int valueLength, string? uiLocaleName = null);

[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetDefaultLocaleName")]
[return: MarshalAs(UnmanagedType.Bool)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ typedef void* UCalendar;

typedef enum UErrorCode {
U_STRING_NOT_TERMINATED_WARNING = -124,
U_USING_DEFAULT_WARNING = -127,
U_ZERO_ERROR = 0,
U_ILLEGAL_ARGUMENT_ERROR = 1,
U_INTERNAL_PROGRAM_ERROR = 5,
Expand Down Expand Up @@ -441,7 +442,7 @@ UChar * u_strcpy(UChar * dst, const UChar * src);
UChar * u_strncpy(UChar * dst, const UChar * src, int32_t n);
UChar32 u_tolower(UChar32 c);
UChar32 u_toupper(UChar32 c);
UChar* u_uastrcpy(UChar * dst, const char * src);
UChar* u_uastrcpy(UChar * dst, const char * src);
void ucal_add(UCalendar * cal, UCalendarDateFields field, int32_t amount, UErrorCode * status);
void ucal_close(UCalendar * cal);
int32_t ucal_get(const UCalendar * cal, UCalendarDateFields field, UErrorCode * status);
Expand All @@ -455,7 +456,7 @@ int32_t ucal_getWindowsTimeZoneID(const UChar * id, int32_t len, UChar * winid,
UCalendar * ucal_open(const UChar * zoneID, int32_t len, const char * locale, UCalendarType type, UErrorCode * status);
UEnumeration * ucal_openTimeZoneIDEnumeration(USystemTimeZoneType zoneType, const char * region, const int32_t * rawOffset, UErrorCode * ec);
void ucal_set(UCalendar * cal, UCalendarDateFields field, int32_t value);
void ucal_setMillis(UCalendar * cal, UDate dateTime, UErrorCode * status);
void ucal_setMillis(UCalendar * cal, UDate dateTime, UErrorCode * status);
void ucol_close(UCollator * coll);
void ucol_closeElements(UCollationElements * elems);
int32_t ucol_getOffset(const UCollationElements *elems);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,13 @@ Returns 1 for success, 0 otherwise
int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName,
LocaleStringData localeStringData,
UChar* value,
int32_t valueLength)
int32_t valueLength,
const UChar* uiLocaleName)
{
UErrorCode status = U_ZERO_ERROR;
char locale[ULOC_FULLNAME_CAPACITY];
char locale[ULOC_FULLNAME_CAPACITY] = "";
char uiLocale[ULOC_FULLNAME_CAPACITY] = "";

GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &status);

if (U_FAILURE(status))
Expand All @@ -221,28 +224,59 @@ int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName,
switch (localeStringData)
{
case LocaleString_LocalizedDisplayName:
uloc_getDisplayName(locale, DetectDefaultLocaleName(), value, valueLength, &status);
assert(uiLocaleName != NULL);
GetLocale(uiLocaleName, uiLocale, ULOC_FULLNAME_CAPACITY, false, &status);
uloc_getDisplayName(locale, uiLocale, value, valueLength, &status);
if (status == U_USING_DEFAULT_WARNING)
{
// The default locale data was used. i.e. couldn't find suitable resources for the requested UI language. Fallback to English then.
uloc_getDisplayName(locale, ULOC_ENGLISH, value, valueLength, &status);
}
break;
case LocaleString_EnglishDisplayName:
uloc_getDisplayName(locale, ULOC_ENGLISH, value, valueLength, &status);
break;
case LocaleString_NativeDisplayName:
uloc_getDisplayName(locale, locale, value, valueLength, &status);
if (status == U_USING_DEFAULT_WARNING)
{
// The default locale data was used. i.e. couldn't find suitable resources for the requested input locale. Fallback to English instead.
uloc_getDisplayName(locale, ULOC_ENGLISH, value, valueLength, &status);
}

break;
case LocaleString_LocalizedLanguageName:
uloc_getDisplayLanguage(locale, DetectDefaultLocaleName(), value, valueLength, &status);
assert(uiLocaleName != NULL);
GetLocale(uiLocaleName, uiLocale, ULOC_FULLNAME_CAPACITY, false, &status);
uloc_getDisplayLanguage(locale, uiLocale, value, valueLength, &status);
if (status == U_USING_DEFAULT_WARNING)
{
// No data was found from the locale resources and a case canonicalized language code is placed into language as fallback. Fallback to English instead.
uloc_getDisplayLanguage(locale, ULOC_ENGLISH, value, valueLength, &status);
}

break;
case LocaleString_EnglishLanguageName:
uloc_getDisplayLanguage(locale, ULOC_ENGLISH, value, valueLength, &status);
break;
case LocaleString_NativeLanguageName:
uloc_getDisplayLanguage(locale, locale, value, valueLength, &status);
if (status == U_USING_DEFAULT_WARNING)
{
// No data was found from the locale resources and a case canonicalized language code is placed into language as fallback. Fallback to English instead.
uloc_getDisplayLanguage(locale, ULOC_ENGLISH, value, valueLength, &status);
}
break;
case LocaleString_EnglishCountryName:
uloc_getDisplayCountry(locale, ULOC_ENGLISH, value, valueLength, &status);
break;
case LocaleString_NativeCountryName:
uloc_getDisplayCountry(locale, locale, value, valueLength, &status);
if (status == U_USING_DEFAULT_WARNING)
{
// No data was found from the locale resources and a case canonicalized language code is placed into language as fallback. Fallback to English instead.
uloc_getDisplayCountry(locale, ULOC_ENGLISH, value, valueLength, &status);
}
break;
case LocaleString_ThousandSeparator:
status = GetLocaleInfoDecimalFormatSymbol(locale, UNUM_GROUPING_SEPARATOR_SYMBOL, value, valueLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ typedef enum
PALEXPORT int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName,
LocaleStringData localeStringData,
UChar* value,
int32_t valueLength);
int32_t valueLength,
const UChar* uiLocaleName);

PALEXPORT int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeName,
int shortFormat, UChar* value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.Reflection;
using System.Tests;
using Xunit;

namespace System.Globalization.Tests
{
public class CultureInfoNames
{
private static bool SupportFullIcuResources => PlatformDetection.IsNotMobile && PlatformDetection.IsIcuGlobalization;

[ConditionalTheory(nameof(SupportFullIcuResources))]
[InlineData("en", "en", "English", "English")]
[InlineData("en", "fr", "English", "anglais")]
[InlineData("aa", "aa", "Afar", "Afar")]
[InlineData("en-US", "en-US", "English (United States)", "English (United States)")]
[InlineData("en-US", "fr-FR", "English (United States)", "anglais (États-Unis)")]
[InlineData("en-US", "de-DE", "English (United States)", "Englisch (Vereinigte Staaten)")]
[InlineData("aa-ER", "aa-ER", "Afar (Eritrea)", "Afar (Eritrea)")]
[InlineData("", "en-US", "Invariant Language (Invariant Country)", "Invariant Language (Invariant Country)")]
[InlineData("", "fr-FR", "Invariant Language (Invariant Country)", "Invariant Language (Invariant Country)")]
[InlineData("", "", "Invariant Language (Invariant Country)", "Invariant Language (Invariant Country)")]
public void TestDisplayName(string cultureName, string uiCultureName, string nativeName, string displayName)
{
using (new ThreadCultureChange(null, CultureInfo.GetCultureInfo(uiCultureName)))
{
CultureInfo ci = CultureInfo.GetCultureInfo(cultureName);
Assert.Equal(nativeName, ci.NativeName);
Assert.Equal(displayName, ci.DisplayName);
}
}

[ConditionalFact(nameof(SupportFullIcuResources))]
public void TestDisplayNameWithSettingUICultureMultipleTime()
{
using (new ThreadCultureChange(null, CultureInfo.GetCultureInfo("en-US")))
{
CultureInfo ci = new CultureInfo("en-US");
Assert.Equal("English (United States)", ci.DisplayName);
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
Assert.Equal("anglais (États-Unis)", ci.DisplayName);
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
Assert.Equal("Englisch (Vereinigte Staaten)", ci.DisplayName);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<Compile Include="CultureInfo\CultureInfoGetFormat.cs" />
<Compile Include="CultureInfo\CultureInfoGetHashCode.cs" />
<Compile Include="CultureInfo\CultureInfoIsNeutralCulture.cs" />
<Compile Include="CultureInfo\CultureInfoNames.cs" />
<Compile Include="CultureInfo\CultureInfoNativeName.cs" />
<Compile Include="CultureInfo\CultureInfoNumberFormat.cs" />
<Compile Include="CultureInfo\CultureInfoParent.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,17 @@ internal static unsafe bool GetDefaultLocaleName([NotNullWhen(true)] out string?
return true;
}

private string IcuGetLocaleInfo(LocaleStringData type)
private string IcuGetLocaleInfo(LocaleStringData type, string? uiCultureName = null)
{
Debug.Assert(!GlobalizationMode.Invariant);
Debug.Assert(!GlobalizationMode.UseNls);
Debug.Assert(_sWindowsName != null, "[CultureData.IcuGetLocaleInfo] Expected _sWindowsName to be populated already");
return IcuGetLocaleInfo(_sWindowsName, type);
return IcuGetLocaleInfo(_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 unsafe string IcuGetLocaleInfo(string localeName, LocaleStringData type)
private unsafe string IcuGetLocaleInfo(string localeName, LocaleStringData type, string? uiCultureName = null)
{
Debug.Assert(!GlobalizationMode.UseNls);
Debug.Assert(localeName != null, "[CultureData.IcuGetLocaleInfo] Expected localeName to be not be null");
Expand All @@ -127,7 +127,7 @@ private unsafe string IcuGetLocaleInfo(string localeName, LocaleStringData type)
}

char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY];
bool result = Interop.Globalization.GetLocaleInfoString(localeName, (uint)type, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY);
bool result = Interop.Globalization.GetLocaleInfoString(localeName, (uint)type, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY, uiCultureName);
if (!result)
{
// Failed, just use empty string
Expand Down Expand Up @@ -204,22 +204,13 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat)
return ConvertIcuTimeFormatString(span.Slice(0, span.IndexOf('\0')));
}

private static CultureData? IcuGetCultureDataFromRegionName(string? regionName)
{
// no support to lookup by region name, other than the hard-coded list in CultureData
return null;
}
// no support to lookup by region name, other than the hard-coded list in CultureData
private static CultureData? IcuGetCultureDataFromRegionName(string? regionName) => null;

private static string IcuGetLanguageDisplayName(string cultureName)
{
return new CultureInfo(cultureName)._cultureData.IcuGetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName);
}
private string IcuGetLanguageDisplayName(string cultureName) => IcuGetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName, CultureInfo.CurrentUICulture.Name);

private static string? IcuGetRegionDisplayName()
{
// use the fallback which is to return NativeName
return null;
}
// use the fallback which is to return NativeName
private static string? IcuGetRegionDisplayName() => null;

internal static bool IcuIsEnsurePredefinedLocaleName(string name)
{
Expand Down
Loading

0 comments on commit c3fc263

Please sign in to comment.